From 3503fc15d2c63bbd86ccfd828a6ed5410b5daf24 Mon Sep 17 00:00:00 2001 From: reo Date: Fri, 26 Sep 2025 16:49:36 +0300 Subject: [PATCH 01/38] Implement PBR --- assets/models/blue-sphere.bin | Bin 0 -> 23648 bytes assets/models/blue-sphere.blend | Bin 0 -> 995752 bytes assets/models/blue-sphere.gltf | 121 ++++++++++++++++++ assets/models/crystal-monkey.bin | Bin 0 -> 68720 bytes assets/models/crystal-monkey.blend | Bin 0 -> 1004796 bytes assets/models/crystal-monkey.blend1 | Bin 0 -> 996532 bytes assets/models/crystal-monkey.gltf | 134 +++++++++++++++++++ assets/shaders/pbr.frag | 160 +++++++++++++++++++++++ assets/shaders/pbr.vert | 23 ++++ ecs/src/components.rs | 7 + game/src/main.rs | 24 +++- glium_platform/src/platform.rs | 4 +- glium_platform/src/render/mod.rs | 4 +- glium_platform/src/render/pbr.rs | 192 ++++++++++++++++++++++++++++ 14 files changed, 659 insertions(+), 10 deletions(-) create mode 100644 assets/models/blue-sphere.bin create mode 100644 assets/models/blue-sphere.blend create mode 100644 assets/models/blue-sphere.gltf create mode 100644 assets/models/crystal-monkey.bin create mode 100644 assets/models/crystal-monkey.blend create mode 100644 assets/models/crystal-monkey.blend1 create mode 100644 assets/models/crystal-monkey.gltf create mode 100644 assets/shaders/pbr.frag create mode 100644 assets/shaders/pbr.vert create mode 100644 glium_platform/src/render/pbr.rs diff --git a/assets/models/blue-sphere.bin b/assets/models/blue-sphere.bin new file mode 100644 index 0000000000000000000000000000000000000000..c26ad24b5dba981471781a330c11c62dc3bd84c3 GIT binary patch literal 23648 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 z%0A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

ZcfXlBp~uv-71&0e4=`r7 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 literal 0 HcmV?d00001 diff --git a/assets/models/blue-sphere.gltf b/assets/models/blue-sphere.gltf new file mode 100644 index 0000000..be26466 --- /dev/null +++ b/assets/models/blue-sphere.gltf @@ -0,0 +1,121 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.0.44", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Sphere" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material.001", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.04434913769364357, + 0, + 0.8024659156799316, + 1 + ], + "metallicFactor":0.8912280797958374, + "roughnessFactor":0.3807017505168915 + } + } + ], + "meshes":[ + { + "name":"Sphere", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":559, + "max":[ + 0.9999997019767761, + 1, + 0.9999993443489075 + ], + "min":[ + -0.9999990463256836, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":559, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":559, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2880, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":6708, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":6708, + "byteOffset":6708, + "target":34962 + }, + { + "buffer":0, + "byteLength":4472, + "byteOffset":13416, + "target":34962 + }, + { + "buffer":0, + "byteLength":5760, + "byteOffset":17888, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":23648, + "uri":"blue-sphere.bin" + } + ] +} diff --git a/assets/models/crystal-monkey.bin b/assets/models/crystal-monkey.bin new file mode 100644 index 0000000000000000000000000000000000000000..2db45966ba391e44f67a7dcc92cd338340566448 GIT binary patch literal 68720 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 avGu<>G5hAoEVHk!#K);QG(HZ^t^5H`4fe$V literal 0 HcmV?d00001 diff --git a/assets/models/crystal-monkey.blend b/assets/models/crystal-monkey.blend new file mode 100644 index 0000000000000000000000000000000000000000..a78412f905b257a446865a5d470597ebe278fc4c GIT binary patch literal 1004796 zcmeEv31Ah~)&FE=*mnph8dl>5NmvpHWCtDyh=7Po1xzAAkx&*z>!;D$_WkSU(oNA? zrTSH4ZEbB`sHFjc>a%LK?N{Ziwxup@YPDLInp*4p&$;*f-Yl6(UP6@QWiCwayEEsU zd(ZjZ<;1wspi76>g6S|GGQXo1iIp#?$1wspi76>g6 zTA-^~ASH!YdxjyjKxl!`0-*&$3xpO3Ef88Dv_NQq&;p?aLJNcz2rbZg76|7XI?tA& zq|gGP1wspi76>g6S|GGQXo1iIp#?$mSb%U(8`-ztg(nRh^oQKc-qzG~)0&aq%PJi<(keXa=*HyGGStG) z0%<8J11F}X@6GMq&&tg(t=zOsD?cT}nnCfrF~?ekM~`{Tk1sse{VkB1l9G8s{{hc3 z?s<%VYKAo>CEc20q**gkGp&W=Ct7(~qrTt$H4QcJv%rY{18VXI4z_Z8_0er#;h(1P z&!hG~d*mo9yLX?j`07?1%Gb)sk-lH{Bz0wWBmK4rOol<4bOFd3uycYsQzD#`)cf` zwr}JAaYjnYu<()-U)xXvuLZ{R?RNu@|Lp7K4;>!A_v8CNt`B*G zhgkW8hFB*K9Q4BQK7H2k{i0V!nC_kypa$K4a(ZSnXh~JTcRJB{!p&l|N*t zw0-jb*$+^4f$s@>(Y$~h>bFU>!>rt+j&2$~V8F$_)6-8N|8OYAhu$eECmxlWcI~K? z)X!(7q+KyGCH0cwDXANVrlfu*!!SZWvSsxC%;`VKTF`H>6-hO%`6-#!Tslh4 ze%ADi-XBg(NqcChk#Y|8Jp+!I;$g!p{82|+6_lPu_|G6eV>;EF zd_B+f&y)K0-_tKO?c@-SPu~KWscEC~hK=}0;Yi>2y9q&jIHXh9!jkvT+lvn&MLHtdl2Oix~f5 zS=NHV!>xHV-dFY>XjM?UXzVy^D)k3^AHjYkW6pI-9rA2}pDh*AGl!XjhyMK2)xKxv zW~OIMBA@MFsz0Y^{CuCM#(u^hJ^=f8)NjwGaeF~(FY6Q|!&*Y)_qlmft?Jpu)_D}K zot$SaB;4l`=9Rq%sAF!QffgSTS~E~iV*&e#Jg)GwCCn@3Qk~*w4AW_S$-gdb6qAhw_ndrGq z@8d^}+CS~sW38#9Myo!b@n1x9{AZkavbB)#FQ(@_#WZ)fuy=p!?Bh?gPNn&s(hS0$ z52^#@X{KPGq3~zlU?J6sQ>g!s%__H^nq6rHVq%W9Y*LO@k#&?cbI?$0Hr)f3(zO!8oDbk% zM!)%p(Aa$nwS63m==avaqpT+g_XE?4t;h0fJ@;_$(PCv-)7^t3XW z85v{83>o@`oUBog&Kf!D-*fuWeH-C@_O$6%InB`)3>;z=Qu{BY`}PtV1N^XO{O79s z!E}ppKQp6`b?21n*8eSDVLe?u&-&SvnbyN6&al=^$yfNVEu8h|GzvY<)?HoS;eGp^ zRW@woN9UGTd>F|&!J5Ii%fa=4`v4pNKC0c95!QTEP#suKzHYOI&fJOvC`v73BV;cX-*IPJbgmr64x%I}Di><%ZZm|Az&RXktr=D*8y7Cn32Q%ha7ZCQ< zg#C(~6MmVIn%bwQ+PAytJv=?*%&GKTu8_t8^a1?4h{lI98V6LHr+a*U)^qy!N!A&Y zj<;6PQ9rG~`pv4(Sbse8T5uHFTVF(&X0$WcC`k%gO6Djx~E< zW4C{<2Sb%FNb@$b%62Dn`)h&Kh-*u?&sGOmsmBC1=jg|aGR%NZROm*9W`WVLHE==EVb7H zeKIn#W{n&7-Q_b1{(4qfdGqR1PWiZM*>bCT`HBx$pStw574zml8JRu%dnNh#H|9>9 zxOs5DeiOYi!gO+2AU!QDy1wspi7D&(n;rx1nh6>3-3xpO3 zEf88Dv_KcJKsa{WK!moiZ4v$oEs$&$2-|nEAsXrvS|GGQXo1iIp#?$1wsq7W`X&OPAy-W zZWxE2vEF=-2=AhUzMo@cXHT6m!N^bPFU--0o;L4EHH@h7OC9%patf#8VUVFxNB!@{ z8a_AkyqX#LHHFj9E65MOp%6Dx_#?DH&$Pf9iw)z48HQnA`i$y7meCh9xt~cioU! z<~v8u=5#g@a>uGVS1(gL(vjWT?y;Zg7->_|IG=UgMwP!eHt%@x%-#6uY^L5&8bjcHn$s&e<@_-I} zG2w$en|YOs4|L$$A$*VrzPkQyKG1=$R`?*#=5v=5bl@u#KF9+fV(14t@R`B~d7w9p zVd^LNKnFh5OUMIXU4J(p=)ea$$V0kLZqPWt96E{mew*O8UFtvRo2A@dT)+={@JBrD zC?@=n1G(S_J@~VPA98mHKje7%K@a{WX}6FI{-E-M9{jt6A94!=a6|b)5B{j|LoVzO zIo|Sv9{lKcAorly19H6lpa(y$LoWDhMGsu(^6#Pq`nS6UfAn*pgWZ}vIKZx;PT96XM9QZ>at*Hb}=#xvl+1pTzp~BSOgk zAJJ#D!T)q%njaDa@@f#8=n$VgORufeT+fxM_=j`my3#rk`1f~x^`%i9{ctJq#>(Qc zjNN=G)u4~d8Af^^sj044rMoTLjIm&oYV=jC7Hx&&6?k=iB<; zOJc8__NHTkqj^q(IBxD3j@x&-aRhF_5#pb(7tv~aWV!4yaGUf7&8^Q$H4=&XoVi-JM=#&urgQ$R+hfjIiQ!n= zK^##|ZjbCspfCS`j0Sx{;}_Eqp#t;sScI_$W072rk5L%IFqUFW#25#9j)C`3`aNO< z_k-GB;=a{ojm2M6?F$irLr z6;b?8o{U91%k(MKeWc@rzCWC{&~<)CSv(G?`@_Uyt&K%4xt^S1Z$6D#9_Kl8jf_S4 z!}VAsu}4h1lToSr4XYBX`wbl&i*jzV`!KHis5{LG)_tmyDx9&L=J2RWL_W~>W1o@x zGr8PmbjI?sjq5L3yLEltMuYx$YVhgO5|$s?qVFpa$E1xRPFN5TmI`sEgNa*_MZs}O z{p5DFihd#AvjgmDj??q7l{M$D-L_$?S4_fb&X4hjUgrkn7nOMiluP#lh5`J|e|8lA zin@z7)ooe7b^XS5#^l`Gyo8J1CZW^bZ4!G38a8SC5$_QE5qroJN}3L;w(v*3LHI|H z?_B($S6ld_TweTR?{yUa`89PH)ok5-Y4(!3>Y6PM6CL)?UWXx}sZQgMxK{8-+!4{e z9ZYTEk9-61|EYdH-C6A)dbNc=%H_o$^KtF(|7kX~ZrfI4UFWuaHnp?8IZW^S6Z+sH z<`oea3jT;ehd4Um7y2GjVw?|(+rl6D2I4R46FLij=+zefC>QL-<3|H23H{Fs7(dS2 zwtfS3@sqdIY^bT)y867j?Ln3aIPc;QplS7ViZO_;=QQ4}a5AUGRLtd%I?vN@=;jp1 zW62qdS8uJW+py)*En90YTD`gE{MDN_*KMlVymfuemen;Iw`|*7vwD3spKH4ti%B^U zBQI~>C?Dv!PJ3yXm&@m4vzl z$TzTm`cdbOPtdEa{t4xRF2LXXM@R8rRDfWc0@e-Nx z+!Jww;E&i#pGewSAKSto`3B;Df9K*4z1qSbpp-?rhC!Jp^YIvN^eegg4! z!5?vZP^^W3y z`pWFN)p~le6Xzz`-UInh2ScskkGLtS{XfL*5er+n$n-6s0%Mm`v1D|0Wqq;NX z2RiUIe@WX3^1ydxFE=0Pz!wuf$OB(~CYLiMzCI9i;M*a5kZ1F8el{QIz*j4LkZ1GN zs+l(E2RiT-3LoTw@5)}xXRl)f9r#S)gFNsVMwx0-$PaYjJNQLyPsjsbekSwT`hgC7 z4Z;U`;4_TL>L>C89r(5jALM~AKa=_F{6Gi3i10xk_-3aa>&g#w;L8#|$g}yLa@hrR z;A@h033=elPj%Z9bl?LWb0l5A@A~r?-8uU6~*B;9uqGPh!IF zt?%FmJ@|8kA97LuP!5y}{GbPa^EJ{gMGp8;4lh6G!5<4=A zH(jmm2f5&f9^U*x5B^=k54mXnLHdIp{88bDT<{0!4|?#g5`M^Sl6DwW|3DA^9N~vt z@CVgD(1RcT3*>?ydU)*zdhp{qA z1S+r6T1O*nI*sa!Q&yz$;LviF2l=DhZ`JQdNF3=e>xX;$tmTDLt;F1()Y@Q?gY{U^ zzJBsHRA^CHQ+l^M-AX=uX^|T73`4Cct^We?(%N9eE!Jc8qqV_{X+0?){1CA&u z@YU%Z6-Mbmg=wJ2tjMy~w_!i0Z8|RQqaEx{e zIcTT8^6Iaz(2_b~+%yVE7LA+n*!wO<`^HW7H`spO(9_0E;07FF58$iQ7m)2=-iK>O zJ^MdL?dSA7Y7mR*;Ai2t3IY5bKF0xRT-RsuSL1jq5%W7WhS87u+r=s0R7Y@s>ud-7 zLX|hLgq{$%J@R^TwC~?1*x!<&yH$wX_nh+ojV(3V%d7YabH|FmMmo3yxSTxxV7$Tj6ZwhEcgr|bDEDc&Zwy`mI>1-*`5q zQp?8p)6bo5BV*Q+gSu~9^Ht)d@dvT*eOrF&XtG9Y_iZeX#OIa08C! z-}HV6z*pO$ipCoboq%Kg=QNIpTj0p=<|dBg>(r6@jX!Q2`w^0MaQ?Q>cF$jEkH8H$ zM*gaC1isxJO3JJT-}|E-%8nmBXF~E)Z;Di z)gsoE8^-td+u}HV!U0b6(v3p7fc@)7LUgPHw{ zzxp_-?h!G=bw{2|Ez`zP;|3hzHvwO5uW1#=n%fm_Iz3*e>#yH`7oB)ejQU%#=be%U zj!5IWTt=jD>_f(-c}+D>nn@C$PrHkbU>w=UfuGnmj_Kru+vwT9P=xLq14rNn9HEh! z(lI|6{GAp1`RQWiJzuPb&;Dam^6JB?p>dJ?y@dMh%k#-{B4;Lp|rtrD@oREn&0Sq`QJ*1 z?)?4UOTK%1Df6FkR(k1Qhs@uTI_|hVM7!r7wf#!3y}PL7cV8Y;LS-l+{iaq$dzWsj zd$p0u(D?6r_wD}t5}6A+qLz@YFv)*h^YH!3zD_k0J;(LN$=Ckh^(7BKGry$IS3hW6_webBn=boa zqto{D9=>&!Q|1M4K2Vn8>1DXTI;Xgxr0~&0jc-2n^~SU}UT@^~=ndR%*U)qi<{v7g z-FoS~Xa|t5 z=jBdcTQhB1b#8Uxj2Tm_a;MF#s;;h?nR{M-RZVq4&H2-(&s?3idU|f|b~+|suxVX4 z2E1j_<9oDH`*8Gms{J_9ic!*h?bF+*HT%ygMrp|)YQIm@maK9XkdUaHDv zw|y?-(>#Wvyz*P|3$0Tx^*iA|-0$+dGv=8w?;PDF^T9IjjCEd^e>Pwx2g>Zk%=B zW#eoz#J~}_0Y}r+I09d-Yovm965A(w({u-?x9<< zPSk!C;%Hy-s|NY_K}t&Nf6)FR2kk%7-$&lwnUz{v*Ym4jKd-hu&aV>OfTKB3{3@v@ z+CJ<@neeOV6ytf0Bki`L-iukUui-05&v27T%FnMFZ4}V1-~~hx)Q>|H?;p!pdV|{| zX1@w8(o5OB(`b*t4LHW6efze{BcO5+j@*~=krov*d-SLJ7>G7*I!9Qj&nINB1JGI;~z$bJ>Z z+iX(RY3Cr#flbi$u5$j<@~d1nTbdc@SHX{hUj;kC-@93_-CI^l^V4bAk z1{`A}HIBenzYhZ3fMZnrCbUb9V;5W}Nj@U{#BiM?&1(v7z!Ci@ z@J0Ga#X&g%Mgy_#;|BNaE_J(;CAYX|0Nh5Pg8ZSF~_tH@}{OGpziyFT;;OBGx`TbXlDgDsXwWAJv?wT)EWc9vh zf75^F@0&b*ROKrxzqJ3==kBg}^W*vDTn2qmX(=48J;HU8+Kz4i)oU%>bcb^elDsnf z()A_YHIB}8lKtv;xzm<2-aAMv4`=eYDTu`PJdbDDbOq(WKA}6LTrHX0`{sfU ze6_*{dEoc%w+lM(6$&5ZfzP|&F6h8#3LoTw&%56)=)iZdUiPE(=&ASH)#H3N2?HJY z8iWt>kY9eLy?-k6few5JEsZDS*?jJOb3q5b2H}G|@Ok&!1s(XdBR`43XBf|`pTHe- z;ENzX58ox3Za&a~FH7hl&(6=i-!ACD*Ch2F^1!F}+qH239r(~*AP;=B_EdRx&<}Lr z10Cccou6s*sr_rET-g6{m83xr{Jr&+^JRX}gFgrDT4K}#)Mv;AKj^{V{2yI^AQ$xk z|2R--?ex&UWxv;yp{(&F#;BOFq$OT_e{-6i{cHxIy z$vpire$TU!OK{Bff%g%D9LzVGWBlZW`@aTT^2lBRj=&8##-v^VUtP+kaR1jM3s+$7 zz!A6sM=$_iou>U?>qi~HHN)Vv-p4oG|JAn2k@&?p@;eiz^wTfVGfK~tq;Ij(eb08i zlU`jC-TxKiB*x9?L$cmW?t|mwrRV<5>qlbTOmzR(NR!Sd9$%sTz3AmSZm)&WP9q`* z?bKIZxc_T=&HJg*14rNn98mz^tJC5Budqv3iy24W{}t^!S@(Z6Kjr;jk)L<}*Fm2B zUy<(W`@dqm!T1xCc~^`}xZlQoFYX_4Kj*CXyj}(GJGJ?~SdP3eCUNXC8Gp7N-uMGK z=(o&EedLAji#cKIQok|&05{+mxkTd#e08D2_rCuY=`?{0v$L z!F?GY;rn8;ueRU@903;a4c`}oMY?9p?NPliCgW{371Mjq>R0mceKBnnJo9$hthM*W z#E*hsh3DDuw=j>6=K?|V`3IJQO78-4sB z7ScvQKMpx)|K?^NdG*K9yFnbf-h22#?~C=c_wa!maExryI09ddUn#wZ-?&fz=D+7u z7<(tHdDX`##e17@cvRIw{oDB$`{D##=XJHEj_+vEI*yl&pQiHRU#D3%_Qm0L;J1!i zbE6c=zb_7O1CCK?-@sQ}g5OU`+YhT42BihZJUUq@7Xoln`ywz&z;z!S3kgF$f^n2A zRe$D(qx+4`I(m%QD$YvPgD759Y>ZM z)E+fAi%agn<6|xLNf>wEA)8UM&Cu1q*64lzUe^z(J@P#l_LKB$kG2FQCTaih z_^9TwWZcarO?|qj7WxC578knM39sMP*~P;=mP@YBevm0d>3DrF( zFW28}I)QRvoqmp#%So$$^AiaBy#UaGulYk=ZphO-;gLQ6$&5Z*?jKzPe2DgQ}`ece1=h$%5x0J4|L!= z_%Cfw$OB(~rkf9R;A;>*$OGS3Qr&!@1K)PxgFKthtsm&X7ZEaH zvV;%vz;|UYHy`N0*Cg!(^1x>p)#@kg2|DnB4)T!B&$RjY{SmZ7tUr%RexL{b-g?gY zGC%0Szsl3T!SAi_;0HbUbA%sqQIBF$uTU=VgC6|NA4t0tIjB#~0sNo`e@yrx7xfC| z@a7MC@b3_Q$OS*j;pGQC_-ln9at}(q3E~Gm_zQ&}a>0*sc=HE6_)X!5T-5U*`+*+( z2mdMUSmb~|$bO&)e}nKtF8HB`xBQ?7|90Vr-0fn&9Rc`*9{dsEhg|Rnl^^ur&k}yf zMZ1j!A`?_*Y39a={-| ze?bra9N~|bL)vjr{RKVv;ZHzrlkf-O4|?#!K9CE3=z;6p|M2`5{1!ZKjQ+^&zaU@O zA9#52#5!VL$MrsqB>dcLamvdyX8PZom}klRHxkEcq~4sTW0W(|nDycJZy*QvX=b&b zysrEH%}ZoEwx2gFPSJa)>+|lX9bVe*oCc1-4LC-mUI1UMqtE*{M*ac5#=ia-O=LXb zV^6-Mzj0;+U03ra^)y*V^CoE|kG`ie#)zfz;a{g&CbxrA>EL$oqEEh>n<+W;rY9#| zeMVoGw7q|UBX9$bkOO>Gy8dRGx4dy5uKmZlx#A-$PvPi%{{}erk{fs2lTMK|u0xK* zdj3n}$n}D7%uR`<@)3;VQo`^`;t9f0b9-{?gk(;hykC*)?uR=;bcaMRjy#8ia($a@ z#^#O$lyC9e<&o8=FIs)^y3KXlHf>qmp-hFzqQE~9AC9abUg!sXpdaX=H`35=OYPcC zHLEYIx%4np{3P{4d09X8{*;WfJibVC0f}!+!*>@Cedx=ReOl;}==)RfbKv*H9*}k? z^EmLA;P=6QgFodo4nOzf$CRaHTfNTeC1qm2Ia%{j?T;Xit`WcIY(GE6nT!8#^s|tI zel~WlkGxW|QcLNCbykOdPO@k}XT*;EKk(U$(^4qpb^{eII*Y9D%R453Q>*9^7Z!iqn4zeC}t{>0el9^_YK0;2kg3I;(vtFB$uYJ_tv~ z5ba=+4~}k+HTl1j0o;INMA|p-)kyfPvl1Mg>#PEBim$T@xbA~vAz{czFpj7z+@H0L zWAd-F0&c(&_5r^7JAnNB_IlMGH*8vUfR4&X{%6~MIx6tJ6LKXD;Kc2O1E1r7w7#Cm zI;;H5eJOmjj$>=>QFH73lld=?kG#$b<8896v+`o*eooo;Ix9S7i?6c!gx6WYzkNk&931P{X&e#z`pt>s81x+!H;&2w z9TeaO93wAl9D#3gRO00S4hnDsj^;|;uL9rXz_AB>2SsoLj*%4_N8qb*pBf?Mg!_n>8xaT915l7$#9Dy6~Mfym^K{&!+<@V_J z-q+4Fz667UDCil`eO~&c(m>5Rx(&_ZIlZP*R^@hiI|MDW@pGQ2;EZ5&bIhkZJ z|2>ZHpd3T8gunj3zH?#Oua19i?@J4eGUk7PzJoGp!PoXYOW#4EGVB>R_Rf;JkNTGk zr|+Op8H$VExM}wjgC~?Oq3@uOjK-tCcH{JG{+aSf+3p+4s0?M79e;E2zN2$Wnm>E= z?pb4>oLYO&z{c^He|gU5(svfm8oS`3OO7o(kbU=^6=N@p?SCXQvd{WkLFK*M@7zDK zJXW#0m;P=8mxFZ;L(j3m*~JAVMUNh8eCw&NH)g)^dSmt*H&T7np|I+yS@kc!FvobX z_-97H3+M1zo@ele&pq?Q;_^e0#+jp<8t;rO+T%lFe+Nb8A=>{P6mPDELGNA`Q+MbJ z<~zSI@9~~=QG8N~6n^p3+@Zv``W+OeNa#B#gYw*?^CwmEP;O{}E^dL=zJuZ|x{P6Z zUXb6<#-Ycz`VznX$t$GY=|lV-6mT6X#V;LWj*?@hLfTNjpLzP}Vw~eXjd)7VKmYu= zG?C>^yR!&L@%@W>zgOm&G4CAtp`H)Md^Dc#H_Q4~%s*p3+A01<>R;Ip<$33kBMsv# zslm_rn;*JG^~Z=~OZEM4Y=PfAu`?I{-?$%x9Qg6(sebalQK6;aIsXKjbL1dtoe2EP zdU8apy!}6L1a81FvRLYe)DvZf zdbT}>u7@MHgR`Zc=rZcRZXEkk9ZAjr9Dy5fjGm%#1io7PW2rsfxZkz}r}aD+`%QPq zg@Ac1v)aUFT+So=7Tk#9pQyavd>=cLmO$-wOo)hXMJ=wl?d`Pw|H3ybOCKD!^q92QB8ejI6B*Hrkcar`n7>wdf+_2Y|E z`ls*_+>ZlCZU=sTmF7ks$s2Qf8MEh= zIUT*(rdQXWbze8{@LYGyODp;VZ>n*!wZ`^wD6&x84Wt|~h8{%lM6wC`Gg=q{~?`G}6Al{$`m{8WeVR=ALYkb{0LR&Lu% z6^8$G=643NAE!A48lMpR&foA`7Ky#5v5<<#_Vb1w_nwB}1{}>YcRkVeaqZ7Wrw*3U zo!UOH?U2SBz8Ig7o&f?^tj8zzHTC$EO5@W}^iBXD{&m{P&h21<^Mw57Hfjfw>`L2L zy8AD*N8kn=(Z1d7(z`#~t~id|C*$73A1A{YA;t>0uIF#a?Dqa_BMC!3f^o$9mc(#O z{`-F51{_g8fp75sY^zjz#CsZi-!)yB*~T2OwjOC*=WDI+&z7GF%!B-XmVt4}uRYol zm@;_-w@2Qe4dZS$smeVO#@`^#flbi$u5uoJ*C$mTybI4Zi-&;k4Dfw&^ds%ZpuNOC z^zm!D@cW{tkdoDLMeQGceu(>=P1~J+Vf+Gaz|s7d#u4~x9lP%PqC9@FPw5SAKP36b zFW?3oBOhoSfv>jDIpuVZsOC2~ZCR^nU;YsXytWHUvp>P%EV?K32|mw}3h5aiAHjYI z>@+TPCo%z`t0WD2*vDHBIA7)mJ@|8kA9BI( ztq!S^|^-{;77Y(H<%-1;o{^Q^b?oH^)SW#6pL z0}9wqh`n;!n{N8^<5QH~S3a50bE(?TKB?&2n@{uCG_JrAxB*Ah3)B-WzlrSr`hGhv zO7A^W-LKL^I(6@fa~ghwVrN@ocnXGOB-XXBY8pdt&C#<9elrfnNPl& zn{#KmdVfymIdh=kc%Ox62mdlK^#{(aEwv$Q0_aZ zPNJZL1IZ(Muv`gLo0-O}pw)@}w(YjMt*cKti? zW6R<_T7OF!BW7OerrI5{1P3spo}yl&p5i*{uTBrBH1>WiK97=a45&2sUUI-R-rko- zx)+N6sE578P?&Qq@LWe866<*t@^0n%4n3zUOkJ182T@oaYDpG4`fI;>D%^@1xH-?6 zgQcEcD*esC8r45X{#V!2+Oa!*`+?fA`u?LK&HsArWnE9r<%!i(vxD`t@Edl2!}S#9 zMm>$DyUS<3_7B(i<*uhybsM+VY~Si{YTvVkkFJW_=OtQi$8}%=Q6gq0_dT?)m=-6q z1P5H;ddm7iALtkP`e#-C(A)fApQ|0tox5`G+|`TAR?J1 z19I?>BM$wlL_fY`VEy!+gRLLxm%k+U*{u66RP_vJp&#^tez6<1e$dHNFjoY{m{Gbo?%eh|T2ffX|x24~fnyp*c zZ(O$pl3R~+R_Sq&>*quN*7*H~ezSfYa{zv7Vnm;6^KaHo{`x^5=oh(8>j%BfCbxdh z_P1gE#+tR8+tYi}bK4qQzgtB=F1#Op@|e9%-IyRNI@}jO=mY&~|6A(^z0KEx_2bTE zQ(gNMto@v=-<56Xhk5%%6wJEHUq9#**Y7ua9EaZKAA|L)+O}nD-9@W6ZLZstVC|&w zsMgl6IzYev($8=mPBdhRKLUERzgp1;`bB>&ev05|9&qb-YWc#s6)T+MUfp>Y)Kn#0 zNwc1u5Fe*@1&r?~Bm6w1TZ^+^jroob&ZcLVya*3~g&*DLW7~)}^c$IwL`{^H@2?;9fqv1aw0_XrYzWe?di|D78`ic2YsMl^f)~~ z0lm#Tg7oW%cSio1L%*N5p&!yyJuUf zYBnZ(*Jqco;pf^vl1c59fgdE|H=9-4Va%m`{Q?G5)E08{m558=mY(lCJ26lqgfE3-=^A2x2&&f$H<^d z8jIQZP504nAiv)v_YXAHnQ+K@$6r6_1N}_V4|#~|=C)$N!twTSnjq$HN^YvMq z|Hrp}LLcZC8K?Dw-eyjKervZ?udhqQvTmAPj{5m(8~P1!v$mSby3k)g=mY&Q{|>#) zi2?d;UcYXA_3HELsxMuAN!{iR)ve}kUyM5Nd$JAv_|3yaLsqrFe$WT{K_2urM+NBD zvEk3iap-q{8~U|pZC+Mckbe5P2)+*%&GuiXh3|uPx9^`pALxhYKX@JlJfUBA`ut~=3)_vSW4^fI~BA)lPI6d`VwH+w0F^HbxUN;kaP3MC+ z^0r>z68*d1Iv2y(F6(8Q?rMGA4CFKjzP|G6Z>-Se?u2zSMghs9bu;l8>&*50ru6iz zUF&8hlI_@j-r!s#haiv1j0 zt$&hw030C?`0DgVvi-~ZaLp*C^-uLr?C10n!vAzS_?g1>($KNq3C|gj#`U-y^+V%$ zD-qlKTAZGGLkb^mN@vR14kq~GXmdl1Q=)mI;CMrSz5YpJXC58-ZDKvb(!_AA?I4Z^ zZ?tjbeJ#+wlVx9v!V*26AdX^Ri!C+T%d56+*wm4mTmn?@YY|75TxlA4XkUw6>-6_o zxW8uq4EbSS3%+OK<3`867JD7(Rn!LfdJY{t;^F;D|9veKQTz|rPxrnSlz%7fYr&bM z&^{XGPa5L)Ioz+AKXRv|8T$Sh;}ymyj8}LM0OJ_e$6}o*){SEQXIwe@F-q3AZKvP7 z&&BAzHEJAH<5ih^yfPou>!}gP-jMO?FaG1z4jHFvzuo$H1v%*F%)j}_D=n(j<(Bbk zXWm>_S|I}m&_&wb=K^Dw znXYjJMR$$4e_sk3yYdAV2$UPsIGcMIIGd~#| zcb%{OkepNe`_^qVUGFOA;fH-v<-uAU zm(7-Crlor79sDZzQShr!F8EvUr||v|<^$kgfu3Wozn4+W_pv+yFuL#0)iesA11_L?3yz3*$XTn+~(KceR`Qgs|1pr4OIPxRxEgZ6K}>f=`#r+1!TwdSkZ zuR`3yui|-t#QmzEc>t#m<@KxB2IfTZt6p8D)@@};tc-UUeONFOAJS|gE%%^@5T|h0ms-MG>-0eseITywsv;kG3daeb%y``N zmc}tEvA0}prJLlrP+N4k&i!f@CBS!IYU|^1ydM^vfqUd--4CNaX!(pYhoD$r?bu&c zhm?H2FCuWLAGUE1@~7L%%Z>X$gPg{Gqx)tl%oXnAKN;NHDpzM;2adoEI7Sc2{jK1u z^Ib#j$Mc=i?5`f+Yk71OY9ixC)k5@l75Ef<73{l%>q3r5w_kE19Pfu2Ur8^dgGelp zwTxLVx7|KI^RdFMm{RCDN*evbIfdhK(ho;Z*W;3vAXWm}@uxw?(^zF<{czJo z9rLG6M?cKZ_fTH%^Jf&@eC?;M^+_ui%spdjB`;9gT35GW%j&hXg7xAWP`4dN9q&)w zP0ydX4xB}XMa&Y~vn~QTW(f{Rqn@H(qMpY7pzA5>uco)h6>l2j4Ew6y;CIeTLupk{ zzs$ZbJ$;}xn68pOzZ-UC-S?7Tx za08CG9{|2OzijgR_C99k&FPp*-$EyM$^{;`{K6e_(*L?1H?DT|H#=z#l@AK7^f!Ka z;hb=*B+YhH4H*N8YV0t_`QsSP*3WejLtdhB>_6X8_f544;uw+lb|Ak{))^ul6i27( z`!dFn`x}%uu)mr1zUzHdervjH#i`4<$Jty{-R>1jwQKBt@-O5w?v#Qe=I1JSA4e`F z6lMM1elEcbsHdoxsHYHy`iu0Did*w<(BJqyZ^k`4mz(<+4$gDt@&5HxQ+E7stVZ7# zM5kQn8iydU)-zt3tGCCWA2E%&)cw6+9{X8O4$4o^KjVKH$DK6jgRiyk1;6mxlF~S% z{0RD9@YF}oD!FXjq~b+C`>2sgf6<)2XW1VvEC1yQq0?>9214dLzkDW#lq^e9Ycb zDnn`hHC zlkcW)A)kQnCv#yad``Zf{4jn0_=hKN+q3!J)Ao>zJ^ktX$1Bf%qxf6Xf5I|~ucPlD zr{7?f9*n%&NHXI3Oj~w+8ObQq(vtu0H4op<^}`hsJ;(LN#n|pdeaRaS&fRlL(a@4( z{&P&@?Dy9;a$SO$5x+^Fh2Mz1-wWpc>Kyv!a3Otj_)Yrea2kDc82t};IOcY{hI}l{ z8&*&o_M>z1@Nb*Ot7~?d`d%=}L16#Q|9ipUMGTI^i|w+p9R6OgX=>l3)$awTrKM%G z`MuzD(rYk(FPQGzD{IbQyKO@U7b+Z{id}-cr|X|vy|ya%yxi$)Yo<-B&aEz-F=J{~ z?zEXz)zvjKbI;4Ks;MrhIe+@}nXB_wPtVQWPRHa6Hm&Ot&~;WJTKitGw-1ywxAl7F z`xL(yJczzw{xp48{ZJ_>?kJZoi|(&WRk`fGo6Fepd%?mlw91SQc`BXIpZLC`Kho=A z+Q0AU;yQYhxt(u`CR8-FS5;z^EvcH`()#T9yN+umj=rJOvHSFMs>pYB+){#lrak*K zfQ!eoY)YUU*ne`DlnZorJXHMzALzgr6+XxV-<7@Ge4qp0D&d1X@EOK2YSs?8`0_K|`;LMRd@;6<|NEjvX7?3kr(Cp!V5Y^{)xt{ zk6O8!EJ1d462a->BpuX`KlCi|-?CzEL#-$R}UT&6FJUKAQS>a{Wbn1a80)S^{5{uD`j0 zR%{Df?bo3&II?e9ZBtjgur$3$P%_alhS=jD0l|N4z{$R#*NJB1vy z(}FO`p?91e4YF-zwyV7qh7zQ zbx6z~25nc5fg^AOj*%B-9ll???CMGdmQStU4!qi(F91j21{}?2b-xOHyR(jV*#{a| z+li$51ss7JaEv^oaRk0vY5ET8-qHgwfcg$<{Wg_$uE+i@^>G|-*HMf0*tjm25qalP z=zDvG#`9@>`1=ZAu#s-qLUBG1>kh2q_M8v=JWlfaQw2K*HrVQ&h^Sceh&LM@Du%TbbB`TdSxy_GU5o_ zfFt~0;HxeF-xY>Ywckd8)72{d0j*WVdHNaujdL826vMsZJZb+{IJ)OGvr=BAovdFa z8h?p;9eyEQ4{DEY9QAr-WSNXO0yp3YU4bvsM=B1&k;{Yj=yK!iD`itZoz}wM zNb6r4Xw569AEkA%6KE~$NqYTjef@VXEc@N@&+R?5z$oLjJ&(}(*T?s)+4IyLdj0DF zTK{^>y$6d&oNTXuopjEJB`b^F>tD-v-=NmNJ~;8_;x!|4N`4bPdiUkG{rShY&zaqL z!6gsPN&WG!)cWHOhZP<;blaU3k8F(XU-Iwy`>y^|LFM|oJNG{@BUW+xNAt_|`XU~8 zbU0jl9IienN3zF>uHE&c)0iEOh3ZSZYjK@xsgF(Dn9zFOL`uNzR;}lid8BYXFYjh; zx3^T<)rSMGKfLvpQN_H!Vz{1HnWV?BXAal%8sU0g*ZN@HE)w~R^}OZ-dYxeV*Yhr` z*;q{fU%K>%)9ry}`5Atk2_xfSbfzK2^$OE5Ytf6@ZdmS+7zy~_WLpndx=Hq95 zs5e+o*&y`@^eC^_zN{zngC6|bg&%TJ?wIHSx!?yq_#?s(x!^}Ry!@aCf0pn=F7gd3 zKj^{V^jF<}AQ$;J2b3T5;NKoxQTJ@})-54nYsZxBD|!M{rQAs75Xwe_qdveh$xtW1jvF`2xq7=;N&q zSdYQ;MEqQspKmTsY2~@_8M2-iaZJx^Mi1y13!H-1^Wr-4kXYg9p7+j8 zd7IYrwuYl~J+B{*n%k385+=D{RqJ`BpMHs^84{V-r|!R71g|dM?_zkBz_^KV662;R z<0i&a_!}5^F%Dxqb;`xO{AxPM`;6yjE>f5M`k3es&h@$AM$X=6+=sMVp8}4+4LC-neFNVkt)ySy#AR|m z_ir0V_jCUvty5>_4;+CTa6}%!H+Y}%h-#lF(&SP;9lUQfugT%SHs-+RI3SJde6IC< z#`81tnTSFVj(iQ{k{^z?1g1>hz&I`?CjsMbHmU0M8T_;dHh$N-(mAYC|D?)rI_a|Z-;DS${i>k(h#u!x32wkKQXut1@O9R^ zBk|j>PNN*$4)`9{?>^S;SMfUatH72}KNlzN;y*AR12^Cpovv|ox64xUkDDH|RXV4_ z*jsym(|j@}7w%NOi6k7+i>+Vx&PUMmqnC`k()b7-k5^{c{h1$*nj3{+zse3N_Rntr z3pfHd;24vB8u)5OX??Ttz4(0&uWz>YXdKyB;&qK2f^fus(*f6ga4aMY`3S~Q=cvae zKOEidk=Hld=-Iy@rTfOf5x49P{$d_mvJ9~Z2hR$h3;vCkLeOnIW< zj+=KDPC9pV;o52Y3g(S|vEbLmy9$V|kbggW(Y1<(Y0s_thDtv&;a3H3AOCd0ZwJ;C zOgi^g^_zK_mSd)^%BU;oKm3qNGkwK^`xR~3_N(GC|7M=cm)u+M=BhgsEz2w0o>Iuy zI6uC|{Exo>A9ela+Chc4%=}rwiN<>cxAl3wfYTKVCR5qV3(rr9&Rn>D#jIEE+c#_2 zKL!@P7g@F)B`PI^*orfxl z4m??2bkY^2Mg5+hU3B>qk)nRD&ML~fti0&sFIE)YI<};!aop^pcN@xz-b^VfI`gJe zijMVB0$Uqm)7TKk2PI-u2k-XA2LcvCVo7U!pQiiudiM73)3m&5 z^ZHF&d7mcddt}J(aN-e%!5owQn3`Xc_bnve{wJM|+@tqrirx9i?$d;FV1J+;Qm&S& zx%V*w9r$X65Awk8-KPn3;42h9$OE5upC-_O&lEn$1D|)FCeVTJ;9s;LA&O*B8)%FDiVHXY;x32|Dnt5xf6#;f;H%O; zMGp8;4sZUT2Y-X`LoUh+JO8)5B}ztwf!I${HRA>{Xq}@nD9d`_@RfFAN1hgA^eaVZNq+`2Y;>b zLoWKQp!`7({zBo0T=1hD-tvPU{HE|jZcaeI3;aP3{)6KGKrZ-$@B=;g8-yQno24HC z4&L&E9{ljXAQ$|wgO?xl;Ky~y1wZt_b=IHX3x}VF_rjxhyZt@Xd$d2aM{j#}?o+pg zMkc*a-I`SPvHjllGFQqzb%^tIy^l`TG0K@}%=)N(>O>CSdyC}y$?LlN)SX4PWBYkS zkK3nCa08C07pCB=?c=jgUH#w#e9gH}-CQ4gG7a{r3%Jhrk1f7CJuh{PF^}Z=)oGUL z+^3Geap(=28)tjKK6TQbfg?0`wM+i)v}2z-{_eDOrN+^@PaSXy+NTcJk%zhFZ9d6ZU2y2K@~ zE{X0_hj9|)X6!q9+{Abae;a-q?tiv>?r$+}E~mS3-cKcs;>9UXDI5FsF_Bj!ABkfp z>G91x(Qlk<+r$_*AqVZ$S6=wt>9!4egeZU`a08Ag4)E1!zwb`x?dNOG?@s?vnDL!y z?{}whUC1QT^Yh;CPCr1k`T?TwtJBQrYzKbt{cCQ?B8el$k-s~QcAl)?osQl6oOuuX zM_X#Lmsf4uuxV?@KwV?dr=|@W+;s^2?lkiAes}sgDF@q~zdMa|SO4xb#v6=3ri?!r zmtr#Cfcs|LKQ_tzFbu|VF5$rYLpIXUiyC=e>V1LZk15~jMjSa_k2}%h4sZN{9Q0fM z^0Ldhyz%c98)GWnX`KrCi~B0|-RZqY6EE-QC2{=Ovh#bzTT@3Hd<6GfoEO`VZO4Z< zxc8mml-J|%a&*r$#vkAY9H9;H)#-j3x4mgPF6H#zGQ+5-IDu@LM+b-R>ZrwEwl@d= z>w5fQk`(f$D5mdDbB*SsZ5+`KSiUo8Zs(cg{LMJ#l4@w*FOmThsj=`4(#0$+hv%2j zFQK1`{Ezlu(SKoH3H~ztW6U$b%J2`iQF%@yA6fg!zgL(2d2BR}}b z4-yw2H#+>}(!O?@{bat*fqfh2zGEtVi{0PyI1;_tnNFeaipbTr$YHb=PA(Z24qnc_}IABuU(?@mW|y3^)O?sW88 zciLRJ&n@pN_x0%ePq?rD&7F=Oa;MGb-RbC4?zH)sI~{$S0N8f+kQ+{_kddQtNpLeIDPr1|PWA1cxn>%e@=uSu1xYOoJcRG5CJ8hP^)6w^T z?kT@J9X;euo6oz`(Wl&L^D%ciy3L(7FLbA)Yussbr8^xx#ho_G-0A512R!9>r=y45 zY4dq^I{K75Z9e8sN4L4t=7sKbbd5W0u5_oPr?}H*nL8bQ$elLtbEl&_-D&eCcRG5l zJ8fR&PDi)7+aLBR=haf&Un9-;LC*AXo;3HV-ur??`d9SNw6KIFBpzo-6m+&5i# zN00ZUA9D8Rq~DL+X?%ZE=TGuvoo76KpPN2i=V_j+RwZ!F`j4nD6O4(j@=Y)%dD6$Z(wHy7{0QbtqIc=~jd>Kz-(o!x zo-bmZ4hox3emTA~!p{|Z_ogf9ebn@f-yDJY3++cEj*Qdo%p7C4H^@#TW_@@)4&>lI zJDTkyul}V9EmP)8jQUDOVc^tw3IF9Vkoa_gYss*^vm*!%UGXO{61{~48 zf$x#l-mh=sGI3wV$7K5L_k7sh0+avySHKN80yp5R(&}3^^sDr9l|Q-nwTdfNJzTkI z#-nA6Hg1#t6nlIcgFg!Y6z>PZZ^b%u zte1zMigoDDBJp$FuhGH&Dc9J%)N56}^&11sGVy~YjvOWa$1op%(wRxXZ;XeKgMKtR z%1_=3=`Y2vQO~PcF#Mc=Oyo44S1bQ?%~wel`89}J_%;31^Xk!Ljn@2A7R_>l{8P=1 za=AxGoK1!pI0Cmgj{m3c%Ym=fwaR^;aX2{Ee@^3wxCM@S-9e%_zD`4^pI_s~(LINd zOr3ljN5Ks^#$M1k0^ek)vxYH%?lY1JJsTC^2;Aa0j@12XRPePqy7RAr#?{@q_^p&Z zj-%iP9L*6LN8qbPtSL8)@9($8QM&#ySabUU+g9Usy8in8chN~a z=g0lvok9p4k;Zjfj{2o=>_eEzdpLySgU0h|chM1yBl|e;6WhkoeczuJIVqa{r#NJc1yU-$h(VF{gG>JFgfsU%b;oIYs0@4hkG-?xgtLBCJmTb^ za2blPd*i0v_x$xAB`b?xZKN_ZzLHfwW6lri_m=Ixp^VBA9bWQZ3qpq2JU~2U(70tch-T&DS=kMFGbaLebGj7>`!mr<{ zxa8mS%ef5tP};TM9-7S<`|CPo57wt(-G)7z@3r-L;I&tZzct;~Cw5<2>A{HBN7#*H z@7qvDGRP)9BV6-v%Ws7ydd`Vg$E;Ofa!zqUN#Uc18sB{C>y2q|yx#aZs!Ndd`NEu{ zfBn9q&PQ5EiGf@Ahm0jU7l2V zazZ-Ix%HM&#k`_*a2|PCG=7JnXImh*dTmwidAZZq)=ZmLom*WvW5(2~+-Wnbs;g^e z=AM^dRa0G1bN=+{Ggs%Wo}Qb#osP*DY+9F8HY=fa5kG9rRQvJt3x=fmT0Qg3!J+$0 zy?320qmzCeN-;`H1{u#h{q!8eI8;hf9p%zx(fOCEa_QfkH<$5g@+sowW&eVC<>2!w zAeLle5%;@1?~Hk7%sYb{^U-+E4fD^KZ^nCXpy!zV(7hCYh2kN!AwXX0uT@v*Hx@*{ zC-WAFhsgc!ARV*3_5}FxQIP{bKISWLXoZ&D3G>eM3{XE$m)PMi@%snq>iHedHYPUj zyq#>v_VWh!e5Ly?+e_P>)4&n90Y~Tre05shcQZz7Jmd4uFS*`#d&E5;LzJqoobbP{ z+Z{vUeBW(XN?z(NlIK^aAsFr85?>sdJNX0S$otquWZ$ND$rgT!K9TM;Qy-u65hii1Fy;e3T zJ6dAX?!fgto48O8>_ORdC}@my@68E1@a+;l$U}j=d%1!Rd{NE zfzP{_E9k(NBYcntKJQ+xpaWmC-~)L!pL_37(18#5LY~dHPTfTU2hf2JbdZPimAz~} zwU?)q3wz1cN*eSiZ$OV5>fc~Hde?<5p7yQt}TYk`kKTG%_cbDYr zZCAh#^x$uj{sVGhw;=n29{lKkAQ${7hqwHo2S2VuF7ov&zk1hP`Z?SqM856r@4yfH z!Jb|`@Qy9789kp`mfp*7c^Z%Fe*Tnsy6k0$c%ZI#(Y`_bA2joi84tCWp~!)68R_pQ zuj}q*xR`9m_Vb3G_O3Z_1CFQ{rr@jX(?sw1zrNqL6{Sm;sAqBP4~~%w{QkaQz+Q#{ z*ZJPN#a@Op)5aJxNuFPw<~+C^oJt3`gGE01YHp_F&;#}|l=ci9wcd2^;A)pUDhy+a z!cAYNwBYF6%MdsP?PZAT$U|a<Nh0%g_%;&F#r436tEfHj`fH zuU|^=evI$$x^@^q_loZK$GE(S?q!H^65}TB6L8;v@e<>ARQw2xpRf(a&06X!?7a*h zR`=6>eN60-9-rfJZ#}-5rr$V+T!Lean=z4tcIqoH+{-Z7kVp6ma0G6^5yb+&I_SgU!7)7XFKp)>!7)jNAkvu zBk$3I_MNPIv_!B+%cr!LA@ai>E&NUhA4?s3wD4YrNO$$U3^Cqd{6UMrxP<#X+&5wz z!u=fRId;DD%Fp`qGh2D*^ql8e&%ShMqBR`u-u?`3N*LQmpxFvA5O*q9Dy5fG@qAu3Z$O= zfA-!5&W@tWAMeQnn)gGOhc6}&WmJSf2nL0@lLyc)h?pRvpkYWROqP)3H8h?upMBHSMcjtkz%d|N@oh-v%KnG=xq`~ zrT1ZFYtbb&rJ{?zDafy0>MPMd0yp3oZI@ynUV4PTnV{v3N1->a@+<+s-zra^Ye*Pk{%HggYg?+1x^L4VwQ%WegO6Ww_$|v0y5iO!Tsij5%l3QZ#N}`K z$>66a5B|b0zklT2pCA47q9x~lW9u<}(bl8hdE}zApTGLl_JseGQ7is#wA{>#Ci&5_UZS@AsQT^bDC@-g{5pApY<^A|VH{n}gq zEqxx8qt{*6??B4Zsd!bBQ@wW8>lW9}ix1fHnzzr|{MJQFHj^$PuS2GKU4kr&mSIN zwS3vp!NqHi9JypHn3CUy$hIzj*C*>i+FxaT7T1k5eC@`KHTvPZ5OR@6H^#LKf3{!Q^}I+2y7^3p{BpW{U2eaKL^{xg zOo#kHr>^HEI-~>L((N4}uPbLizqdeDzDJ@SRV>FpHc zkMy9Q&-BO_^fvh;J?KNGN50Toz1;$TqzC=uk4yO@U(jD;(I2D-{RGn^-;n8S&yVz= zzl7m$}T z(R){?_3>x&`gnGe+vPk#^tJZZSs)+0?+m|QmY-iA4}5ntm-I6I&Vg|Qj?fF>E6eTI z$M0zPp*+A5xB*ApFV;(1A20WYqx-uA*DMPC`gn-y1$7C>=R^rYZl|B@nf39sI}LW+ z+&7c4>-u=~ljt|2>v`Qe_n+u5(eFQ}^`Bsfe$#b*eEeg!^Womg^Xs>i^>g52cTrDc z&Ik20Da+5VkGBi*1-=6ufg5l{v4F1(`}OgV%Z}y}j*M#; z*2g2fqpy!ge}n!fzE1WF=$BxxhurVL{?qMX(*MJRzLDMbJ>l*mUJlRh+bn#2rQfnd z`<$5WbpkiMS@t{OP3`qR$Or9K^yRYro)F_ix_Etj@F^`U@7n&-bJ8Wx(0)|Vignd> zc1!m@nk~-Irk{Pi5#>ku(#|*}KmC%_h%K%@LToR%=zoA4aE!mi`oVf4pY_WNq}vG` zKgl?<+a`|J3k=KlR4U@w`|IO@8*og%C~*Y7FSO3u){RkJM0<8!NqsEVz}{cS3EY4q z+G*e`aiqQ4zqmEWl)~S-o5Je4>W`MllhR;awdFp|PY)^IrwjXje(*7|SNq3Ael{E_ zU%IGYYL4R+*sGoUSKycmNBjZ4GF<2WklZW383Z2{vj5A?Vy7#8%>c*??rg^Qa~7Nn ziM2o@98n+3+tn)EH0C=&kow1UuNEt%c>MIV<9ig_T>T93%qy(VJ(q4P*RK~)K&%f* z*K!ga`R()ysrPybcnNq3cnNq3cnNq3cnNq3cnNq3cnNq3cnNq3cnNq3cnS2p1n8Bp ze_z=1F#8Ae67Ul667Ul667Ul667Ul667Ul667Ul667Ul667Ul666lx&T6>?E@6|{J z$d;(?KO{o({Bw%ioqxhfuNmx4XP4Gt(!K}yUIyRKgnuW$pTT!E_)Z4j&0rq|?1#YF z1+NrLu}>*|{~-9pc}T49-uv$S@y`|EN7C?JA~N9{H&}%AMC1D<>-C=u zKG#RTa-F8^)cv!|)_E6^D}Hyu#pwwLI084|7;^mvzVcz|JB#TGjue(Ss^4AMaKv{P zmir|*&KDTc&yFM2NBYrs7tL{;0^eOQZom;r3Veg0e?bsDDeB)>1ddlPI`59z_Z)rw z#}j^eyAgyntg`S{0^gW!)R(>{*7KlY*_>eqy> z&>zvI?KSgree`RLBl@K>93?en*V`A>$5A0C^tZop{O%&(5n{)bPyA^)jzH=5geP^y z{@OgAfg4`)o%H-FxJ$(@mm_1RuU#>6=Ge$cfw--}do*g2|AdG`Ieu-CKgu=pPheJP>az8{es1%Khk{BF+cxTD++&zXJpY zbami6-KT!V<3)B$$BXXYTiebfD}JY29CgTN>5ad@5x4=z`TO{^WW1 zk75&lp^EKXa-1RbI6pUsx4kD75T z*2mq&&RM;UBX9$b=x>2D;>vTSau0~ZQNZ^< zxZF#J-!sy`V7!0f>X9|V0P8<_)#{O)s(s(0H=O&n@sY9dbH_&3p1XMM@(mY_tXV%E z1d`oR!IAUl92KlxvvSo1M+M_Ywa?Gz;3eQC&{GmvJ9gpty5Z#`<42xPtwv8hn|~ND zffqyqVsNm9MhLi2h#}z#gHW8cGCh5}zpofG&_&~ibe@{e^TB&Zc{hI_iT8_m--!K& zF<*`Jm}kX%M>^MTubJt4$I?Bx!l%D2-xsr+e46dpE2WEkU0saFkq^e>;nijNow-=% z+ktuN^FAf=67$q>jrWdw&e%PjqjsC8w!L?pYV*{<4LHV++?DDJ@Rj&2U(`7qX{`d` z$gY87&=QV67dBPdd&eS$z2X@3_b)ZbZ5x4=z_*2~PGQN@(trPj*>HDcHeCN!2$gi#&IY7K~ zpzC_+1pBdzbtAaX(QwpxfI!Xz%nuHnMnC)eDy$o!@x_0Yyss+0KQ7MaxV+Cn;dXQv zI084|nDDp>_{!%vc2RKN^(k&Le3%T^|LX1=M8sG4VgH5Y`B@GFM}%>oZ^KD(++7Ga zpO^be@aXg##Ltc+)^WMvSX{@|i#P%|;0WOYUxZ&MSAFNGmO&lwmydJAVaq$=6`oLL zFW4W1>fBZMXlrH{Ek}-MydMe9YOO z-LiS)lXua2Wum`d&^*7bHcuq4e_7-;A?ObmG(`U%k?+kSuTaw05B`7WFIW~ne`oN; z=?jSdn*HY1?)~sbw|wA%Q@4mRZ29@$Y^?RYy}$O0zx@T3Veqbz_s;v&eJ2d<{quV_ zi!yBf+7BMs{O?a+e&cAzIoG%jV6QbBXx%oTAi3y~uu2`>F1v^+#_%IV|r}eZo(sm){n3x&}o* z?F)u#`-=Me;E6%-)R4$uDHqC0?vVH)Or@xc=Xy}|p)yXCmHJbnz$xe1Kl-Dhx|bI{#}c7NC&!6rbB+9J7$*`3BAhCmq$9# z&1X8~m(#sshDwKYpbMD}`Q>yMOjqfU4s?&RJ|I8Py?@sW(h&pjLpsnwpO7EuW==2S zfOMckI^>7&%;~v22Sg&6p9?zkd zAL&8=9Je>f_c@lkUXLMvqz8S%^vHMAQcpmS^q{|n>5=c_tPeK+NDul^w3qCH-iANY zgML2KAz#!B$U&DM(t|!^dgKdw$U&z^deGxO@&!Gf1NTXOH2;cz0rRgR_Y+8m=bq0v zKp%8{!a1+BYS!s|^rd`0I^0k2`}`|fd;fuQ((|O}YI8n#pA~()EI)rfI^?#axxf*) z0mqp2B4m7JxhKW>hCjbMe+CiebG7Nc{>%Bse8nct(~5UwjqlU=a;PpoVia~XeJ>!^ zwZ1$!emec^@0Y0_yhZ$|9-LOfS5l)CyNNXsJNlP+C~Ipja0G6^5xD?g85ZZG-?>Ve zClKLIpN}rT7ohnbf#aOM*v75 z?xLO|AJo&*{QUXoc0s*<2= zk^ZI2H>_S)@m1P+!9D`Q>}wbi5IxVIoU#PCl00dI1%CSZ=nr!_DBYDJ9l|^O`RLr= zp#O>ay&mj0*t@VhZFV5ZiRM;loI~I1)A{H#r|&M_L6wh!jDKUh!Fr$6u0McmrLafoqbw@n;v z>t>5M_WrtA;07GS%O#G$S8DOOqCT$MnrpNO4_$B%h5uRnp5z;UC+bJpg?+j#_fz}3 zc&_%7!FAK=XRnWGT@?4JJ!`&BulLt?0XN`?b{hCfT+Uq>1RuT|7;W3OY++E}km9JW zx3sLo%g#r)+%Lg#zF4D2KRb?87Fx$?!%SMY{^zfFqt3_{y+yKKjs& zTY{k{gzRtI5(NE2NZ-hv$wK_KD$GU%%DqPGUNF8WTcBh1bjDx?>Mm^0@Cjy*2>}>TKay5_Jcb@ zd_P0?%htq(KjnSr*^U2PhLiF3zMnxpSRb5RR+3-+`9-qaIPdb*#l^4;+5Wzk*9E8F z&tSiKxW;!EwD0^8;_DasmCJ(oKz_FGXCyUB)cfDh05{+mepl{S4t!Hd)Nj^sYXiqS z|5W5f-$Ai!;7H#=x#MX2ex`_H?|(l7+<;^B5T74VRxhF6JDuD6`_2P5;22)Y?KjuU zoer|n0{8yD^S})_Mi)yQfv*%xYu`a#&$7gR<=A(=lixv6|9GPK`R}0Q{!YLRIK~^e zK61U3kfeJ@itnCq<>4Dd)K@qfIDMAGz!6jyH~pUNV{%_0K0koQC30+1Hs+&zuWH>4aZRIz>Hm}6q_Y4!%pkNcZt9aIO2hTFTyXBTdo6e33lldj_LUUOh>v+w)Mxq{3O+n!c}&rfbIN%s{7^N_phgj z`dzP`EcV-v#2))Mi+%M)A?owQ-uqXI{q`rs9{Uu&UF^d@TkO4mgxqhxe(UEBTkzOl zU3uqMUw88m(O>Y#V{7%t_TKWqXXJkSA6#^Q?Y>(+I(WeAN5hMbn|sCGkvKo#nP}UhUw>fu?kE5Fu|=o*^8*TcvpoZ@X9bj{BfV*_to7Qt zgRig6zx}Drzj)xP&C{Rx`DW531Jg+}LAjr-RpmPKu7jFNQaC(QssSQPR+vWU#;He$^{DAQDa-ZiMjYInd70Q8q#%F`dab-?yI1iFBYli0P1D z&aX&^bfBALc_Tm2>E{O^9q4XjI^>tr70(YqI?%;Thx~H7;`sqc2fDMF4*7vjKR*EJ zKsUg2$PaXfYUc+a9q5n_`5}C$c76c#6#JhithY!H`RI6(zR~#sNDumJm>&5~a`|<9 zK#%mGA7y&v3wfa&C>Q9F9`y5>9{GYE<5(twW|KeCgZ?0#< zliXe+U(nn5BR%MEV|wH}!1@O{==_l$^k}b3C;6x62XMcD zdGwIONQdVJ{xLs~u20wpncl}Zogc7-&kuk*Cf@_cYorVQt}Z=KdVT=sgZEj{y0ZNI z`2ntS-QMYdBX9$b(2J1qmF1%I10KISFQY#{AeY4p>jRF!4LG7yz*mOFc>%$T?!mnv z=)(B{`Fzg~eSScEhitDVzERe0X>f8hQG{ZGvAWzjF;eFfed*z9=H z|5?1A1?N8;+Ti?$Xy3tIZxmyI%v~k>o#_1b`k#pNLAw=SP?F!B7Z%q&*P~*%NoA(z zq$^#&UO!#XignL$OV>RgD%P&k&%Pdr@}qoh>(?bUrJ}32%1g%e#$WV5zzsMiBdi~c zuYB6yER-&PhtLj=0qKCiF>~cS73H6w+0VjUF7TPVtp|3!TR9j-6i?m`Ju%!-}JkS`N5rw)NoUld-M8W_wyh25#P?x&%RzC z^0R$EBdJle-v53ExBvp3{Y(+Z z-v53ExBh2pvg0HX~Z0A3GR=dwZIO%tZyNdxRpBo_dyZnkk;f53m z+jmg@{0AvM;0WA+BZLcl5q_au8;%&S+RhEwXD9uhEu2&F+yJJ-xdAhN_*1GMg=>49 z8!+pUwRh3}#jTzjP+PWm!Q4y!_nqq>{+A)5fBTo-SU5M}+h2RQaBje~Q@56$8?f&3 zwF^WU7To*rpACNU%4xNwhyL~(ui9_cEk8c!s?C?(v-jZr;@p5&?f3WdJ~MjoJ!gLC zBa0@(8}I(#|L>Ho)8-w#_>Qwaa`&wd|HGnNpFc%>7oUH>PC~4DjsoP-?(-CY?BPJ! zc>u0)60>%MW1bs8x`Z6Na&7>>e^?|24B5LBFf4KNH2HfNnXG?-*b{tknlkX9elh$_ z$@_;?w~0#6ql94#QF+;js4tiiboAVS0Otm@P$ZwU2PNRo4e;j%2(>oGH!iBabdgB) zQn3$a@STwMPueJc#dfQ4E=w0JT|TmABv?Ou!ALN?YE9$Rt=$#H3c`1-If}~F>~bwL z+W+`Exu1Bme9P}Q>nHO2C>Oc#t9izoR;SMwex>-F4?Xx`dY;9r&)w%l`pum?caYAw zxOU^gsk`5_{@|VaI0%NBoxWCp)W)@R{qUL0jazQ@hOK5Dl<6U=(BGtb^ zdo|HjEH~|Mx_cUL{{y`Ls3jo*N+D8;G~XKZg5v99Ki~N?#0EpcPrri#I{opT;ui#O z6+cRQ5Czf;<${ZNxD=1yoy>>AeZjtpI;qt;)v?p%<1*Gs@lU`k2gkeNZS z=ahP4|FB*HrUdBhG5qwtUxewEqPTY9^Q6$f=C16|>$Qcaopk!*la?$y>7=txI>}^{ z#>jD;2_63GrRlQm)(W;1bObl zHza;_zC(Wf3Q01#boAS{a5~Od-v7G(g~Q_`{fpMFSwA*BzTTGFj9+lU*}0)rHwqr8Z|Me1>k`l=5FtW#*NI>bM%lnMd_OnT*&2FC`?$?*vgQAJ;7l~1~ zMZ-Kq^_uoGra1BsiJy4A%ljSIY0qPNzCn>jvNM{#NJ)t&#nH0}o-M|wMA@Iy*Y>Yob;0QR{^i5VM@RZst?6Gsx@x?CJe^d^ zQgzcaG_?C;hlyt;+2t3l2a81z^G_r{DTh1_`Kp{LUCg0ch{|`3P&~{VeS)zY{p!Ya z9`A8>lfN#WiyeJU-oK6Af!kzw@@nbkrK#7_d4aw@v3j#F;5mZ6S#sO-JQ@3-_hBU0 zS41b5t+O!5jEL~8d(!epeEy9t-*|B`UomE@6E0=gQ08X~C8hd93O+43gLGV~a2nVB zKf4O)iPx&ZF#*jOWwS=LVz{n_gSNx@lcnuUCLcUJ=6l1hPiXV2^_r{?Y0BwAU62YL zkPCP-DN{`0>J#%+hl!X6zjZ0qBT;^F^5xZ6rEQ#Ew^YJeY2cj2!JT_9Ca;;Vnp)(a zj+u&a#Za^`4^y2+-f-nz`2bX_=_=O!e;ochdIo|mcw(P7rR@UgH}S#tel+h3 z`Hl#`#6nGryu|uUP7l4hOe7}Vif_F?)wlQxb~i~E`iXwS&`;K9T|c8Ui~5-)joah5 z3;jf`gKm-w{X~A<($CRHy?&}1)7TGv(p{4((>Io{vwpH(x#%aIZ;J6uo!ifF!M9R< zPM(nCoLO4Yk~50>xoyRYI_WU!+-_-F_1S{pOrf7?y~-C^WNj$X1i{dP)RF!nQ6hxX zenRvQWYZ5)|3GedmgFBVDV78Jj{Dux_xKU1@9|Tzp66xvK2rj!E~tJpCG;Kgbj|Bj!M^a+m}Fun}GF8j;yEyaG5_y{p8whG1M*E>c3 zt=`P~SC8g-Ml2;XP1Cl_f)@0fqDq}y3|7&xW ztluwhh9RyE_zAu zhpbPem!wlM>lySA?_)oV`wvJxJV?_+lqf#6H9b6+^ibrD3wnrrx}}E$|K`Ujka=@g z)jid3QbG?QUspYZK4p5ydImkje869@9)@RaO~)kz7ClVf)S4c?i}VmLDuow%hhtM}*znia3_kPL+fu&> z@_X?h5Z}Gk5P#zCZ?7S}z*Nj{KbYnNUBR4VRb6SemhbHE6xa4px&396fT|y=-%L;V z%@)y+FX;;GKwvDQGM&(|kHk&PyZw#u)3^#loS5r(c;7Dy^_`F;3b zalcDxsNN9IeZ-IC0)59^IP@Jhib$fEz$mBb@^e_*#sS|~k|-T7l5g41Bq#jHMumJ` z`pP*XJ=R1}UrD-)wG#0Len;~Z+pGu3cQEk-Z0qv79^$)6eBgy|j=#=2wu!$%zLQazJ)R9N#{}+#pU$}nlI{$qJ#N%Br0WSeB0WSeB0WSeB0WSeB zfhj71{snRkR^!JfyX9<=1op<)*lrnR`vcUAMu*p|8Cl&=wo*G6_#uDr3t{iD3w}pe zWCnX3{E$ERO&l-nDwG5KXl-?sOWOPL{Q=1n`Ga4?{7???n_GpMpusyFJRkB0zsdRX z`A`n_@SB{+^^f^x{3evI`GVgu726k@KcEZpME>9xGe49g<5#hL`4h=5}-zf7#IWm3~+n0khe#jsELgt5ZWc(_&FX?$% zf+zF^`GeoY9Ik)NH{(~aeR+xYe8?aCBIbv3Wc(_&FSLIwU3fm^4}OzJas6Yy8NZ6{ z%gZFYj34p`znJ-<92vig?aRwGe#jsE2AChp0e*8UwlA;H_#uDrOOE9F$9yw>72B6X zG=9h*{6?7{%8~J_*uL~@{E$ERh0G7-$oN%kUuJ3ikU#iMypHQ1^Ue5GY+nx5_#uDr zi;Hc%s1m#v3)sQ3s#r9=D}-vIMNIWm3~+m|CXe#jsEk^!!N%s1m#v3mTz4zj-JqC}@lPP;J2v`Ga4~{7{aJ9}3zc zKU7=rL;m15!2D2-j2{ZxB0p4H@I(IKm%NheAM?%lp`b1DL$w7z1Wqv3}#;;=gGNkDX@&~_=`Jo&c zzl!b40*xQ?2fv9~T>qGF#;;=gvQXoP{J}3`eke!AuVVYc3yU-SkU#iM_H+GXz8SxY z?aN~A`H(;O#mo=o$oN%kUry5aA%E~2V16hE_`SYj`*O0z5BY;%atPNy=9}@W*uI>i z@k9RLH_H4_j*MT$_J!8*(}ng0`Ga4`{7{aJU&Z$2O_E*45BY=N#4EV|G2e_|#rEY- zHGaq+{37Oua%B7}wlAk@{E$ERO}?D#AM?%lRcv2Q)A%8O@Qaxr%8~J_*uK13M@Qauq%8~J_*uE^)_#uDrn|uk^Kjxe9 ztJuDrsqsVp;1@GLlq2I;v3+@)#t->}-vIMNIWm3~+n2L6e#jsEl7qSaG2e_|#rEax z8b9O@exu9}<;eI|Y+ugS_#uDr3z;9vk@2h8zPv-@hy1~B;vlYn%oqHQuh_o4Q{#vH z!7pNdC`ZPxV*7HA#t->}-{gT@|Cn#auVVXhuEr1fgI~=2P>zgW#rEZ08b9O@egn)8 z<;eI|Y+v53@k9RLmmI+LkNIZ&Dz-1nG=9h*{6?7{%8~J_*uI>n@k9RL7cxJTBjZ=G zeHqsHA%F0j_!F*w%s1m#v3)sTLm@oJpJ*Qd& zvqIyC{J}3~eke!IuUZ2`-)+(bdj1p&S{%iVJvgxHakn@&~_UU#@@5H|JNaeOaqLAMyvkQRatomT#Y`BiIQ zF4p)VfAAY+eke!Aui^sUOEiASAN)e*hjQfns-z7b*6~@`Dl-|_0KrBDN z^B12b5^9I@9{;bPEz`oYR*hVI+VG_#7;}nbEnPD?b@Iq2dA{U7OBb2M96o5?*V6lN zCr^;!ZKCnya@9g*r!&wGXy@J{Ud0h-di{LhfT8Fe@|;|_NZ%=U;u{N&q;pb9x9IH; zp8I#gPtOr)&mVr*+;8;JX^&>bK|Y{EK9C#o4}as959p8&7$JZ29IYUDRQ#iB+cx=J zvG7Cgq41aK4SnOA=}!Nf7Wc0kTf1&#Z2hVcSQ8>a3s?M3i5pyy>byx_R6e9@RKDex zE?-@-IAEW-ldM;8al=+Vq_dTe=r)XxEL$r+Rcr7L3;DL|>g7UyB)_@{pnlva z{AFoRTeW6n=}7fnnA$1&t`~XJJI`IkPru?Xd?($nm&!FHZlZnoZ{Y{6N~51!i&OK^ zp1>ttrVIHZ-j;8?N#8SbUzvgSSIzfqQ9zW3p0BLD8hI8xWyw-HI#Qfiyn6M>^7X6M z;-yWNqN!(wEd{>svfWqXo0a+TedM{|a8+8>-_P<^o_hHW5HNdLYyadhJ{WY<}OVl)~pBPFy>FR!j3G?ngmzjI9oUJ_n`2* zmzRK-fR})q1p0zKDPip}@n;5)1kAQep5#mGD_5>uN&AqCrTDG1J(O0x>a`cELB%i5 z6Ref-SCVLX(V5~8VyV88th`^R1oW6qcogz+3Ct3S@Dtv&Jm|1L`QB#=@*)|kdCD-o z=VxtwZDctDB zhtqL3O5oi_642`i!X^#jNACe3UwTKA56m;11`ql0g{(V6;@s*RgkI5w`_fJ!J%C>5 zdO)e66FD8|b64t1t_Q!JF>ed~e#gIor~J7hBulr8KF81dYkAlw?q-PeBA7kx)}EzCO*CH9}0q@v@fDO zZPzSu13#oehyBolr=_xb@t5u!q#wx;ev)CiU4mSs8~;F_Ltl)o*OEUL?Gp6c`goMe zlFI{ln67S@JhkopTaH+^?RIhx9Nu<&=<)-LQ^zaj5o* ztsAU%3G_nO1LEwW2U2$%*d;&mc8Q6iEv8HdQ1^;7nO(y2ZDyBHdIA*f1AR-HO#qh`ogoH+SwWS$@0r%l;tOaY{w&XYxxe3t}lQ`aRD1mTve5=_bGB z@Nc9W{aU*5FZup6eE(_bBF}JRaXygblG^9Y2lar~0+|=EFkSUoOgbU=$9z2~lS_GA z>-7Ngb*Tr0cl@l>i*DgP@B@!`Iksi7t>X>(y5fy?qg!}~zJ8YhYl&+eZ^+jbZ}bn{ z!h7^VU%xHjmPEIXH{|PzcgW*_Zs8sI^W#f!wMDg#H{|PzH^zJ2!h7IxU%zeWn(?jU z4f(p_jd6X~@cy>P+r-rtQvq+5uPfdO+i%^%d&0NpHgHSgTgMynb;UbmJH1n^8EOFM#hFOAU+i@>#LeWDrRUQOW(gn|0Ulu^y)lvuRfOr zQy=Xm;3eQCP$B_Zw_E3RyK;84Y)*sbNw&wjT`FK-FwNT6iQ7CsNGa1x4)-;MYmpy|5NtU*?bW9+Zc}mODYTeaz2Z4qVZDEZ8kri z{DWV&D`n>Lx3%#ot=olsX`CprAU+vRgRb-A&dAr0_V0yY2ay_zM79aK! z@DlJ6uuFhOAnn>Efz>vUV=9cRPzw9HUHYD|d~FqIQ(nX``{8>+8gE(F?H0T`YT#kI&Fqqx?UJ~EtN*Sr zlc&u?@-6$B>B+7ny9DxW*Dirx=z2h$J7Jf^PkOt=M9~&grUR&ZMH zqPktOVJr~)oE%#$h=ON1@Jw*qE4Qci67Uk3Y7*EH_c`fn)HoMx41v~^-sdEKs%V#>-`2;Y z#lZvh7j&7fZkHJQocQ??DLSXW=$CcZB!*oQv3zZIiR(Tm&`cTE+0dLx7 zB|q}>B~@kFJ-Y<@+{mq3YES1S;3Y8iBtYGYoG-z%=i?T{G~0hM&~1gBFneFDM2R@t z{)?3A6>3t`PrUSBnu_))x6GFma&en4>HIz?bLWoJo_TNNeNAdwSeq{?6xk9G^Cdql z+9l|>_3az8Q7HTOC4tyO@Krq4)k}{)S&g^bCA5w! zA3uYpp6&T1Vg#Qd;+6fT@;a{gUyF7L`fYtYN+@??9ar*s`F%;)=j{?`r`?6_>#j)w zy9DyJ&6l{^CD0394~TOo?2^QPUt)r2jnVN(*d%~m0{ObyCE;t^u}k`wt{+>qZak=T zXcaXrrPu7z*vgS<|B^iB?UJez^ma*A$$2_20WX2=D*?qW;f^F9x8x<6DZDnjTXqTYh*2kk%mVA$A;9OQLlT`0q;^DMWe? z<4@f+31OE&zU|s2A?t;%2gEtA-zNUDWXqrcF7 z-8E@omq5Pl+9l8nT@Q$JC+w2R@A>abOcZS~I{pZo>0y^ZzOHsjctCZ#WbK-fb{2A3 z?UGlSkJnC2+GZsW%l2;P`aYZIKN&OL+A$xFaX zU@AypUwTO1Cs;mjSk-?O{fd`T9=p0eklTW)rVo?GMaEO8q@ z;VtaP^~v`>BhEX`+iArvNi!`!N7uzp5#N`5w`iB3-`2;Y#lZvmLDSXk68*dPknNJj z((res1aiz({L7>Ry9DyJ*(I*uy+bc_Js{qBy)f}dxuw3?zk5%f@ODWVj^%N!=>hG> z1^K$#CGj3@*(J1)Ywd-^to5tdv?%n4EB+36({?|4$j_Hlm1Xzr5}SUj@l{Lh>AVEI z1g4+_M88t6(HzB$2H%&ET{0kkG=`=*i6{Q>e7ZghZezKr?UVF3`tBX$ru_YWA$RJZ z^k15Q=yu%s5-~^grTYfyM>2$;WM6KV6rMSV{`H}fbyH%#=M!&0u=4T=>>eLOsM0<^5r^Ej8{W1RRbk{nnO1)tO1mI_OO{C z)xR*2?N)TJbd&p~8{fm>tTnTqeXrf)DCJ`5yF;k?C5E zE@nLOsILb$49xh}>jC8JQV$4k=u@}wp7iGjo3L7ATE`pmb;TR)M%VEES6{!a@ESKN z;LY-N#T)%YxA2aC?C~~WwMMj#H{|Pzcf#Xv%)Hu6W06zjY1oA9%bCSgjEi@MihC;+?Ra z-YvZ2Z+X0}@ESK-#~bo(hWElH;%qDN!anS4V}5-7s8Qk;BCM}!Xx^fOSNhI8yi~q_ z+E;m-+Cd&^Rxji{a<4vDr!n&Rc?oz4^s)q~Tj71-o6L_l`JT`sB=pJZ?Z)2|zC=xG z`t`YSp5vACF+1}7xOkLwZ2`}Rr_Vv#XK0HoUb<^ zOz1M*X7l4Q&yOc(`0oj&3>*E0?(42egZXjDm&T(K3rd^eH0U}%4!zLzfO3OQDES)t zAP1ZFb-R-fdAo#U>2~S(BWzNW9?<+a@n2xqPs+o$}g+!nip$6YcPddjkAly|HBoh8Wd6W+r2gx%bK zF&Qt~CFr;H@hG9ZNA_rh30Ak?wT~POCaBN?GosPt_Q?9 z*L@RzSu(7>Y+#o>>Fp8|MO%!HKf-4E%r0U1y4oe-O;zoZ#lvG4j;smh9A_5BEyik> zyvB{*HC5VXg=6wLyS*QXStwv8>&z4T+&@%oKh^uXpK_CQ($)P4Q{BvKr&)&fV!CIS z;Q93DM!3CldulHMFM+8gftkUc^uRmZJ|_)ODA*WOcGx94jje^>-gZeh_c@8y73~uA z+xmF4ICwxmXu7&xV(fF`zbDkytU(z4vhJEBuuDRgugxxT-RA^)q3Z#0&g+GVzbsjE zyCizR+a)H7wiq3Mgw6D@OCVoYyCmLJ*)ACyzBo8?PNk!&(()IZ7Uhv%tN1(MP22r= z&GxZN@;ZW&12b7?yj@b1M{aMa@$f^2-t`jj67Ujmkw9*j1T*APAG1xCXC2wsamlvY z&P~7Hc)p~JzmoS(YnNbe6SsX%-1c8gRu}CO^xOJ)lm-v^o|eGFbalIg_BlCn*|yus z9UI|;q>=M^?T+j<$4~TPKzfAmP$(q|GiT~c;1koB( zrUQ_DkqCAPB#!=VriU0R=eZ~CvxXRX}cd@D4(-ezr*O& z=W<5bC-f5V67UiTs9WLhOIRBDxFr`)<8@qtZY$)3*|jQ^o*olX|bQUDC*A5xEdA{g-A=`xNiE zc1c=9QEu!5uZOGICCNz9EUs)gA z*e>}KmE82}Hc7_2Y`%n^PXGSog_$p@*XGV0ukHTW4R6GLTv~XMuvwI!`QT=Zm@hfM zXqTYh*2klrvP(j?OOi9ZU1H*)$3XXWS5FrNuuCA{cI^`Ag{}w0Ij`STF1k!t4mR!c zC8OW=-|;K+9mO`_oefft?$Qm(wNweYpg#S9$R4+$0bIpN=eG2 zO5)mxN_90p&)X$cW$5jas*>|`UIJbM+gSo)m{PC3LNML2&e64YcbOf+a>~N_cuU^V z!;q_+U4qnZ?ktfEKTzNa*J~sj zX&*we;mehnl zcTtggGq0*Es^6qTJ%D^&>H*;med-$CKlJZoOtfq$iUst?C9tU&_@5s03 zCRo-O6=T(J5&&<=*A;J!_qv7m=>5Kan`qf$RE$->nI3pUzOHy9sj$>+eFJ2qhhT3&Gf(<@^!^KWIMfEc!$3KHo>ySs2Hn$ zlK^-_zRmE?&e46k&H3>&)~;H!-Ykzp3|_YzN#__bZAGN-xuQ4A_fNgLA4so0S6j$? zVlM$NfvF||@m6__~*Bue-RZz&7YZ8kq1`}uLGk%Oy_K~=v=O8C(@4)Ue(sKkQOW;i*YqYJ&z^?-77 z(F0la?DONJKk#-a$jHS7||x0zi+=?PG@58av{FSZ{vKideG_l@%TAk z4>F-Q^{Bd{`b|RA1IX8<9;E#r>r=PzPW=0I6D(_tim~c934k}`>xwtpjc(x``S

szp)$umVqQ=Dk<=b;r9*zAf#;V_>0^X3XE8Yo@1GlWUlKk@b3M9UVVVyybj^uQbPb;TRw`flMp>G$I_!Lr7v z7^{Ah0C+>bu6W06zjX`mfuH&MZK7q1Q88BiW_sWa`MTnru$|s5yd#gd36?cR#aQ*5 z1i%~eZH71I$K$8koF8Ahc4f8c@pQv>^Fc(+(WSAKBhz>1;qmhQQ?Je=_v&+1M%@#7 z33v%iMG1)M3O_&IF7UHN2KXT_Imbx*YMnMnGH&Pl@%UfL=f|Vh%YK^1Gc-SdejDRa z5tUxW`7Ih7(Pg^L=EoyHKQ2Y*^j95&s(zE0@T2*0$d|^W?aYruFLXU1&d`Z6{OLZ$ zOWkcSKknCQOHn!fRk2t7CNb=iQI>BryM)pcplBb=`SBoIKcsTYbwG_R`tx&x0?qi4 z^W%r73aUR3bVuez=SS%m@lNK-b}Kway74^eMsqlPq;!)bq#M4L@4uSwA1+-zZ-mhv zf)DCJr{~8d4!aMeOn_yXERE$->NdUYdUst@* zZgdOp36Hl4mNiDjSoNC(z#H;)#T)%Yb-c~8s4ySs2Hn$lK^-_zOHy9&{kgqG=7}s|T?*Wgu z36?cR#aQ*51i%~eb;Ubk`>k7eM;>nzENhI4vFbMofH&mpig(O*de`vw>zz!ntT8Ia zs^28Y@Mifo!yEJC;iK)&kFOtFdm(Xa`NHdVgYXz5rmcwdeOmHL`TnU_=aGB$x!OYB z6MG4G2~0H!i0KMHKi)3rvqT2?Aun{;otPhwzF$5+9?q8iG>vCyegOS8#-k!Cy*f2N z9{Txl!0X_uV^Gy^k`jJ2KMwiQ2(g{{ap;Av2gDgV(boL9w?j#kj+ctP>Nm+@myEJ} zo7p9lo&ZJrV9t*dKl93Uz`TbTb#9y5542~Zm01`#em}k!GE-dQ9rw$2D|v-EqKu$f)DCJr{~9GUk?g$ZyZo{U-g?*s0Wa*OFc;Y zKh~#i;XUE;Ho>ySs2Hn$lK^-_zOHzq-RKtHiO1Un%NnC%tols?;0^h@;*I{HI^Jek z)R;++wKTvgk{Nz=uSDWQ%Atrx4 zr4h~5!fgiA_i6F|^8M4!Igd;dQ1RFOLC~wu)h70y+)KbqU`k0~U-7i{+F{~vv-8JK z6=_zkTv>bQ!3R$?+t)@yqxM(5#JALHQ1NfQ?dBTIpX76c%1!Cj@(T2DMqrx*qP%VQ zGU66JvqXCQK#C6glka^dl~+68k0Z~bTdyVi%6^*0Gc-SdejDRaJSUX}eu*9B1YB}g z%02&X*f@Xu@BR7XMGP8+NUmi+lZNo4`SFD1OXJbJ4rKT^@2?KM(Di^gyXb+`-CPfT zJ7eA!`u&c76W%T%Q952_IsiuVd|mC5@UC|3lKur_Bg4glxQEcp85GAFF>OVp z?R>b;&TN<1^dOJlIqj0X{N?RkxvsaR4W8Ibz)PT)B_M_=_1aAFgw5=d0g(oF$rFEg zKHaMcZes!I?ULOtp?u`HsZU&`x$1UE7S@yNVdS>G?UGKc+YSG@eBExeSJ5s(zpal) zJ7t%|-Y$U}Ik>j|+9ZWt0{OOUmq0IcJs{3`y+FM*-DY-4;_VU>L~BfIdT@})2l8!Z zmyq5NplBbkZnyaE$ShbNYk^Fc-_La;2zJe*6`jc==cQit*(jo(zE?u3-QZDN;*sno z+pRE^ZoG$dqg^;WL%PW{>4trL|9#T_ivL`?O11?r$t7LK$$U@`I=yap($|B6+#3f} zT~Yle73_1!*QFj1-q5FR;XN?v*Lj&}*J>c;+!Lr7v7^{Ah0C+>bu6V;v?-t&X$J+$U8lz&Y`b`4h4f!_1J6pGVYdiDf zM=c&1SvRsKm@}usSxwgYarvIge8!3~>3lRjKc1Ec9X8F6@BhK`YU88(4c>g+uTG>j26|Y?WKrXvB7w|XuZ5S&ewxNJ z>3k6P+Zc~R22_?@u7n9)rYkidU$3dJ+l_dBJX~;9cq1NAX6FAI5`gaOu8}Yx{L*uu zI<#&VdZFt9afVKm;V(;uvwLzq(AVvbdb`8~(Hf)UkFZGqy9DxewM)XAD%&Nihu5zk zS##8)wQJUo4UeyH**LMb0>_z;*G^2@WOOLTE|MDrPwaF5P_bQ5 z@3-RzKks@8cnNq3G?M^zEA4(y7!=0+MUH68)Y!&$3E4mmF5*@AO=Y_z-cYnl&~NMG z(N5VVA&;A*UtjI*k_JjDZ$bBU*Cd2p0{PN-RN9D?mgrO0`Fld>g{}vbODF7-32&FA zh`9aH@kiLChFt>ry4of2rmA+yDPzOuw`_~31-IHIuUAvG?@#MMGVbk?s-pCENma>t zIxhh)f$b}S>4IIoMsp#!@^K4df`Imfx6PNpF33}>zOp{Jv0XA#B{%&An?&5EeOGq) z`4W=x+_~em-5i4>jFUmbshO=8$3kguy<5??sI~^Sh}`# zT=eW1H<86uav63B(h!|x9T$?MuG>1Uy09S*J0S@Ahk{^;$8Uvf3V~T7gdc9mu^pA^ zv~*9HcJ^OPzE{4_NxY_Lm!RL)$D>ph$hB-7D$882HCx9Ovt5E`^ll>w6m?$8o#M<3 z^CggPyLJimLe~T0?4k!!ckOn`q_;~<5Unw-=>hF?0{J$xOGs}BP_z%&=LAK$cH_qM zoejcpa~(j8=^kiC6=HTNgb=_QK&fXBAX;&WcX**}x1v?jO;$@czJS9c(hXNgH#(p1 zpU3y#EnOwsf|uly?t8&}P!C$)=cJIW5eR?o_q{NLVvT6M9zeb>^?>k(K6MN40gty8 zUGqlkctgIfc%$9u7T%G^+l18`(>mUeuPff@AG(G2sK?uiu6d(%ydhs#yb~S=bPMm; z<88ufjcFZk$k!EbjQ6^QcjEE3qHEr09dF3j6>p5|yM_0p$J>O}8q+%7kgqG=G23t5 z!aI7#+hbOEjT^1w4f(p_ov@wWExbpc_IMkxS|eJ=8}e<2cec;T74p5z8KMj>@0d{3 zg-hPFW~KO|a71i%lDe%g;l=BAWSP0gXTFYj()YLFyX15B>V6=-`dl5+0H2AMfS14& zkpOioJJ$Dv`FB*M5E%30JIA`+^jSpN>ovJiGE2Q)tp`MTOA;pNrsk_-FK5PL6HzN~i18!Bg6N|@?u{0?uIluE}T z(y!Zf5Ry;gCEz8nBS=6DQ|dLbK()*3cGX9`tuPjUO&wx0}4ZXqTYh*2kk%mRuh8@o2MkyP;pV3wRye*2be`mq5Pl z+9l8nT@R?xE_%?+E=jyy;($Mrw5A8?`GhRrW_AhI?V^3ax?TDG0{EgFxtr@i9;f^0 zy9xS^T)pPby(L=*{K}B-fJeQycK_*bq%hh&x~uvrOc(Kv&Xny|yhOU;S<+3;;P6|d z8@*Y&@u__OO?*F+Zkl1qAH1glAJl_Z*X_z33-k5sH~jIUE$_JHj?G70e*5O7tJZ9J zZv9<;zo!!Umq+EgqUudqTEcI(xB>Z6J0h$@aIe&DOSCK>RCd|mMlc}&nPyd#gd36?cR#~ERh0C+>bu6SeY z*DbtbkGBbyH3sS-jJ^=-XYuU-NHNZc$;8ZV|1JmHVJ??HpZg@-1MTdz%S=1D4NDcMYq{pdGZ;# z_pYS#e;Z2x<4@f+sR%!sD~Eh(JSwrEv>8s$Yj~j-x*kw&E_xt!H=iTa&nlesb_t2n z@zU`}*d&Kt0{J$xODH`7iuM6>HAcl-^_v8QAI+6RzAkNGx;BE_kZ$2U;PE!Wvc{+w ztA3LJctgIfc%!eVj<;DBHD=V~t%{?GU&UDUn`FQn@^!@0{knzsgvZ+i%NnC%tols?;0^h@;vMrCzgu`G9&ZyYYmADq>Ng31H{|Pz zcfz(@xA30yc$;8ZV^oY)zexbRAzxR#L$=wwg?IQ1zn0ZR%NC6mxm7g|z>Bv~?yIkc(S6@NpvBF2+1a|OB-+4uIqI=!ex<`*-DAZ@alxe$GfnPyJKjC+4D0q`OpZ z%Xv^~pGIluqB)?lXH0Ka{4-0?;U~Q5`+IcQa;`khSd^o)`(4FH%6^*0GwED8_uCkc z;yI}-o!IXx=DG6lF2CPZifF?>(0$!Ci3mTMD~Eh(JlfV=IqQY42gDgVA?qf^Q2C0k z)Lr{r`GB`eq{y89>i8pU62mTmd|mC5_^HZv$*K!R*NgqG=9z_Yi$ODIq)r=UBerrx zs-wvfvc2n5w77DP7DCz=%DjM?tSe9KbN^7W-l+F=KjkLrq^tW8rn;HePO}W{#dL4K zD?Fe6+z7W7#?vDET*SEg%hmz42W@-NsJ zBEE)wc$GA%*Ot%rwo5+w-e*J?jW)Vqm!##C`|{Elu+dL{Ittfo;cJU_3Hoh)JX#z) z01wlZDv*yy_kQesTaNqY))UD+Qn-;{!&?S~B`(KY#lI{u>=MY=W|tiD;R{*+hr~I` zH}KF8IOco3KreJXAl`Ys0G;VJvr8ttT~Y?4C9X9+NcZ7o`MTOA;iFaUk~a^JT{tq< zzi{p96_(OBi7qxRO5$u|EG4cDZ>smvtNnaQRT*~AF0tvi8eg^4p3Y0aOJE90Knzpr zwX4O0&tStOAGhSvAiHEh{AdhKa}rPd;d#;R)N63_lx04QdvMQgmr#l_{-6}DVVB^Z zPN#>FZadf|DJyi?o%pse{Ep?@!o!Pp3Hoh)JW6HBOH2^0 zF=aY{x>qE^d+%r2qy1Sr}Ed|QZpc(2{KvD7@sb)d*!4bi-Hqi+jmJM6cc zsej>Y*=|LLN;i3>bmM*wze2j<%cUE=lG~h z)0{GW?Uv;$zqa|rm8Wg_zq4MlCAr>zYn@BInXk2akbY~;@-4Q9lFzxH*gE*7Q-)Lh zUb*sBwfDaGNNyX!Cx1@^J*%n*&Au~?eS6ppeC0my(Oo48d(j?3zAo({)qAuj-NHNZ zc$;8ZV|1JmHVJ??R{@lJSr&@H?tJ>Di* z))*aUgiQkA4f(p_jd5VN@D6|Jze_REvc&*j-8It#Z^+jbZ;bc5h4+BR+XTxRqvMRQ zNdUYdUst?iw(GiucjWOl!Lr5xU)?ncfH&mpig&{Hd$;f&^>~|LSz~mZ5jF{cH{{z4 zZ+vGMKhWm9`J&NP<0Grh0yU4pYj`82K9OJ~Xe}bmE<8ZKm+IB;@p|>ST7%lB_7d@^$G8)4evh52=o~ zSr#>B)Z?v+qlq8*>aIx!ydhs#ykj0KbPMm;<86XvjnQ#N*dzenkgqG=7!!62?+K5$ z36?bm`0B1n0K6ezSG+Oy?-t&P$J+$U8l&Tkut@;CAzxR#BewCnh4-Y#+XTxR1AKMY zBmmx!uPfd$+w$GQJN&hOS7oAQi_vjL*h~++A>U?rV-7vMtNI-J_=RRUT8P0bd4pug zh-oV#wMCO@@*P#L&NcVybG3!MC-xHX5}0BV*k3#^_Ue^yD)TwETojG>#0s*xnVbT? zvOd`U?K$n$`yxR)6Xd2#y!2n1ipnj&xs*i8a}>%)PpRCKiQD`;OVHzotk7Z0IrJe> z2KkeFVCmsT`(feRN8}7w?1|MU`)L}_q~Ba|zm4%Io|DRwPI;xIb(V+eN(}(Ndd>Lu zoaYUqFMl_@5f6yscvq7E$+zsMB@;oykLC@+4$YxMFLXU1&d>?WHI%s$+FTFxb8QE_ zT|%OCyvlR{b+4EO>=MY=)h>xQRJKc2T@s8I3(;E0+6pXaEuUto(>5#GET6O6d)k_X zQ85exzgKTj9_8&E(wNfpFSyCm{<35nA2((y;wB!^uB`MTOA;f0m$k`1fZuX^3O)x&E>`j;*r8(v2&Te*r& zi$b|j@pr(R+9k>D@;Q5TUZGc?s{{D^EW8A~1g4Axs9VYBOK4qxK5of{FjII>6+i6N ztG*wZJ9jR>f6vqBzOp{pK3_s~2j;|OK5>p>%8z*Bm!_j~Tjom&`MAxO)WsPMhlyi@ z`-kK_M%0L?mtFzhb`R z>qWZ+{kA?HrLuHlz9eS5B;MrbOB!QCccJ^bYf{24fqZRtiR)gy&h>FEz{J7=vccNG=+*PF|_5MqiW9 z>E}yY7j5^>5TUX_r_0>d%jzk-1iS>Mt^}t0vkf}}K5PYrrT`3B=YkmQglv#%lc@@yqE#(63EwPmpIRtuwLkTKwLXvm&D#KDFWCq)S4d9 z_ur7Ot6dVF)s|f{HZtD7c-6R=HCaA#(a4(h{U?nLkB^Ksd??&3HZ6!1?qbE?0dLx7 zCAY}u?A3XNUVW|(;P12W67Uk3G7_L}C7&;$$IizsxeywEUs68SlGNyv^Sp@Pi`Ag! zKST>_LF%9MUz%h$@pt>qm*geW?2_~bI_zrSm*jcdePX`k=AvDKep?@pQdv4NUlOui zl3eNMOYFGd4s>64aX)uqmq5NYyTp0Eg!Mw#1LBz1Zz>mErYm*VK3_87?Gh z2%E&1FM)hr?UMM^&e|nQFB%zkGfM(AbH>A*WW=-;k?LynWp9^Mm16hiOQ6q<+^VJa zbY22p0#i={)U8OnB;W<@`M4#Q#!TV0t>Yqk`&urvRe4^NzL8zhuO>D9x=q4kt00() zc1irDqFsW1TOW^h$}UO%PJUk!UheG@Q#JJ%=)Ugi>GJR1A>VfG66l4l2gEtA-&8KT zOt+a`l6bpBiq7e;jz7XCG3*k^*VQfw7gV)NPT#QNf{|d(oTJUcxW!oQl9S!&T~npH zntajQC9VR~(|Nl@mxvF033v(Ym=ehCk{L6kVUmwq@)9?mF9|Z+Ag6$@tPi%^CA1&c z9w04uiC5u=b1kUcmidxG`RFOjp0S*rTkI^63_sy5tmDF&R&(c$i*HtLc%$5pi)^_< zSo^-j<$Dt`Uvg8?E%NJV|DS+=r%&9qC@=cw1DZGdqO-Gd)}aM5ivU-B|FYtvt_NyP2U=Ad$0?2w zckg+&vxFBv#G=FQ<~pwU#-d$=MYg zUAqK&q3Z#0&g(bIV!BdybNf`^Cpi3#KflC8(H7I19?a~Lf(`NG}10oIf<9gx` z&x<*MdJS&lGGp9>RQCB2N|BG7kiOvR%9rstQieA~54pclFxP&r-nps8K*f4p5%j(J&ZYkH8{B`n`&b_vdNkw4qo{inZ?+B39!lx7$Dqg|wb@g~`BCD%(g z`mA)r>p1+k(v3GtH~BQ*znbq~C0#A+EJ$*p^PIp3^-2eS4dCtpGk^&L&(>q9;D}Yu|9PR@5tkAf@O_SF;@L10q}-=UGYY{(Jj13J>Di* z))*CI)o&62Z^+jbZ}bn<@ixn%#>5_PRUA$HD#ohcBm>@%uPfdWj{~}e_k_pW1j`zu zVyya20^kk#y5f!TUbpa0Jl-Z)))*CI)o&62Z^+jbZ;b1^h4-Y#+XTxRqhhT3O#j-lHCG6D(_tim~c934k}`+YIl8 zOT@bt@jfVis*U;a<*QZ)Yv@miptKoIgRc88LN9bZpxj*a zK)>JkZ&`)gwhkBXdm?X@oP68oUI>Hx#c>b#uojQrqTTP z;STfTX_8{Lss2SDm+e-3g>=JDNH_T?hhynRe<9uYLwx@OeE)LkYFTGNw1?n>deG_l z@d;lKGMP8`sJgHEO-j@Q$k(MFr2QZ3Q@8L=Jl-Z)))*CI)o&62Z^+jbZ?qfT!h6!= zZGvTuQ88BiCIRqrR(ySs2Hn$lK^-_zOHyp5|yM=e`@ixJ-#;6#p zev<%rL%yzfCv3lU3-1Y!w+WUtM#Wh5n*_ic@^!^KWIMfEcqblj6D(_tim~c934k}` z+YE2akB5)8H$UFLbnVKDyIbLPyYVo8YQ(e^k-krh2Ic#wUY$qo)#qvpc~9&m;3Y8C zBp{|i>NVP@aVMG|r#?#krI^ut&W^jUl~{w&#`ojl{pIuH@d>h@rtu8T51`-1c$8#9 zSEuI3V?RGG1?u!y9fPWVlbGp(na)rbRt~d|m26+W)aWbqntSkGBbyHAcr7VUqxO zL%yzfquuBh-jT=K1j`x&e0A3(0N#+VE8gfIx`p?s$J+$U8l&Tkut@;CAzxR#6CMY2 z3-8$DZGvTu0lvCx5&&<=*A;J!_qv7mgvZ+i%NnEOjIc=nydhs#yfLot7T$@++XTxR z1AKMYBmmx!uPfd$+i%^%d(z`=f@O`-aYooA0N#+VE8Yp)>D|IReAe6FCR(-_;H$f4 zdf*NDHp3h9{VD2vaUQXl1K~&WmEdZNs(?4k*A;KH8{NWt)Z=Xf zw9sakUFD(Jq64g51kgqG= zG27|g!h68uZNauIx^=uE-)4AYemuOZt@-hz-aLH4npG<=4d%=_y6gcO#o@J+k>)fb zrmcwdeOhv$eE-y|^T@sWTx}umiM<581g4k-s9UMm4ikTy&5zSMESx_s=hV>O2BmW_ zQjl318BG7$&mZ4<+s(Di5!&l|64VeR~JnwwOAWvs>d;|IuoI-3vTejDRaJSUZ9w=4_7 zgf7#SbF%ro=u_L?zvYN!+ioZKz~ODThde)?TzOUDdrg_2|67y*bYFLisVIc`ambg( zqwUO(LoakaAYRakGW_X2#Y^4I_29QN=53+h@Ax<1_nnoZa{8;|kFZHhdXUbKvwU6c zl6XU9yJYP+F{yr`nKN>Dvk}u)MA~LWFZOmxRVnsOo37M-_56a+(Qn_b963(OK>{BR2$b~U>MxioU?wRr!cU4njFACFR5@-tYd zf1%5Co7pAFwesAj@N#dLNEtTzOY$xInKZCVAm4WF66l4l2gEtA-zNUDWX_3^e4E)Nq&Ea8+6Vo6!fQ8fti0bYm7~$M zYw1ncv{KI=EJ;K97w;k4tzQxr%@yq`o0W& zP!Br&Jz?zYfenI^_}1$IV;RJMnniz%7Yy z9dF3j6>szp-NJj)<6VNQEvj|AAzxR#BOV8I3-9pv{`(Ufx@LUqctgIfcw@ZRExZRj z-X^TpnAY)zd|mOzxV~F>M;>o0y5^16@rHa|@ebL3>lWUl9&Zy?YfS5SL%yzfM{K8e z3-8$DZAI6-(K_CcZ!^5{Jz;!a)%o!y!)sQoy=c{CBUSg@m9H$#hYsFS64yplIzJxY zA)m8Xzq{+z=V}9YPwFM$B{20Qu*1%e?^=$9J~y8BSCo|IU3TPkyY0-6N8c)+A5U(V z{d6`T#QiqLqa+i$IyFBY`T21vP^Z7GjYre{c3HkO9?jQ>P(F0k2Oqm(9s7rBpV+$L zM2^qu0Q3NQq3Z#qhfd^k^;!Pl)AW1732&E3(L4QZO%G^Z8uD#smtcMz?Sno)F0#sW zLoI5q18Qv1-{kv=puBx9&X0e$m}MFw-qF`&yA|Im-SF$uO>W`v&C-p&EZz7^eE*Al z|0d~{WS>T%9)J((L8s@(6JHN(5R}BXUJoE&mwJ${hhTl`7T)1=em3Ud*71gXUGYxXe(M(A;UE0? zybRg$xYqH8d|mMl*-q~k-UA-*a%f9qTgMynZH71I$HTMadzmu?f0uVmDCWW?OV+L* zUOy7FzRxbQu+EQDSvq*7@6)0$$mi_UdE{Pwt`6#f&&o@{OQ7c^K;25a`|VP{YrD`V ztM?o4x4XBR)btat!fz_?w;SIm`{`^xi2H4fM=?erlsmHDF3;aabH3~M+ckAli-GRz zu9i#$2|t=24?8qJ4!zLzfH*@Z+L<4Zyj?<~bi8!@5jM$54`_ZI@^!UK;!~^IB}Wq=$3&*P*17hFT>aFJEwG)%}G0Eq>T~bwueqXDqlJj(40$u{!R|2_Rvde6_ zQZOI4tO!dsPe|}N@ zdcEiS)N_36ThH^YYpRc3I`qDT@AhrsRd7!tzvTag`;xUT8<;$&Zq4eMxBsgbzXRf5 z)-|H|&0&0B($>tyad8IX4E(qm z$laH;_IHXnAv_I?K>yDie!C%xY%^e_AGL;TA@ zxzO)`G!)*K*m>K3J<3>CY3`!Wl768sqyCFEx?=`s;7Y#ld!8V)>1FUy}Z6Zua?Q z>F?t@YRFk5hVql?ZGG8ct|^UQN+(GBy@b_`i#~Q&MnU;0=$=bNyT5`YVq()=*@a{|e%Tew_ECtr;dCBdC z_a%1T_Fs=u<&OA$!j!viPAn7Omsl0T+j`YJh~fylFR}c#_rApDg?}Ef=8m{8$;9_1 z#YGI&1kC|-#V@-rvHV88FG>8P&G#ikhn!Yhd1UR-L1T)C6GanxUvhMm=;%J#e5G!U z?@OYmtk5&QFDcANycB03&cKqIfsVK@2|mYqTWsg#{Oxb~zND4k#p3+rC)@dweP2?a z++~+@^jqxr?Csr`6fOJS$CaP2Fj>z_rWM|o*m>K3J=#(CB?)(5k{WSi{P~iwvNY>$ z?)%$jhi1X1FUy@$X=KGSf zY6p#M(+5l3=tJ*Ijw#-}_Gq&AC7GMO~9s@He7e%LVbUC?s%Q| zKCW!{?moqLUlQf{iJq6-RCr%v=WYM>sEx(j9;-)j>wh2D>hF(ze)s2I>_yvMZDy+X z`4X?6_;1S@$kjZ*{y{NzUt;+UyD!;v!kO;+riOdm(C>w}dEuW2tUEXNi|g-=&A!_^ z{C!-BZ{zP@3#wzVuC#f;|79${QSVC<@3sBDWcct=b?vw@DLk~~C7@rP+yP>C-lwL- z&r8~x%ktfqgw5}KeOpHz$Hf_lGw{>P0H+muAD6fFx$Bl(Hdc@rc3;xbpL-8I(Rst+ z{8Ye?{JHn+y4@{YbAs%igjTPD`w~81;{Tkxe_1tX(|t+$`ojAXJ8%21N6BeNd>>cZ z-It^X#?MQ#m2UA*{&_iHE)KH$63cJceM!{ctFn3Fp9ictw|_GxUd64q4~E~DRK@ot zUg{$K&98mFTpVoAODw-p?@Q8ewC}#8tv{lxEeJhaA6vY6?a^fCYG!hLU((hj#`h&{ z&0HK8XCThNkDLLWSL&1NYo)Dy?!8Jr{BNJnaD4d9_qz73Pg>jZnDfuYp4_E{fZTOc zpi{v;i9Fo3m3O_hcEjc}d~6C*}tKj&9=G!ut|CZ~L!Dxp>Is zl^XCWZvD?o7W{C@^V^^N!!xw&4f^4kRQ$ZeOI@VD-dy+coG%v#+kJ`Ux4rizHZT11 zfHg;%2d&+gq~iOM;v$A>@@tAV8l#_w(gqSKALPzftxB>TUC>T=i~@>s?%|P)&Y~ z^X1|ItGDGhYQ62aQLcJt;(8YsD^!zT<9xX|!0K)JjaqLzKa{K9Ro}($11YXnm?po* z`Es$p)!XtLwcaUr9Z;@%r~Vt)ySQ3mn*18)%fCJr>-uulyD_eJaj`-*`8Cd$ivz6QmfxuLPPqHGa@9K%*Sok_p_=>} z=gY+bR&UF1)Ox4feR{d--4xfmxLBc@{2J%W#Q|1t%WrG-ZvGwJ7ux)MeAu8fYb#U3 zho9NT_wI&1AManhdF|0;|976=$NT@&()xR|OY3&Er|@xXoPjt4Kcx(Cj^wmr@7wjB zjs`tD;Wwnh-nW~3&Ki_EI}hjETK+uYYWc3kGUulPe&qM<7LQ1p&GL{~^uAqkRND8P z_}|AHwA%V)W-sqN&Gk(7d5}AA+x4i`k}7w^`*suV`FN&p{J!0wiWKT>?)%$9FP5lZ z_IGq$ez_jay*HeAUiH;c-?wY?!aon_#pXnAzisZ{){|`Q`FJA!zapuO`104^mW#vf zzQpny^}ZxMy3O|`!v~M59ahm}r{cjx(}doa92YGcmKD(4}?w3-^cv2 zEwgXt_Rgq@iRxFr_wCyD*}V3*QAZg2#Tke*5NCi4MEjnTw%?bqj@)sVC6>e9w`<>X z(!$Z%T|0R_>F-badrne&6yBHEdE0+IT6pnb^(b!r@7pcT9nd94cm^Rq1% z2iko}#^pEcz9j1RoY=hZ&jZ$++qa7A?~SbW?>VW8?@Nk{7^<R#_<2cNb6LLo692poT5jv8Z8PIuU$^1NF$O(`0lb>waxZYY?KTnu>G5GU@nOzF+OYFSuzaAx*QLab*_a*+% z6Q<+)lI&cI_9y84jIQ`)_a&C!u=|q8&r96A@XrI*9AzGO`(F6>oYcklCB;Py)s!|5 z`2I!9Z)^7@%o_@7$A^ymJmET_+v6EJ1=lw{-R`yf&cwtctww{MgoG`$0#4p0FwYyir&Q0`#Tt2bSL``$6{o0&YH) ztKRAOxED~@FjeXGw){q|w;eaiRqw{Q-eJWJ(3f6s%Wu?r+xekf_0Gig4ybFGs`PqW zexueq;jRP9Rqv*_-eJWJ(3f6s%Wu?r+x1?#>Ya!`FC0+UFjeXGw){q|w_VqltKO-& z-eJWJ(3f6s%Wu?rXWac;x$0dP*E^uDVXD&WZTXE_?}WQgFITTRDVOb=}H^YPKAj2b!U)KLM$j;0EETu@PQLbUj(UfKWOrn-6me=7I;TZ;#kU&D%u z57&6DCilLtd|QxAZ|JS{%eEZ9Z`aFF{5Q@(oPnQm29~W@mCbMU_X&UO_wBY&6aVwY zpY;27)0N(Ny7_sKJ8#?ds67iUym+wlU~%hzKJI_tZYq90-on)Mo5*hve!tx32`#@| zj|P37FzWkuZC?220c*B7QFyI{B{v^hdp_P6-DLk%`qx}5% zWy@jQ#u{C~m|Rg2-k8RAkM%~E(1ERESaAt%w0bf)P(bs zpR5D^OY7$e(=P;no-nmpruchKvXd>+AOCfzEf)vd zeTn5a?0HGl`x2WM{&~Qfqs#;EVl({ngiY~%NpTTFHCA_jTO45bC6?dT?o05epmu!l zf1Xfl$X?I;*OZ$BeoJmS?7OJUqmu~*t=-dl^X&Bko43Ha6YF}%tyCAU&8+9O>9yQ) zXRl4H;kBvNoP8B%U&(6&y3eY}3!g8u_1J#U(Vr(w{4f5!o3I)b>;3cJmWw@YKd}5p z*$=XxA9eGoT=lMs>s?%|P>t2s-xdd0y)C~{>utx4a@9K(*Sok_p&EakZMitW>TUUr zT5mf)l&ju#alMO+6{@lN`rG0FtGDGhYP~b=I-p$jPRI2wE>@_^#l;HMSbhC%ae&p^@*B0@c3oetdS~K#7Z)p3TUUrTJMazPcK)!6W_=8-^JAm)A;Lb%f~N1Cj0-iM8f<3)6)7Jd1>9Q_7pyjjWZBu;HQ=W zPAm0EetXbfLC;QFe&23I;4>Jnzo2t)%fADX{e+_prnvP#8*lzRA>$5tK3*_Bo&g7K zWAUn@Yc`XsA+g|nyKl}rM9by*eZtg=-g%nqne6i*ciy(^QQJ94>3 zwp<)+_a&C!sP`r5C)<2qGGtiWzvF}r2)!>kxp?#1qsh+I^z!k2Nn4W`{|-o7GZ)9j z8Hh9RBWFODDL=;hcJsF?{JsP)@46}X>9_ny$(*13WL=00xi87u?N7A(lKSMz?{B_O zMMY&zMMX{aoM`bu?PgLMX15cz-Iv&B2nP=yoHUu-v#fUCLaZ0B&5V61aldugY|`(h zFI#wDV&`rD^(diUUJ2&9D7bCyeY>gneY;-jBK;-LK@HAk5T z-c$>J-)`cE_<2cj6~i<^bHMh&!Wg?RvHZ4nUxGgcwc~^TeY-mL1RlF`bD(hj`Ae(i zF9gV)0(8vx{KM4h0Y0odHQPI$rJwcM%rjn_c-m`IPdWRO&i=Tw&vf<~UK`MTRz+U2 z@8fjqvHhT<-?v*8?+0Ne2+)_lA6R~)><84_=2N-qor>!nP}eY3>Gih!MyUb*U>iR&Fu*DzJ-^|t&*t#`&<*O#l_O>w=$iW{IWz227JsP#^``?qq{I~6}K z4XA6Fs`PqWexueq)Xy`@n*wb}wZApO${q`~TC@`u^6Xb-UVK{kVfT191j^N*U03r9Qc{ws`sFy{Dr= z&rW#XZk6C!F3+Gp{O0@Ydr_@zIbp@;<(9rP#n!(#Z1#PZwPeTh9Ex8sBV`S>lB_)ZJd2ZgBQHoc(sM4d_0r zvg4tx$M%Dcem>q9?+0Ne2+)_lA6R~)><8KB<8D5ctKOOU@%w@T3LBy-z227JsP(qv zM!D+U_`A5?A;k?)mtJqnZ`69*`Jr6(PQ>*NsB4(2^m+d?^IK~e}`4K zSYLX*Ex%FgZP$C{s(0ptxZcHe4bzlfZ_97gdfRn<+3Nj5T<@^z7VFzkZuvdsTJMCrPcK)!8{>M1R=3ztdc7^bt<~F}kEa{j`h2`!?Z{EJ zV=8-}TRU>l>8!5QSLpNcL8bQ8vVZpZchJzXCTf%K?d|2 zv`gaW$3x!_=6^PxJzud!+2=8Oc5hq9a?tDl;^)Ux7X>{Js!yh;dHUw(LGHY5*Q1t$ zj(R?xxiS9Tr=U3%tSjw$l;`7?UoH^a^L*Uqg?}C}PMZ_CXZ4JeSFUaC`FJY6FY!_q z>2GQCfal|u-`4I+?D@DIANcPyGdFu} z`g*rK*=rNmdTr`zXTQqXukhM{?z1X89@=_rKj`S^<8|?V5LSW!ed+sw@ zH=oK?@ARDb|E&Rq4N;X|Z_97gdfRcMT=i~@>m5?tAa&{Ww){q|x1Ar#Rqv`7uvdsTJMaz4k%Z>8|TLL4z6vGw)A>iexugguJ_7T?|AvQC#b-SXMIrfY*5N9CHK*f^!d_4CUudqm*pZsJ!2v}M_Kc2WS=y_0m zGBwfDH$M+@=WV+lwIp`b^YL{2e7tZrwzyQ<^=S5YpIm;q9&OL_ahn(ZdBAXOPUJri z_cp_>mF*d4_~*wP|okN4VCwOiiDYcqR!ZDLPn-^1B=^ICu3wrs~kTaWDr9sPVf9q$L0 z@(69|`+?;*%6`D}ahp%&s&`#n?+A4^m(uHP`Hfm{J8qP#-l@3WR?G-(>Gih!MyTE8h*W2pSk=-t#``Zzm==rjd8t;ixsK~sx`Xe zm(|Nou9B-A2gDhOGw@T+z>@lWJgg@EXXDM+b3AuhTAz<+&JTJX zRG&=j?CG1I2f6dMU5}CzULEy(JQY75_YxZEZ)w+~JRi6Gay{Cf=i@dn{PTce+ni|6 z^YOa)zJydpe3dp2cs_3VZSB5<{hor_@u4H1kFVa!)^!TB7d{_9Ex?EQm+tNzw=&y% zZK{XYCbo0S+jwnyYp>00>FirLdpEBQ=sv5mdo>uvdsT5mf)l&juN|BIiW z1r#PmfuJ|7SIA5^KG=lN%!H>4}Q|359Q&yknb z?aHfTEXEm#GZ1H>wG1q&&&O8?8Te1~`FMIv(DNYu{`e-IzWI5OJ8#?ds67kqsORH} z`1yFqvRadu?Vtx4f>`rn-1-dM#(~?CfiJ zZ9w-~l^qXlJ+>co^z-pVydQ*>AV6RGeqi~HvL9rBf85Qda@D)(yZHH8Kw(2vrPtf? z8@1kc+$dMQGjY8`Y8#|3z227JsP(quvdsTJMCr4k%Z>({a5+ zY8#|3z227JsP(q%y>ivNF0OY_VMBDK*W2z#4; zZ{@0YRb20&!iMNdueaqlYP}QgKD}J^PQ>*Nscn$D^mruN#>8R)9nJMw_j~7nImX=Dp z9_9JC<(F%^+zvoLyy~mpzh{J#Ym(Q#IJ%e9J6%PE%>$bk{&|4E&58CrA8-0^{G7CJr z^Fz7nT^HB8h5GtUrPtf?8@1jUcO6i!dZ*%g`-|Gba_RN9{6?*}UGJ5v-c@nETd1$! zRC>KFzftRL*Y)M9cOtI0zo;!NmtJqnZ`69*{ad-}-SlnC_k}HN{7=wSdc7^bQR{8@ z>E)_-Ca!l0^S1PQTYg)sw>=+Eoaya1R@_>9N-$xk+txTW>`)f4A>&xu@$T%+gX z_j~8*=I24~ylvN`mdlQMKAw)Bj~AAp#iinTpL-@<7)bqiK5qHt0p$!db?iu?~m7ga58h9`F5|y0uR(L6I62IN?Dwm?{I+&qf={Q~d%FTgdn`zqn`{S&md&r7XPkHkv^SGNwGrczbq}QfqxaCK^HuJF8 zCLVJ32fWtC>Tm6MXzQ{4prfCUH^%qp{?xay9JCMEF{d!h_5;grl>H$4{pxN$m8;(A zxZZ_@YH_LbdRu;@);r;j8|A8ZU0m-LD_m$Sz227JsP(qfIREyR7xL{I*tadp@2X z=>1=&&CkdC3?1FJkB?`6LNqWJ%)JhLW&c;2x!2pyrS&=T(z;z8I1l2k;ta$YSo$-t z8Rp|txC zo{w98xgKrL^KqLO{&~RQZBFDr4@aNB+}iW;s>R;CODyx%it+PtFMHvCOPdGT?^k#E zZSB6qo{!t{VM%*FzC&)Z6{III-J3t@2CvQB;kBu|-MqTpE#K<3>1ke@x!Kuo^4fw< z^A_xQXzQ{4prfCUr{nwcysWn-mcAcYexvLM+3#0(^Qm0*PQ~?Zt;l)y((7&cjaqLz zZj`IuRdK!Z>e-rDdc7^bQR{8zhjP_B5!bu5BInslueaqlYP}QgI-p$jZgSUIc^egr zp&2N>-j?5}^-j6#y>it%6W2Smy2Xal>uvdsT5r3qFIT-A<9ZjDHB?i2y)C~{>uvXM z<*Ii&u6Jm4iw&jM+wvQ=-U)Y~UaorA#q};OYpAC5dRu;5tG7KLPxSNtFVp7d*w$A^x5KE6>a z+tw-2p8I}v=GpeS$=2+RDs{DY+)7{NwTWxJHgknrPJ3-?qSvO!JNr0iztn4+JM{^6 zJhb)Le$dg+#}n~>;LChVi>2=emftA*LH7B$n@{DcchlD`&%2hk{-fIREyR7xL{6?*J!d(ZHtKR9j-es+~F>PbR^l74O?~aPnQy%|{gqq(x7Q}V@Y>WO zXaCG=3p&kPu;Zbv$M%Dcem>sxpLqV~rM(rg^!>o{8)ZM>`MAxea@9K%*SnP(=bKBf zx8*l#z3sSBu6j4d_0BJ7D{|@ew){q|x1Ar#Rqu3M?^bG@Z!W#wmfxuLw(Ed$)w?dP zcYZ-zkxQ?)IJmxt^c^&`cGM`=)5e$gFUx@7GXiu`RXyLFm))M!xrP64;{QvP z>cs3vN@jZ#&R?Q?<>$Pj!s5)&;^xou)?@vB`NhbjK}Qy6eiqm0;;g@>PX{NB{5bQo zxG^ry`up&A$0d#YIPH8GZ_S%mZRE$9pT$jean|4C zGiO#C`EllFadj@v`kOX&YPFFcXMPsf)5TeTm)6%;8~JhOXK@J^XZ>kzYWqC$acLK4{r&I17bZVjI8?Q-*e8E|=4WyJU7YpD z`rdo@=;{gMmoHeK`B_|*i?jZI{dV8xoaC?1{48$qt8V{u@eedsCBI#~xYvKa996JB z^Ru{&i?jZ|ShR?Fz0VctL_wVSS=>|?XZ=0$@b+1M#>mU+XMPq}=i;ote}8^<^5Z|B zl6>{${j=L?apq@nJzbpjM?OFO__X8`kMB|tXMPr!aB1*e7}IHLDlInV-cqx;X3agZcfFldoMP`Pie67Ua|XEN+a8v;Jm3*DINM z<(2F)CO@C%XK^VPXZ_uCcQ+lc>#MCEmQVAuxUMeF`eR+p&nKVQwP2j)XK_t0yZz6` z``&xicQsUIk7@biG(U@*<>IVA&FkvxCQt75;+&d-{nq>}F74v1zrX$U^yHgw{4AT( z{Pmfi#r1b_*57a5IU+k(*yr=(%+KPgT%7gy(hI$lGk?BQvf_vD3-V%q7Pok=+y7kr z@7_N)spEC>=QDZ~Y@hjAT*k#&e;fz1>tm35S^dn<;-yP+kv5v#Y;dJf`~ZO-%)PF+Yo& z|B~DPTs+5IUB48q&-^T|(ZyMRAy#Y&o+GkWPbUocMdv$(X2v;J{GW4^Km6}v<%Ij{-1^MV z;<`G2>+c_b8+_1@%+KPQ#(H_N{@(f3;o19Xe;v%v;%2!x>+hilw@tqG>VZ0s z71q!EEH3ThtiMH{eO8^l#`49PpT+fean_%%BYWu>U+v{4mlyN1xGEQC{gK0~Crzqm zy!rVwKZ{#@v6mO?@4099;W$zK*}{bd`>pv|T*k#&f2@P!?!D8e7sQ#L#Z7f_*598O z4D1CrPU%t*XMPq}=i;otMV}2ze*M+xF?}PdCs~>%22VH|6P!MN+7T4(FtiK84 z$5%i2z&6PlkL_3xXMPqp#>H8G)FHd?`mVFg&*D-p&icFI`s=ITe(SC3r)SM7sDt@g zTvr!o{XIRaXZ7v3-Ij$_+wsNxEUxK7xBt0#u3KKuq^kYrBlEMkSuW1{Q@u4`78ITT z%+KP|F3$R+b{t!DA6Jl1^Ru}AF3$SX_GM$3Yx&z}eim2d;;g^N9_^m}|Jg5|+^rza z{48$q1#bUy@$7fp!*TzZzdrM`xQvUl{c0+ZmNs3{IVA9e1)Pjyc!C%WdHQxIo<7S}Y!?SC%*vT@5MfBT!G z^#7j1>pJtZxLGdF`pX_40n&*IW9&idnjic@Y_JL$ilHb0B&@8Yb#d#7(voqsK2 z#~1UnxGEQC{gK1VCr;FTe?`H3F+Yo2e4g9?Ts-;b|BC*+n4iUET%7gC`m)z9zT>X> zS=>|?XZODb`=^+PW~#{4B1~#aVy3nis~IpT&)Fan_$pqng4v^Rqa6?qmJwf34YhV(Ybe zW_}iD{ab&wY;gz5vA-S|r5n8K{?p`8%JZ@VkKA|o(4n=bjv6w2SjA4wy?2OkW;=Rm z+Whk!_&vE<+jwo{u6~H@*(OJ;lZ|hXdLiFkyB3Z!@hk87rLDx`%+KO%`>emj7r}An zXK~iA^_O_MamW1M-#ffdMaA1I=sBY6ub*uHB-lInc&W)i_ z2hj$;SkbK<`CGBF!>O0pX61|iQPZX2_jATPct=g)AUO^i z`4-dFC%66n>g=^jw>t};k=QXb7v&vObIVBix_r5!;*zO5CVxonmF*kPuAkgHd((lp zRo~aMHmjR3Z>!|~du{yu?ce?-d+aUThWg}K#S?=cE21yriJ?EKcBU^EU;pK1%RD>r zSEGCFzW%N+c;hM@kBey@kGGudCs4PHwoUA!x$wC2-GzS9v8-MAadAI>EX!{>ISC~M0t>EqL%e|M~?|*Rn{<3l>HfWq)Tu1#)9C1tb{SF<~U))b{1~{)QlF9OY zupQ^;1o`LJzI}6m-&cN9*R3nRe^2&#YS7xkbQ)p@z8Y%itoeNB&by_b?+zJqdfliF z99OQZVL7qGCAYhGc)#uKFmb1MJ(}N-4nF^9&v&ffJi zU#m}ka^Y6VW4Ak{*C(HR<~!~O#T52lc&sk;-+kef+3Qanm0Y;mDbEr0`@#D^zvrWe z%XXF(K{5Cd-&K7P|Ak_-SWmz7b*~p+|IXU0 zB^UP|JbUGp|2ikP-9+ZU3diVK!gSel+s^XIp6_%X+nIBl=1KN^m+RGwmws!pt9-U* z{gPWb->o1QVuRLiy(e-Xk2X~s+Qi}U`K}FVD>B( zwIx@e^?*xScWJW+`}~slO3W<0|7MJgbw#e)oxCL8-*tU?i|*W)CS1#yTh6f^PyNT# zFnxCO-JhVCd^>W!PrmH8S)AXeCBLk1y^Ew31v*UmI?36WTKq~`my2v}IwD@3C9)kZ zzl=ApmU~Z&%T)aEgIBW^R7wAOY}wl80WI;ugM&7zS>gv-<^giPqvW=qvby~J6FV0$ zw*Tha_wB9!pR#Ye|2>`Pi^aF4*S@{W_dmelT5)>C0R{iFY-udPw(O-_l6z5o<4qs^ zT1KC{R{wjAw-0pI@*HI#@1Xwi`TK&6&;F-s%k6C&m-ptWwbq}1-8Me+YiWF^o`34l z+HcZQ?)>o$tsT}u zRn8wzRpD{XnQVME9vfd`C2xE-ZpJrs(5NA!MxR!D@>xSV{NGa?Tk}V2vW?HiW8-Ud zbKc6`H!onEd4a!zV#e9hyL6Z>k@3f}l^ z+`jR3cmiA7KWsdfzeG1T|6Cp!pY6XRh7Ub|*zmK43>td!nS5_uN4z+f2lZq1wed8M zZ*6aUHtw+Tb<`tJt}|L3pBs;juWmCp|6Cr!#>YR>I^xAOP*6XXzvl7CDLen%`6J=R z9X3ASKlOrE5lP4OS&*ZR&(4FEKl8P5hmCJnb8KfDcI|kGj@o0CF#F&5cUlkmi~2gG zcln-CjEFjjR{Xr}v3pJ03-;ssoa<73{Qs?1!ECQj_PmmSk`I z5`3=}^h7o4>(Kr6y^FdCwf_m#sMD~1gZeyl|MLBBu0Cjm?bM~*|Bn8vjr^y+YxhgF zGdd~%XZH5~x765W5B|sWgL|<$lm9QZ)R6IV{IGHJ)xxl`@V^qjIV*dAYrg;2TDB*D z%(ngL$N{W`lkb&z=l}M0=WjDIn`b)PZd(5<(QnOyd2TWOmN?Vp=hSpdLd%@aABWBJ zjr<#3;xc^(hdid^_wnMQ%@}j^a|dy0_^otB;0X*s%*@UpnXE>4u7TRbk6Q)*74L?-g$=mht8%9Xh#e`XGr#a zt^u}ERH@4D1?!(b+gm@^-!1JQ-Os7zowg^RTRXB{Z!}39_DaG3O=s=?RCbxV%-B?Q za``z}HR7Bczv2wU8Hh6wXCTf%oPjt4aR%ZH#2JV)5N9CHK%9X%191l848$3TGZ1GW z&On@jI0JD8;ta$Yh%*ppAkILXfge8uJjb8Ap5A}q-e<6f-dy25$6uiLJMQWzRr)Sk9;o+PuBz7LCltSlmgnew$WN%BW?A~4(pShIeX}fm zN_xJ#5538gw0wy4jAxScCim5HHT|ofzS5gqDg9Q`GoDK6O>WR~mGom|Z+I>zch)ko z=sU~bWR_VM?HK7zw(&GdZ{tC>@nn$YPd~^u9`b7ALALxXpUmYaE6YYsn(&#OCu^CU z%yN2@>5FlocyKq2RQwg(wqFT^o;powI54wGI=;m`Z;RZUX#;WKGON8r8l|G=^1C8(<7UI zC9=~a)Bn+KJoMizOV7F%Xv01+NX%zU^YylI(pSyjPpSNzdnawd9`H(P}4aY`j?KT=m&kE%l)PzQW`N>FINU zbPd8};*fV1Y_G}BN}rJKH1VwTCO1mIqI4rgqx2^Clzw&T#)zKMo6Oj{NOzG~ARqea zEIZ>cxm!}-J|=yo_P=gwO=kN(mS0!JQ6G~BNZ(b<otArO zc>~$Mo1K%VOHYmZNIzYAlPjfX4)>J4QhJl?mFpVm>91a6HMvU52T1>}+A8^*T%l$5 z!Q0eUNN+MVV$S%FQFElmUWRt(itdISR^)r5xE0f-FtVVXWbt{3qh0{+(c6wyC ze_JiH9_E_KA8EO#mKUh~Xm(Dn*D~9_NNv6JCR0;##r7=Fn2@va%8AMBGt{7;SR}p4 zYy)%c6hRJ6rcY{gn&3rlQ(7l4a>bl^Td~MpkriulMYZQ&hrb{ed4}_!g6#Z}=T&<- ztdVY>mdPjbCP^=k0~NQ4bmSCyFX_n_b=ym=$<@+VX_P8++CJ%k3M@Jq^?k_$0|5&>I!sL4C z8DDmuNpCX8OyX^k%x&Yr$^?+{2S?H@=&9(^J4BWu0}!b zzC>26$@EDbJFBHnlM`C*<6;xio6LM;?y(JvwEZSGNKY)=-XOinj3HlUy{vzZ{8N%A zAuB{uJ!|AcjhN3h@-dk)AXiGy7))ke z#4-lfWpab`he=;AeS`ESvo7Q*(vPq9j>!$kVs+_}$usj|46-n}r_B zNKY>7rJp0c$#v4RjSHl&lip-%jXYH?wKloG)6-vnr$=TEu5<1c zeFd`9BhweP<~YqbOPay1(qJ%D%loUft;@BRf5ELV9xbFtS>c z-_**TdQj!W~@PL|$e;_-i5?ZeWW+}G)QsO{_Y$m9W^I%KCuChr?bzYzJk zoQ(e_(r=se{7q)PU8V0K{ll|!@?z=PPPTio^d@)L@^@NplAUce`4H(BO3(N?Zkc?5 z^yIxtdgiIg8%Y1I^t?8Z-sA=HTO>XE*#fmD<2OaigB8cCLECwelebq(4tUk5W!=1J zFHqmSW=KBF$bkd*}mh|$3U%fM76g+n>;}J8m()(^z6?jvt5ieE!ZxT$@{L( z#`;a(N4m-CzeaH0FuAMrRf?&QFLf|^3+X>XuX|XIHzrfBZDkv%F+HpplNtBhT7E)| z5GMDOe!Q076+MN?Q>34(WsZ+i)S6r`eMZX_($}jsxl#IgT0TSiMztn)mi}2Svrl$b zYjTD3v$VXj^lY2SRnjle@<{2c)S5g{`j50cSqu~=_m_T=mg_`+VRB#T8DkIW`U;cr zr{9dg-{h&%r$vKwQ-#Shq+2=ZogZgNZ*r6LbHz!rHA&wrOE+1$ALjJUvh*Y5$2`K{ z!e%CB( znPb}~PH!^(q||qfTKZ|0wfwR4?BD23rvF*elNb6onSME!G|IM#^d>LX@~YC$(f!5Z z**W=0>8Kh0N6O!1w!f?VdWg4$$sbAIU!(d{e0=Ci$|n4`3|RFR_z^5k9?VEknfk`GU-hoC;b%Z z-WB7dH+ib`8R-^?snVN#xAbfies@c6@~uwapmE;n^vL6-zfHcC+OF}^n|!qN3F$e$ z94)=cL!}=m{VXw5dXq;=&pL@`pEUUt>B%|&Lpw!!lga0^((@vpCbM14nK>F8+hsC& zL#`L(&E%V;CkKq@Ch1KkpXg=?@@aB^=@|p#>o2{@{iL5HT@TSudXxJ&J>%))^vKLZ zd}bg!Ju>~H=f0T!P3~)Y<)N?XCA0rdRvfRrq&K;z^o)hP^_0F@mcB;%O2wcznf;4x z=_}a3OlF>^r5hud=gqS8)1@2X^d>V-9-l-3&2RdXw*x{yXW|uDhf+nR%Y|(Yl!DCQp=p zzI=I2l-^|KCplqznV%+)lAd)kPew^^GWleT3)DaPGA}QdXpQaONfEm-bU$7rjFEN#&cdBO+Hw9a#b;{zwgm=A@5%gc|2sZAnfDn!KBBiw{Q9Y8pgVJ-f;r*7FM!X}@>$yMqNP{& z+S5+2&3>A+eP1z^YAF0X?U-kBpJ(NBz+o@@JS)Qp^NFHQ%otYidDg_vxzDrKZK^9A zH@@BV9E|Um*j}BIoBaIP=kl@bAox5oWcMw#J36p!EAS(R?-K!Z+X=qq1Bk^J9e(5i z_-;8M20wkxMHapX0f^(p_p$>q==og|U_9vgZZ=rmVSa098CG);gAbo;2G-3w*L2{E zy^DkIFadn=S=+IWTEL(6uInHUy&gAaVZGS8IyO)X=C`3_Sl>Y$_Kh5@n{{sNz!!U^ zV-vN2KRzmNmd(@x{;a#3qf#yU%^j>4+ZK+k)Iz1ePhSk4WlOa{9QG;)>t>zXIPk~L zd|(dXhmE>Zf9$M_xwfOA&mL|WJ)ijobbLP)-*E)^;g62-?&`o7`)&@#$M*7tMt~o- zJsfN=_2}uq2iu+w@=rbYa^Qz;ZwK{czUd^C1wU*_2lIP)iF&i5QFb6j?>fvG5FRxZdD7!;Cq{6uv#Dn-_sqp zs|8~4y~9zb7Kp)jh~rMRKn%VOjx*E(G5DV8xJxY%(;)743{?xn;M>QsuUa4mUu10f zqC3lRiCVCJ>m|c52eH^^I&8d;YZ>aD{$jOY+doz^;Di1`hvoAkEdxH-&UaXSF3>XI zgY7(r)q9MV0UvCm9X5Z?)iU6N?Hq^A$5C1ae6XGExJT`DG17q#wkI4j)Pnih@{`g5 zal-}qCFjT^oJ>xUe<9Fg|G5L*Q%=UmWcp{_W@jB7QzkmnYT+^0Ual56=5Q<-@4z4Z z1P8|$jveD1_+h)u!7=4t@r+}ZTDV^{il-gFR15bB?DR(;*onQ?@tj(C)`5*UmeEgk zpxaN-7dB-2qJ>KxwjP#Q$JLHGYGJkm8+||TctI^(b0-qo+@FCbP`gn6qp%dAdhT7vzCF|Jq^t-)I@g z>$T!t2Y%>(>!42e3he0a6MeQU$-h%OQD8&{wIbD ze2~8v=tnsD2eoGjY{=ZS;4{q0XRH0MgY~hF?;IRoIbMI`zz^HEj!|kyi?1E{VO#7t zSM7P?D+hkq{^J;<_I&Yg2Y%STbX=hJLh-o+KWtw(E>e53_?H7eY>OOY)y@?EaNvh+ zq2qD2mxzBl@Wb|*qh2jJ!;X&Jtu0tazl>x+*F`Mrzz=_P>j?VA2YFqAp7kKFDHsm$w-8)_NQ!A6o{sN^AHW66LI`b`{X$acEeRLg)5wn|5x zY_(!DEdxH-x;X~RcBYD*8#rj(nIvf2?%K2eB*}8}h*d{V~!Z|4bYy z85{B;0{v0aA){wZjNj~xpRwUb9`<*b{QxZkdBN6KeH|gty{+~bwQ#KCJ?Y*T{k07J z9Pda+Jih3NKTaGbJ+a8g3t|qJ4jJ8cf*P{Cqc}u1*jF-~?7#*C9s8*Td=C}a@IBFi z4o=cC5Z6PX!{-DCIvC)@3^%vAmnuT5KbFIneDcsvX;^WgeqrtUq(? ztz}>v*^Yf2*t<*K!BMLgPIX{|(;S1<0{#cPSbPUL(7`EM2I5!;I(!%d>&AAb*j#KW zhB(j-6=yiMQoDuIouw8#zQY~phKUgl;)q9gj=+xZ*$#B0#7GBm_@Wyvu;Y841Kqh| zjDt9Q(c!bF<9xMX{ufAwy&S}2XZy*6`H=_mfiHQq@sT&`VB1H|Sw7uid8*SgV8>?1 zNpw4igOqo`h7K8@4aG*Ho735{`E#t_O*V2(PMJ?E@8KY)N4M ze329MmxvPt`^TjYboFAKgZ*QG(zXTy$<5g z-{)W)Y}fq`{IE4T*e>$)fCE2l4?4&bd4I@(AGU`bA(+LgM+$0Ett>DnO{24 zJtLlVFn5^K=fw&ihZ5O`8 zqI*%`LyT=FI&61}2Ej79A`U-tM*eJlVM%uw@+Nllr{uzz^Fi4(f52_?-hkY#%u8R{JZ# zJY^e^-xh2a^A`CX!92%?{A+=pd5`=X!G3}b`CWmY{Ra8Bg8d8|@_Pb3`yKN8g8dX5 z@_d1w{T3M=<7dn)|6Y&>w)Kw=bbk;F9OQsJq5F%#j_;ow=sp&Ib`Xazx=#dleE;e| z_o--d5Qi_ie+caO{>_1Iq4>LlIDFAzBbVgc`X%4w6#GlU#y3|obH=uhxkE147RF3Y z$PZ&>Oyq|=G8WcJ9?3i7Vl3pHI@$QB(^ukhF+qIoK=&W9*fCLU+UfqS_6mUy@|ObN zE1mqg+N%UMTdoo$X+Z_|dP8n||@xQ|u+^i@4sRk06fsB`jJ__vU%e=cTS#CVs^kh%@lh$bj~D zjsx}D@$L65rb?&7LiYVEtEvrqbw73Z$YHf3+x>m_>3ZM$lb-b6pR|fr+&OIJ(%bsm z`~IZVg2MGTKIN@{C#|DP3+q3o-=Tv>v|x{F$_%=78-72>UQ>Gw*@9*2GG`N=pImGfw!GL@tRV2gww=J15Gx9N)MfK~oABkevcL!5l?1h5 z9rS~Zd9k{{XAQBMzz1J+*r*-rSX1DGjdfuouWJc>))wmseDJ03bp<~7Vp~tJ4t%h6 z6&nbAHWcd%eDFnwZ6m=tHWv6`V_n!Pg;JaK*;MUj0v~+oyPK#K_+r~!unv5%Z6UT2 zm11kLrN9T@N`b9Pu#Rm6KG;|nHfls|@S!Hu1|NLsySu;#Uu-)H)`1T;e0CN+#4Z9G zKFnEcjB!_i&u)UT;e#(WwsCiX&mMwp!v|k%4fdU_Vu?-S83w)-EQ^Y`l zFSb*~G=a}8;xvH|zSwHTtpcCh#9)CBzSvF|w+npk5Oo3{e6bA?cM5zO#2Eq~e6gJ= z?h^RiErtqw8U(gJVqbv|Ez9`edzQFF5R1J*)Qe#PJGPmEb>qW29~bqaL0l{tH+KB6 zGw!hhzY7K1jSs%q*zSu2KIaSah7Z2j$lC=1pYsHH#|K|**TxBau(6M!!*-cq zU%OX4BW8*F#na-K;y%$Rh$jvkKIn)e9^Y%lbK+S+KeQ}Q7W6}m>GzY29X+uuUn*D! zek`M>Z}co*E#`>Xf^|JFUJ%y^)^U}1QOpteVtYwU68Kyx<_di9#rCp5kIxn20D%uW zY%HT^nQdXctbc#O`dKe}mI2$F;#cA=fe(6?>FX8os$d-%w~YQ}r^EJ|V2rN|e6TTA zblBbybHqG>4>q=Sj=;wH83*gf2OHyH-Pq}a^$?34y^V!&5|1D8EMv!y99$*t5zNWy zVv<15GB)!5Yk|*i1bN2?Uu@TkcLhGb732>ee6cY_=6ZP@WJ+>K!j@_~46;9FjYH$Qik-7x<#Xwv6;^i)95q z*t)2tPkgYgBhX=^?{y_JCVa51DbQhKY->qooAAN5hCqjnZR;$VoZy3PHGvKrxmjH@ zImHLtDgqrga=WTzYJv~8l>|C$)MjPL)D$0Vodh~;)HWfRIe`zh6$CnL%#9T#GpF#u zww^$Tjk(oTGPz@JE+?5BE-#p)EiwfJB=NT9=Zj5t{Gk!tb5_A`MF+fm{W$>3brdhFEs5V5b8PZm3h zfnq;_o@KyxqBvB#lhopao@Kyxg6JUz2z=194A{ucu7aH4gPvu!4I4geBQ|{4Mz(cp z!MyAxb`#ZNTTvx;7eCW__EyU_FlNTSkJGcfgXCIqs^~6G6N3eMmI2!!aj^VPQHu|H zmH`{8z#_UJ6oJ1MhbkejS}dv zohL?%F#;cK=ZZbW`2rh0=q?a@349qhI6RcZYIbPcKooDn{EQXgOo$`^s|E?&h#vAC^)Wi?4&P_nH*O) z67+!`J$=v@vFNe0%rP7vWI&IOnr$t16VzvSQ6;Dy^+CUfpf=d4Nl$?vc5D|5YJ(3o z87uI?m%307?D%1)9@GWD6U2$)QgMkGCr%OrM7`)G&|^;u?C7gSjo3zPC-A`++xB8x zfe*H0#W4b(exkp?2VeAz0UOJ#7a!t|6U5?wydZ|{=`Ai4%%h7$AHnu8kMt!*;W{LQEC-V7pSF!!}J^E^ZO{U`q>h*lraQ z#BBl}Y!d~xd&FG=pS#6$fe*g)eXqa=Uu^dY)`1UWyIv}1Y@Aitou2~+4ivlJGo)~__2O+gdIO@Y#VlLY#(;k#X4USABwqxb+gWo1UAO}roiVd!PxP^ z7aQCAhQMc@V0-bw7aRF{P2lsoAYb_4i;bMVD)7k&a*GeX*r>B{+r+jpC*Ky#XMC`|BhX=Ee*apqKj4GyHv%0t_K$Z3`x8Fc zek;&nWB+`1qxij8AlPnl^9O+r+n>Z= z#GeH|*gh8Mu>DnhBANs~*gh5Lu>DQ^L;PLfgKeQ8FXRqC?C8lMdBXmZU>(@;!%p9G z1%B9>7mO1>#>KqAjvw2H4WBmzHhkE=dE#Y3Uf4$Rz_yW>R|I(=Z)_iS{IIiqPFY~#hZ0zG46{MZ>kW5mzw^v(G2V|=UwJAT+08+L4r5Br|tV!`_9m-USm zdkOl*j-K`Q7OWRN_C6vh&|@dR*wNPr@@vnv?YaB@((zndO~tZeIl*fMv7(TvVtIjn zC9$$dh*bsp)x;`db+Lx%Ebv=PtS#_cQ*;sQ2>e*jx?(-SdhqKi))yNH{5BFB3;Z?| z=r<9Y3f8lkK+k$AMK`gz*h1j9mDpO~x232O+X(zv&$ePa!Fur9UhE*c3;cSBodkY6 z3iLaRT?FgdO`vBzyNcb#9-^nfZ!fX8z;92nkLV@vV?EU(DOeAFHKMoZBk@V=! zSD-&Y94J`NK>|JN;X0iADC)w!6n@m@P=O!!Z-)t9_^}@9dbnUc_+jT>_(;)LaIcU3 z7{PsZjp!$i75MSL1IC9R|5sr9ju$5g{P=$e+lSu(L4HpX{2v9sL4y3^H&9TYQ^ctP zzgj_k@HAKSH}z;C!XTVNaE zmd{Z;QqUi^Q34%(jTYw$#)6*ppC{0d5o`l?#(loPc7b5qu@4j%3Tzh%a)zBeT`aJT z733N_`MpG7V|>&IJ9S}<*e(_0#ATvhj29EcM1d|Xt`PWLF0K?;iAe&#Ys9qzzpKS$ zahEd2-pSVZdFB-)I z0>6jE!vent#UtWT@tDBx=VGS7Z-#hWJRzPG_&p_N3H*K`o)*uDUkd!56SD<=&x+^8 z9Pxs{?gC4-V^w} zE8Z9L#qR`u$R7&)J`lebe-OlfB>pHCh(8J9|1AC@SU-Lri%$f8tpBfqvE%cpV9ZV8 zZvx+c2(}%+zYDf`q4=l3?_Yx4;rE#!XN$z=0>6I?a*y8^f}DRTz7qI-Eglp2{YOx< z#o`-*-+u+Q$M0J~&A$`>6Zrigm^=7=FYXcS)XNC`mQ}l)SYF_}qS{UZpA`f-BeyF_ zP6%@PC$Wm;l?D3M#Oh*IC!_By))4f)mcYKISX;0TWbEsRb;UUK*~Q80sqHGpOSgeq z^jAn;-|070yOFq3x=qxgzgF_bPQR(zN-lCf_s zZjwxFmB7BO*iLLOwsCTIwL6L(1bt)MN%RotSjWx+-L8UuvF#%0cQ+^Fvxn#@xIWok z>@D^Yy#&`LT$j`c{HjH7(MRkn@Y`P;An@By94J!aAc5b{#32H|gT8Ku?|+ORXTDbubR%2Mfl*x=t62oj&ko%;@l;FLE|SEHCQB z8G_s)uOP_znPRBGZEvJ;5SlGJN(WT+lbA?IRd|}#Et^LQDU?>S75(I zbMHL0W5n%}v7aw45cH4Vh2kQCAN}7cKgNd7#ey;2C*4@J`0gs|#iimBCo}&p6XV1t z0{sLrUZCGpTrMVx4FvitL|UNVP+TRh6zd4|SBpsk{d!KmMlJfu;yQ7yxIv&tzFyGp z6mg@#?`A>2_}wHJ*Hkf0;CHKFT=?B0*sj~e?E*h!whO;I#GQh5Hwb)KCpv6*3G}R+ z{#hS7`dv%hBc_YH#eHIJalg1%tSZnyAQ}bw&f+2QphyVxkBEl_`c=eZ;!&}zm?3^H zSkL34lVCkF1;^?q)IKTjdrB-L@cV_}SU*ec(*nO=3Xb{sJtH_*JgfFOf#36ja|V91 z1?QSMYF`ley(Bp2;P;~7Ts2qi%L2bw1?MdMUJ;z@GHPEF_`M-G=i&Fd;9NOR?VAF> zUkT2c_`M}K@4l_}9f99(1m|7+el0j}lec#Te&lU|bol*NpnqS?7w?G=1o{uf?*#p# z|GoG~pr_wIia!X(h5k=sfk4l={v!S?*e>*+h>rz&w(GCrQ$b$P|4lRr^yK9q;_rgI zqW`B@DA1GFe~HfobwU5RSR~L>mw$^d1a(FKmH1Mir>gE*brPVqbxNl5_{C z-CtZS8GT9|D9~Ro-N9-P5;sUje~9>*KtENw!_*!s4i}8QufT^fA1SaMA?TNNGk$zo zC;j5X_>UIElAoglF~^90g8WR=x>zrM#|qYahjjhb;&+@lUYsD#*StJY?MdPy$ph39 zbF#p1v~&a25<5mReiy4H_7pKl5HnV~)6|~obhT=!af8-7SS_}DB~$APoPIWSD-&%>?FpB3j}&(<`Ox>exYDHv6DNt z^CH2#9V;#o7mG^;^LCu57vseQ!FF6GCW_0Q%ywKMt`t{^w76PahrpNybiY_AFWSyiBaU7%ad$y=zsSiB*q-#D?Q zpnmhjo8m1&?eO`Pcw1njhVO`93u=eoZ^XL-KWa!Vs2e`N71WKIyeIG-CFYCYiTA~a z;ym$@_(1$YVEd!^y+HRT@n^9>Y%l&I@Ea>W7CVSb1%981{ApTS_SP=I&@pthL!JPZ2_)IX@77FGTwttBU0v&T~qF5x@jxPj#d@lYi z*p4p+eGM0134F-Qf5b>ZUht(~>_Y@L`Wz}2i*LlY;%o7p_@DS*{8yl>SWedu;s?pg zsl~Ru+GU-7MYWy83SuRJer1sm=vNi1iB-fJ0)1z(xJ-$-mAHWr(RO$C0NiEaYFO0l`vLg2@GwiH_l)`Q>HqDtV$IJXho3dV`wc4B*h zAKSTu=q}h!{B{&Q1b*acC$Y02Pxz77UDWO>@a22N$tym)3Fc-`wR;MFYjGl)*3u?Tt+5^OaA|oAdiE^>4HAkPL3(W)(MU!Y$tgcA{Ya{X9)6w4`U(kY#%;s z8#%{^^$itgi8IA;!TLssVS@3VEk+9bMhV7?-#LP99WBlk_>B>4Gk)g@a&^AAK;UD@JkD7i{Is9SHX5&DXtJ~ z*KT5xxJvX8=&uo13-mjQ$>LhEy+D7xxK5zoK}-=hh$?~pCUN8cWA9DiqbjoK;bu`I z`zA!z2nd)#42y_J>V`R(C+Q>&Nji3SLO?}8lpvz0h>;xxW!(31k1L>}xQpX9 z>Zs`GppH7OQ{TDu-g(^}K_JQezweuW!te05y0@xsow{|a>g5HH`=i0I}PHfIRyP@DLz&dl-;3j{u@q z(&Pi8_fbH&BWZ;;a_hC7swr(g=U#zSu>0(+YSVcma40coAp|yaen8ngVkFWndp5_gez50T%iDO&7GY8o!!pef>)->(Q6yS{ILRVlA3nXziQZ&RSA;qP1$@1gp)1 z)2!WRq+7>dI@P-J&RJHU&7-Y;TV}cGD3^Cl*m`D0g4J>Ah1NJ#1L-y7geaD7SZLx~YHLvI(aC)N}uixu*W!A(^KBt{$g6Oh>to zpBZlI&w4C+CT!|YJuDCFMY-wCM;m+hsBIa$QBSAk7aBX&sb6U9O+D1hazD`QT+@!! zL%o#Cn(sI5#(Gc>q8>^2YT;Z<@?oG(6oPtH-oMo>Sej59s50Us%d}GL%m{e zk&|{yI_P)n!SYZ}cqH`~-bnpf9^r}9U-&QerylBMI#&+-m-!+yekLp{{XaQ=Z`cuz=OQ%_*{xZw>4Y3>Tc=uDYthc*%wy5XTU9+rV=Ve9h=Gc*XDaUrKTRh$5 zPdx?AGfe*EAMH=Qlp~KHSTfC&n|dBSARo&zYX(<8#`T@_bG^P@d^&f66f(?OriG z+naJsCvw8yis>aE;VI>qPRb>Gt(acwEB%6UOegg{lz!Tga!g0NQvb~d>ROY^=BF_IEdxAB!<)x{A z%RbI}>&Dft9_poB@4WBS*7DV<-{gF)`dgQ}dWM~lV7>6j<*CzV9B*yEe4DF>dMS5% z>9@+Wb6e`h)wZrvRr$rQsoj4{Q5S6a zO1=Bod9K{w{9VBBA~oZUj@B(VTY-lBjhK*PJs(`5 z*52FIx@f`GuAb~3cd6?(U9BGeX_MNwWreGUdMS5y<1?&jYqqHqw|BF8oVCT(bMn+X zRMFHe>bJ|bsuLdA=IWtd%6)O?pRC+Lx2g|*I@R+1y4}@d?YvhNx7?vV8N5@~nRTnH zhk7YD@U@Os%cmbxXMNn(n)~Q|uAU{YJgcsG{yw$!$A{F~mmhQWP%q`)`T1n)f@ROE z{Lflh`x`y(>iKNbmwmshr_IKHs4wT;uWnd>P~AUem#c?* zDVM)TtNzD7qk8Sqs?+E@Ts^$zbLyeq$zL?G?(BH0`lW6QtM7}qy7{sk%;&93-%>BU zeXDBx(1&Vw_gfX^DfhZ)^>&v%>W^^gq-~ zIr^KX;SW`xW-Ao+Q2w0V4Xl@XU#Ye}s?~zDC9XbRPx{v^&tm{?epY^P#lh-OqEVf^J-a_&sdk*eWO-om#%fkyNcr>pZ8n(tNPHlE|ufj)@`*dU#lId)I&Lr zhfGJg2mbw&`l92GRE}#GJYL&cFsnS3dML;7km)FQ{WCwQjO_AMj%ysZ(#F@Z%5O+e zf9p}ly1v$KcRXDB$&adegWaj)?)_2i-khK~j&WRL{>+c#AN4RD^JD(h`}I3@t>Z55 zrV?(~s`bsMs2_J~baie6=@~@=VWs`8;Kd zRvVi?lFE8eAD=Tl%gywBKBDjhtKOykRQ8zTtZV;%wi&x_f&Licfb**Fl)ZD z`(9_ddYGQiS+0xj*6QM$lv@2=ElbrM>el1T3x845jvuP{oatFF+KqBImDRCE4IS*- z@si#@s(vE}E9#*f?aFqeT!Z84TEF(|@3!Nc)Bdf#7|>r)59Qdd6_0jLquYI*ytM`UJnEIE~YFl^hdMK6Gd`>_3?Uz5RB`-eY zjyu#txpUgowt^WuQ<;u(9EUzV{uecU-cEPi6nk>7f(pZ0G|Wje}n9Q@b0zp1<5 z+vtwF96u=+#CUxD)$3B3j&dA_`J6|}HD2toUcF*RYQIg0BNlB>EgJA2b^cX5Ts=(B z=agf)uS%>Xad0a2cpLnt_W5sd>%sC+FXd>ri)PodQXjrMm3m%Z@~gU_{BGBdvg99RhvlX`%gN_VPkE-J9Lvq}QJ(1;UoqbLxsF;p&j5q)JFXb5DCw_IZ_0N5)6!koL`wMEt z7pvU(o^d+$QjYVBHBYv)MtymWqMmN`o>A)(u65@ZoF`B(ZKg#m*1Yy%KFExH!AA+&!SgU@X;IH`6cIx)Jr+ed$V3_Vf{98 zyP}>(|9V3mOyBO#b2(3?UdoY&qyJPqK8!EoxW! zTk7w*n-uj>?yUd3u6(_>xaFiC%FTPYrPb{1&5G$L$8v8-d{r&^VzX-})|YbD`H9vO zgElFqqa5wF{??b!rkmV$qP;2i#TTuuH}V`=4oPJviek#dMTozdh-vYsZy_Dm+;VbPsSe?IpLs8Guz9-cc-@oCm6LQ_KucMc8T(|sWPO|lU z;%-Gfo=$Ztq z`&#w0kcU(CKa_4aoXtu~oCsn;!fSmjUKnR@oYa@8ySn*ptV zd0qwDKQW;B(ig$4+|*qw>RaJQS`DBc%Dqyzk@fFAT~e8ja+4YyP@C>)J>Z&CKUNJ_ zbxmDyy;g6vNO1K~&cEt->&ykCQkjl&On+A5x2os9QK{Tl;rZ7Qa@?;;{pL$`N}UC%+*hF<%H6xRk@Zc(OH-MSa@?<(*We>{MfRns z+*hF<${pz0(z5=3dn(gWj{7yoefX*xe*PV)Oi#Pf{*`U{?y0%vmTVE zzNPKjSS{{8kjng+KY7Ibnct7Co>OzCf9~>%^G(`;dZ?FjtT*?YsE6~-*_XDp@>eCO zqPtpKbKh>QIA1)+^SoNusf{{u;x2XaM+xqHlk-L9&-|!|dMU^Jm_O&OoUbxJ=09!W z$<~goT@>?Yew?p*3iqnZKk1?<&-CoS?9c4KOiy|4pXL2yp7pJFacc242O+OgG zbd>wx*}>M3zIUbG^6gOT;koSxYpnRO+Ez-bbwLi`J{Iq*g z|K0yotd4&=U~7xpRgcN{rcw{(+B|ZfdL{KA1DK9-DDRN+^wds z@L9>9Y*e9@Mbp|kI;ac&dbj$m=ZmS{MQ^FOExV=u>6ZuGbd=lI zBgcAa@}=svA1=18?z%*+J8@U)C$C)U>Y?1f_clBGm6w*d=_to~uwE=T^-zxW=#+D| zHM{X0?szwUiL!ouYQ4I#)(~s&tQ%GCy-U^9AJ;36cklh<3N<9<4%K{dP(8TjMmHVh z-uZ2m_58T2RQnZ^trcC$)CbEat9l!+a`jMd;Ke=Fs)c24I?By{Xp%Mc=Ot?U2Q#eU zdrH;!8~Ugv=Ph;hP_Av~7HWCV6>d7prR<+--Cpij2Tq@D-O#;2O>EIkWo*rM^-!+X zUyoO(pAuAST1>DC=L}L4o}F&xhx)m4)WdY&4^LFh7oDxz|EyH^5rbX5v^VX~e5sG} zv_IQ}`Y6ZtXT7P1{Kj-|?)y>+)DkH{P9qda+&`_fXi>i*q|`WkhgY;|bX8M;3d#f`~ z{l@J_mp?zsTL0-+s@V^zR^9Y3RiIXXYvx7oDVFQcZ?9C-7QUzc>RF~{jsH^pwttHH z&p*FXEC=P8p5Y*It1ddbG z!*Pjm1NBfZ)6))=<8#)RdYB*M3+7Axj4PNA+vmY+##%FH^-%McPO)y=-O%-KiJebZ zFTK}Dt@y_X)oj!mu0NwcqCcbmqCCe%$}>Iv8OKSs1KWl2EDz<`&TJQ!hrD1sLwV|> zJmVSig!))c%25x~Q=a90-P_o@^0*>v#q3X2`l=%9hp)a=(q#>$h#C zT8Qh1Q?4=3S8wgD?nL^Lmrhjw#&gOueX}!KS*u=4w?13Z+w$Gyw{FO4p)SNV(^J0P zr}a}O;5pM#?v>P5)@l1jSna0$$y)!yKNW*5k{R z6*#}ObzAFx)~>m|)ka)X59JO{KS8a;bEc!*`TdWx2ITg&PJFMS)n`i&>-xUuDY>Q| z%H4JA*{TzsGaco^Zyr?ZUOB`1<-o71OY6?oBM+6T6}YAz%KdHZeDj=gGjg@M?3Mx6 z?uWio)>-{5>M4Ema@7OZUycu%=hQ>JEceYX9B=Jf&E!sF@4SR_v$rgJt99sf%77tQuYNoO(FBrQ6d)N2drUh_o|6+&vE-N`!oA* z!Oyp14SkNaqv^eB3evMbv%KWpMYq4Bn*XJ*#qz)U(FSu(9+Gz~|G_I?S3h0W-@0w* zld9|YXS#CK!*tJn_JEp;=SyqhUBc!4E$V%!^(*SD(+9fp)K|OVn`%m1%S})D2a9f2 z9eND3f|qQ=yPcL*tMoPX&sRoU|2+Olm2lStR}b}4?vfRMQT_3}Fky#k{OV|ndT2Mw zQ{Q!s-dDRHp5msbJni`Il6C5HJSXp{hjQd0(@~D?L0+<*dnSyrN)``S?~WL2?YVcf zVmwjOZIn81)fm&<$;|M4<46VE9}JMTI8p8DmE z5f=6QdCNN0?#>ae-KmFqDaZCb_w={bjMoQS)RUUISzU;0wlCY8dMQVKc`tfbb=@-1 zqMl~WH>i=gCcmhMdMQVq-n0LG)qAqCsHgldH>fGNCeNvddMU^L(dthhsqt5zV^Plx z|5`N%*X$qEL%oz^fBo*~f2fE4oN7_eMb}+nuGwFyhkEH3*^X?tJ730oN7E--Y$vuG z{p7NX&sXE|oc@`5C|8_c$2!Y1-eNk+(SM&fFH;T2bB+(xL%AV%U;UkeQ5Mrtj^oEG zmQTrZj!)D>xlt$9vW|absKs=YtlOP-SF93QENas}`FTkRh?&|*5uas2%7zQyJ_ z?M^+EV>>V%<=75v7xIMdz3fj1RNk6_*0mpftR5XO)cPuGwYmt`Y;W@B{oB{5_wk(R zDEHI9zfr9dhgwf#JvZEVq*YROnYpGO%H6SPrCNsPOh>sMpZ%;hhelY*&;Fn~G#O(p zpLBt_rXI=-e_@GA!E>gg-0EzPr9K;FU4E5TDX)*Uwq6!ga!oyyYf?L;2I4vSLf%my z<(Zy5B%dfxKf!uXp86=ya?@{6ALZ#UXiw^+z349}Pkn4Z%25yf2j!@T?MMH?eoDTu z9mxl_8~MV1OFocqY(L6VALZG8-%_H;m62kEf1kZ1q~*#`<9L$=2qNyINgzZ`BmnjL#UK z&wRPQx(d%ZKcF7UC3Zf=I&)!9i|HuG`9W6uw^Li-Ip-(TL%Ed`yIPIL^tG6da-5$G zyXWTAFGu&an$7KNCGG3w>Y?043r@3UY&yeYI?AO!_{e|=(p%xRsq)-+@>$mBZ+39? zP);4+)jH|#o!oSkD}QsxfMrPkNW)uF|MhVvYu!KkSTEk%%+*7=KlSQph0aQJ(^2le ztN$@T(oeqQ*P!@|2_fD8KW{cGgpm zHL@=A^s!bxeWF``mY4N=GE_J913aIy=lj&xke=nGJkztjd0pyQh4t&Xd}Dgbe|=e& zIvvkBpI~~*lfSeV({nyS{>txVd+@!fJ+I`SA>X}`Z)@RTzLAZ?VSjpr{dxY(h_^79 zx8Sgej^aVWo=Z%=OnSii``*291I0S*PdL{XD)eUiGW~~5a#RlzcE#tPcw+SLi_3TK zV#_Dxsa5myf1L%Sj~Fh*aHWO||JTvLR9Lw1I=)+xIB#GN@t5*LKUEj!2|xp&3D5#a z1lj^gKxd#E&>ctt`T+fa0YDls1Q-E~0VV)bfOOzIU=EN4ArJvdfD3`ez%rl| zxB^%WtO3e^4Zs#)JFpYD4Y&)q4|ouG6nFx78rTIq2kZs*0j~mY0Q&*?uIBr|hrr)~ z&j9&8t9+03Yv4QJN8ndLj8Yd!02%;IfEGX^&=yDnIs@H+?m!CA2j~Y30MdXVzzARr zFaekXqyy&xbAT)$7svw&fe0YqRg`ZpE(VqXrN9-yYG4ge25bPf0Na6`z-_=?zRjKsGQJC;&o0F|ZI=1S|zE11<-y1g-(r0_%WHz&79};1=Kx;2z)s;9=l#;3;4i z@I3Gm@G9^o@K@k{;3MEu;0xfNz_-8;z|X*cfZBCYf1n=F2xtbh1X=^_fsQ~IAQ|WZ z^aA<<{Q(6G0)_#jfN{Vi;9Ot^FdN7KvVplk0T2R;frY>#U@34Ja5-=#a1F2)SO;tZ zwgEQ*w*Yql_W%z74+D<_PXW7t=Yf}iSAjQyzXINHl7SvTFQ6~bA5g$RUQ&fTmh^G)&OO|24D-Y9oPxn2HXYQ z2RsNo3OoTk1MC6z0xtuv1N(t@fxiKN2Mz#V0$&5)0Y3u20v?Q)b%6w+0nh|!0VD!# zfh3?a&<*GgqyT+@e!u`A4HyE90LB30fl0u*zzkqEkO5=^bAbXN1QY`cfknVl;4UcvHG@Z-R{VbI_;7wIA)zwDHq|p}ef4O@2)2 zLdTT)*b;{_C!`W!k7Y|Qwvyu1Y)Jzn2HLlhaJ19ai%Vl$Ys8J~jQtXBd&wN*lEhq? zxhU++bbo=ktsy4SADoVxGC9|m4QnDg`>w*T+a zKl!{#FTBVF5m-O@zP#{)^`uE{!+ z*?v4>qvO}t#UBUxGUw%lf<*z>JfHBbb$zi0E+(H!o%o?5pJG&- z)5=8wQ7u)KrzV-d=*G2c<}WnJf9aT>8|70!<2~tQ#5nNt&CI2E6#5)l$amk zT^i1~{?!cUhtelLL;fA@&zQ;fK2qtQ?Rc%{Vaj;Rp_jxd+a0ZP=%i(>d2i|9$kk;Uc!Si zWXSueN7W8ehu9z1DdrfhM}@Spay+M;`O}%s%{_ynDwrL~tRL6$SWjvxy=a$R|6=>d z`#iFjR(5OVKPOB%@lg9R)XRL1bZk<~-j|X0sgCXE|IEG&%aqtMYJaod?%0>}nuH4u zZRe`)t5j8<<*(V7p(T#)SXn<&_HsN=$P4nMw8Z8K`zK3NvoAwS{NHg*-xtpl@`5}` zaQrs;L#qr4gbVZV27bQ3pi-t*wLDKCma63`{l4P9z`6Jpg=62k=r+?Hr>Vc@ zXj>rhr6^Alaf^0(1Tny&!pVy3Z{!hqMZ1!3yq=O9$edT;4_Ee`o2tsQ9N76Vyh^C1 z_1rxRYIqg3ZS-va-YBokev@OTKU&W z87R#2XC)1qJY*QfkNVi;xKB=~uzwuR%8qJ1T18)fYVRLE34yx!cl1BpKQ6>Pqry7a z(JT2~O1=vuM|piS;bnWilGr$=oiM4Oec7KA&OdbAImj3GCsl5X)|9QP@~mKW?;m%Y zR@0wp+t_2-3o$&w1KNrE#}(H+WFBeWFLU@qK9v?Y@u|~~na5av#Fv*B$mI309;+%J z%`eR!AC@0-|9F`17=w@8i8XwR+BSNY{(L;2XeW*jjiX6%7V|!W!yob~Ah(_vz8XG7Z5ur++ZE3z+KGI!$2;!pMEw)_JZ`@Stx=rbX8b1}aZS*YhnJBMh-G+9epJK}2tphszA)iVgbok`(jB=BU zGQ#$+5+zlN8da5NIq?2b_0IEZ;_|3zW6t(J70;(!$4=zaVdr@cf5@kV2b^)w;Tf}t z;lm_fR-ouu*O#j*&vMjq_*7l{6(h#g_$S&n_E`30lut75(N6K>-eP%gOTD;vFMgLn ze)bG<<8y~cJeCXXzKl^NDtfT zHpf3Xeu^@a3;mfzdA?8*yi?K`f3g1<->8 z9Ph{<@+skF$3HndV-t*)$X6NYR0>$ZKG#pCGmXf=Gdu%e{uw(;~n`! zK9z2F{FCFSm_rO7k|qTszDOWgz-LEutg3u8kLq2AN|;{5r>JeCXX*3f`9wP%iBIGY z`INBD@lTGQVh&S$1v!;`W!9*usyxe4-TN;iX4UYCwv9a|7DxGH)}b6b#jis}C4RSF z?(m4bD&6e(DaT(?a!SZo5Y7*T!|+d)SuwAwJj+qtABZFno%>EO*Rr+*MVc<*05xCFIqNdr{j)&(cHje4?FV`E=D&-w&`QqH#I-Lp~*x zIsVDjgR1%?`7bgd=ICQb8O)pTRO+K&N0b3%KI(-aL(_19qzls{H~t+ zeG`7q#QFXkzw;;h(x8vugXDLIlWI~r)vDDT0j|^P*RP*aw{BgTV*OWsnm2Era?UyDTy@@g=N+6iYnGlo zd9t23aiShMa-<$MY?!tzOP_VtSs!%m+I4e_7A+=JS0UQBZ=W`2&YXQkMMXLs4(nhr zsPppjbWTo=mU5)0r<<~j8a2w4W8lDnTF|dwzYnTXj+0M5Ic3O@A&W1&>@vN4`EtE% z*)qLk$r8P2(IS1p1sCX0D5U4kovSl5Gfg?pJ@;JK9)kxDHf4}1f*|5{Z;=-n?0F*|J5iU%y`8aKjCH?b@|k$|Gfwa)@2T9wj9u zrd{&$^L18MmTQaAqetrzBSvW0&>$@>?O?-(4aMdB;ScP*Z^w=u`lg$1(sJCob*m}I zh7B9sa!46gty-loz4TIiA>X!xa6`%DzC=UEg}^t@@T*ZqYa2e6!xZeY+`#lwsYvb^5yNuG3dveYIY>a;4$M z;>C;g!i5X9a8&r2o11IOAl#HPu#ZST>C~yy;zQ++ew>874&HzN{rc{^@78zSb(bke zY#Fv~+om^e+~}6!s;jQjmtTIl;e+r}_<*)EWe_`{-{@)6rs;9x#+kk$wh**z+46V& z6TB*a@WBT)aqqqN>U-|FN8fSB9mWU;3<#~;^^J@%M>>y>h=9+5^A6bTrF1kq1pFcmYU(A>>Lr<76!L)8dtPQ44ovNi@O8&x! zHf`F>p$)*h#m_$btbXR1XY|ugKW)lUUS1wo2J%7pDgA<6kiIECf?SY(DSe`%4W>_@ z?s8G`@6n^jX66su-MM@BZY|igYnLg9*x`vMo-l3km%sc)3m?P=(l^8g(m$mgq)$jY zhz+C+;wvw?&pl_#A-0e*RJ4PX zL2MxXQndJF`NN+3UV7;z zEoBf$8N?3K7E%V`W7uQ&ej$}6vQ`-Aky^Upuu_-FA+(hm-m zKl+x*pJfm`NLvUegp-HjqqGBUAbml2AU2Tv#V^Ttb>W2<8Xig;_>#x7AUwiE}Ep~{tf$%`uL2MvAc<7;rTpNgQl)fN5knvK+O11&V z$Kv8*xQr*fwBajI{yVKsX?6Ao&Xi$U%uu*asyhka1AP;m(~qFRqv`uUofn zodjDStk@6emxTx74~2)4zqA247;Art)5HhR56d_t{;z-k{^duCMWqa|^+EPS@rSIx z^h5Ro)?fTF+n;_&@)!Pz{bd~V`~6xeRsKh@7i}QNE?v5$fZrd)_JLRqvi>qAvHom- zjztm=NK7Q$Lx0~VbEeolj&$CjL4zi!!=2)TDvkxx2RJ5jJQN=&W1;jv@xfwysrwmc zoUyoKUprFXvCmUeQzyWuqS@_skByBaPUKi9`HTORF;ILD+PfTa-Z9lVHs6Zps7D&& ziaXJ__KDpkZ{fX+LBe~~`5@}M5B+$t%-Jf+SJ$<~ih`u1q!f%HX^17$+O}<*A^^YK znC@uvN&@+?C;(tI7g=YZyiaABz5S&pT2+4o;V&IsT_&huQKU{7v_7( zPW|}3WY&{48F7z_d7rOK>jwA z$Ft?&sd0aAm;520O0#S}u^%&&s($ads`4y{yWd$&?~9G7dA}!W+L*KbpTzTtcH(|# zW2AlXsyTLi5&OMlhd<;~LXI;YRPa-ke^bmZWL4#(RjuCl#rEXaypIvJZS*Yhvv@wy zPO*G?{IZaVd1Jr7P5zKir2)r3IXq(yRrnPSqoS(vEQjl#s_A`4C5JAIX+Mpflmj_MxwO3$s~Q`EN6v$B82^NDsMpX_mu z>0^Gs+ToAOrzH-b9G+1kh7bQ!-%CDFRKq8>aqMyb!6=_(+@qc1$GyXUFS*p=k;|)f zHm~Tfm{$z1{-?f|Jfh~i?rh??F~+n88eCDQ>B&y zsw&TNRQLCi6Xw+TsiGy6=DC zd&whez5~G~jytA*8_y@&DV9%BfoL2>{*X_lo3FP2&aA^T%DCUBg?IX2`d;#g^cw%f zHjX`({i_0>96MFu)9?3R9R83`3AfpNBF~tI%cn~Iigi`xS&r)7AKp{*`_(jU+;RVR z@qD74xIY~KE>etta`;0&m0srfCx>Uu#2w`R$G(@GQ1kcT*u-(i#2@1ML_1aRPmWBK zPvj5zl(5Y4PY%zRNerLUheRSyeeJd_$kLmD+i+HB?oe<*0T( zjj!=jQQJn(5`U?{C&x}l${ap9JYx=q50zRWw>4B%p5<`)R88;M9avbyr>JeCXJx;| z^NDsMpX`)}`d+flAM&a6I%m9d{1kIAe5m9al<26cJj+qt>re?b?;FsxamW4tiRTmT zbfk4C^2g=VN{3I5pJFD44^?g zHvjS8|Gt95AD2&8I(%|?#w=_;R6a1Tsyxe4-F)g+^Y<#Frj0o(D~RV4?G(!=B>&xh zUV_6PmrvK)zfVg)#Vl+-R6dZZsyxe4?QvAi-|eDl2Xoj?;TfFp5>|eUNSB5-#w;}it;H1S@HWAvENIU z68zEklI3|4AfLl=zh~lnr-k1$!6Wm-@3@q%bmFaL_L0wdO8JadBlKHj)smVa_E zbDlq9f02giYL2!AD*N4|vSHEwZg}GGf;>rBVe^FjllDG0m{*ivd75~tD$jD%@i^a= zsNo4s@?SY7j*RCCdExTJ@!J*s%8)>~Fb@tTzmk7My{hswMzyaK*Sxnu)5aasheml~ z)`=ZE9ql@Cg2N;7ipt41UQfvlWX?P0ze-GLkEg2gQB8a7ajKg3M-ys(&p&F~n6tzo zQC`V9G3``wo)i0iSwBhVd=K!jf2X6ucLYoM9X%&rLg-x`#5tClx=9-^lw?JZx7HNsinx(bG-v?>lpkZ2*diBy8*RP*;?3#{VqJ5h- zY14)dE6<-WQD+Surst$8J?pG~dV0@ZdSddQ^r)`g^x$r%>8>ZYUfdYvIeN`UPPq(! z|KY+JGj+-M33}o1k$QgGAbtMX19WchzIx{AXXr^?PtzllI_lwV+UXJPI_R{Hopk3` ziHn;yXi%kc3{Oj2ymDTiUNLi)UNmlkzHsOWeSsRN&p)fbp4Y374)jRTbCY{$f0txE zqvNT1eA^CsOv~1KP>WXDYTZT;J0nFmuV1+`j2bj}@zn)Ey=ssFGQUqsB^GmSDl~KS!cKJs59H1qP?v< z=t+rf^w{RD^x%d~wQ2;kOw_}`4}9GBsN3R(MHlO9a{_wJv>E!!QDgNAYp`C?_Z+>X zM{m94v>tk4r_*#vhf{T^ZAYEgI!Vt>?4UCe+vz#2+UjYr|9IGcY-7~FUL!rEadSQR z$m!;3tYf$%V)F6FT?|%A<$9@;4U^B+*AE@3*PgBPb!VKZuRZm2eO0?IdPVajy)>bXzMyU^T~y~JU0Az?&a2a0 z=N#W$XV!17XE$u2r#CuDPe%Tu8Z^~|5*nfm4fR0qaagBQb<;oK!Hui0)|UcF{IyI1Y;%T4@J@xclPlC=uIlQ$S>lr6B z(NpU;(-Tkz$$#jHl7D?|p$!J2FC31C$@t#&_T{B|YeAvjGIO@xHfDmpb>MLQ%#cy~ zLtnN&c)=olAds(L=&kh4C$!U7c$(@9QU5UVo##1G`%#8DwHoP}b&-F)rg}W`9@(&& z9t{3j$0L7~V<7rP@AmBv$${jqUDI|T|82;B8}i>VW}<#@*ckmce?aToZrA$dSG0cY zas4^U@Nk=CeVwPJUhHXvHmI-jk$*PegFVix+gP8A{3kg1r=i{hP=9qIc+kA19t3}M zC=UJ!^}h-Bzp?Oqz3n`&zIogvT|RoEKDgu(t@ppJ_19l({mwi3yJgGuGhO@Wb+r=F z290zv@+?5!IY0(@a31PEP4b8RM>cA%(;76<{gD4TPX4{xwmW3~yW^YVH{<*6+rtsP zW%e9>(}XGdQRM%5@dB-1*sJwtpJ}~&kNzq=Uzc}E(PiMk(wO{n!GjENaMlS;^|S`f z^+fP*B>F%a{NPy$_4V2H8ln%v{vDDInZMX%(cF3E+e;Sct(ks(Bl_RnBgg4ircBr0 zE?=R4yK%ez?y^$-*6?xqu9jW(HMMN}7sLKR*dQ1EArt(c4eq5Q?}<%M(j$R1@V+m6 zz*+Sh>9oYwy5mVL56l0>p-}mbix=yyxpVba_}(3(#_RhAkJNjR=WAmp>lf58{XplQ zdSl~``tmv_>qWIs(nYnK>mU%2{@K8iCUrg3&$;6#EWQ~W*mizMZ$=wzh7Z^_c!b{Bf1tj#cYl5JX}$D@4yWm>T6WaS z8nx3G)N8FH^%8ZkK`R}A@5^kOsAt3frk~VWPsBJd0zNObNi*H2UIX2)X>&cWOV>kk zFSaa;^76`WyznBu1>?`gbEoU|Bgg7>YOpTr(_gRY-dkVUxw~H8zKdRba!0+eWd}0` z6trlo11GiBnXTIC*(bNt(|`#lx7EX2o~-*L|K1H6=>cMU#3ZqK9qN4Q`0?%kuJ4Kz>m744^g8r~YcUpFfif&V z^K8BJj6V9JKb@gVP>%4a$-1C(Hy!BIRcChUqGurvntDoSJ*It<;aeZnKec^_qt2V< zSyfbA{@SL^`uz=?^y7I!y&3)Cn&G4L<%k6?LF{)?pMJU|rH>Au-cuKJKSSpxpRO}b z>#k>ZP1ci7?W#v4oubc%zdgH6TivO3>&4W2EXO}zbyfL~_?tEVdg4j_!8L32Z8N=k z&6x4J6fwbK#6t^Ejxb`P0>q)Yh{=3s^wiV4_t4`J2V3BJf7H8E>o%3jo8{cNVnzAm zJ8mlf@uhwG%lq!v&n>)2mrb3fmyaH&7Y!Y*iw6$Y!Bk7<_CH6@>Dy0FMLa$NK6cQb zx*xO6nPF|xWuDP!K{<0g&UthmLZ^L0nuoYP8P5oM2YkKt1uC zb9A>39ja94iuuQ0cj?$ME#Et*JZI{;<#Wc3E1x-XWcgIgOU~@x{lAuHZ2mRp)oX!~ zV?CZk{DIPhwNEC<{>N8Pe=O?)Bgani7i9TENt68HqP%0@FZ-j~l>E`dnrAf@sIfqe z1^y3NpxXt{8UI*XVjoSm2_DaJp1Qv)0d61mr{DHE&jho5zX@4ir6Zo{w;j6`yl`n)Udj^=Xk!nD<1qz7>_lT#H`wccf#|AKET4LN%u~ z7O1g6jRk5fP-B4_3)EPk#sW1KsIfqe1!^o%V}TkA{7+aw)&*oeh3gVwtWn3V3&`_Q zJm-4GKX4xV{76Rlybe%DPKC0aQ*0WoJ;xkER0!4!u905U?iy;{?0Y? zqkiU>>*U9BnEZMn<0}3zKk8?G2~K`2hsm#Z6*Gf4^P_&|w{wAQPnN^v*QfgOqkiT$ z!pV>2F!`NXefd#8^E=?IJF*-mzrNL%AN4aou5+>+Ccm?)FF)#Me$>oznEd3$+A97G z88@(J`bV`qs~??lz5G6v{7Cb;$!oBpC7K$w_iiz-#UX z3;iLVF+v2QwnQ)F42^+^Ki?bjXZu6`f=s_x)JE^;1v7nl_PyUH4PdJf7I0M9N$q=w z&>P6As8#MrlN9=qG*mDG`dKkP})4WMG7%-jXuP%sE% zg(Ja`F9$UU6%|ARsEX9p+f&RFj`$))VNZBops>)NB~{K0B9TAKy_FR}lSSN1)WyCL z^5^>sr8(wHneze}o@8G*?2p*CwHpHM2<3syV1XBjpvRY&=^VU8s6)_ORFD<)78Zqb zdE1wbMvu82_J@To-l8JOCbpKrVt*(U$ntxUH>$h9lN<~MawMbV!Uf(y0jh?!Gewif z-uZstyl9(xXp3Yis#oeFlG%YgzafXIK+lw3u#w!8_VD^L!ZYn#$%Q^JLwM-JFVba6 z3frhDGf`)drw|1YBA|>?e3JmAh`n@*?WKB0HE*LsxJH~m6HT8h{|U?Gn3?M<$U%ci zbE2a8g>n;g$c&hi9Q^MWBs zofjzZdn4|zhB(6B;*6jtIl~vp%!SztU|sv%bXG7=h~+W1GJVsW8E;_DLQ-b?adE&u z-}G4XAj3D$AH51^+iCpHF(cEy_TeZ_L5hdpghKfWf}wn0Ui3Pcoec)U^%SDD!eEZTmhGYgnGYFd&HN7O+77D@CU<#z1 z8->BJP$zoB$t~^{b9SYiyuBIdX<5FI>9Rs>5Bfv0TM{lIWW8ZNM z7<=I@S+G#7?c76I3&MHEg~r@PiOnsqzbMadb19f9&%=R*emUa49HceG1tI;wl0aB) z$!S3jS`8=p_%&5Sb1^W|NmAO|xy-_E#Kk-{ml0neFXo;xRG^?ZRza>G!yIxm+9bVP zL=Ko$EL)noSNrk%bAl?P`DH1yOf<+PD#B)A*hgQ9IXbf0et%X5{H;VC#r`}D?fHcgfXLC09Hp=Y{ydx*M&mOuo=mww zD}?PZA`H>cJh)_*&c1}_Hv^?{hF;?sJ@z>qqYOHJaaR~egGC`3QQ)yiDKJ3EjW zFs{`Ej?(GT)AAO0gCV>`vNlcg_?j2ghS=gb2IdyIeInnm^1Lq>yj(sKGK&O(@NuM@DqA7qUb`TL8 z2T&n9OcXMDjYu+L1=D$)F&e(fI76|gI4|1)nxcsPXQI6WS*C^IkYr>=lbLwUX&(;; z;~ak^JhR^{=>=Zr(va6gzM+5*Rt|g6Bp8Jxq?p<3OlefP;mr(6?>49501WI$+>vPl zfvgZZVaTb55I;G?zYqnCD+$liiM3P&i?|u*nhuta3WA(`H4&Wt*k0F^X6w@(>c zn!p2tah4s5L2u05$qCq`S959r4qsEY{7=y@Vw2LPDvLYyYo<9u z7zZTdm50M3M?@1R$=KU_mN@!AL9Ra(h}cnq)8Pi7X)NU}3S-Jh*ynW?N~E9}9@0MDNnG?OTv+72b^=aS(Wa?#V_>kAiP!i4|`v%gG# z0oDvKk9LPvnN!1Aqd9TU2^ihEjiIDHUF1DecV`O8dl=;~Va=Ao0dpIPG|>pQSj;11 zir$$2gb;9|<7Z&vY%@F?qcc5IIFM6-IRH$a7Yxqx`V8L?`Iyi&Q-(9)rs z6hX#@`EV=9Jp+NvAUp zFiSSol?!in(ZYq8N`=FoV*5)V*;uE*AcW?`bOS}QHDbbpu{Ou?$tHcS9e-x{3g(HA zE(}Oy>dVP-MhGcX5j@TUbSs&7`2rzXr}5g=Fju~k3Re)Z)8UD)*p|V%LO3Y4GjR%v zg_vM&5vI7dB6E#6tm37pa-WGVb8#qTh_N#f_5>xkGW7*P1NpLI;l!C}TVEbVXVXa> z3t+&JnRy=D*TlAPrS?#6uI2{d|3bD9EC6zt+vYS*4ED(i=1UlbRxoLtJMsGL;V`cl zp{E4Wn9pYgiX{>=<*<978Bff$y-p%wo4JhSLVIoy^Dx_U$t2!a7>-%>I@6xs_p;NQ zez!m}v$aW%QRTMlw{HXrFx&TrC9(qNii*8<;XN54<7Q-K59>i;3?xS0adLKLl5iWkUUfCmzf`AcX}=gMRi&Me9+gscRR&bi#TZ-}+o z5$vKGs~SPa{JB^;WJ_S3%2};*?%2btF^*#mp+r^yZUO^vphRW@nA{@z$85hqoM3Sv zf>k2t7MbE)J6v(DBnz7kSVEzQxk+2a-7Lav%T{aBNU>wD!_l(G-nJ`%xY?|=Na2$) zYGRN@#OI!Q;a(8;XL)`3vM^__fWWJxHBFAP3>(Nt$mlHV*u`)z_{{Ze@TV}u03?&XOa+9P}%%X+LyYI6QA4KcgsH zW*athWQ`0f8cu0V3bdJ7P;&w(dld)kb7n9JA)F(ZOt|9-X4u3v3>Oz@_+$!UdaN{& z(|q#bO^@a99@c$<$C!w|q|#DSnX(lk0>WaC5tl1_f{%4#zYGzwI_YGR zg?Tqhgs}qCTAZ?1JH5v&dqq1XD@0qf^rj`XXA*m9_iejs=DFQwF*l6cj=46iWRm6-!ai7=iMw!|MclpE3@K#iW6K(bhvUj+ z0Fi-6I$81*bgv}71?W;m}jA3jaK^+Y>GCr6=HN1*Wt=w z`5TK?G1rc2=MGvD?#1{3*%r$PW-V}8NLLeE$gusfKqt1b`(=d&h);oDfI&OrwR;dY zoXp1tDIa5PXMshQ%dzr-?(TKEdAUc8WV>&ms!{d z!PqA2BD3rP$~fd`LQ`tqi%XqMFN1I-f<|W8;QAQ%er!j}^hr2@898cT_pC!ab=-)| zncN=g2pTu$!9I+q*j|BkXHKXyd=%DcWHlv>mc_&h6DenpMXX{^>1B2}vro#|SO%&v zqKZGnuqn33X(Sh$J~E%iLI&#O$MiCZw~#;p>JlJXeE&Ni#$Jn`B~l4eHbgrBBBPACj9Os|5OV$Y%i z^eXqto#iGw>pS+L;+~oF6y@@vT&Sk(90w763d1?8LzhJ5a-{BuzG+BT7%0Y;DTW6u zDoSd*w_tY+TO_}`8^JsuBc2EGgG^De5{}hZbIvtDY>GQ;#wbqMYqtqZFBc%S?b7YF zV{^|fEjnsQ_V2ve*qtf#xRNh1|qo<<;n6dVyE)(T`&Ce_AhWsb6Zhu{;-SRt$u*l~`TE!iur zcmg*eagRB{_C{_X8yd}FfoJ+KZ&ol{Y`_%RU;`GRv45TKxaRCYIM=@bb_o`P_V&5S zQJlCeESq-`q8AuH5xM5XJc?f78CA#_Wue?S06* zeXz71%mxWO*q@b}dIT9*Zjs3@LQt^Q4@#NdB7uEQ5M669?BN+)9FVY2T#i{R!UDJq zP8ef~GG$_f#R;rLh}STy1bABFm2-1qr?O8?Dl=Qh#=R`;7Gp`FFoaX{Qis`HFdL_G z=CmJ@o04E{-=3vA_vIQ&vYbndq-JH;E0bIdEJjBDv-NxAG&>Z)TBzH(p>)C1cGSUJZ5E0EdQ zaQ*0McHE3zZkrZn9)%Za?0VUZ!T2e|CdQzAV+}bMe(=7W$3AoJo4Use**A<%`_wpL zd)dg)OB6Zt(nCGMQi@EYq;2iu*@Q)BHCgp$k<#=(4O ztpJ<^oK7AXPL8=JF46FqNo9`$Yfpw#blPYO+_be|k+BeVlPP=5DQ3myZ6>DypbsTs zg)unDRc0P|Z7Z=)&8DDP7sDZNcNZ@1Q9`S||*#Z-8+9$HWk1cSF zV{#@N;5bdTpUIxB1inVXK9}7RCwJa%z3Yv^WJE1N2{Hi zBt~PK#d#AC89tazRdM6y7B)D8#u4ZE@)318OI6OUkiE;-OQzAX?+qtjj9m#MXKxUs zz&7&>>@{qR0`^I=IWR7>FXH1eKlVjrVOsVJ?5!!xQV}F0L`N*bD%&*p^}A@Ww0MBd{_`Y%_z!4$2R|)VbwVU&FnL0 z*p|@-%d1&<;l(-YB`^2D4cUABSRb>gRv4U*_}J!&S-dm*P98s2K<&L?voXpi_M1=k zbVCMagv*$<+pjhi6`E8SzU^oodkR?P3t@t4CtnN8lBIFyk5aUd7G|DyMKB9YYOZu%453?X_5itC=>xC`6L)F)d>LEY7@*)zw zu=li$Dila`+voPnM$qTnl{5Pu(~6$h^t`rZVy_HmV$#Z%kF;`}w0p;qkGqBj_u6g; zkwpRIMJ-PW$_S@}{}$T+ffusKA3d12F@8iJ@YLv;GPnHxKc z(DK5Bz0r*z)h8R#vM4NX%jEX-2A8#9M%FixIcT{9h~`219^-CGAYfRotJhGx=0 zh$`{!0SYZIi%6i2){+GVS;92EFESra-su76)bR&lyo{M=Hnj|U5M@Mi#mhj8sjrOY z65-gJ{>T#Vy_H1d{U|X8hDE$PBd6HG!YLLC3X0@^#Dx*Fl5ZB?jVm>s--l>OUXim4 zX>OSsac$Pzk$42lANb!)E3@rgT^QVU$=II?V)Y}>8Olm9A6o!Odb^bJ7-1m-Clo?n zwzMa>-uYr{dA0igVeCx+{5r05uiLhqD3i>L=e;oTkUS7DfTnp5EXkQTY|q-IiDmbw zW!WOKVK;l&^MD6o5BvLl-*@UR-ED*A z+yAOlb?erxTXpK}#UG9AI(9t>(w}T@9cO|*HDumDjJ|(2>vu-tV5Z@i?HuV9^`o|; zgg?wL?*;a|r1#_Za{XQcG1v^E!TY@C+hw6Qza(Rb>PXh!wNDD$cIqnRIYfu?N`$T@ zzqul7iZbX8^|*0KsLb2?<(85Swr7LBu6f30rQrj+8GL={iR8T z{o^O85Gz`ds_22)8+m~`y`?3VP=zq?`dBb3^|Q3cY^WBI46_wg#sse{)+4~es%su0 z#`ZBfynWH=^RRDbiu}PfoUT~7s)Ti-LbU;X9^A`R32n@-kOUBq2vh+}3ga(?2e3&U zyBNeaH}ZGge=(92`R%`8$?d0T9_FKQ4LcI!?tJpy1OHk#As&Hw!YE5vD1;#5Dmcy} z3uD_V3eUN8xXDh+94l_Y+68l?UGB(QH|+}v6T#_lgV5%@vn6^?V}9d)C|6OJ#q@yg zAV#xQMxLjlLAsp1AxxBe&N7nv2QHj#U~=OGR8oX`21aa1*`@O*A)|)g&Z^`drSb{R z6Xk3ESG$BWX~xMxx>6;Y169jfD=P>OpZa~v1NVux+@a$ZDK>&g!6sAFn)Ai!mRPNXgV;N zBWmYh?(B*NI1=TB8n!H3G8md0xE3Ld-OCu%=W%Jin&@BJv1k~z$sszeL5;$KHlw!h zzd{3<&Jw_)2$m@ch#X0@kRZ!No!77Sw|!aiNVdr{As6&ls(bxvm#eJq*3^fOU=2zP zz=DR;1Lt6}DElBSNA@cC#}E*mxK7Pt-Kt+3*(nj?QmqS(fYMac<}RBnS_U16hwjl= zs>IYt32nHSx7Qwd=^57r_dPACK|Vh1M{1(%(o=s~W=*r3*Oo3}Ap=0u`Z?xBm;=v5 z7W<`nuJEy#n5S$+>amg~lA+cpIVun;N3FER7$ms{5>#qw#Lo-A={>N*-rAW#U%^IT zdML3H(_-*pDX?S_U%tdXHowmFowrP`ek@uH1j}KKPh|>``CFCE`5*c zQS8JdpNV?q7rea6s5niRwR;NeB=u)doVl~5Fiu0-X<(bE$hW6?$SK{_mp@NFL*gRl z9y7bzdc?*dd2EC<-LB08cQ1JgCeuqA1ks#~HT1Uo$VUg$Jq8JAE+92-EhP{A8QaQag{^EzKe{uMOb^@M z*<|!sTVYj`g}kg?BzdiWR8H6ns9%PS?zLcEWZo5hjUE{bbL~8tB5aub89aAihfgIl zp%DOm-vode;c9&#_IwNOuukh$wF9h2_v@zUtbSq>(R|Hj!8o|-k!rm-IQ9KCk*H5) z>|j%*SV5`a^FcaMWH%Y^P|?=5LsmTbHUvrs4YbUMW(q=d&_I0;i@o!$ z*~-?orBTkEJw$jnbv*J;qCk`-17w8y{L3Pa`)0in^|=rP``fEM|N}T zG2aCdH0}SW@NgF@zo#q4w>4xjX^X95CLT^cDJ;US;mAlNTh7b9Wki3lwdqR<)FZA| zUYc2b#n<%}P4|Y}WtECn7(>MOjq|(9(sY|sISux@J)^oN&>7%1KcK_xx?_@?e(4F- z+et|nrv_qUt)%B?k>hD8oL*@6K-#mUx)8w;XpqkY6!X=cz2g#)q@rhdlAHDw*YJH& z!)MH8yq_fNu<;I-;Xo$I?Q~_GeW95}yQX;nO()|FHB*p=MPyxWgGZ~`x_#$O+xNoW z-eeUeDGI!-Jk%rX(jwK+TLh$1O9a>r50aA~#>bu|gTVHJFT12ZmmyLhiQxGxSHELlt7&%K-SuA9iR~SNMxk7&kFPJ5M zQ~fC#BGREIH|x7YJ?U`rxDix}ONcZ$$Ox@WarER$h7S4v_S03F;S730vGg{JYK5L} z`81jsmo7=9AWE<|p)oifI^pm=nT$n?^BmTq9#s=mJGCF(W<4Zjo5F4~!V533`Pxch ziDf$jBeHjZ=>=pOo1!xeC+JF^XHLb&!O3i;POj`gTxj!MKcse<-)ego`d7ncCfBsZ z#b*t+CH+z}rvTCHb&WY_Iu=!N%hZ}K-DhiahG)rBG-Ulfo;7;m*VMCy$}H*XWf+Sp%AJmkZg-mdqqZARIWL9uAI?@iDc5AA88LuNQ zR}RyD%>;d~Ok3UPuctSc+{9dt>rrC2w#=vqe@e(?5Mz$2CSs%pz<fV8MC?NLi5QtXt#9)xdQ~j@{f6_hw~_CG`_LXPznB5ud)Z6! zAq1F_GHd6aNv5j7a0f8xw562B%X=ll!$ye@`6v%Ep0>NxpdwfA9kyu>NYN;EB1eiW zPqEA{eWg{s<>(9Kqo_n;L=R`QZl8~dn3T`k5~4}M^=KDM7k%RlA)ZIt2Bh$k)q>~o z@Pb$cNu;gEbK_QStmPt4DkrOPh-9`bqF1ydRq9k3P@hkIUKPh6jKFOK%e&a5ila5fjLWlqO_Xd$MBwzB}f;O=zYJIY?0rf>COb? zC=SDY%w9!UkW-}O7JSZj@4iRB<=A>gMUsbDsE}nLU)E$nKtsk$h8jXPeR1WzN_U&3 zplgJ!Ym%|rfYF-WLv(U!?&1extw{uY44TQvpz2s-dOBVcUk%4W7)DjA$j z$%sT+WKo+CQ#)A|n8ddRzr zrv8`|()h`e@98gH{H5461>v$Uq3VUO(F4JqmFtJG0UENG&DGAHWF^`@+nb0mXlT23 zX)pDD)6PDNHEW~Et-SQ0_4ipa5GN>!SZZlFskLGDVxXytnhO`3{ZyQQUmHh2lyMNb ziuNN~H>)gZwN;@ku*;4XJ$7aAWe1omH*1(ysTNSnG63a5ju%uTpOyK3dKxQR_x zDmygWGKR-Y9idVQ6klkBKw(; zEMd*`Hv}k)C*jjHfj}4GlObfoD(qut4U)pQe}FjPNQn3<6)Fqe zaW2f^ybQrg*_}^d?Q@w)+D5}0eMN|9%P`qA5pqJ_Vis)od^llHq^j{NVw*bx|`wVf*v1rZF+U`ZbPLoBcEt;j%-J$(;C3Zhj%Pm7^~tHGIG;yc`1l!0>75d(Xf_(h03wry_pjDQffz48y{R?0nd_{c;5O(@A{(hp%@wT|%> zR#?v<6S~!>-751P{cf0E{7PgNB|Vs#TakSF%-{m714mn^j6n;U>-BuK$3=^N% z`Jpl>MBsTyQ*oyr^ozX9+M0-!3RIZ25F9nSwarXTF+OJY>Lqp1-!!r5miKuxv5=mT zs@f-F(>v1htn3}{%2`dJV_a(t@P-3&M-#58BO|e4GZB&nzeC1445j*SHjiN;0OF%= zjok{KZwHp51ymse2Zc?$DT4t%z>F46y3Q9RpO-6?H-*`XUHTbZdokc**C-UPe@6ge#8H;P6tyt40KtHU9($I7h;u`ZJt6tmhrvq^ z=E^p3;k(!(ZUg}}SHxAQBS`ngV-2s5L>u3g*`_EPhyx64D#VYdit+c+EbwR49y=;v z)ebT;f4l)Qq=pBEc#$vR$tN(Bk{NG99-EhO$i&f;GH64&o!3IOdN_v?$CXrPU(*C> zZ`Y3w6xr(W_@Z!srfdg zsP}Xe$4~Z)Y}_t=6N$2J_g{=jNfYVBt|QCGh%mDKx$oWxL;B4pEde+9XxT19n~<8T z2&7?1hGB-daNL0eNYRRcb98IAeiR!C#@u7%nVO=e{d(|}iX%`mH~gcX`3VtIc|^S6<7)!{h=DD z|K&@Fhcjs`xFg0F;&AyFiJ-Mq6YitUfD!Cb8TF1pB0NnPDZZzZ5)k%j=u3(JB~X$k z0LGIA-Di_DHKzq$*>ar@I7=T=kuQ1(HOQ4}jkq?KTQN#3VL+@$T?@(JJ7nLCTFp^B zTp)3XN0@E6{U+u^NFd7exJ1IT(~w?3+ikW${v0ED?gn8F{X>bk&QvzVu&`+^Y`Ti+FWrL_$PmP4?M- zv#VmSkl@e;kr|pG%m@3rr-hd5Z)kuK3t2Zbsa&gpT;2ksz7xj&>TJD8^?8uK`O^uH z?5v;`_0HgdYBHkUL6I?opaRwK7s`*nGZ4iwIL)GX0c45?Sp@_g&f%jC%bzh3V2j;E zWLn<7{Q5IC&4GDY(f$3Sp0X~=*DjhS23nEt%zXx8Yx4j!)54@?`3%Gd`N5PnFgF{e)iKf9KZ&$W4OL2g=Yz!tlPz4Jif5L*{!Z&PdHpZ6n1Ajzh75?IclJ(ChF)vqaYgZ36ZSx`^)lv*h=IEn_QO~hTE zk3`F?imbps^B^T?-k)RLc()XxxB82oMaZz!4f~Bj$U` z=p?l@V}*kwt-|UXVHzSyHm#%~Vo5*sIxeHCWxI_OlUY7TarH`f7+nR#@0DVVngZGP z-fG0!h%1lX#(HaT(XfnySO8*3n->9RA(jTXL$H)f_z6ptJAfpBqtJplek&$>k$g~m zQQO&n7e@ewLcRy(Srl1!b(KNThUu{geMYh=D2u-I5el!#2?k>bX*G7>qHP!9&{J>E ztjyFVE+R&fO4dCtdPzquS0p)3zI(2oO7w0l!gfSpijJ@T~ilGyyojdc3VBz{1A}=ZCw=x_@0&@H0s3 zQ`8r%Ty#v;50uWb2@4J0WuJ3{fn+bK+A1f5yj@?_G}cd_P-F9(ZL zF^<7qeGNrQP3SZB>}Qfh6;l=38IBK+SeSfOrS3&nopBjWUgqu>*?5O}>eBZN%FF}&Y< zLTn(k1i=TPmZ~xswK57o$P9rjGiKKBl3@;UHSngvuZRPl$`VVPm2FxbI50!X3dGEh zk={c_rsn2}0frtoKfyebpUFylB(h^8fp28<@LYD#pjw)sQ@IVp(=5sHE1qCIeV*)q zvg0>rsnf~^5mstqt?!^nS&`mSB$3he9&6iC$dtf$7N0PHlTO_EQaK!R6yA$S=N%@+uyu`Cu_%0fkipRe}0xS#J|uTVv5OL>#bW zLO_!$&K;DcBcsHuX<`Xe&e>dC3!7@J=axnml_kz)rc00MP_=rBa%Tl4yIqkwZh zHA@YIMiPH%tGV#+syZUGl0(XEdMhpIrU}*6H`VB^9?LvGam?a9xo(LB293y^X#A0w3 zl>&%{R+LC;L<&GaK|HQD8xZ1qi3oFS13ghl-|Q03Mzd9TB>zCTI%o*A_!<#yypbj4 zUX-fbG@v8{OXb%PgwOYt<%>2y?GlyE#;4Xwe5YlO(8@oABeW@uDZ$bw?eEFf6dsj? zhri9(RZyi1NVveUS3J=64l@4W(+EQv>X;uZ?D9vc{}DNy4AP?Ir~p?IUvZ{dgIM zgQcY?4~l)UkECN4fBTG*=+>~p9<)s6jG#X`<>5SoP;u^vf+r>hSl_kymm|+Dt5k;i z#+PmkEhwlH+}3Ozky=Q0bW}@`RVf;C=_mTLpxTWZJTaF*0EHMw=J&6$t@cRgAbF5eCrduvnzv@Fhxq8QkG_>9u0i_Vd>ks{>Nl^VCH3gOMA&lE&@nsUz7o(_OcQHj+~+(i-Is{T!VVZ)(bdBAYO%tK;@j)~aYZKe5?jssa8jxM|=P6;6U&8Asm+K4r*SdH65^W?WA$THGvu01UgV4P#=O@~eg)Cs2;$#nQsk2h z@{MbpxljHBtZ4?vcRQkp*UGRlseq-(`+2g}3!5ft;+et(ExWRDNM)ggL11K5qn7Oo z=3Q1?C9Uz%%Vp=iq*QK(MW@g*!dnS7Yn z5rPTR)1R0ZIA1l1iVNZl`wLSbbaWYRAzXg4L&s2qu`Z)o5(eVX2RtPe&V*&G5nD%soX()vU-q(q{`% zW^zDnR|3WYxnB2t2a_NUe<6OX<+YVg_#cdzHZxuoE>y;X<`XQtUt3XIyrRHuL~9=} z$AErBbm#-)PcM55D)D3^Mxw_j!Wkl{YlfD=l^~uWmEV^?^RhJ2GCgBxiT0d{U<%{Q zR}fSYL5cWD2UlbmNj<4EF_TtMUM8r70fW0%;oR=%Qg?K@JHFH%6VwXsle~YD1Fjrp z$15w@&}Xp%Ynu$Q_n^W+{Lke~7Y?vdH_vXI{6OOiQnQUN|3)i*@RtZ6bwu z2H3%3JoAuRP0(^W#X(!zl2)DqjdRHXi ze19S!lJ{oDkSo7ezuM@PpP?F#>6W}i6`joZ86@iIG6HELGrjzSz9S4yqQc82T*ONR z$&b+rvDA@AU^Ue9uIi((I|~{6+;lI(d0Lx(^>UaoWo?1yhl{%6s77l99g2%1|cL2nDQ0S&Rd(qkp`8@WezeW_ zGm0D+vsP9Rc%jfP$I3B5B?ZyyL;Qh=NGW4=kuM0v7#ZjdQ8q;;ZLETA+0bL?ZCU`X z$=@{`X|(C$5WEA? z%3E5jrq3Em%|<|OU^+y844Vk5j`Tm4Zh<7au@;(i2+=~j4WxlakSNA<%BkJxmpS5- z)v?-+a~oJq&LP$(5-|o)Id0oB2E`}(MhNG7OaS28J;xC2o)UWGp--mov##FfZnp3u z!c*#*?7|mC9yxUrL818HK$H^1Ox)X6eRueSqP}5&yw6O+mIL43hih9$^($i<<)4G` z_pH}l820zs%+2blBtf7a8NT@~Yu+;b-eM}R$ig`$pLcN`exPX~)VGLVe%_7>a#vq_ z!19%aP0KHoB%YDx`IIeMw<+A*0QQhW&sR0?Zm+8=rz`5Y30CfK&(-$*!MNBspg#y zZ0T?_%SS@DqIUhe9{7@H1iOU_^s`H+(yCMF>l83eKh>6tvtN>5;Im|%ZrV?SAM35qUfhb{XUVV6b=^hzxCTb7mI!2Ls z;vT1YOM|<+alB7tHt0WbE$y~NhEV4i_*R|Vc;MFFF-CooDi1ck!x&jH8$@z+#>PtLiJ`?I3VmxRN29X!k@LfJ%cI@-~y;2DraVG=c6(YGXvCy2G9tN$TbiM~=kP?UfRg)h0o8DoL1N zVX7Y}t!+$G44Xki8iD|@+E*%fme3sD4QUg?W!WfU7R4Sdv(ZBgJWkYzuBOaBqC>kl z&!>5QY`Q^e8TCs>G8W~ZRmh%Z758wLNSern;X(-+#%Q*P{W?WJ@BU;>-dphQ){e;$ z^8^$Ud`$;4!1UHVu{G8($8CTcYDvqS{0WS1g3A&rN`!fuN;Yc!Pd(y`*#6ceC2!(xExKt}>s4BK_++du~tM>P-Egmjh-Wu9i+H^YWO zCJ|2-nzcPC+h-^K;{&*O#LwgtjCjdR+>aS5(rqdoLEB1tGAIk{0toK!;&E-mh#9iCsK2wY9w()~*31z7hx6HGH z=pbuE2S*&+aSj#~%lvEybcs!_QJ}EE>XW<)A$yuUz!pDwEw}`^=VhmQ+_nlim-x#_ zIeaVv51xg`j>{W=n&Ou0Y3L=C;g7J{XaqODunJnIO-#F{Le&jvdv$A#_9Bbf;m$Gi zzvOt@w6du%m-Kpsm4v*4Ph*$%RAuzVj>uAu}PQ%gNj%9XdB}zEs9jX zvCglrN*;eqc%m8OjAW=~Qmh%GJH!km3E`VF(*Y~(vnhBBVHq61pag6=g>C2ZW8@^v z8TQw(o07AM0Mz+So-ty2|VZuV-A(hh*Rs}DL3ep9y`v#i>NA16m6_0m`ENdW0jdQrUOeZOBSh^k586GOvK6W>S=Wg5|ZD z@v`XFq>_X=#8Z+c`N7Ky*r%WGr)m(q7xuWKoQHS}4wrS*}XyY${ z_&q7pM<8^}_>l62*_g~8=xq3mLC;>>vTmvM*JEO$U9qFQ(hP#S2w!h9KS^e4CFC6- z=_{?Xlf*5_N$7qyMemip5HoQDKp(n;PwG*uoHlCMLafSS(%slWZ}?1-kTHzKK)s-o zXge;Hj-(&jE-*pVYI=Xpi#&0Tt}>9YbEwtz$Ho)YmW8kS<^F)O25C-~c zg%+CJ49&4hRO{>#-2*WJ^SOJt#m+v|A4BKc`WTi2E*iVHqyhrxZtX7EKz(Iakd;{g z>nke{B(>!C4%;R^lEhp1(%SJYEy58kR8|^POaWXqa#v2Z z#4}2wKMlvM$0cP}DViCq4oxb<|8;_g64#xD4b!{_k;d5i0ZMawOS8jRkl|*Dy5ra& zaO^GbdaZMIaZf*RA@%iGJ3wvKlq5#6uCB%u!hQ)B~9M*jdpkM0Er)eUXGeI#F;hE`#}aI?5sgoxv;(QknDZKiho% zJR*e#k+!+cf5UbFb*P6kxITkvSwLhUeq`gG+x+&HxXvk(jAXXQ3Ns5O&$fm&h5nj- zmJc+i=n~u`dJ1iDQqYN5REhj^;BGuPuZr9nRJk={kM?>) zcTAAhlShyelC-evOP{Hk7sa_0G8s&#Tq&t2^_*@|Oj6lVcrxhf(HDKrtC~>^0)1E0 zPIk1dV1&DyD`k+uM@LTXzZicg5FR#mf-N2Dqr~uV-N6o@>}nh4%wLx)&4JwZNy)R7 zgZqS>6ij_H3G-qG}xUbWLPTd_W2PxA( z#$vzIfOe-A4aCIe%475R{=_=eTbs|Q02Qo)-Po)U0!ca*KBPW{XW;8iy|)B!-OQbs z8Cnlv$~``&u;aMbJ&CqCn&mQoZ|@}<4B>Z$9AmYLmsk}c&qu=hN04O>^Rp}O0eaoy zf@QBpChoOE(!?Qr0umc`4#-F!1z*fYIp}gO3b=Hf5hBqC?^Vrxq>|o5T!R2X&AMuA zI7`QEpfK9k$#a&0GcWKXKzj<&U$}$~9OH;GD?q0)$O;tMX?taJi+=ius>-@Iw4SvC z@vC#88tKU^IcvmGhF7d^MQM>lSv<#{P9IhR<+@Ui%<7Ngl+O}#&lwht#2REHh}kPC zLVZsSv0%PSCP8iYl=1-`c_F(@Lm>~)cu+u5*o>zLtCSUnk9-Np$6F0>4z6L(~0r@M&2KK`?$2-jc3yE`0=z_ zl4o^$lNuMHTSWlJg_G3&b}9X_TwwpxZXdOnXNHR%LaWNE4WnzD?4I-(l{SJgv+9ni zBRdd0hS2IFE6h`y!=-|GnaA2=^=jkjGvzqyJZzpKX@?JEual<^BWxh2Y90Dxo`75K zATtrR*^R;WX`0P;V*dfe6P^dr3t7u4y2}E-%v=^s|4hG1q=I!fbd%APH+{`?#{+Qp zC7Kr}rYUCt$Na)&$!me9DsZrG^Wo@4p>X28Nn(5O)<@f;$3ffRg zFjhm*g;U;K+N{X1sDfG%U0en-rAB@lgggJNeu;rhfX-dm7=j=ajFb=|uI$!Zs3et! ze572J_qPGPrlImWEPpL~pjzkb)o-WlEYEC;apea=;qHijsJST+BVSvl1k0!nRu24H z${|^LjMG8tmf%1wZ{qF6hUtTUTLA5BI4$>-@PCn5_mImCf){||k~1&+NcREje9X3=LmlkM!;l5pdUv{Mo zQOkIQfQH)ju46lNR-DKw0zzl?*tX7<`xIya^1KhK(9F7GSe1D*Y*YR&o_DtA9p;;C z*r3!iJEa8}Wp$Hnuu|$Q-c!DE)C+)KngV_TbDQT%LHS@?!NBxIr( z`|D?sfB-SDvu0RS9PINMGb$_6E5hrHSy-$c1E#h?obk3piuX7ioryY6h0Mn+@Zru0 zR7T>PZId~(Qd8#s~mYYxp#6dIGXZD@K-X*gfm_MArCNk+4pbYfST-WclH1lHa7 zf~E9QoHspZX^ZnS?BJ!f5MO}Y1i6D#lAcxyMXto7+6P_qwL_3vWU-#U(8vey(hwV& zc1jskalD6;5g>}*o-#B$RYnYoL}Z=-69~c!vuPu@A{NKe{yuy`zfX*Th_72D#7X70 zU5UQ47xXg6NwZ_Zppp{b0d3hRbrviO6Uzce6e;pOzyYbUXzRFw;yjCMiMu!&-8xwg ziV8c3O}e|jE|O4o0gAo@NMY*0r>uh3l%OMp`m|sO6E|{XaH4ygro02 zkkOqwm^Snn@YVs-TPD(`l?V92G66Wvmca>;uCZ&NeP+uzjV=?mVnovH@9*^;Qrfba zJ{*%E!^kREX9(IQLHTt!hjl_2NiGuZR~{hDI%$ta01-i>teG~y*a+A!#`$bjV@MxtVhjwu z8_i!0RWo$Xz&T8#-@1xhtkp4Wn6Onz_TPHcp_!*CEmZPSJhrA*ff= z)7cYk48l5Y)4|?5z5g=DNQ34>ECgGc^&I>nO{XRe$wn64qym*7+H$9 zD~qbZXqYIY8Kikx$6=5lHGpi3zGEfewVQ$I^P-b5B^-9uv!ui$v!{%w zd4AqhbPy(J%#EK4{1wsut=l)rj0qqjWa1n+xe=*ZTX-XZ;X?Bx}L;?A`;m&0+Hp}aAX?EOqwDwqk zk4m!~OY*JILBRl!CLma~F;50EgN_K=wtY;T44GXTSRbmsHl^^UoMsug|I9c9T$thTb#|2qoQckNmpU z1eHj@A>8$jA;5?opU< zHObXs9n9quyE0ygrX-_NB@YXfF-E!=zV0n^ustgw&~%WuW&|IJ0(0p@+>T_`fYBF)0T(Eu@vY0Zcon3pT{gjw$txL_r@_t$h&dZz=RkW3hk&E?l(A)Zq~&z1KdOjd0OY&46+#r!=85Qo zvRV)_vB=>Zt4tZ`TnH8dCAP&75qU!i`(h>LBqr4oqxMX)5RL5Rn&F}b38A6!Y)m8f zWJAfoHQ{l3E0M#gs(sPPd*&5q&^^=p(l7lyVe3Zy5x72r3j7Pcw}(*}H%v@<6w?0m z$9yq|_B1)Y{r!v?@|wJ@wo@iX&h_!m6~i4~ffdP`li7XJ;t4^f{JreISG1jZE6DEK zT8mDZMWDW57u#%sGDjuG=wHu5je5s;ceN0$cCnBYGTXdW*sU|xjx+IVu-o>20ucXt zqGG9wd`HE{>UHum?xOeJ1mV1i3a&ZTbG^tHc5HcHZ^-R6^%;%65hyhFWZ9fK@~+xr zP36QM*v8WK3FILeK&cpL>#QXXn&b}S?%K> z;nHOeGq0Bpj6hj-=*D!MjCTJofBurbppsUq8i>+sUD{Pc$F#dwvIst%hS_J>DKri2 zty;12bXaCiT*>^*>W~yddTYg_@E+m3M%i?lBCbSyQdzn%#U05-ZbfX@h;Z1R>=8Z- z*0Whd9FjD0wjGWocZKE@9fXo+7qK`Px5xKO#B%2Rk>_jd1C^Fbw7*4)SPzb#HUdRJ zRMj{5=sUCwKc=P*(HbrBs6o_&@`B z(=9pBg_wOOqLwHxpf+0I1HOmIheV9GYh;J(TuARF6UyBea1>F-W{EQExTlqMUK zJ_0g2^b3|o&@?p6q|kJAwb(oAudo0_W^R)NkNvzqG$qa+5eloGQ9|w6wZZWiW}jF_ zAs9U08WCvge>=)Tm`g?xny?*F#WKlMezd{Dk3wAgIJWa4(;sMdC;1RoW*IYHxCm3D z2OF%zZRu6T9VFPUL>5BdAk4m4tg8vJLZPf6#VXahvAW-KjN&PSl@-c4R<&)4Y(eDgkwP(X*dY}P_tMF;L(z$ zJ&kxeD*PmDstH?MPm=NRwjr#03yFHDeyHTHP$hnymEg3Z-AE2^`vxKv& zN9e;s5!d&IF>#&|03G10gaiI#X%*zRWdhqj<^s|*H1fvczoCXcNF)Yg`;cQ;y(DODG{kyp zvvn&p^Aho~SG-9nRU=T$Qz)Vnz#s+C2?SsE&9+4OedO>$uz@OuOg1w_!?s2itbw3Q z0IO#{k1hhH>JswTS?$QkFF5yzT^`fItwTt5+GG{0rET~&r9p@XFq7`jhDA?)#b+EB zu5*`>!dS~B(adGpd*K!jjn1YZnx7DvvKj32*`u}R96k|tjaQo!@$L}_CX^6H24?E+ zLB`-WD*}$<5G0OBqs3+PuGik&Jum~4XgUEv5~L&Wg@??edD<4oC5Dcbp93(g&hxD@ zhAezI1fpno$#Vp)KRPRiTov9%D2919d|JE?ygld-Sh{aed9On@5!?LLMl2V|Nf0Sk zf(08VffBg8W%MhG2eE^=Wb~~)Fj;TRd|KV*^aTw*DxXJ(Xb)_SC0t~B-dG7*zTrDR zGl|T|JjaDPeJ*uLy`231H zg<8g@d&B`?QnUSfEc*eFT`3%(T%0sAr9QXkE>C9urKRCpVTW?)KwlMl3pw>=FMHLCGc0m;V7T7v-Eg$iMd5a&xUVr}qHbZcA02ow4gd{IjNQtcO%t)v}`lmKO5*{M|7Kq-kCq?^!AsXQ3Q&v0l50qg?g1pUHF z$!}V}ON!Se2G(=Fg(zr8qdCrXQ#aa5S3!d;ZVwj8p)Of$l{Pm#@_b4%+S8+}GK&-#L9i1^fp3ZxmMwSVA*YwD^}%3tPaZAZ(H}7C;Wi=8 zAR5WFqmY!0Cn+ft%bJKSl=3znfC&1;4|Y$9wZ?G|{JreISNxX~x)iqdXji|wm7^Pd zE~m%WuQqz?0XTYHCojvyBNQHTnImX;!nqPNu_ZW}fgRBh62gfw-d)Dw`=~prJsX2BVjG6R;YstR_Bvv ztv3)A%IRgB7=gylPROGNDf9SJ_xN)6_zLZd+=5^OGNdnK2Z}u$u>8suM==}$RHBDm zX^__{!4XcW!|_V;=+UF4j^5K}&^c}ULcBnWlhAmKas=GS9g+g~$w13WLpxw4k-kYI z!&`cd^PsXTiDc0q(9An0i`a^(bw$eZji${L&Va|*LY|AUb_Lbv@#r@cxf`h?{$S#N zWAC9IXa>?4zYLyvI!Vd;G&!V?GIZCbsy#VoE+#J^6y#9Rr7%oS8t_Dq8u+Z9wX*hp z5ujG%P0p(2*D6j%r2#|O^mSTe8LU>xgIfJ{x>+qTr^+Be(AB=g=3x8-0`WD>p>7>IrZA95_rLRAAS zMv{4SIXieRF$H6~q}F621bT!z1MesSOh5jPCC%hV(=s_E%jyl&V2D2^?l$Va^t9zh zRZa|={O$Hd_?QgRo7GYFY>)^?a;&|LoZ-|mc4pYHUvjXLh~YEOQh^@2&mUD`2vk2( zzcyfdszm2-?NI*c9?)u7psQ8ExsMfC&O*xZ%jNSSXPn6diLR)LWJI^4zJtI-ThV+J zyo9h%MTEo-Wj{m&66F~qc^o{fLXX8#(k61E6)_vlu1v@d>&cKlB9O#`1D#o= zQ_*oaSynFLwO|^^rFOC*(XziPa0&>4uy`;9lIc~#1i#a$=!OhLi&ZqP?PN$J{)+yU zn99^spO8ew86Koxb{7Y4&~Ga<^_>w}lNuv=NiHlYk$Xd0h1l72D}IJ^t&Kofv%{0g zOiNeypj_;oncB%u>xfr`kAw;u8X92Jx|%bO)^uWJb*})eR&-4&7DJ$4xKoFwQTiF5 z0mkPMYUjOVqz)WXGmN7ZA+8{dSKWfFa3{&wEVNr5$uYsL!bn{j$}l!+nldlH!@5 z?xodHeU5KgrY}7x*y%*p8top&hV5WJJwV?<+RXNlNRs+&8FaDvl{c6X0=o=!j2CSd zKtJPcb^>4F6=Qj74~b2oTK0%$nCkWj5GiW;LLs9p@+RMlH%>ODmgA%yZ&WSc79!|< zJcEMaR4FM0V6u7~?S!<8Z1lR>DI{Xt5rcqS1{7utn|Y4g^F^7w`9y4o<~3 z7ay9d?6UWUWaOaG*Mw`s6WIHZOLJIHU)(=c^#s-LsJG4{n#2+wBLL$RgW6Bq}SD`%$S>gQIMZ$49ke;+rlPSPwqYu0SW+cK4?3yB6|b zPqa~Eb_B47D9_<&6;UdXyXqV{$dV}l*-aZOKBXiJlTX@Un%;PNtCa?Fm?r`d&`|}md}F{sFtj#?=hWIZ&uhdHs;9`EW&t#lW(MIb9-78h;G0;0ZxfD{ zUP^E!xvF&{)?XohQ3&zkF04WD=Z`YMdrp$3DU$R@Oj3uRqWqEKVP#1A`vvNKOp~Tm zByLeEh+me3NCJIkASUI!2!c#CF~CaJJ%N?q#RMkW08fSK&E5z7^r6A18m9G105Ao{ zj&KTVvhFDqu}_YAZr~+{eIE=&r|<~N1k)INQHiQ5s~~f@g84X!J|Qb3XRwjbuSG3N z^wgarVh9pz3Z+cm;uVR@Bsp2u7|Kw^cuF}F5<@mS`H;`-qhmeJSEs)q-O;cpB``2a zE&|-)a|VK-kWJ>U9ykGvme~ooh-|&Ij5nHpz!gubLhBR^)ud}Z76zBTaKT)fP_tiruSzF=}w;X{i3Styn2fT>D z?2Y3E-e*sg^%$#Zx*Dm`@|fHq^V8=@&&v|Ez``4Tg(+B1J_DSaBw;zDki#APs1GR~ zrT{8ht;8J%QnZjwt@SfYrYu%fPM`x*Qd4pOXqGV}2Y^&l9LAtSenRZZcN}J;fJIRx?GD&zP|V4XUP4d)GGn>}K2XO|=wFkrj1(`AJdbm>DUlQD zf$WT!IO`l~79xNINUK99z6%4#y=XoU_m#B7Oaa>zT;D=KM6XZ2d>NaUfhRBR2+`Jz zZ!7!7#$jRg15p)&AXxH4)};e02_YrINuFYb4?Xf*OHVFQi*2NPMK@f*NbOBdpN8m$ z1Od2!hp^bN+qyE>S5O_6@ zATdFarL0K``}8w*3byfVc?(m(6G@>vCQ!KBvVZ=nf~;3s$CSs$M*3HUv9=i(Vihgc zLIu)7A@M9bC>!{DASDQey2|k|(t?e$Iav-)gs24sS|w5{$MTd^kPjt4TclnxQ z(N;;h;7UL#;~=7^3d%x3txZ?b_p%IeQ9n2uN6VMXmVPONVZ)?x%d{mQRIhfokTBED zxrVJvn!PKnm6C1nE-Y5Hqn9dWy%sIYK;33Tf=Cv?=2eVm3fyz63Zc_Dw3%Vd#PrF9 zpnmIkwB}0%hZU$eP-eGZ+`d9uZeEbd(&z~Bw~^F8H`-$l(KDI zC?BPFUrB44;AeIJ?lDV?^9?H7+eEv!o0nc6ilUs0h_u>QQnoO2%_PWQ`b|%6rfG8t(936jf#R|K{_5wk)7|vh|Y*+in4<+8Xh+kkPcE2 zvk^2S4yoXu%TPs!8D|aO>k0^kJ#Qdd1J-CX(;1W*M~WvT($UoLDo!}4E}XWpyHjPV z;dJq~Mvc*$*`WkhCA`(@?q>914Z#q?gehP2uGWBc(|+VrPzp}OiI!v*M)W0ST1hiZ zmc21rAN#A)2d5 z3RiK@;0KH(k)r3cZPKvBDqMkDII9E4Q@Yipi}8I?(aE*J1M>_vD|fF*wJiGbnQ7LS z)0@rvTzX9(RLCN9gpk{?*SGYeqM6qYnMVZ@JhPb5lD2CG4M{APER}WK4R`#C&Xd(rVs1MSVNC7h z;J}+bF9g4j}!8QBDy4s<$ zWq>A|NaB7iEweg>eH^Q&Uq#FjnNbzEHz#6l5t`BJ2#i56$1z}s!{GReC&5gpRWb3Y zWQ?@)2xgKKlQ_*)@ygIU`Yc4-ITwNRXv;DbQ-H#d5#;-BBi7(|JhyrwKf41{1VpA! z3iC4V$r9ThLKWWh!)u87(5WPlkhA4>1oI>+M%xbg;GW>+lB!M72wfzmmICiF3P8)Y z#cp;Im|;Hwm7qdFQQD675iOVx zH-(8|D7t1^(A-pfxLb1;8aDMai!~COu@Eva#w|(+eQ5VHu;06fi$L_qE%;S6tsR?Gzq&6cis9CPSemCP&kxBugLZagyEmf?N~@GALYbS8*J04oLD({7>U;! zCY$nKU)yyJCG1qTwF-ZL2eW^VV`6D>P9^okT}NBfcjTCreb})wwD-J54Zpb!!l=#* zB}{V=9#zJ05=5ZYRBkBKeTbX9pTH7n9I7e&Br+~_d!HA!Vh~kTNkswD*-8cwu5fdA zhFg+>PqAtZt}>m9ZRgO910*mCysr^uAMLX_SZc^3DV0H>Q4tzRHRm3(#apgG=P`QG zUpWRGd4`hnXw$qp&*5DD>#q2rXAQLb8U&J%k5OKE~kaNd*0ZVv%U4DB)UW? z%#9*5`jWElJfoxt2iLZbOndyeK!RoMEa+=6^%5baH3Jpdt3mpL(HD^-IqGJ=S9DE| zH=3lNMM(CP!~kng8Z_giTo|8Hm&3%GZErq2ED*~`Sjp?QroGZF zNO;c{wKXv*j8t9c3OMK%c%r6exi|N4bSmh$CF)CG^bwcjlv??LY9KP0z>!IL+6|4c z9*~nUHh8=n*TQyXwqu!$`Vl_Np%*OM%FB!)2tacx@u-$blX0bBw^0rD1LVb4VES45 z%2F@t05VR%u$xyiglHdAVP4jD2%G3T`dl?NB||DTX={)5%k0S= zDVj%wATcY}+a>!+7A0S)I3spqOrr`s2}M{7gD6svaI-#VsTnx80MKzeZ084XR)gyK zWA-qe;dT=Ce1HvWtKT28{3;}`Q1M=W!~&8_y)`g9b(mY zMfOfM(Z`00Qm{V?wj^=xltfcoHxnwfu<%R*!_Pw=X;qjVQYMcR)r4Xx@m=$6Lw_Qb zW2-0lH^uN5yEuK7!Z2RLv@F%XBp+a~rf*PaKcd)gf=Zyl@lN3s^>JSP(MO}7``uS} zX7Ws9q_PT-Y{5fu3Q;c=!ILUqPV9%=ei6YW$^N_NlwVgy)5v8Mem<{1?{0%0AZMU@ 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 literal 0 HcmV?d00001 diff --git a/assets/models/crystal-monkey.blend1 b/assets/models/crystal-monkey.blend1 new file mode 100644 index 0000000000000000000000000000000000000000..247bc47d76bdc1544d22bf8d953253da9923d8d9 GIT binary patch literal 996532 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
lJ9?6`a%s(~4u9c|nuX@} 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

GXBob?VL~-?9JvTaR0( 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 { @@ -45,7 +45,7 @@ impl> Platform for GliumPlatfor let time = time::Time::new(time_cfg); // Install rendering systems - rendering_system_manager.add::(&display, &window); + rendering_system_manager.add::(&display, &window); rendering_system_manager.add::(&display, &window); Self { diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index c31a0d9..25c194c 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,4 +1,4 @@ -mod basic; +mod pbr; pub mod debug_ui; -pub use basic::BasicMeshRenderingSystem; +pub use pbr::PbrMeshRenderingSystem; diff --git a/glium_platform/src/render/pbr.rs b/glium_platform/src/render/pbr.rs new file mode 100644 index 0000000..8851759 --- /dev/null +++ b/glium_platform/src/render/pbr.rs @@ -0,0 +1,192 @@ +// AI Generated + +use glium::{uniform, Display, Program, Surface}; +use glium::glutin::surface::WindowSurface; +use glium::texture::{RawImage2d, SrgbTexture2d, Texture2d}; +use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction}; +use glam::{Vec3, Vec4, Mat4}; +use crate::RenderingSystem; +use crate::system::RenderingContext; +use raidillon_assets::include_shader; +pub use raidillon_platform::Camera; +use raidillon_ecs::components::{ModelHandle, PointLight}; +use raidillon_ecs::Transform; +use crate::model::Model; + +pub struct PbrMeshRenderingSystem { + program: Program, + white_srgb: SrgbTexture2d, + black_srgb: SrgbTexture2d, + white_linear: Texture2d, + params: glium::DrawParameters<'static>, +} + +impl RenderingSystem for PbrMeshRenderingSystem { + fn initialize(display: &Display, _window: &glium::winit::window::Window) -> Self { + const VERT_SRC: &str = include_shader!("pbr.vert"); + const FRAG_SRC: &str = include_shader!("pbr.frag"); + + let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap(); + + let white_srgb = { + let data = vec![255u8, 255u8, 255u8, 255u8]; + let raw = RawImage2d::from_raw_rgba(data, (1, 1)); + SrgbTexture2d::new(display, raw).unwrap() + }; + let black_srgb = { + let data = vec![0u8, 0u8, 0u8, 255u8]; + let raw = RawImage2d::from_raw_rgba(data, (1, 1)); + SrgbTexture2d::new(display, raw).unwrap() + }; + let white_linear = { + let data = vec![255u8, 255u8, 255u8, 255u8]; + let raw = RawImage2d::from_raw_rgba(data, (1, 1)); + Texture2d::new(display, raw).unwrap() + }; + + let params = glium::DrawParameters { + depth: glium::Depth { + test: glium::draw_parameters::DepthTest::IfLess, + write: true, + .. Default::default() + }, + .. Default::default() + }; + + Self { + program, + white_srgb, + black_srgb, + white_linear, + params, + } + } + + fn render(&mut self, ctx: &mut RenderingContext) { + // Acquire camera + let cam = match ctx.scene.world.query::<&Camera>().iter().next() { + Some((_, cam)) => *cam, + None => { + eprintln!("[renderer] No camera component found. Skipping frame"); + return; + } + }; + + let view: Mat4 = cam.view(); + let projection: Mat4 = cam.projection(); + + // Directional light setup + let dir_light_dir_world: Vec3 = Vec3::new(-0.5, 3.0, -0.25).normalize(); + let dir_light_dir_view: Vec3 = (view * Vec4::new(dir_light_dir_world.x, dir_light_dir_world.y, dir_light_dir_world.z, 0.0)).truncate().normalize(); + let dir_light_color: Vec3 = Vec3::splat(1.0); + let dir_light_intensity: f32 = 3.5; + + // Collect point lights + const MAX_POINT_LIGHTS: usize = 3; + let mut pl_pos = [[0.0f32; 3]; MAX_POINT_LIGHTS]; + let mut pl_col = [[0.0f32; 3]; MAX_POINT_LIGHTS]; + let mut pl_int = [0.0f32; MAX_POINT_LIGHTS]; + let mut pl_range = [0.0f32; MAX_POINT_LIGHTS]; + let mut pl_count: i32 = 0; + + for (_, (tr, pl)) in ctx.scene.world.query::<(&Transform, &PointLight)>().iter() { + if (pl_count as usize) >= MAX_POINT_LIGHTS { break; } + let pos_view = (view * Vec4::new(tr.translation.x, tr.translation.y, tr.translation.z, 1.0)).truncate(); + pl_pos[pl_count as usize] = [pos_view.x, pos_view.y, pos_view.z]; + pl_col[pl_count as usize] = [pl.color.x, pl.color.y, pl.color.z]; + pl_int[pl_count as usize] = pl.intensity; + pl_range[pl_count as usize] = pl.range; + pl_count += 1; + } + + let asset_manager = ctx.asset_manager.borrow(); + + for (_, (tr, mh)) in ctx.scene.world.query::<(&Transform, &ModelHandle)>().iter() { + let model_any = match asset_manager.get_model(&mh.0) { Some(m) => m, _ => continue }; + let model = match model_any.downcast_ref::() { Some(m) => m, None => continue }; + + let mesh = &model.mesh; + let mat = &model.material; + + // Base color texture (sRGB) + let base_color_tex: &SrgbTexture2d = mat.base_color.as_ref().unwrap_or(&self.white_srgb); + let mut base_sampler = base_color_tex.sampled(); + base_sampler = base_sampler.wrap_function(SamplerWrapFunction::Repeat); + base_sampler = base_sampler.minify_filter(MinifySamplerFilter::Linear); + base_sampler = base_sampler.magnify_filter(MagnifySamplerFilter::Linear); + let has_base_color_map: i32 = if mat.base_color.is_some() { 1 } else { 0 }; + + // MR map (linear) + let mr_tex: &Texture2d = mat.metallic_roughness.as_ref().unwrap_or(&self.white_linear); + let mut mr_sampler = mr_tex.sampled(); + mr_sampler = mr_sampler.wrap_function(SamplerWrapFunction::Repeat); + mr_sampler = mr_sampler.minify_filter(MinifySamplerFilter::Linear); + mr_sampler = mr_sampler.magnify_filter(MagnifySamplerFilter::Linear); + let has_mr_map: i32 = if mat.metallic_roughness.is_some() { 1 } else { 0 }; + + // Occlusion map (linear, R) + let occlusion_tex: &Texture2d = mat.occlusion.as_ref().unwrap_or(&self.white_linear); + let mut occl_sampler = occlusion_tex.sampled(); + occl_sampler = occl_sampler.wrap_function(SamplerWrapFunction::Repeat); + occl_sampler = occl_sampler.minify_filter(MinifySamplerFilter::Linear); + occl_sampler = occl_sampler.magnify_filter(MagnifySamplerFilter::Linear); + let has_occlusion_map: i32 = if mat.occlusion.is_some() { 1 } else { 0 }; + + // Emissive map (sRGB) + let emissive_tex: &SrgbTexture2d = mat.emissive.as_ref().unwrap_or(&self.black_srgb); + let mut emissive_sampler = emissive_tex.sampled(); + emissive_sampler = emissive_sampler.wrap_function(SamplerWrapFunction::Repeat); + emissive_sampler = emissive_sampler.minify_filter(MinifySamplerFilter::Linear); + emissive_sampler = emissive_sampler.magnify_filter(MagnifySamplerFilter::Linear); + let has_emissive_map: i32 = if mat.emissive.is_some() { 1 } else { 0 }; + + let bc = mat.base_color_factor; + let ef = mat.emissive_factor; + + let uniforms = uniform! { + model: tr.matrix().to_cols_array_2d(), + view: view.to_cols_array_2d(), + projection: projection.to_cols_array_2d(), + uv_offset: [mat.uv_offset.x, mat.uv_offset.y], + uv_scale: [mat.uv_scale.x, mat.uv_scale.y], + + // Material + u_base_color_map: base_sampler, + u_has_base_color_map: has_base_color_map, + u_base_color_factor: [bc[0], bc[1], bc[2], bc[3]], + + u_metallic_roughness_map: mr_sampler, + u_has_metallic_roughness_map: has_mr_map, + u_metallic_factor: mat.metal_factor, + u_roughness_factor: mat.roughness_factor, + + u_occlusion_map: occl_sampler, + u_has_occlusion_map: has_occlusion_map, + + u_emissive_map: emissive_sampler, + u_has_emissive_map: has_emissive_map, + u_emissive_factor: [ef[0], ef[1], ef[2]], + + // Directional light (view-space) + u_dir_light_dir: [dir_light_dir_view.x, dir_light_dir_view.y, dir_light_dir_view.z], + u_dir_light_color: [dir_light_color.x, dir_light_color.y, dir_light_color.z], + u_dir_light_intensity: dir_light_intensity, + + // Point lights (view-space) + u_point_light_count: pl_count, + u_point_light_pos: pl_pos, + u_point_light_color: pl_col, + u_point_light_intensity: pl_int, + u_point_light_range: pl_range, + }; + + ctx.target.draw( + &mesh.vbuf, + &mesh.ibuf, + &self.program, + &uniforms, + &self.params, + ).unwrap(); + } + } +} From 44489f9fe356d1357d96652b50ab1a8db848dc90 Mon Sep 17 00:00:00 2001 From: reo Date: Fri, 26 Sep 2025 16:52:14 +0300 Subject: [PATCH 02/38] Move models to git LFS --- .gitattributes | 1 + assets/models/monkey.bin | Bin 68720 -> 130 bytes assets/models/monkey.gltf | 107 +----------------------- assets/models/pink-monkey.bin | Bin 68720 -> 130 bytes assets/models/pink-monkey.gltf | 124 +--------------------------- assets/models/tree.bin | Bin 13584 -> 130 bytes assets/models/tree.gltf | 143 +-------------------------------- assets/models/tree_texture.png | Bin 845 -> 128 bytes 8 files changed, 10 insertions(+), 365 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7dc2605 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +assets/models/* filter=lfs diff=lfs merge=lfs -text diff --git a/assets/models/monkey.bin b/assets/models/monkey.bin index e7992f36d75135cc5966e2abe94e5937420da413..56f4aa06d5161846423e6a1fd1ce39019a203efd 100644 GIT binary patch literal 130 zcmWN?K@!3s3;@78uiyigKp`dl4TMyfQRxWw;OliSd&zsW{?c{MbL__4+q^xFvHY)} zyl{V-aSEHus@{qoHMxMhBex1BPsGuS7ZT3QjIfpv8A9?A1-4j%1+j=inH?1jAOij% MHQHA+#?68H0qdA2e*gdg literal 68720 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 diff --git a/assets/models/monkey.gltf b/assets/models/monkey.gltf index d3e2438..2651b01 100644 --- a/assets/models/monkey.gltf +++ b/assets/models/monkey.gltf @@ -1,104 +1,3 @@ -{ - "asset":{ - "generator":"Khronos glTF Blender I/O v4.4.56", - "version":"2.0" - }, - "scene":0, - "scenes":[ - { - "name":"Scene", - "nodes":[ - 0 - ] - } - ], - "nodes":[ - { - "mesh":0, - "name":"Suzanne" - } - ], - "meshes":[ - { - "name":"Suzanne", - "primitives":[ - { - "attributes":{ - "POSITION":0, - "NORMAL":1, - "TEXCOORD_0":2 - }, - "indices":3 - } - ] - } - ], - "accessors":[ - { - "bufferView":0, - "componentType":5126, - "count":1966, - "max":[ - 1.3671875, - 0.984375, - 0.8515625 - ], - "min":[ - -1.3671875, - -0.984375, - -0.8515625 - ], - "type":"VEC3" - }, - { - "bufferView":1, - "componentType":5126, - "count":1966, - "type":"VEC3" - }, - { - "bufferView":2, - "componentType":5126, - "count":1966, - "type":"VEC2" - }, - { - "bufferView":3, - "componentType":5123, - "count":2904, - "type":"SCALAR" - } - ], - "bufferViews":[ - { - "buffer":0, - "byteLength":23592, - "byteOffset":0, - "target":34962 - }, - { - "buffer":0, - "byteLength":23592, - "byteOffset":23592, - "target":34962 - }, - { - "buffer":0, - "byteLength":15728, - "byteOffset":47184, - "target":34962 - }, - { - "buffer":0, - "byteLength":5808, - "byteOffset":62912, - "target":34963 - } - ], - "buffers":[ - { - "byteLength":68720, - "uri":"monkey.bin" - } - ] -} +version https://git-lfs.github.com/spec/v1 +oid sha256:6513b26c81eb59be818a8ddc0331eccd2e54d8c2e9b0d7edb01b491a16cdc421 +size 1333 diff --git a/assets/models/pink-monkey.bin b/assets/models/pink-monkey.bin index 2db45966ba391e44f67a7dcc92cd338340566448..627ca3e113752425edd688115646fdf9ceb9a10e 100644 GIT binary patch literal 130 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 avGu<>G5hAoEVHk!#K);QG(HZ^t^5H`4fe$V diff --git a/assets/models/pink-monkey.gltf b/assets/models/pink-monkey.gltf index a27e895..d4c6adf 100644 --- a/assets/models/pink-monkey.gltf +++ b/assets/models/pink-monkey.gltf @@ -1,121 +1,3 @@ -{ - "asset":{ - "generator":"Khronos glTF Blender I/O v4.0.44", - "version":"2.0" - }, - "scene":0, - "scenes":[ - { - "name":"Scene", - "nodes":[ - 0 - ] - } - ], - "nodes":[ - { - "mesh":0, - "name":"Suzanne" - } - ], - "materials":[ - { - "doubleSided":true, - "name":"Material.001", - "pbrMetallicRoughness":{ - "baseColorFactor":[ - 0.8002749681472778, - 0, - 0.07706787437200546, - 1 - ], - "metallicFactor":0, - "roughnessFactor":0.5 - } - } - ], - "meshes":[ - { - "name":"Suzanne", - "primitives":[ - { - "attributes":{ - "POSITION":0, - "NORMAL":1, - "TEXCOORD_0":2 - }, - "indices":3, - "material":0 - } - ] - } - ], - "accessors":[ - { - "bufferView":0, - "componentType":5126, - "count":1966, - "max":[ - 1.3671875, - 0.984375, - 0.8515625 - ], - "min":[ - -1.3671875, - -0.984375, - -0.8515625 - ], - "type":"VEC3" - }, - { - "bufferView":1, - "componentType":5126, - "count":1966, - "type":"VEC3" - }, - { - "bufferView":2, - "componentType":5126, - "count":1966, - "type":"VEC2" - }, - { - "bufferView":3, - "componentType":5123, - "count":2904, - "type":"SCALAR" - } - ], - "bufferViews":[ - { - "buffer":0, - "byteLength":23592, - "byteOffset":0, - "target":34962 - }, - { - "buffer":0, - "byteLength":23592, - "byteOffset":23592, - "target":34962 - }, - { - "buffer":0, - "byteLength":15728, - "byteOffset":47184, - "target":34962 - }, - { - "buffer":0, - "byteLength":5808, - "byteOffset":62912, - "target":34963 - } - ], - "buffers":[ - { - "byteLength":68720, - "uri":"pink-monkey.bin" - } - ] -} +version https://git-lfs.github.com/spec/v1 +oid sha256:a955cb6bcdcaed40dee676c707ba562f93ca960806107c6e9f328764be49a303 +size 1613 diff --git a/assets/models/tree.bin b/assets/models/tree.bin index e7826210d4c2a288d9825d6987ca7d963ef294b5..225ddd1884035a2c5d098123da7d4e5ca48c572d 100644 GIT binary patch literal 130 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 A3jhEB diff --git a/assets/models/tree.gltf b/assets/models/tree.gltf index 51bf9ed..58b4fb9 100644 --- a/assets/models/tree.gltf +++ b/assets/models/tree.gltf @@ -1,140 +1,3 @@ -{ - "asset":{ - "generator":"Khronos glTF Blender I/O v4.4.56", - "version":"2.0" - }, - "scene":0, - "scenes":[ - { - "name":"Scene", - "nodes":[ - 0 - ] - } - ], - "nodes":[ - { - "mesh":0, - "name":"tree01", - "scale":[ - 0.009999999776482582, - 0.009999999776482582, - 0.009999999776482582 - ] - } - ], - "materials":[ - { - "name":"Mat", - "pbrMetallicRoughness":{ - "baseColorTexture":{ - "index":0 - }, - "metallicFactor":0 - } - } - ], - "meshes":[ - { - "name":"tree01_1", - "primitives":[ - { - "attributes":{ - "POSITION":0, - "NORMAL":1, - "TEXCOORD_0":2 - }, - "indices":3, - "material":0 - } - ] - } - ], - "textures":[ - { - "sampler":0, - "source":0 - } - ], - "images":[ - { - "mimeType":"image/png", - "name":"tree_texture", - "uri":"tree_texture.png" - } - ], - "accessors":[ - { - "bufferView":0, - "componentType":5126, - "count":395, - "max":[ - 123.4296646118164, - 509.0864562988281, - 168.36563110351562 - ], - "min":[ - -184.9799346923828, - 0, - -155.37196350097656 - ], - "type":"VEC3" - }, - { - "bufferView":1, - "componentType":5126, - "count":395, - "type":"VEC3" - }, - { - "bufferView":2, - "componentType":5126, - "count":395, - "type":"VEC2" - }, - { - "bufferView":3, - "componentType":5123, - "count":471, - "type":"SCALAR" - } - ], - "bufferViews":[ - { - "buffer":0, - "byteLength":4740, - "byteOffset":0, - "target":34962 - }, - { - "buffer":0, - "byteLength":4740, - "byteOffset":4740, - "target":34962 - }, - { - "buffer":0, - "byteLength":3160, - "byteOffset":9480, - "target":34962 - }, - { - "buffer":0, - "byteLength":942, - "byteOffset":12640, - "target":34963 - } - ], - "samplers":[ - { - "magFilter":9729, - "minFilter":9987 - } - ], - "buffers":[ - { - "byteLength":13584, - "uri":"tree.bin" - } - ] -} +version https://git-lfs.github.com/spec/v1 +oid sha256:7f5b405308b025a6fdc8e5dad59d374350415338102219f5a31562064d3624c8 +size 1846 diff --git a/assets/models/tree_texture.png b/assets/models/tree_texture.png index f5726d41ea881c58cf74e16442f6e7856ad98742..1d2807bd98744fa735b0b5ac83e02b7ac3c1be8f 100644 GIT binary patch literal 128 zcmWN_$qmCG5J1s8RnS0;*}>hg%mkz)SQ+d?st>2H_iy-8A0OW4Jn0bP+2`Yhw5_%) zZ)Lm%olwLr+31y1OUk-K7Y)_tJ%F@YO3cyu;0kJtMY&LePD@6*jHW~h3NfSi#zER) L9Y*~JJQ&OVsp==K literal 845 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911L)MWvCLk0$B5l7=R`)GBhwSI502( z#RSL@CQB1Gn;9NBk>>&o!x#> Date: Fri, 26 Sep 2025 22:44:43 +0300 Subject: [PATCH 03/38] Implement HDRI skybox support --- .gitattributes | 1 + Cargo.lock | 114 +++++++++++++- assets/exr/citrus_orchard_road_puresky_4k.exr | 3 + assets/exr/qwantani_sunset_puresky_2k.exr | 3 + assets/shaders/skybox.frag | 1 + game/src/main.rs | 4 +- glium_platform/Cargo.toml | 2 + glium_platform/src/platform.rs | 8 +- glium_platform/src/render/basic.rs | 10 +- glium_platform/src/render/mod.rs | 2 + glium_platform/src/render/skybox.rs | 149 ++++++++++++++++++ glium_platform/src/system.rs | 2 + 12 files changed, 282 insertions(+), 17 deletions(-) create mode 100644 assets/exr/citrus_orchard_road_puresky_4k.exr create mode 100644 assets/exr/qwantani_sunset_puresky_2k.exr create mode 100644 glium_platform/src/render/skybox.rs diff --git a/.gitattributes b/.gitattributes index 7dc2605..7182511 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ assets/models/* filter=lfs diff=lfs merge=lfs -text +assets/exr/* filter=lfs diff=lfs merge=lfs -text diff --git a/Cargo.lock b/Cargo.lock index 3665a1b..25cb3cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -319,12 +325,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "cursor-icon" version = "1.2.0" @@ -384,6 +415,21 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -603,6 +649,16 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -636,12 +692,14 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "image" -version = "0.25.6" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" dependencies = [ "bytemuck", "byteorder-lite", + "exr", + "moxcms", "num-traits", "png", "zune-core", @@ -769,6 +827,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.174" @@ -864,6 +928,16 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" +[[package]] +name = "moxcms" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "ndk" version = "0.9.0" @@ -1267,11 +1341,11 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" -version = "0.17.16" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "crc32fast", "fdeflate", "flate2", @@ -1310,6 +1384,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" +dependencies = [ + "num-traits", +] + [[package]] name = "quick-xml" version = "0.37.5" @@ -1388,9 +1471,11 @@ name = "raidillon_glium" version = "0.1.0" dependencies = [ "anyhow", + "exr", "glam", "glium", "gltf", + "image", "imgui", "imgui-glium-renderer", "imgui-winit-support", @@ -1419,6 +1504,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2408,6 +2503,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zune-jpeg" version = "0.4.20" diff --git a/assets/exr/citrus_orchard_road_puresky_4k.exr b/assets/exr/citrus_orchard_road_puresky_4k.exr new file mode 100644 index 0000000..c57cb53 --- /dev/null +++ b/assets/exr/citrus_orchard_road_puresky_4k.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8aa6cc5bb4a5a8f5fa12870cefdd6a6b600072454add837dca45e340c1549d30 +size 70720809 diff --git a/assets/exr/qwantani_sunset_puresky_2k.exr b/assets/exr/qwantani_sunset_puresky_2k.exr new file mode 100644 index 0000000..86cd3d7 --- /dev/null +++ b/assets/exr/qwantani_sunset_puresky_2k.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f738ddd35a5e1a291eb45b30eea73b4d296dc4a05a412ae2f187ef5e95dc076a +size 18335204 diff --git a/assets/shaders/skybox.frag b/assets/shaders/skybox.frag index 8418e54..864be50 100644 --- a/assets/shaders/skybox.frag +++ b/assets/shaders/skybox.frag @@ -17,6 +17,7 @@ vec2 sample_spherical_map(vec3 v) { void main() { vec2 uv = sample_spherical_map(normalize(direction)); + uv.y = 1.0 - uv.y; vec3 color = texture(equirect, uv).rgb; frag_color = vec4(color, 1.0); } diff --git a/game/src/main.rs b/game/src/main.rs index 05ae11e..bea5fe0 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -101,8 +101,8 @@ fn main() { let platform = GliumPlatform::initialize( engine, "Raidillon".to_string(), - 1920, - 1080, + 2560, + 1440, ); platform.run() }; diff --git a/glium_platform/Cargo.toml b/glium_platform/Cargo.toml index 0c0ecc2..6e51be6 100644 --- a/glium_platform/Cargo.toml +++ b/glium_platform/Cargo.toml @@ -18,3 +18,5 @@ indexmap = "2.10.0" imgui = "0.12.0" imgui-winit-support = "0.13.0" imgui-glium-renderer = "0.13.0" +exr = "1.73.0" +image = { version = "0.25.8", default-features = false, features = ["exr"] } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 16df321..f03cd96 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -14,8 +14,9 @@ use raidillon_core::engine::EngineTrait; use raidillon_core::time; use raidillon_core::time::Time; use crate::render::debug_ui::ImguiBridge; -use crate::render::BasicMeshRenderingSystem; +use crate::render::{BasicMeshRenderingSystem, SkyboxRenderingSystem}; use crate::GliumAssetManager; +use glam::Vec3; pub struct GliumPlatform> { event_loop: EventLoop<()>, @@ -44,7 +45,8 @@ impl> Platform for GliumPlatfor let time_cfg = time::Config::default(); let time = time::Time::new(time_cfg); - // Install rendering systems + // Install rendering systems in order + rendering_system_manager.add::(&display, &window); rendering_system_manager.add::(&display, &window); rendering_system_manager.add::(&display, &window); @@ -95,12 +97,14 @@ impl> Platform for GliumPlatfor asset_manager: self.asset_manager.clone(), window: &mut self.window, debug_ui_buffer, + env_light_dir: Vec3::new(0.0, -1.0, 0.0), }; self.rendering_system_manager .systems .values_mut() .for_each(|system| system.render(&mut context)); + target.finish().unwrap(); } _ => {}, diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index 0f1db93..8551ecd 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -55,14 +55,8 @@ impl RenderingSystem for BasicMeshRenderingSystem { } }; - // Direction from the light source (0,+Y) towards the scene. - let light_dir: Vec3 = Vec3::new(0.0, -1.0, 0.0).normalize(); - - // let asset_manager = ctx.asset_manager.borrow(); - // let any_ref: &dyn Any = &**asset_manager; - // if let Some(glium_manager) = any_ref.downcast_ref::() { - // &glium_manager.models; - // } + // Use HDR-derived environment light direction if provided, otherwise default to downward + let light_dir: Vec3 = if ctx.env_light_dir.length_squared() > 0.0 { ctx.env_light_dir.normalize() } else { Vec3::new(0.0, -1.0, 0.0) }; let asset_manager = ctx.asset_manager.borrow(); diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index c31a0d9..f49e308 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,4 +1,6 @@ mod basic; pub mod debug_ui; +mod skybox; pub use basic::BasicMeshRenderingSystem; +pub use skybox::SkyboxRenderingSystem; diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs new file mode 100644 index 0000000..49ea594 --- /dev/null +++ b/glium_platform/src/render/skybox.rs @@ -0,0 +1,149 @@ +use std::path::PathBuf; +use std::rc::Rc; +use std::cell::RefCell; +use glium::{Display, Program, Surface, VertexBuffer, IndexBuffer, implement_vertex}; +use glium::glutin::surface::WindowSurface; +use glium::index::PrimitiveType; +use glium::texture::{RawImage2d, SrgbTexture2d, Texture2d}; +use glium::uniform; +use glam::{Mat4, Vec2, Vec3}; +use raidillon_assets::include_shader; +use crate::system::RenderingContext; +use crate::RenderingSystem; + +#[derive(Copy, Clone)] +struct SkyboxVertex { position: [f32; 3] } +implement_vertex!(SkyboxVertex, position); + +pub struct SkyboxRenderingSystem { + program: Program, + quad_vb: VertexBuffer, + quad_ib: IndexBuffer, + /// Equirectangular HDR image, tonemapped to sRGB for skybox view + equirect_srgb: SrgbTexture2d, + /// Dominant light direction estimated from HDRI + light_dir: Vec3, +} + +impl SkyboxRenderingSystem { + fn build_cube(display: &Display) -> (VertexBuffer, IndexBuffer) { + // Unit cube centered at origin + let p = &[ + [-1.0, -1.0, -1.0], [ 1.0, -1.0, -1.0], [ 1.0, 1.0, -1.0], [-1.0, 1.0, -1.0], // back + [-1.0, -1.0, 1.0], [ 1.0, -1.0, 1.0], [ 1.0, 1.0, 1.0], [-1.0, 1.0, 1.0], // front + ]; + let verts = vec![ + SkyboxVertex { position: p[0] }, SkyboxVertex { position: p[1] }, SkyboxVertex { position: p[2] }, SkyboxVertex { position: p[3] }, // back + SkyboxVertex { position: p[4] }, SkyboxVertex { position: p[5] }, SkyboxVertex { position: p[6] }, SkyboxVertex { position: p[7] }, // front + ]; + let idx: [u16; 36] = [ + // back face + 0,1,2, 2,3,0, + // front face + 4,6,5, 6,4,7, + // left face + 0,3,7, 7,4,0, + // right face + 1,5,6, 6,2,1, + // bottom face + 0,4,5, 5,1,0, + // top face + 3,2,6, 6,7,3, + ]; + ( + VertexBuffer::new(display, &verts).unwrap(), + IndexBuffer::new(display, PrimitiveType::TrianglesList, &idx).unwrap(), + ) + } + + fn load_hdr_equirect_and_analyze(display: &Display, path: &std::path::Path) -> (SrgbTexture2d, Vec3) { + // Use image crate to decode EXR as f32 RGB + let dyn_img = image::ImageReader::open(path).expect("open exr").with_guessed_format().expect("guess format").decode().expect("decode exr"); + let hdr = dyn_img.to_rgb32f(); + let (width, height) = hdr.dimensions(); + let width = width as usize; let height = height as usize; + let mut dir_accum = Vec3::ZERO; + let mut weight_sum = 0.0f32; + for y in 0..height { + let v = (y as f32 + 0.5) / height as f32; + let theta = (v - 0.5) * std::f32::consts::PI; + let lat_weight = theta.cos().max(0.0); + for x in 0..width { + let u = (x as f32 + 0.5) / width as f32; + let phi = (u - 0.5) * 2.0 * std::f32::consts::PI; + let px = hdr.get_pixel(x as u32, y as u32).0; + let rgb = Vec3::new(px[0], px[1], px[2]); + let lum = 0.2126*rgb.x + 0.7152*rgb.y + 0.0722*rgb.z; + if lum > 0.0 { + let dir = Vec3::new(phi.cos()*theta.cos(), theta.sin(), phi.sin()*theta.cos()); + let w = lum * lat_weight; + dir_accum += dir * w; + weight_sum += w; + } + } + } + let mut light_dir = if weight_sum > 0.0 { dir_accum / weight_sum } else { Vec3::new(0.0, -1.0, 0.0) }; + if light_dir.length_squared() < 1e-6 { light_dir = Vec3::new(0.0,-1.0,0.0); } + light_dir = light_dir.normalize(); + + // Tonemap to sRGB + let mut srgb_bytes = Vec::with_capacity(width*height*4); + for y in 0..height { + for x in 0..width { + let px = hdr.get_pixel(x as u32, y as u32).0; + let mapped = Vec3::new(px[0], px[1], px[2]) / (Vec3::new(px[0], px[1], px[2]) + Vec3::ONE); + let srgb = mapped.powf(1.0/2.2); + srgb_bytes.extend_from_slice(&[ + (srgb.x.clamp(0.0,1.0)*255.0) as u8, + (srgb.y.clamp(0.0,1.0)*255.0) as u8, + (srgb.z.clamp(0.0,1.0)*255.0) as u8, + 255u8, + ]); + } + } + let raw = RawImage2d::from_raw_rgba(srgb_bytes, (width as u32, height as u32)); + let tex = SrgbTexture2d::new(display, raw).unwrap(); + (tex, light_dir) + } +} + +impl RenderingSystem for SkyboxRenderingSystem { + fn initialize(display: &Display, _window: &glium::winit::window::Window) -> Self { + const VERT_SRC: &str = include_shader!("skybox.vert"); + const FRAG_SRC: &str = include_shader!("skybox.frag"); + let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap(); + let (quad_vb, quad_ib) = Self::build_cube(display); + + // Load EXR from assets/exr + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let path = std::path::Path::new(manifest_dir).join("../assets/exr/qwantani_sunset_puresky_2k.exr"); + let (equirect_srgb, light_dir) = Self::load_hdr_equirect_and_analyze(display, &path); + Self { program, quad_vb, quad_ib, equirect_srgb, light_dir } + } + + fn render(&mut self, ctx: &mut RenderingContext) { + // Provide view and projection without translation for skybox + let cam = match ctx.scene.world.query::<&raidillon_platform::Camera>().iter().next() { + Some((_, cam)) => *cam, + None => return, + }; + let mut view = cam.view(); + // remove translation from view matrix (only orientation) + view.col_mut(3).x = 0.0; view.col_mut(3).y = 0.0; view.col_mut(3).z = 0.0; + let uniforms = uniform! { + view: view.to_cols_array_2d(), + projection: cam.projection().to_cols_array_2d(), + equirect: &self.equirect_srgb, + }; + let params = glium::DrawParameters { depth: glium::Depth { test: glium::draw_parameters::DepthTest::IfLessOrEqual, write: false, ..Default::default() }, ..Default::default() }; + ctx.target.draw(&self.quad_vb, &self.quad_ib, &self.program, &uniforms, ¶ms).ok(); + + // Share light direction with following passes + ctx.env_light_dir = self.light_dir; + } +} + +// Provide a getter for light direction for other systems +impl SkyboxRenderingSystem { + pub fn light_direction(&self) -> Vec3 { self.light_dir } +} diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 7f1f55e..891fec0 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -7,6 +7,7 @@ use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; use raidillon_core::DebugUIBuffer; use raidillon_core::scene::Scene; +use glam::Vec3; pub struct RenderingContext<'a> { pub scene: &'a Scene, @@ -14,6 +15,7 @@ pub struct RenderingContext<'a> { pub window: &'a mut glium::winit::window::Window, pub asset_manager: ModelManagerRef, pub debug_ui_buffer: Rc>, + pub env_light_dir: Vec3, } /// The internal "rendering system" trait of glium_platform. From 46c8c3281907ed42d45b6a2c0eef41732b533635 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 28 Sep 2025 01:31:14 +0300 Subject: [PATCH 04/38] Huge input update, FPS Camera controls system Long day. I now store winit::window::Window in a mutex. --- Cargo.lock | 5 +- engine/Cargo.toml | 1 + engine/src/engine.rs | 9 ++ engine/src/input.rs | 69 ++++++++++++++++ engine/src/lib.rs | 2 + engine/src/system.rs | 3 +- engine/src/systems/fps_camera.rs | 114 ++++++++++++++++++++++++++ engine/src/systems/mod.rs | 1 + game/src/main.rs | 74 ++++++++++++++--- glium_platform/src/platform.rs | 28 ++++--- glium_platform/src/render/basic.rs | 3 +- glium_platform/src/render/debug_ui.rs | 21 +++-- glium_platform/src/render/skybox.rs | 3 +- glium_platform/src/system.rs | 11 +-- platform/src/context.rs | 2 + 15 files changed, 307 insertions(+), 39 deletions(-) create mode 100644 engine/src/input.rs create mode 100644 engine/src/systems/fps_camera.rs create mode 100644 engine/src/systems/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 25cb3cb..e1b6adb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -523,9 +523,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.30.5" +version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d1aab06663bdce00d6ca5e5ed586ec8d18033a771906c993a1e3755b368d85" +checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" [[package]] name = "glium" @@ -1444,6 +1444,7 @@ dependencies = [ name = "raidillon_engine" version = "0.1.0" dependencies = [ + "glam", "hecs", "indexmap", "raidillon_assets", diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 726ac44..98202e4 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -10,3 +10,4 @@ raidillon_platform = { path = "../platform" } winit = "0.30.12" hecs = "0.10.5" indexmap = "2.10.0" +glam = "0.30.8" \ No newline at end of file diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 82b1f91..68f17e0 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -5,11 +5,13 @@ use crate::system::{SystemContext, SystemManager}; use raidillon_platform::PlatformContext; use raidillon_core::DebugUIBuffer; use raidillon_core::engine::EngineTrait; +use crate::input::InputState; pub struct Engine { pub scene_manager: SceneManager, pub system_manager: SystemManager, debug_ui_buffer: Rc>, + input_state: Rc>, } impl EngineTrait for Engine { @@ -21,6 +23,7 @@ impl EngineTrait for Engine { scene_manager, system_manager, debug_ui_buffer: Rc::new(RefCell::new(DebugUIBuffer::new())), + input_state: Default::default(), } } @@ -35,6 +38,7 @@ impl EngineTrait for Engine { scene: self.scene_manager.current_mut(), platform_context, debug_ui_buffer: self.debug_ui_buffer.clone(), + input_state: self.input_state.clone(), }; // Engine Loading Stage 2: load world @@ -50,6 +54,7 @@ impl EngineTrait for Engine { scene: self.scene_manager.current_mut(), platform_context, debug_ui_buffer: self.debug_ui_buffer.clone(), + input_state: self.input_state.clone(), }; for system in self.system_manager.systems.values_mut() { @@ -62,6 +67,7 @@ impl EngineTrait for Engine { scene: self.scene_manager.current_mut(), platform_context, debug_ui_buffer: self.debug_ui_buffer.clone(), + input_state: self.input_state.clone(), }; for system in self.system_manager.systems.values_mut() { @@ -70,10 +76,13 @@ impl EngineTrait for Engine { } fn handle_event(&mut self, platform_context: PlatformContext) { + self.input_state.borrow_mut().handle_event(&platform_context.current_event); + let mut ctx = SystemContext { scene: self.scene_manager.current_mut(), platform_context, debug_ui_buffer: self.debug_ui_buffer.clone(), + input_state: self.input_state.clone(), }; for system in self.system_manager.systems.values_mut() { diff --git a/engine/src/input.rs b/engine/src/input.rs new file mode 100644 index 0000000..27eb6fc --- /dev/null +++ b/engine/src/input.rs @@ -0,0 +1,69 @@ +use std::collections::HashSet; +use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::keyboard::{KeyCode, PhysicalKey}; + +/// A utility to help with buffering input. +/// Meant to be plugged into systems. +#[derive(Default, Clone, Debug)] +pub struct InputState { + held_keys: HashSet, + held_mouse: HashSet, +} + +impl InputState { + fn new() -> Self { + Default::default() + } + + pub fn handle_event(&mut self, event: &Event<()>) { + if let Event::WindowEvent { event, .. } = event { + match event { + // Keyboard + WindowEvent::KeyboardInput { event: key_event, .. } => { + if let PhysicalKey::Code(code) = key_event.physical_key { + match key_event.state { + ElementState::Pressed => { + self.held_keys.insert(code); + } + ElementState::Released => { + self.held_keys.remove(&code); + } + } + } + } + + // Mouse + WindowEvent::MouseInput { state, button, .. } => { + match state { + ElementState::Pressed => { + self.held_mouse.insert(*button); + } + ElementState::Released => { + self.held_mouse.remove(button); + } + } + } + + WindowEvent::Focused(focused) => { + if !*focused { + self.clear(); + } + } + _ => {} + } + } + } + + pub fn key_held(&self, code: KeyCode) -> bool { + self.held_keys.contains(&code) + } + + pub fn mouse_held(&self, button: MouseButton) -> bool { + self.held_mouse.contains(&button) + } + + pub fn clear(&mut self) { + self.held_keys.clear(); + self.held_mouse.clear(); + } +} diff --git a/engine/src/lib.rs b/engine/src/lib.rs index b3a2dc8..18ce5fd 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,4 +1,6 @@ pub mod engine; pub mod system; +mod input; +pub mod systems; pub use crate::engine::Engine; diff --git a/engine/src/system.rs b/engine/src/system.rs index 1ab49bf..b863a75 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -5,12 +5,13 @@ use raidillon_platform::PlatformContext; use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; +use crate::input::InputState; pub struct SystemContext<'a> { - // TODO: time delta etc. pub scene: &'a mut Scene, pub platform_context: PlatformContext, pub debug_ui_buffer: Rc>, + pub input_state: Rc>, } pub trait System { diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs new file mode 100644 index 0000000..cbe14b6 --- /dev/null +++ b/engine/src/systems/fps_camera.rs @@ -0,0 +1,114 @@ +use crate::system::{System, SystemContext}; +use glam::{Quat, Vec3}; +use winit::event::DeviceEvent::MouseMotion; +use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::keyboard::PhysicalKey; +use winit::window::CursorGrabMode; +use raidillon_assets::model_path; +use raidillon_platform::Camera; + +pub struct FPSCameraSystem { + mouse_delta: (f64, f64), + mouse_enabled: bool, + position: Vec3, + yaw: f32, + pitch: f32, + speed: f32, + sensitivity: f32, +} + +impl Default for FPSCameraSystem { + fn default() -> Self { + Self { + mouse_delta: Default::default(), + mouse_enabled: Default::default(), + position: Vec3::new(0.0, 0.0, 2.0), + yaw: -90.0, + pitch: 0.0, + speed: 3.0, + sensitivity: 0.1, + } + } +} + +impl System for FPSCameraSystem { + fn load_world(&mut self, ctx: &mut SystemContext) { + ctx.scene.world.spawn((Camera { + eye: Vec3::new(0.0, 0.0, 2.0), + center: Vec3::ZERO, + up: Vec3::Y, + fovy: 60_f32.to_radians(), + aspect: ctx.platform_context.frame_width / ctx.platform_context.frame_height, + znear: 0.1, + zfar: 100.0, + },)); + } + + fn handle_event(&mut self, ctx: &mut SystemContext) { + let event2 = ctx.platform_context.current_event.clone(); + match event2 { + Event::DeviceEvent { device_id, event} => { + match event { + MouseMotion { delta } => { + self.mouse_delta.0 += delta.0; + self.mouse_delta.1 += delta.1; + }, + _ => {} + } + }, + Event::WindowEvent { event, .. } => match event { + WindowEvent::MouseInput { state, button, .. } => { + if button == MouseButton::Right { + // blood and tear + let window = ctx.platform_context.window.lock().unwrap(); + match state { + ElementState::Pressed => { + if window + .set_cursor_grab(CursorGrabMode::Confined) + .or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)) + .is_ok() + { + window.set_cursor_visible(false); + self.mouse_enabled = true; + } + } + ElementState::Released => { + let _ = window.set_cursor_grab(CursorGrabMode::None); + window.set_cursor_visible(true); + self.mouse_enabled = false; + } + } + } + } + _ => {}, + }, + _ => {}, + } + } + + fn frame_update(&mut self, ctx: &mut SystemContext) { + if self.mouse_enabled { + self.yaw += self.mouse_delta.0 as f32 * self.sensitivity; + self.pitch -= self.mouse_delta.1 as f32 * self.sensitivity; + self.pitch = self.pitch.clamp(-89.0, 89.0); + } + + ctx.scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + camera.eye = self.position; + camera.center = self.position + self.front(); + }); + self.mouse_delta = (0.0, 0.0); + } +} + +impl FPSCameraSystem { + pub fn front(&self) -> Vec3 { + let yaw_rad = self.yaw.to_radians(); + let pitch_rad = self.pitch.to_radians(); + Vec3::new( + yaw_rad.cos() * pitch_rad.cos(), + pitch_rad.sin(), + yaw_rad.sin() * pitch_rad.cos(), + ).normalize() + } +} diff --git a/engine/src/systems/mod.rs b/engine/src/systems/mod.rs new file mode 100644 index 0000000..8489571 --- /dev/null +++ b/engine/src/systems/mod.rs @@ -0,0 +1 @@ +pub mod fps_camera; diff --git a/game/src/main.rs b/game/src/main.rs index bea5fe0..b7c45e0 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -10,12 +10,56 @@ use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] use raidillon_glium::GliumPlatform; -use winit::event::{Event, WindowEvent}; +use winit::event::{ElementState, Event, WindowEvent}; +use winit::event::DeviceEvent::MouseMotion; +use winit::keyboard::{KeyCode, PhysicalKey}; +use raidillon_engine::systems::fps_camera::FPSCameraSystem; const TEST_GLTF: &str = "pink-monkey.gltf"; const MAIN_SCENE_ID: &str = "main_scene"; +#[derive(Default)] +struct InputTestSystem { + mouse_delta: (f64, f64), +} + +impl System for InputTestSystem { + fn handle_event(&mut self, ctx: &mut SystemContext) { + let event2 = ctx.platform_context.current_event.clone(); + match event2 { + Event::DeviceEvent { device_id, event} => { + match event { + MouseMotion { delta } => { + self.mouse_delta.0 += delta.0; + self.mouse_delta.1 += delta.1; + println!("UPDATED mouse delta: ({},{})", self.mouse_delta.0, self.mouse_delta.1); + }, + _ => {} + } + }, + Event::WindowEvent { event, .. } => match event { + WindowEvent::KeyboardInput { device_id, event, is_synthetic} => { + let PhysicalKey::Code(key_code) = event.physical_key else { + return; + }; + + match event.state { + ElementState::Pressed => { + println!("Pressed Key: {:?}", key_code); + } + ElementState::Released => { + println!("Released Key: {:?}", key_code); + } + } + } + _ => {}, + } + _ => {}, + } + } +} + #[derive(Default)] struct UpdateAspectRatioSystem; impl System for UpdateAspectRatioSystem { @@ -42,16 +86,6 @@ impl System for RenderingTestSystem { fn load_world(&mut self, ctx: &mut SystemContext) { self.rotation_speed = std::rc::Rc::new(std::cell::RefCell::new(5.0)); - ctx.scene.world.spawn((Camera { - eye: Vec3::new(0.0, 0.0, 2.0), - center: Vec3::ZERO, - up: Vec3::Y, - fovy: 60_f32.to_radians(), - aspect: ctx.platform_context.frame_width / ctx.platform_context.frame_height, - znear: 0.1, - zfar: 100.0, - },)); - let mut am = ctx.platform_context.asset_manager.borrow_mut(); am.load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); @@ -72,6 +106,20 @@ impl System for RenderingTestSystem { dbg_ui.text(format!("Frame Delta: {}", ctx.platform_context.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", ctx.platform_context.time_ctx.fixed_dt)); dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); + + let input = ctx.input_state.borrow(); + if input.key_held(KeyCode::KeyW) { + dbg_ui.text("W".to_owned()); + } + if input.key_held(KeyCode::KeyA) { + dbg_ui.text("A".to_owned()); + } + if input.key_held(KeyCode::KeyS) { + dbg_ui.text("S".to_owned()); + } + if input.key_held(KeyCode::KeyD) { + dbg_ui.text("D".to_owned()); + } } fn fixed_update(&mut self, ctx: &mut SystemContext) { @@ -85,8 +133,10 @@ impl System for RenderingTestSystem { fn main() { let mut engine = Engine::new(); // Define systems + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); + // engine.system_manager.add::(); // Set up the scene let main_scene = Scene::new( @@ -102,7 +152,7 @@ fn main() { engine, "Raidillon".to_string(), 2560, - 1440, + 1080, ); platform.run() }; diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index f03cd96..9418ab1 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; use std::rc::Rc; +use std::sync::{Arc, Mutex}; use raidillon_platform::{Platform, PlatformContext, TimeContext}; use glium::backend::glutin::Display; use glium::backend::glutin::SimpleWindowBuilder; @@ -8,7 +9,7 @@ use glium::winit::event_loop::EventLoop; use glium::winit::window::Window; use glium::Surface; use crate::system::{RenderingContext, RenderingSystemManager}; -use winit::event::{Event, WindowEvent}; +use winit::event::{DeviceEvent, Event, WindowEvent}; use raidillon_assets::ModelManagerRef; use raidillon_core::engine::EngineTrait; use raidillon_core::time; @@ -17,10 +18,11 @@ use crate::render::debug_ui::ImguiBridge; use crate::render::{BasicMeshRenderingSystem, SkyboxRenderingSystem}; use crate::GliumAssetManager; use glam::Vec3; +use winit::event::DeviceEvent::MouseMotion; pub struct GliumPlatform> { event_loop: EventLoop<()>, - window: Window, + window: Arc>, display: Display, rendering_system_manager: RenderingSystemManager, asset_manager: ModelManagerRef, @@ -45,10 +47,12 @@ impl> Platform for GliumPlatfor let time_cfg = time::Config::default(); let time = time::Time::new(time_cfg); + let window = Arc::new(Mutex::new(window)); + // Install rendering systems in order - rendering_system_manager.add::(&display, &window); - rendering_system_manager.add::(&display, &window); - rendering_system_manager.add::(&display, &window); + rendering_system_manager.add::(&display, window.clone()); + rendering_system_manager.add::(&display, window.clone()); + rendering_system_manager.add::(&display, window.clone()); Self { event_loop, @@ -62,20 +66,24 @@ impl> Platform for GliumPlatfor } fn run(mut self) { - let (w, h): (u32, u32) = self.window.inner_size().into(); + let (w, h): (u32, u32) = match self.window.lock() { + Ok(window) => window.inner_size().into(), + Err(_) => (0, 0), // fallback values + }; let ctx = PlatformContext { current_event: Event::AboutToWait, asset_manager: self.asset_manager.clone(), frame_width: w as f32, frame_height: h as f32, time_ctx: self.construct_time_ctx(), + window: self.window.clone(), }; self.engine.initialize(ctx.clone()); let _ = &self.event_loop.run(move |event, el| { self.rendering_system_manager .systems .values_mut() - .for_each(|system| system.handle_event(&mut self.window, event.clone())); + .for_each(|system| system.handle_event(self.window.clone(), event.clone())); let mut ctx2 = ctx.clone(); ctx2.current_event = event.clone(); @@ -95,7 +103,7 @@ impl> Platform for GliumPlatfor scene: scene_mut, target: &mut target, asset_manager: self.asset_manager.clone(), - window: &mut self.window, + window: self.window.clone(), debug_ui_buffer, env_light_dir: Vec3::new(0.0, -1.0, 0.0), }; @@ -129,8 +137,8 @@ impl> Platform for GliumPlatfor self.rendering_system_manager .systems .values_mut() - .for_each(|system| system.prepare_frame(&mut self.window)); - self.window.request_redraw(); + .for_each(|system| system.prepare_frame(self.window.clone())); + self.window.lock().unwrap().request_redraw(); } _ => {}, } diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index 8551ecd..663b2dd 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -1,4 +1,5 @@ use std::any::Any; +use std::sync::{Arc, Mutex}; use glium::{uniform, Display, Program, Surface}; use glium::glutin::surface::WindowSurface; use glium::texture::{RawImage2d, SrgbTexture2d}; @@ -20,7 +21,7 @@ pub struct BasicMeshRenderingSystem { } impl RenderingSystem for BasicMeshRenderingSystem { - fn initialize(display: &Display, _window: &glium::winit::window::Window) -> Self { + fn initialize(display: &Display, _window: Arc>) -> Self { const VERT_SRC: &str = include_shader!("gl_textured.vert"); const FRAG_SRC: &str = include_shader!("gl_textured.frag"); diff --git a/glium_platform/src/render/debug_ui.rs b/glium_platform/src/render/debug_ui.rs index ff29fe1..be4ff0d 100644 --- a/glium_platform/src/render/debug_ui.rs +++ b/glium_platform/src/render/debug_ui.rs @@ -1,3 +1,4 @@ +use std::sync::{Arc, Mutex}; use std::time::Instant; use glium::Display; use glium::glutin::surface::WindowSurface; @@ -19,17 +20,19 @@ pub struct ImguiBridge { } impl RenderingSystem for ImguiBridge { - fn handle_event(&mut self, window: &mut Window, event: Event<()>) { - self.platform.handle_event(self.imgui.io_mut(), window, &event); + fn handle_event(&mut self, window: Arc>, event: Event<()>) { + let window = window.lock().unwrap(); + self.platform.handle_event(self.imgui.io_mut(), &*window, &event); } - fn prepare_frame(&mut self, window: &mut Window) { + fn prepare_frame(&mut self, window: Arc>) { self.rendered_this_frame = false; let now = Instant::now(); self.imgui.io_mut().update_delta_time(now - self.last_frame); self.last_frame = now; + let window = window.lock().unwrap(); self.platform - .prepare_frame(self.imgui.io_mut(), window) + .prepare_frame(self.imgui.io_mut(), &*window) .expect("Failed to prepare frame"); } @@ -40,7 +43,10 @@ impl RenderingSystem for ImguiBridge { let ui = self.imgui.frame(); ctx.debug_ui_buffer.borrow().write_buffer(&ui); - self.platform.prepare_render(&ui, ctx.window); + { + let window = ctx.window.lock().unwrap(); + self.platform.prepare_render(&ui, &*window); + } let draw_data = self.imgui.render(); if draw_data.total_vtx_count == 0 && draw_data.total_idx_count == 0 { return; @@ -49,11 +55,12 @@ impl RenderingSystem for ImguiBridge { self.renderer.render(ctx.target, draw_data).expect("imgui rendering failed"); } - fn initialize(display: &Display, window: &Window) -> Self { + fn initialize(display: &Display, window: Arc>) -> Self { let mut imgui = ImguiContext::create(); imgui.set_ini_filename(None); let mut platform = WinitPlatform::new(&mut imgui); - platform.attach_window(imgui.io_mut(), window, HiDpiMode::Default); + let window = window.lock().unwrap(); + platform.attach_window(imgui.io_mut(), &*window, HiDpiMode::Default); imgui.fonts().add_font(&[imgui::FontSource::DefaultFontData { config: None }]); let renderer = ImguiGliumRenderer::new(&mut imgui, display).unwrap(); diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs index 49ea594..ac22210 100644 --- a/glium_platform/src/render/skybox.rs +++ b/glium_platform/src/render/skybox.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::cell::RefCell; +use std::sync::{Arc, Mutex}; use glium::{Display, Program, Surface, VertexBuffer, IndexBuffer, implement_vertex}; use glium::glutin::surface::WindowSurface; use glium::index::PrimitiveType; @@ -108,7 +109,7 @@ impl SkyboxRenderingSystem { } impl RenderingSystem for SkyboxRenderingSystem { - fn initialize(display: &Display, _window: &glium::winit::window::Window) -> Self { + fn initialize(display: &Display, _window: Arc>) -> Self { const VERT_SRC: &str = include_shader!("skybox.vert"); const FRAG_SRC: &str = include_shader!("skybox.frag"); let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap(); diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 891fec0..3460bc3 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -1,6 +1,7 @@ use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; +use std::sync::{Arc, Mutex}; use indexmap::IndexMap; use glium::{Display, Frame}; use glium::glutin::surface::WindowSurface; @@ -12,7 +13,7 @@ use glam::Vec3; pub struct RenderingContext<'a> { pub scene: &'a Scene, pub target: &'a mut Frame, - pub window: &'a mut glium::winit::window::Window, + pub window: Arc>, pub asset_manager: ModelManagerRef, pub debug_ui_buffer: Rc>, pub env_light_dir: Vec3, @@ -23,13 +24,13 @@ pub struct RenderingContext<'a> { pub trait RenderingSystem { fn handle_event( &mut self, - _window: &mut glium::winit::window::Window, + _window: Arc>, _event: winit::event::Event<()>, ) { } - fn prepare_frame(&mut self, _window: &mut glium::winit::window::Window) {} + fn prepare_frame(&mut self, _window: Arc>) {} fn render(&mut self, ctx: &mut RenderingContext); - fn initialize(display: &Display, window: &glium::winit::window::Window) -> Self + fn initialize(display: &Display, window: Arc>) -> Self where Self: Sized; } @@ -45,7 +46,7 @@ impl RenderingSystemManager { } } - pub fn add(&mut self, display: &Display, window: &glium::winit::window::Window) + pub fn add(&mut self, display: &Display, window: Arc>) where R: RenderingSystem + 'static, { diff --git a/platform/src/context.rs b/platform/src/context.rs index bea1cb1..892b69e 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -1,3 +1,4 @@ +use std::sync::{Arc, Mutex}; use winit::event::Event; use raidillon_assets::ModelManagerRef; @@ -8,6 +9,7 @@ pub struct PlatformContext { pub frame_width: f32, pub frame_height: f32, pub time_ctx: TimeContext, + pub window: Arc>, } #[derive(Clone)] From 7732280229cedbeaa8771c0b487a87eb51cec823 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 29 Sep 2025 16:04:40 +0300 Subject: [PATCH 05/38] Add WASD keys to FPSDebugCamera system, rename to FPSDebugCameraSystem --- engine/src/systems/fps_camera.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index cbe14b6..3cc0a3d 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -2,12 +2,12 @@ use crate::system::{System, SystemContext}; use glam::{Quat, Vec3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::{ElementState, Event, MouseButton, WindowEvent}; -use winit::keyboard::PhysicalKey; +use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_assets::model_path; use raidillon_platform::Camera; -pub struct FPSCameraSystem { +pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), mouse_enabled: bool, position: Vec3, @@ -17,7 +17,7 @@ pub struct FPSCameraSystem { sensitivity: f32, } -impl Default for FPSCameraSystem { +impl Default for FPSDebugCameraSystem { fn default() -> Self { Self { mouse_delta: Default::default(), @@ -31,7 +31,7 @@ impl Default for FPSCameraSystem { } } -impl System for FPSCameraSystem { +impl System for FPSDebugCameraSystem { fn load_world(&mut self, ctx: &mut SystemContext) { ctx.scene.world.spawn((Camera { eye: Vec3::new(0.0, 0.0, 2.0), @@ -93,15 +93,32 @@ impl System for FPSCameraSystem { self.pitch = self.pitch.clamp(-89.0, 89.0); } + let front = self.front(); + let right_vec = front.cross(Vec3::Y).normalize(); + let input = ctx.input_state.borrow_mut(); + + if input.key_held(KeyCode::KeyW) { + self.position += front * ctx.platform_context.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.position -= front * ctx.platform_context.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.position -= right_vec * ctx.platform_context.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.position += right_vec * ctx.platform_context.time_ctx.frame_dt * self.speed; + } + ctx.scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { camera.eye = self.position; - camera.center = self.position + self.front(); + camera.center = self.position + front; }); self.mouse_delta = (0.0, 0.0); } } -impl FPSCameraSystem { +impl FPSDebugCameraSystem { pub fn front(&self) -> Vec3 { let yaw_rad = self.yaw.to_radians(); let pitch_rad = self.pitch.to_radians(); From 6e42d94b4416c62fdfed4a11056fc2b215b1c033 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 29 Sep 2025 16:05:54 +0300 Subject: [PATCH 06/38] Remove the older debug system, rename FPSDebugCameraSystem --- game/src/main.rs | 59 ++---------------------------------------------- 1 file changed, 2 insertions(+), 57 deletions(-) diff --git a/game/src/main.rs b/game/src/main.rs index b7c45e0..d5a826b 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -13,53 +13,12 @@ use raidillon_glium::GliumPlatform; use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; -use raidillon_engine::systems::fps_camera::FPSCameraSystem; +use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; const TEST_GLTF: &str = "pink-monkey.gltf"; const MAIN_SCENE_ID: &str = "main_scene"; -#[derive(Default)] -struct InputTestSystem { - mouse_delta: (f64, f64), -} - -impl System for InputTestSystem { - fn handle_event(&mut self, ctx: &mut SystemContext) { - let event2 = ctx.platform_context.current_event.clone(); - match event2 { - Event::DeviceEvent { device_id, event} => { - match event { - MouseMotion { delta } => { - self.mouse_delta.0 += delta.0; - self.mouse_delta.1 += delta.1; - println!("UPDATED mouse delta: ({},{})", self.mouse_delta.0, self.mouse_delta.1); - }, - _ => {} - } - }, - Event::WindowEvent { event, .. } => match event { - WindowEvent::KeyboardInput { device_id, event, is_synthetic} => { - let PhysicalKey::Code(key_code) = event.physical_key else { - return; - }; - - match event.state { - ElementState::Pressed => { - println!("Pressed Key: {:?}", key_code); - } - ElementState::Released => { - println!("Released Key: {:?}", key_code); - } - } - } - _ => {}, - } - _ => {}, - } - } -} - #[derive(Default)] struct UpdateAspectRatioSystem; impl System for UpdateAspectRatioSystem { @@ -106,20 +65,6 @@ impl System for RenderingTestSystem { dbg_ui.text(format!("Frame Delta: {}", ctx.platform_context.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", ctx.platform_context.time_ctx.fixed_dt)); dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); - - let input = ctx.input_state.borrow(); - if input.key_held(KeyCode::KeyW) { - dbg_ui.text("W".to_owned()); - } - if input.key_held(KeyCode::KeyA) { - dbg_ui.text("A".to_owned()); - } - if input.key_held(KeyCode::KeyS) { - dbg_ui.text("S".to_owned()); - } - if input.key_held(KeyCode::KeyD) { - dbg_ui.text("D".to_owned()); - } } fn fixed_update(&mut self, ctx: &mut SystemContext) { @@ -133,7 +78,7 @@ impl System for RenderingTestSystem { fn main() { let mut engine = Engine::new(); // Define systems - engine.system_manager.add::(); + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); // engine.system_manager.add::(); From ef055a1bda1f627c8e28fc51196e425e99980c53 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 15 Oct 2025 22:33:04 +0300 Subject: [PATCH 07/38] Replace contexts with resources - Implements a new macro to generate code for a new structure: TypeMap - TypeMaps are wrappers for HashMaps that use TypeIDs as keys. - Refactor the entire codebase to use the new resource structures. - This commit is the first step towards getting rid of "god context objects everywhere". --- Cargo.toml | 2 +- core/src/engine.rs | 5 +- core/src/lib.rs | 3 +- core/src/scene.rs | 16 +++- core/src/utils/managers.rs | 44 ++++++++++ core/src/utils/mod.rs | 2 + core/src/utils/typemap.rs | 122 ++++++++++++++++++++++++++ engine/src/engine.rs | 85 ++++++++---------- engine/src/lib.rs | 2 + engine/src/resources.rs | 3 + engine/src/system.rs | 9 +- engine/src/systems/fps_camera.rs | 35 +++++--- game/src/main.rs | 37 ++++---- glium_platform/src/platform.rs | 7 +- glium_platform/src/render/debug_ui.rs | 2 +- glium_platform/src/system.rs | 6 +- 16 files changed, 287 insertions(+), 93 deletions(-) create mode 100644 core/src/utils/managers.rs create mode 100644 core/src/utils/mod.rs create mode 100644 core/src/utils/typemap.rs create mode 100644 engine/src/resources.rs diff --git a/Cargo.toml b/Cargo.toml index 102844c..23332d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ members = [ "asset", "game", "ecs", - "engine" + "engine", ] diff --git a/core/src/engine.rs b/core/src/engine.rs index 80d2a69..b70a945 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -11,7 +11,8 @@ pub trait EngineTrait { fn fixed_update(&mut self, platform_context: Self::PlatformCtx); fn handle_event(&mut self, platform_context: Self::PlatformCtx); fn current_scene_mut(&mut self) -> &mut Scene; - fn get_debug_ui_buffer(&self) -> Rc>; + fn current_scene(&self) -> &Scene; + fn get_debug_ui_buffer(&self) -> &DebugUIBuffer; fn reset_debug_ui_buffer(&mut self); - fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Scene, Rc>); + // fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Self::Scene, &DebugUIBuffer); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 4b7d531..238d5a6 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,6 +1,7 @@ pub mod engine; -pub mod scene; pub mod debug_ui; pub mod time; +pub mod utils; +pub mod scene; pub use debug_ui::*; diff --git a/core/src/scene.rs b/core/src/scene.rs index 9d1a7d4..1a075bf 100644 --- a/core/src/scene.rs +++ b/core/src/scene.rs @@ -1,19 +1,31 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; +use crate::{define_typemap, DebugUIBuffer}; pub struct Scene { pub title: String, pub world: hecs::World, pub skybox_texture_path: Option, + pub resources: SceneResources, } +define_typemap!(SceneResources,); + impl Scene { pub fn new(title: String, skybox_texture_path: Option) -> Self { - Self { + let mut s = Self { title, world: hecs::World::new(), skybox_texture_path, - } + resources: SceneResources::new(), + }; + s.load_default_resources(); + s + } + + pub fn load_default_resources(&mut self) { + let dbg = DebugUIBuffer::new(); + self.resources.insert(dbg); } } diff --git a/core/src/utils/managers.rs b/core/src/utils/managers.rs new file mode 100644 index 0000000..aac2ce7 --- /dev/null +++ b/core/src/utils/managers.rs @@ -0,0 +1,44 @@ +/// Unused as of now. +#[macro_export] +macro_rules! create_manager { + ($manager_name:ident, $trait_name:ident) => { + pub struct $manager_name { + systems: ::indexmap::IndexMap<::std::any::TypeId, Box>, + } + + impl $manager_name { + pub fn new() -> Self { + Self { + systems: ::indexmap::IndexMap::default(), + } + } + + pub fn add(&mut self) { + self.systems + .insert(::std::any::TypeId::of::(), Box::new(S::default())); + } + + pub fn remove(&mut self) { + self.systems.shift_remove(&::std::any::TypeId::of::()); + } + + pub fn for_each_value(&self, mut f: F) + where + F: FnMut(&dyn $trait_name), + { + for value in self.systems.values() { + f(value.as_ref()); + } + } + + pub fn for_each_value_mut(&mut self, mut f: F) + where + F: FnMut(&mut dyn $trait_name), + { + for value in self.systems.values_mut() { + f(value.as_mut()); + } + } + } + }; +} diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs new file mode 100644 index 0000000..4a5eaf5 --- /dev/null +++ b/core/src/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod typemap; +mod managers; diff --git a/core/src/utils/typemap.rs b/core/src/utils/typemap.rs new file mode 100644 index 0000000..88d3dc5 --- /dev/null +++ b/core/src/utils/typemap.rs @@ -0,0 +1,122 @@ +#[macro_export] +macro_rules! define_typemap { + ($name:ident, $($trait_bound:tt)*) => { + pub struct $name { + map: std::collections::HashMap>, + } + + impl $name { + pub fn new() -> Self { + Self { + map: std::collections::HashMap::new(), + } + } + + pub fn insert(&mut self, value: T) -> Option> + where + T: std::any::Any + 'static + $($trait_bound)*, + { + let type_id = std::any::TypeId::of::(); + self.map + .insert(type_id, Box::new(value)) + .and_then(|boxed| boxed.downcast().ok()) + } + + pub fn get(&self) -> Option<&T> + where + T: std::any::Any + 'static + $($trait_bound)*, + { + self.map + .get(&std::any::TypeId::of::()) + .and_then(|any| any.downcast_ref::()) + } + + pub fn get_mut(&mut self) -> Option<&mut T> + where + T: std::any::Any + 'static + $($trait_bound)*, + { + self.map + .get_mut(&std::any::TypeId::of::()) + .and_then(|any| any.downcast_mut::()) + } + + pub fn remove(&mut self) -> Option> + where + T: std::any::Any + 'static + $($trait_bound)*, + { + self.map + .remove(&std::any::TypeId::of::()) + .and_then(|boxed| boxed.downcast().ok()) + } + + pub fn len(&self) -> usize { + self.map.len() + } + + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + pub fn clear(&mut self) { + self.map.clear(); + } + + pub fn contains(&self) -> bool + where + T: std::any::Any + 'static + $($trait_bound)*, + { + self.map.contains_key(&std::any::TypeId::of::()) + } + } + + impl Default for $name { + fn default() -> Self { + Self::new() + } + } + }; +} + +// pub struct TypeMap { +// map: HashMap>, +// } +// +// impl TypeMap { +// pub fn new() -> Self { +// Self { +// map: HashMap::new(), +// } +// } +// +// pub fn insert(&mut self, value: T) -> Option> { +// let type_id = TypeId::of::(); +// self.map +// .insert(type_id, Box::new(value)) +// .and_then(|boxed| boxed.downcast().ok()) +// } +// +// pub fn get(&self) -> Option<&T> +// where +// T: Any + 'static, +// { +// self.map.get(&TypeId::of::()) +// .and_then(|any| any.downcast_ref::()) +// } +// +// pub fn get_mut(&mut self) -> Option<&mut T> +// where +// T: Any + 'static, +// { +// self.map.get_mut(&TypeId::of::()).and_then(|any| any.downcast_mut::()) +// } +// +// pub fn remove(&mut self) -> Option> { +// unimplemented!() +// } +// } +// +// impl Default for TypeMap { +// fn default() -> Self { +// Self::new() +// } +// } diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 68f17e0..7c366dc 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -3,15 +3,22 @@ use std::rc::Rc; use raidillon_core::scene::{Scene, SceneManager}; use crate::system::{SystemContext, SystemManager}; use raidillon_platform::PlatformContext; -use raidillon_core::DebugUIBuffer; +use raidillon_core::{define_typemap, DebugUIBuffer}; use raidillon_core::engine::EngineTrait; use crate::input::InputState; +use crate::resources::EngineResources; pub struct Engine { pub scene_manager: SceneManager, pub system_manager: SystemManager, - debug_ui_buffer: Rc>, - input_state: Rc>, + pub resources: EngineResources, +} + +impl Engine { + fn load_default_resources(&mut self) { + let input = InputState::default(); + self.resources.insert(input); + } } impl EngineTrait for Engine { @@ -19,12 +26,13 @@ impl EngineTrait for Engine { fn new() -> Self { let scene_manager = SceneManager::new(); let system_manager = SystemManager::new(); - Self { + let mut s = Self { scene_manager, system_manager, - debug_ui_buffer: Rc::new(RefCell::new(DebugUIBuffer::new())), - input_state: Default::default(), - } + resources: EngineResources::new(), + }; + s.load_default_resources(); + s } /// Initialize systems, load the world. @@ -33,60 +41,35 @@ impl EngineTrait for Engine { for system in self.system_manager.systems.values_mut() { system.initialize(); } - - let mut ctx = SystemContext { - scene: self.scene_manager.current_mut(), - platform_context, - debug_ui_buffer: self.debug_ui_buffer.clone(), - input_state: self.input_state.clone(), - }; - + self.resources.insert(platform_context); // Engine Loading Stage 2: load world for system in self.system_manager.systems.values_mut() { - system.load_world(&mut ctx); + system.load_world(&mut self.resources, &mut self.scene_manager.current_mut()); } } /// Update the engine fn frame_update(&mut self, platform_context: PlatformContext) { - self.debug_ui_buffer.borrow_mut().reset_buffer(); - let mut ctx = SystemContext { - scene: self.scene_manager.current_mut(), - platform_context, - debug_ui_buffer: self.debug_ui_buffer.clone(), - input_state: self.input_state.clone(), - }; + self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); + self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { - system.frame_update(&mut ctx); + system.frame_update(&mut self.resources, &mut self.scene_manager.current_mut()); } } fn fixed_update(&mut self, platform_context: PlatformContext) { - let mut ctx = SystemContext { - scene: self.scene_manager.current_mut(), - platform_context, - debug_ui_buffer: self.debug_ui_buffer.clone(), - input_state: self.input_state.clone(), - }; - + self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { - system.fixed_update(&mut ctx); + system.fixed_update(&mut self.resources, &mut self.scene_manager.current_mut()); } } fn handle_event(&mut self, platform_context: PlatformContext) { - self.input_state.borrow_mut().handle_event(&platform_context.current_event); - - let mut ctx = SystemContext { - scene: self.scene_manager.current_mut(), - platform_context, - debug_ui_buffer: self.debug_ui_buffer.clone(), - input_state: self.input_state.clone(), - }; - + // self.input_state.borrow_mut().handle_event(&platform_context.current_event); + self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { - system.handle_event(&mut ctx); + system.handle_event(&mut self.resources, &mut self.scene_manager.current_mut()); } } @@ -100,16 +83,20 @@ impl EngineTrait for Engine { self.scene_manager.current_mut() } - fn get_debug_ui_buffer(&self) -> Rc> { - self.debug_ui_buffer.clone() + fn current_scene(&self) -> &Scene { + self.scene_manager.current() + } + + fn get_debug_ui_buffer(&self) -> &DebugUIBuffer { + self.scene_manager.current().resources.get::().unwrap() } fn reset_debug_ui_buffer(&mut self) { - self.debug_ui_buffer.borrow_mut().reset_buffer(); + self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); } - fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Scene, Rc>) { - let (sm, dub) = (&mut self.scene_manager, &mut self.debug_ui_buffer); - (sm.current_mut(), dub.clone()) - } + // fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Scene, &DebugUIBuffer) { + // let (sm, dub) = (&mut self.scene_manager, self.scene_manager.current().resources.get::().unwrap()); + // (sm.current_mut(), dub) + // } } diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 18ce5fd..9a87556 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -2,5 +2,7 @@ pub mod engine; pub mod system; mod input; pub mod systems; +mod resources; pub use crate::engine::Engine; +pub use crate::resources::EngineResources; diff --git a/engine/src/resources.rs b/engine/src/resources.rs new file mode 100644 index 0000000..cd4be0b --- /dev/null +++ b/engine/src/resources.rs @@ -0,0 +1,3 @@ +use raidillon_core::define_typemap; + +define_typemap!(EngineResources,); diff --git a/engine/src/system.rs b/engine/src/system.rs index b863a75..55cccb1 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -6,6 +6,7 @@ use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; use crate::input::InputState; +use crate::resources::EngineResources; pub struct SystemContext<'a> { pub scene: &'a mut Scene, @@ -18,10 +19,10 @@ pub trait System { /// Initialize the system. fn initialize(&mut self) {} /// Spawn the first entities of the world. - fn load_world(&mut self, _ctx: &mut SystemContext) {} - fn handle_event(&mut self, _ctx: &mut SystemContext) {} - fn fixed_update(&mut self, _ctx: &mut SystemContext) {} - fn frame_update(&mut self, _ctx: &mut SystemContext) {} + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) {} + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) {} + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) {} + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) {} } pub struct SystemManager { diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index 3cc0a3d..6bd99f7 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -5,7 +5,10 @@ use winit::event::{ElementState, Event, MouseButton, WindowEvent}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_assets::model_path; -use raidillon_platform::Camera; +use raidillon_platform::{Camera, PlatformContext}; +use crate::input::InputState; +use crate::resources::EngineResources; +use raidillon_core::scene::Scene; pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), @@ -32,20 +35,22 @@ impl Default for FPSDebugCameraSystem { } impl System for FPSDebugCameraSystem { - fn load_world(&mut self, ctx: &mut SystemContext) { - ctx.scene.world.spawn((Camera { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + scene.world.spawn((Camera { eye: Vec3::new(0.0, 0.0, 2.0), center: Vec3::ZERO, up: Vec3::Y, fovy: 60_f32.to_radians(), - aspect: ctx.platform_context.frame_width / ctx.platform_context.frame_height, + aspect: pctx.frame_width / pctx.frame_height, znear: 0.1, zfar: 100.0, },)); } - fn handle_event(&mut self, ctx: &mut SystemContext) { - let event2 = ctx.platform_context.current_event.clone(); + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + let event2 = pctx.current_event.clone(); match event2 { Event::DeviceEvent { device_id, event} => { match event { @@ -60,7 +65,7 @@ impl System for FPSDebugCameraSystem { WindowEvent::MouseInput { state, button, .. } => { if button == MouseButton::Right { // blood and tear - let window = ctx.platform_context.window.lock().unwrap(); + let window = pctx.window.lock().unwrap(); match state { ElementState::Pressed => { if window @@ -86,7 +91,10 @@ impl System for FPSDebugCameraSystem { } } - fn frame_update(&mut self, ctx: &mut SystemContext) { + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + let input = res.get::().unwrap(); + if self.mouse_enabled { self.yaw += self.mouse_delta.0 as f32 * self.sensitivity; self.pitch -= self.mouse_delta.1 as f32 * self.sensitivity; @@ -95,22 +103,21 @@ impl System for FPSDebugCameraSystem { let front = self.front(); let right_vec = front.cross(Vec3::Y).normalize(); - let input = ctx.input_state.borrow_mut(); if input.key_held(KeyCode::KeyW) { - self.position += front * ctx.platform_context.time_ctx.frame_dt * self.speed; + self.position += front * pctx.time_ctx.frame_dt * self.speed; } if input.key_held(KeyCode::KeyS) { - self.position -= front * ctx.platform_context.time_ctx.frame_dt * self.speed; + self.position -= front * pctx.time_ctx.frame_dt * self.speed; } if input.key_held(KeyCode::KeyA) { - self.position -= right_vec * ctx.platform_context.time_ctx.frame_dt * self.speed; + self.position -= right_vec * pctx.time_ctx.frame_dt * self.speed; } if input.key_held(KeyCode::KeyD) { - self.position += right_vec * ctx.platform_context.time_ctx.frame_dt * self.speed; + self.position += right_vec * pctx.time_ctx.frame_dt * self.speed; } - ctx.scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { camera.eye = self.position; camera.center = self.position + front; }); diff --git a/game/src/main.rs b/game/src/main.rs index d5a826b..acedefd 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,8 +1,8 @@ use std::fmt::format; use glam::{Quat, Vec3}; -use raidillon_engine::{Engine, system::System}; +use raidillon_engine::{Engine, system::System, EngineResources}; use raidillon_engine::system::SystemContext; -use raidillon_platform::{Platform, Camera}; +use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; use raidillon_ecs::components::ModelHandle; @@ -13,6 +13,7 @@ use raidillon_glium::GliumPlatform; use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; +use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; const TEST_GLTF: &str = "pink-monkey.gltf"; @@ -22,11 +23,13 @@ const MAIN_SCENE_ID: &str = "main_scene"; #[derive(Default)] struct UpdateAspectRatioSystem; impl System for UpdateAspectRatioSystem { - fn handle_event(&mut self, ctx: &mut SystemContext) { + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + if let Event::WindowEvent { event: WindowEvent::Resized(sz), .. } = - &ctx.platform_context.current_event + pctx.current_event { - ctx.scene.world + scene.world .query_mut::<&mut Camera>() .into_iter() .for_each(|(_, cam)| { @@ -42,14 +45,16 @@ struct RenderingTestSystem { } impl System for RenderingTestSystem { - fn load_world(&mut self, ctx: &mut SystemContext) { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + self.rotation_speed = std::rc::Rc::new(std::cell::RefCell::new(5.0)); - let mut am = ctx.platform_context.asset_manager.borrow_mut(); + let mut am = pctx.asset_manager.borrow_mut(); am.load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); - ctx.scene.world.spawn(( + scene.world.spawn(( Transform { translation: Vec3::new(0.0, 0.0, 0.0), rotation: Quat::IDENTITY, @@ -59,17 +64,19 @@ impl System for RenderingTestSystem { )); } - fn frame_update(&mut self, ctx: &mut SystemContext) { - let mut dbg_ui = ctx.debug_ui_buffer.borrow_mut(); + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap().clone(); + let dbg_ui = scene.resources.get_mut::().unwrap(); dbg_ui.text("Hello World!".to_owned()); - dbg_ui.text(format!("Frame Delta: {}", ctx.platform_context.time_ctx.frame_dt)); - dbg_ui.text(format!("Fixed Delta: {}", ctx.platform_context.time_ctx.fixed_dt)); + dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); + dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); } - fn fixed_update(&mut self, ctx: &mut SystemContext) { - ctx.scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { - t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * ctx.platform_context.time_ctx.fixed_dt); + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().unwrap(); + scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { + t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * pctx.time_ctx.fixed_dt); }); } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 9418ab1..db8e56e 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -98,9 +98,12 @@ impl> Platform for GliumPlatfor WindowEvent::RedrawRequested => { let mut target = self.display.draw(); target.clear_color_and_depth((0.1, 0.1, 0.15, 1.0), 1.0); - let (scene_mut, debug_ui_buffer) = self.engine.scene_and_debug_ui_buffer_mut(); + let (scene, debug_ui_buffer) = ( + self.engine.current_scene(), + self.engine.get_debug_ui_buffer(), + ); let mut context = RenderingContext { - scene: scene_mut, + scene, target: &mut target, asset_manager: self.asset_manager.clone(), window: self.window.clone(), diff --git a/glium_platform/src/render/debug_ui.rs b/glium_platform/src/render/debug_ui.rs index be4ff0d..ac4a06e 100644 --- a/glium_platform/src/render/debug_ui.rs +++ b/glium_platform/src/render/debug_ui.rs @@ -41,7 +41,7 @@ impl RenderingSystem for ImguiBridge { self.rendered_this_frame = true; let ui = self.imgui.frame(); - ctx.debug_ui_buffer.borrow().write_buffer(&ui); + ctx.debug_ui_buffer.write_buffer(&ui); { let window = ctx.window.lock().unwrap(); diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 3460bc3..51a292f 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -6,7 +6,7 @@ use indexmap::IndexMap; use glium::{Display, Frame}; use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; -use raidillon_core::DebugUIBuffer; +use raidillon_core::{define_typemap, DebugUIBuffer}; use raidillon_core::scene::Scene; use glam::Vec3; @@ -15,7 +15,7 @@ pub struct RenderingContext<'a> { pub target: &'a mut Frame, pub window: Arc>, pub asset_manager: ModelManagerRef, - pub debug_ui_buffer: Rc>, + pub debug_ui_buffer: &'a DebugUIBuffer, pub env_light_dir: Vec3, } @@ -35,6 +35,8 @@ pub trait RenderingSystem { Self: Sized; } +// define_typemap!(RenderingSystemManager, RenderingSystem); + pub struct RenderingSystemManager { pub systems: IndexMap>, } From b86bbdd237706dfea4c9e5db182d23d83daab8ed Mon Sep 17 00:00:00 2001 From: reo Date: Thu, 16 Oct 2025 16:11:53 +0300 Subject: [PATCH 08/38] fix spacing --- game/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/game/src/main.rs b/game/src/main.rs index acedefd..4e0ee6e 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -65,8 +65,9 @@ impl System for RenderingTestSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap().clone(); + let pctx = res.get::().unwrap(); let dbg_ui = scene.resources.get_mut::().unwrap(); + dbg_ui.text("Hello World!".to_owned()); dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); @@ -75,6 +76,7 @@ impl System for RenderingTestSystem { fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().unwrap(); + scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * pctx.time_ctx.fixed_dt); }); From 1a48e58a1cfca6baea4cd60437abfca69fe0d6dd Mon Sep 17 00:00:00 2001 From: reo Date: Thu, 16 Oct 2025 16:12:18 +0300 Subject: [PATCH 09/38] Implement get_many. I believe the repeated code can be generated with procedural macros, but I don't need that right now. I'll do it if I ever end up needing this. Right now I need to start making the game itself more than anything. --- core/src/utils/typemap.rs | 436 +++++++++++++++++++++++++++++-- engine/src/systems/fps_camera.rs | 3 +- 2 files changed, 408 insertions(+), 31 deletions(-) diff --git a/core/src/utils/typemap.rs b/core/src/utils/typemap.rs index 88d3dc5..bd28a66 100644 --- a/core/src/utils/typemap.rs +++ b/core/src/utils/typemap.rs @@ -7,9 +7,7 @@ macro_rules! define_typemap { impl $name { pub fn new() -> Self { - Self { - map: std::collections::HashMap::new(), - } + Self { map: std::collections::HashMap::new() } } pub fn insert(&mut self, value: T) -> Option> @@ -17,49 +15,33 @@ macro_rules! define_typemap { T: std::any::Any + 'static + $($trait_bound)*, { let type_id = std::any::TypeId::of::(); - self.map - .insert(type_id, Box::new(value)) - .and_then(|boxed| boxed.downcast().ok()) + self.map.insert(type_id, Box::new(value)).and_then(|b| b.downcast().ok()) } pub fn get(&self) -> Option<&T> where T: std::any::Any + 'static + $($trait_bound)*, { - self.map - .get(&std::any::TypeId::of::()) - .and_then(|any| any.downcast_ref::()) + self.map.get(&std::any::TypeId::of::())?.downcast_ref::() } pub fn get_mut(&mut self) -> Option<&mut T> where T: std::any::Any + 'static + $($trait_bound)*, { - self.map - .get_mut(&std::any::TypeId::of::()) - .and_then(|any| any.downcast_mut::()) + self.map.get_mut(&std::any::TypeId::of::())?.downcast_mut::() } pub fn remove(&mut self) -> Option> where T: std::any::Any + 'static + $($trait_bound)*, { - self.map - .remove(&std::any::TypeId::of::()) - .and_then(|boxed| boxed.downcast().ok()) + self.map.remove(&std::any::TypeId::of::())?.downcast().ok() } - pub fn len(&self) -> usize { - self.map.len() - } - - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - pub fn clear(&mut self) { - self.map.clear(); - } + pub fn len(&self) -> usize { self.map.len() } + pub fn is_empty(&self) -> bool { self.map.is_empty() } + pub fn clear(&mut self) { self.map.clear(); } pub fn contains(&self) -> bool where @@ -67,11 +49,407 @@ macro_rules! define_typemap { { self.map.contains_key(&std::any::TypeId::of::()) } + + // --- tuple-based multi-get API --- + pub fn get_many<'a, T>(&'a self) -> Option<::Output<'a>> + where + T: __tm_get::GetTuple, + { + ::get_from(&self.map) + } + + pub fn get_many_mut<'a, T>(&'a mut self) -> Option<::Output<'a>> + where + T: __tm_get::GetTupleMut, + { + ::get_from_mut(&mut self.map) + } } - impl Default for $name { - fn default() -> Self { - Self::new() + impl Default for $name { fn default() -> Self { Self::new() } } + + // Put helper traits/impls in a private module to avoid name clashes. + mod __tm_get { + use std::any::{Any, TypeId}; + use std::collections::HashMap; + + pub trait GetTuple { + type Output<'a>; + fn get_from<'a>(map: &'a HashMap>) -> Option>; + } + + pub trait GetTupleMut { + type Output<'a>; + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option>; + } + + // Manual implementations for arities 1..=8. + impl GetTuple for (A,) + where + A: Any + 'static, + { + type Output<'a> = (&'a A,); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a,)) + } + } + + impl GetTupleMut for (A,) + where + A: Any + 'static, + { + type Output<'a> = (&'a mut A,); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + Some((a,)) + } + } + } + + impl GetTuple for (A, B) + where + A: Any + 'static, + B: Any + 'static, + { + type Output<'a> = (&'a A, &'a B); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::()]; + if ids[0] == ids[1] { return None; } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b)) + } + } + + impl GetTupleMut for (A, B) + where + A: Any + 'static, + B: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::()]; + if ids[0] == ids[1] { return None; } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + Some((a, b)) + } + } + } + + impl GetTuple for (A, B, C) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; + if ids[0] == ids[1] || ids[0] == ids[2] || ids[1] == ids[2] { return None; } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c)) + } + } + + impl GetTupleMut for (A, B, C) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; + if ids[0] == ids[1] || ids[0] == ids[2] || ids[1] == ids[2] { return None; } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + Some((a, b, c)) + } + } + } + + impl GetTuple for (A, B, C, D) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d)) + } + } + + impl GetTupleMut for (A, B, C, D) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + Some((a, b, c, d)) + } + } + } + + impl GetTuple for (A, B, C, D, E) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D, &'a E); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + let e = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d, e)) + } + } + + impl GetTupleMut for (A, B, C, D, E) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D, &'a mut E); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + let e = (&mut *ptr_e).downcast_mut::()?; + Some((a, b, c, d, e)) + } + } + } + + impl GetTuple for (A, B, C, D, E, F) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D, &'a E, &'a F); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + let e = map.get(&TypeId::of::())?.downcast_ref::()?; + let f = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d, e, f)) + } + } + + impl GetTupleMut for (A, B, C, D, E, F) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D, &'a mut E, &'a mut F); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + let e = (&mut *ptr_e).downcast_mut::()?; + let f = (&mut *ptr_f).downcast_mut::()?; + Some((a, b, c, d, e, f)) + } + } + } + + impl GetTuple for (A, B, C, D, E, F, G) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + G: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + let e = map.get(&TypeId::of::())?.downcast_ref::()?; + let f = map.get(&TypeId::of::())?.downcast_ref::()?; + let g = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d, e, f, g)) + } + } + + impl GetTupleMut for (A, B, C, D, E, F, G) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + G: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D, &'a mut E, &'a mut F, &'a mut G); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + let e = (&mut *ptr_e).downcast_mut::()?; + let f = (&mut *ptr_f).downcast_mut::()?; + let g = (&mut *ptr_g).downcast_mut::()?; + Some((a, b, c, d, e, f, g)) + } + } + } + + impl GetTuple for (A, B, C, D, E, F, G, H) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + G: Any + 'static, + H: Any + 'static, + { + type Output<'a> = (&'a A, &'a B, &'a C, &'a D, &'a E, &'a F, &'a G, &'a H); + fn get_from<'a>(map: &'a HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let a = map.get(&TypeId::of::())?.downcast_ref::()?; + let b = map.get(&TypeId::of::())?.downcast_ref::()?; + let c = map.get(&TypeId::of::())?.downcast_ref::()?; + let d = map.get(&TypeId::of::())?.downcast_ref::()?; + let e = map.get(&TypeId::of::())?.downcast_ref::()?; + let f = map.get(&TypeId::of::())?.downcast_ref::()?; + let g = map.get(&TypeId::of::())?.downcast_ref::()?; + let h = map.get(&TypeId::of::())?.downcast_ref::()?; + Some((a, b, c, d, e, f, g, h)) + } + } + + impl GetTupleMut for (A, B, C, D, E, F, G, H) + where + A: Any + 'static, + B: Any + 'static, + C: Any + 'static, + D: Any + 'static, + E: Any + 'static, + F: Any + 'static, + G: Any + 'static, + H: Any + 'static, + { + type Output<'a> = (&'a mut A, &'a mut B, &'a mut C, &'a mut D, &'a mut E, &'a mut F, &'a mut G, &'a mut H); + fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { + let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; + for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_h = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + unsafe { + let a = (&mut *ptr_a).downcast_mut::()?; + let b = (&mut *ptr_b).downcast_mut::()?; + let c = (&mut *ptr_c).downcast_mut::()?; + let d = (&mut *ptr_d).downcast_mut::()?; + let e = (&mut *ptr_e).downcast_mut::()?; + let f = (&mut *ptr_f).downcast_mut::()?; + let g = (&mut *ptr_g).downcast_mut::()?; + let h = (&mut *ptr_h).downcast_mut::()?; + Some((a, b, c, d, e, f, g, h)) + } + } } } }; diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index 6bd99f7..0d98577 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -92,8 +92,7 @@ impl System for FPSDebugCameraSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - let input = res.get::().unwrap(); + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); if self.mouse_enabled { self.yaw += self.mouse_delta.0 as f32 * self.sensitivity; From 54967067bdf034e05280c59147ec297071b902d2 Mon Sep 17 00:00:00 2001 From: reo Date: Thu, 16 Oct 2025 17:01:23 +0300 Subject: [PATCH 10/38] Enable InputState event handler in engine --- engine/src/engine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 7c366dc..a5c8a1d 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -66,7 +66,7 @@ impl EngineTrait for Engine { } fn handle_event(&mut self, platform_context: PlatformContext) { - // self.input_state.borrow_mut().handle_event(&platform_context.current_event); + self.resources.get_mut::().unwrap().handle_event(&platform_context.current_event); self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { system.handle_event(&mut self.resources, &mut self.scene_manager.current_mut()); From 5f3da6ab99b7a3e8e86a440e212a1ee11910726e Mon Sep 17 00:00:00 2001 From: reo Date: Fri, 17 Oct 2025 23:18:18 +0300 Subject: [PATCH 11/38] Add FPS counter to the debug UI --- game/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/game/src/main.rs b/game/src/main.rs index 4e0ee6e..ad230cf 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -71,6 +71,7 @@ impl System for RenderingTestSystem { dbg_ui.text("Hello World!".to_owned()); dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); + dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); } From 8b5a6167ebfd06c345edb13db62cfea95ba4a9cf Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 19 Oct 2025 17:40:51 +0300 Subject: [PATCH 12/38] Physics Support - NEW CRATE: raidillon_physics. - Added new models to be able to test the physics support. - Added a new system "PhysicsSystem" to apply physics calculations to the ECS world. - NEW COMPONENT: RigidBodyComponent --- Cargo.lock | 521 +++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + assets/models/plane.glb | 3 + assets/models/sphere.glb | 3 + ecs/Cargo.toml | 1 + ecs/src/components.rs | 3 + game/Cargo.toml | 2 + game/src/main.rs | 75 +++--- game/src/systems/mod.rs | 3 + game/src/systems/physics.rs | 34 +++ physics/Cargo.toml | 9 + physics/src/lib.rs | 3 + physics/src/physics.rs | 82 ++++++ 13 files changed, 697 insertions(+), 43 deletions(-) create mode 100644 assets/models/plane.glb create mode 100644 assets/models/sphere.glb create mode 100644 game/src/systems/mod.rs create mode 100644 game/src/systems/physics.rs create mode 100644 physics/Cargo.toml create mode 100644 physics/src/lib.rs create mode 100644 physics/src/physics.rs diff --git a/Cargo.lock b/Cargo.lock index e1b6adb..525e6ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.6.0" @@ -64,7 +70,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -79,6 +85,15 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -130,6 +145,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bit_field" version = "0.10.3" @@ -198,7 +219,7 @@ dependencies = [ "polling", "rustix 0.38.44", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -393,12 +414,33 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "dpi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -455,6 +497,18 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.5.0" @@ -521,6 +575,96 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + [[package]] name = "glam" version = "0.30.8" @@ -659,6 +803,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -673,6 +826,30 @@ name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] [[package]] name = "hecs" @@ -784,7 +961,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -849,6 +1026,12 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.9" @@ -888,6 +1071,16 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.5" @@ -938,6 +1131,49 @@ dependencies = [ "pxfm", ] +[[package]] +name = "nalgebra" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" +dependencies = [ + "approx", + "glam 0.14.0", + "glam 0.15.2", + "glam 0.16.0", + "glam 0.17.3", + "glam 0.18.0", + "glam 0.19.0", + "glam 0.20.5", + "glam 0.21.3", + "glam 0.22.0", + "glam 0.23.0", + "glam 0.24.2", + "glam 0.25.0", + "glam 0.27.0", + "glam 0.28.0", + "glam 0.29.3", + "glam 0.30.8", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ndk" version = "0.9.0" @@ -950,7 +1186,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -968,6 +1204,56 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -975,6 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1269,6 +1556,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -1301,6 +1597,40 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parry3d" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "017be73f24c8ca8b10f9727616e5cb9af82b98488cc6d5eea468e727ffa780ca" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.9.1", + "downcast-rs 2.0.2", + "either", + "ena", + "foldhash 0.2.0", + "hashbrown 0.16.0", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rstar", + "simba", + "slab", + "smallvec", + "spade", + "static_assertions", + "thiserror 2.0.17", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1384,6 +1714,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "pxfm" version = "0.1.24" @@ -1436,15 +1785,16 @@ dependencies = [ name = "raidillon_ecs" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", + "rapier3d", ] [[package]] name = "raidillon_engine" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "hecs", "indexmap", "raidillon_assets", @@ -1457,13 +1807,15 @@ dependencies = [ name = "raidillon_game" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", "raidillon_core", "raidillon_ecs", "raidillon_engine", "raidillon_glium", + "raidillon_physics", "raidillon_platform", + "rapier3d", "winit", ] @@ -1473,7 +1825,7 @@ version = "0.1.0" dependencies = [ "anyhow", "exr", - "glam", + "glam 0.30.8", "glium", "gltf", "image", @@ -1489,22 +1841,62 @@ dependencies = [ "winit", ] +[[package]] +name = "raidillon_physics" +version = "0.1.0" +dependencies = [ + "glam 0.30.8", + "raidillon_ecs", + "rapier3d", +] + [[package]] name = "raidillon_platform" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", "raidillon_core", "winit", ] +[[package]] +name = "rapier3d" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd27b8eb36d0833fa0f2aea40164fabfad0fc34b9932286ae9e84f3452f5364" +dependencies = [ + "approx", + "arrayvec", + "bit-vec", + "bitflags 2.9.1", + "downcast-rs 2.0.2", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "parry3d", + "profiling", + "rustc-hash", + "simba", + "static_assertions", + "thiserror 2.0.17", + "wide", +] + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon-core" version = "1.13.0" @@ -1533,12 +1925,35 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "robust" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" + +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless", + "num-traits", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "0.38.44" @@ -1577,6 +1992,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1649,6 +2073,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -1681,7 +2118,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.44", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -1701,12 +2138,36 @@ dependencies = [ "serde", ] +[[package]] +name = "spade" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a14e31a007e9f85c32784b04f89e6e194bb252a4d41b4a8ccd9e77245d901c8c" +dependencies = [ + "hashbrown 0.15.4", + "num-traits", + "robust", + "smallvec", +] + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strict-num" version = "0.1.1" @@ -1730,7 +2191,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", ] [[package]] @@ -1744,6 +2214,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1808,6 +2289,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1929,7 +2416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", - "downcast-rs", + "downcast-rs 1.2.1", "rustix 1.0.8", "scoped-tls", "smallvec", @@ -2051,6 +2538,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 23332d0..dd544ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ "game", "ecs", "engine", + "physics", ] diff --git a/assets/models/plane.glb b/assets/models/plane.glb new file mode 100644 index 0000000..e755152 --- /dev/null +++ b/assets/models/plane.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b54f00f327a5edc76c03f83d4efa2a4400215d7ca9c961b38537e7f836bca710 +size 1240 diff --git a/assets/models/sphere.glb b/assets/models/sphere.glb new file mode 100644 index 0000000..ea99045 --- /dev/null +++ b/assets/models/sphere.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78583ec562098f7fa4eaf95c6e21b7d1ced53416eb4c71351e246d53f93aa0a3 +size 24744 diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml index 0e1efb5..a47173d 100644 --- a/ecs/Cargo.toml +++ b/ecs/Cargo.toml @@ -6,3 +6,4 @@ edition = "2024" [dependencies] glam = "0.30.5" raidillon_assets = { path = "../asset" } +rapier3d = "0.30.1" diff --git a/ecs/src/components.rs b/ecs/src/components.rs index 758f0f4..fa30cc2 100644 --- a/ecs/src/components.rs +++ b/ecs/src/components.rs @@ -15,3 +15,6 @@ impl Transform { } pub struct ModelHandle(pub ModelID); + +#[derive(Copy, Clone)] +pub struct RigidBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); diff --git a/game/Cargo.toml b/game/Cargo.toml index 484e434..9bb72c4 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -13,6 +13,8 @@ raidillon_platform = { path = "../platform" } raidillon_assets = { path = "../asset" } raidillon_ecs = { path = "../ecs" } raidillon_engine = { path = "../engine" } +raidillon_physics = { path = "../physics" } raidillon_glium = { path = "../glium_platform", optional = true } glam = "0.30.5" winit = "0.30.12" +rapier3d = "0.30.1" diff --git a/game/src/main.rs b/game/src/main.rs index ad230cf..8ddce6a 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,11 +1,14 @@ +mod systems; use std::fmt::format; use glam::{Quat, Vec3}; +use rapier3d::dynamics::RigidBodyType; +use rapier3d::prelude::ColliderBuilder; use raidillon_engine::{Engine, system::System, EngineResources}; use raidillon_engine::system::SystemContext; use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::ModelHandle; +use raidillon_ecs::components::{ModelHandle, RigidBodyComponent}; use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] @@ -15,9 +18,11 @@ use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_physics::Physics; +use crate::systems::PhysicsSystem; -const TEST_GLTF: &str = "pink-monkey.gltf"; - +const TEST_GLTF: &str = "sphere.glb"; +const PLANE_GLTF: &str = "plane.glb"; const MAIN_SCENE_ID: &str = "main_scene"; #[derive(Default)] @@ -40,28 +45,45 @@ impl System for UpdateAspectRatioSystem { } #[derive(Default)] -struct RenderingTestSystem { - rotation_speed: std::rc::Rc>, -} +struct RenderingTestSystem; impl System for RenderingTestSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); + let pctx = res.get::().expect("PlatformContext missing").clone(); + let physics = res.get_mut::().expect("Physics missing"); - self.rotation_speed = std::rc::Rc::new(std::cell::RefCell::new(5.0)); - - let mut am = pctx.asset_manager.borrow_mut(); - - am.load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); - - scene.world.spawn(( - Transform { - translation: Vec3::new(0.0, 0.0, 0.0), - rotation: Quat::IDENTITY, - scale: Vec3::new(1.0, 1.0, 1.0), - }, - ModelHandle(TEST_GLTF), - )); + // Spawn Sphere + { + let tr = Transform { + translation: Vec3::new(0.0, 5.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(1.0, 1.0, 1.0), + }; + let collider = ColliderBuilder::ball(1.0).build(); + let rb_handle = physics.add_rigid_body(RigidBodyType::Dynamic, tr, collider); + pctx.asset_manager.borrow_mut().load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); + scene.world.spawn(( + tr, + ModelHandle(TEST_GLTF), + RigidBodyComponent(rb_handle), + )); + } + // Spawn Plane + { + let tr = Transform { + translation: Vec3::new(0.0, 0.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(10.0, 1.0, 10.0), + }; + let collider = ColliderBuilder::cuboid(10.0, 0.01, 10.0).build(); + let rb_handle = physics.add_rigid_body(RigidBodyType::Fixed, tr, collider); + pctx.asset_manager.borrow_mut().load_gltf(PLANE_GLTF, &model_path(PLANE_GLTF)); + scene.world.spawn(( + tr, + ModelHandle(PLANE_GLTF), + RigidBodyComponent(rb_handle), + )); + } } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { @@ -72,22 +94,13 @@ impl System for RenderingTestSystem { dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); - dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); } - - fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - - scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { - t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * pctx.time_ctx.fixed_dt); - }); - } - } fn main() { let mut engine = Engine::new(); // Define systems + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs new file mode 100644 index 0000000..0ab4645 --- /dev/null +++ b/game/src/systems/mod.rs @@ -0,0 +1,3 @@ +mod physics; + +pub use physics::PhysicsSystem; diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs new file mode 100644 index 0000000..c5d4807 --- /dev/null +++ b/game/src/systems/physics.rs @@ -0,0 +1,34 @@ +use raidillon_core::scene::Scene; +use raidillon_ecs::components::RigidBodyComponent; +use raidillon_ecs::Transform; +use raidillon_engine::EngineResources; +use raidillon_engine::system::System; +use raidillon_physics::Physics; +use raidillon_platform::PlatformContext; + +#[derive(Default)] +pub struct PhysicsSystem; + +impl System for PhysicsSystem { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let p = Physics::default(); + res.insert(p); + } + + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().expect("PlatformContext missing").clone(); + let physics = res.get_mut::().expect("Physics missing"); + physics.step(pctx.time_ctx.fixed_dt); + + let mut query = scene.world.query::<(&mut Transform, &RigidBodyComponent)>(); + for (_ent, (tr, rb_component)) in query.iter() { + if let Some(body) = physics.get_rigid_body(rb_component.0) { + let pos = body.position(); + let translation = Physics::rapier_translation_to_glam(&pos.translation.vector); + let rotation = Physics::rapier_rotation_to_glam(&pos.rotation); + tr.translation = translation; + tr.rotation = rotation; + } + } + } +} diff --git a/physics/Cargo.toml b/physics/Cargo.toml new file mode 100644 index 0000000..dc34e98 --- /dev/null +++ b/physics/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "raidillon_physics" +version = "0.1.0" +edition = "2024" + +[dependencies] +rapier3d = "0.30.1" +raidillon_ecs = { path = "../ecs" } +glam = "0.30.8" \ No newline at end of file diff --git a/physics/src/lib.rs b/physics/src/lib.rs new file mode 100644 index 0000000..a5dafc6 --- /dev/null +++ b/physics/src/lib.rs @@ -0,0 +1,3 @@ +mod physics; + +pub use crate::physics::Physics; diff --git a/physics/src/physics.rs b/physics/src/physics.rs new file mode 100644 index 0000000..009984c --- /dev/null +++ b/physics/src/physics.rs @@ -0,0 +1,82 @@ +use glam::{Quat, Vec3}; +use rapier3d::na::{Quaternion, UnitQuaternion}; +use rapier3d::prelude::*; +use raidillon_ecs::Transform; + +/// Tiny wrapper around rapier3d. +pub struct Physics { + rigid_body_set: RigidBodySet, + collider_set: ColliderSet, + physics_pipeline: PhysicsPipeline, + island_manager: IslandManager, + broad_phase: DefaultBroadPhase, + narrow_phase: NarrowPhase, + impulse_joint_set: ImpulseJointSet, + multibody_joint_set: MultibodyJointSet, + ccd_solver: CCDSolver, + gravity: Vector, + integration_parameters: IntegrationParameters, +} + +impl Default for Physics { + fn default() -> Self { + Self { + gravity: vector![0.0, -9.81, 0.0], + rigid_body_set: Default::default(), + collider_set: Default::default(), + physics_pipeline: Default::default(), + island_manager: Default::default(), + broad_phase: Default::default(), + narrow_phase: Default::default(), + impulse_joint_set: Default::default(), + multibody_joint_set: Default::default(), + ccd_solver: Default::default(), + integration_parameters: Default::default(), + } + } +} + +impl Physics { + pub fn step(&mut self, dt: f32) { + self.integration_parameters.dt = dt; + self.physics_pipeline.step( + &self.gravity, + &self.integration_parameters, + &mut self.island_manager, + &mut self.broad_phase, + &mut self.narrow_phase, + &mut self.rigid_body_set, + &mut self.collider_set, + &mut self.impulse_joint_set, + &mut self.multibody_joint_set, + &mut self.ccd_solver, + &(), + &(), + ); + } + + pub fn add_rigid_body(&mut self, kind: RigidBodyType, transform: Transform, collider: Collider) -> RigidBodyHandle { + let rb = RigidBodyBuilder::new(kind) + .translation(vector![transform.translation.x, transform.translation.y, transform.translation.z]) + .build(); + let rb_handle = self.rigid_body_set.insert(rb); + self.collider_set.insert_with_parent(collider, rb_handle, &mut self.rigid_body_set); + rb_handle + } + + pub fn get_rigid_body(&self, handle: RigidBodyHandle) -> Option<&RigidBody> { + self.rigid_body_set.get(handle) + } + + pub fn get_rigid_body_mut(&mut self, handle: RigidBodyHandle) -> Option<&mut RigidBody> { + self.rigid_body_set.get_mut(handle) + } + + pub fn rapier_translation_to_glam(v: &Vector) -> Vec3 { + Vec3::new(v.x, v.y, v.z) + } + + pub fn rapier_rotation_to_glam(r: &UnitQuaternion) -> Quat { + Quat::from_xyzw(r.i, r.j, r.k, r.w) + } +} From 713d865dd7799c59c2a5ec018258ce00d260fd1d Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 26 Oct 2025 18:29:59 +0300 Subject: [PATCH 13/38] MASSIVE Kinematic Character Controller Update - NEW kinematic character controller powered by rapier3d at kinematic_character_controller.rs - NEW camera modes. The ability to switch between the free debug camera and new character controller. - NEW keybinds system to support the camera mode swap --- Cargo.lock | 2 + ecs/src/components.rs | 10 + engine/Cargo.toml | 1 + engine/src/lib.rs | 1 + engine/src/systems/fps_camera.rs | 29 +-- game/Cargo.toml | 1 + game/src/main.rs | 35 ++- game/src/systems/keybinds.rs | 52 +++++ .../systems/kinematic_character_controller.rs | 200 ++++++++++++++++++ game/src/systems/mod.rs | 4 + game/src/systems/physics.rs | 6 +- physics/src/physics.rs | 8 +- 12 files changed, 323 insertions(+), 26 deletions(-) create mode 100644 game/src/systems/keybinds.rs create mode 100644 game/src/systems/kinematic_character_controller.rs diff --git a/Cargo.lock b/Cargo.lock index 525e6ad..91f50f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1799,6 +1799,7 @@ dependencies = [ "indexmap", "raidillon_assets", "raidillon_core", + "raidillon_ecs", "raidillon_platform", "winit", ] @@ -1808,6 +1809,7 @@ name = "raidillon_game" version = "0.1.0" dependencies = [ "glam 0.30.8", + "hecs", "raidillon_assets", "raidillon_core", "raidillon_ecs", diff --git a/ecs/src/components.rs b/ecs/src/components.rs index fa30cc2..3c20ec7 100644 --- a/ecs/src/components.rs +++ b/ecs/src/components.rs @@ -18,3 +18,13 @@ pub struct ModelHandle(pub ModelID); #[derive(Copy, Clone)] pub struct RigidBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); + +#[derive(Copy, Clone)] +pub struct CharacterBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +pub enum CameraMode { + #[default] + Kinematic, + Debug, +} diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 98202e4..79a2de9 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" raidillon_assets = { path = "../asset" } raidillon_core = { path = "../core" } raidillon_platform = { path = "../platform" } +raidillon_ecs = { path = "../ecs" } winit = "0.30.12" hecs = "0.10.5" indexmap = "2.10.0" diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 9a87556..a7ad724 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -6,3 +6,4 @@ mod resources; pub use crate::engine::Engine; pub use crate::resources::EngineResources; +pub use input::InputState; diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index 0d98577..5ff9cc7 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -9,6 +9,7 @@ use raidillon_platform::{Camera, PlatformContext}; use crate::input::InputState; use crate::resources::EngineResources; use raidillon_core::scene::Scene; +use raidillon_ecs::components::CameraMode; pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), @@ -35,20 +36,10 @@ impl Default for FPSDebugCameraSystem { } impl System for FPSDebugCameraSystem { - fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - scene.world.spawn((Camera { - eye: Vec3::new(0.0, 0.0, 2.0), - center: Vec3::ZERO, - up: Vec3::Y, - fovy: 60_f32.to_radians(), - aspect: pctx.frame_width / pctx.frame_height, - znear: 0.1, - zfar: 100.0, - },)); - } - fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } let pctx = res.get::().unwrap(); let event2 = pctx.current_event.clone(); match event2 { @@ -92,6 +83,9 @@ impl System for FPSDebugCameraSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); if self.mouse_enabled { @@ -134,4 +128,13 @@ impl FPSDebugCameraSystem { yaw_rad.sin() * pitch_rad.cos(), ).normalize() } + + fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == CameraMode::Debug + } } diff --git a/game/Cargo.toml b/game/Cargo.toml index 9bb72c4..9579d22 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -18,3 +18,4 @@ raidillon_glium = { path = "../glium_platform", optional = true } glam = "0.30.5" winit = "0.30.12" rapier3d = "0.30.1" +hecs = "0.10.5" diff --git a/game/src/main.rs b/game/src/main.rs index 8ddce6a..a5a3d9c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,14 +1,14 @@ mod systems; use std::fmt::format; use glam::{Quat, Vec3}; -use rapier3d::dynamics::RigidBodyType; +use rapier3d::dynamics::{CoefficientCombineRule, RigidBodyType}; use rapier3d::prelude::ColliderBuilder; -use raidillon_engine::{Engine, system::System, EngineResources}; +use raidillon_engine::{Engine, system::System, EngineResources, InputState}; use raidillon_engine::system::SystemContext; use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::{ModelHandle, RigidBodyComponent}; +use raidillon_ecs::components::{CameraMode, CharacterBodyComponent, ModelHandle, RigidBodyComponent}; use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] @@ -18,8 +18,9 @@ use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; -use crate::systems::PhysicsSystem; +use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; const TEST_GLTF: &str = "sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; @@ -50,7 +51,7 @@ struct RenderingTestSystem; impl System for RenderingTestSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); - let physics = res.get_mut::().expect("Physics missing"); + let physics = scene.resources.get_mut::().expect("Physics missing"); // Spawn Sphere { @@ -59,7 +60,10 @@ impl System for RenderingTestSystem { rotation: Quat::IDENTITY, scale: Vec3::new(1.0, 1.0, 1.0), }; - let collider = ColliderBuilder::ball(1.0).build(); + let collider = ColliderBuilder::ball(1.0) + .restitution(0.7) + .restitution_combine_rule(CoefficientCombineRule::Max) + .build(); let rb_handle = physics.add_rigid_body(RigidBodyType::Dynamic, tr, collider); pctx.asset_manager.borrow_mut().load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); scene.world.spawn(( @@ -84,16 +88,32 @@ impl System for RenderingTestSystem { RigidBodyComponent(rb_handle), )); } + + scene.world.spawn((Camera { + eye: Vec3::new(0.0, 2.0, 3.0), + center: Vec3::ZERO, + up: Vec3::Y, + fovy: 60_f32.to_radians(), + aspect: pctx.frame_width / pctx.frame_height, + znear: 0.1, + zfar: 100.0}, + CameraMode::default(), + )); } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().unwrap(); + let input = res.get::().unwrap(); let dbg_ui = scene.resources.get_mut::().unwrap(); dbg_ui.text("Hello World!".to_owned()); dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); + + for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { + dbg_ui.text(format!("Character POS: {}", tr.translation)); + } } } @@ -101,7 +121,8 @@ fn main() { let mut engine = Engine::new(); // Define systems engine.system_manager.add::(); - engine.system_manager.add::(); + engine.system_manager.add::(); + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); // engine.system_manager.add::(); diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs new file mode 100644 index 0000000..6bc3e70 --- /dev/null +++ b/game/src/systems/keybinds.rs @@ -0,0 +1,52 @@ +use winit::keyboard::KeyCode; +use raidillon_core::DebugUIBuffer; +use raidillon_core::scene::Scene; +use raidillon_ecs::components::CameraMode; +use raidillon_engine::{EngineResources, InputState}; +use raidillon_engine::system::System; +use raidillon_platform::Camera; + +#[derive(Default)] +pub struct KeybindsSystem; + +impl System for KeybindsSystem { + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let input = res.get::().unwrap(); + + if input.key_held(KeyCode::F5) { + self.toggle_camera_mode(scene); + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let dbg_ui = scene.resources.get_mut::().unwrap(); + + dbg_ui.text("F5 to switch camera".to_owned()); + + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + dbg_ui.text(format!("Camera Mode: {:?}", cam_mode)); + } +} + +impl KeybindsSystem { + fn toggle_camera_mode(&mut self, scene: &mut Scene) { + let q = scene.world.query_mut::<(&mut Camera, &mut CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .into_iter() + .next() + .unwrap(); + + match *cam_mode { + CameraMode::Kinematic => { + *cam_mode = CameraMode::Debug; + } + CameraMode::Debug => { + *cam_mode = CameraMode::Kinematic; + } + } + } +} diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs new file mode 100644 index 0000000..4608e67 --- /dev/null +++ b/game/src/systems/kinematic_character_controller.rs @@ -0,0 +1,200 @@ +use glam::{Quat, Vec3}; +use hecs::Entity; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, QueryPipeline, RigidBodyBuilder}; +use rapier3d::prelude::vector; +use raidillon_core::scene::Scene; +use raidillon_engine::{EngineResources, InputState}; +use raidillon_engine::system::System; +use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; +use rapier3d::math::Isometry; +use rapier3d::na::{Isometry3, Vector3}; +use winit::event::DeviceEvent::MouseMotion; +use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::keyboard::KeyCode; +use winit::window::CursorGrabMode; +use raidillon_core::DebugUIBuffer; +use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; +use raidillon_ecs::Transform; +use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_physics::Physics; +use raidillon_platform::{Camera, PlatformContext}; + +#[derive(Default)] +pub struct KinematicCharacterController { + character_controller: RapierKinematicCharacterController, + character_collider: ColliderBuilder, + + desired_movement: Vec3, + last_position: Vector3, + yaw: f32, + pitch: f32, + speed: f32, + sensitivity: f32, + mouse_delta: (f64, f64), + + vertical_velocity: f32, + gravity: f32, + max_fall_speed: f32, +} + +impl System for KinematicCharacterController { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + // create the rigid body, add it to the body set + let p = scene.resources.get_mut::().expect("Physics missing"); + let rb = RigidBodyBuilder::kinematic_position_based().build(); + let rb_handle = p.rigid_body_set.insert(rb); + self.character_collider = ColliderBuilder::capsule_z(1.5, 1.0); + p.collider_set.insert_with_parent(self.character_collider.build(), rb_handle, &mut p.rigid_body_set); + let tr = Transform { + translation: Vec3::new(0.0, 2.0, 3.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(1.0, 1.0, 1.0), + }; + self.last_position = vector![ + tr.translation.x, + tr.translation.y, + tr.translation.z, + ]; + scene.world.spawn(( + tr, + CharacterBodyComponent(rb_handle), + )); + + self.speed = 5.0; + self.sensitivity = 0.1; + self.gravity = -9.81; + self.max_fall_speed = -50.0; + self.vertical_velocity = 0.0; + } + + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + + let pctx = res.get::().unwrap(); + let event2 = pctx.current_event.clone(); + match event2 { + Event::DeviceEvent { device_id, event } => { + match event { + MouseMotion { delta } => { + self.mouse_delta.0 += delta.0; + self.mouse_delta.1 += delta.1; + }, + _ => {} + } + }, + _ => {}, + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + self.yaw += (self.mouse_delta.0 as f32) * self.sensitivity; + self.pitch -= (self.mouse_delta.1 as f32) * self.sensitivity; + self.pitch = self.pitch.clamp(-89.0, 89.0); + + let front = self.front(); + let right_vec = front.cross(Vec3::Y).normalize(); + + if input.key_held(KeyCode::KeyW) { + self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed; + } + + let pos = Physics::rapier_translation_to_glam(&self.last_position); + + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + // INTERPOLATION NEEDED. + camera.eye = pos; + camera.center = pos + front; + }); + self.mouse_delta = (0.0, 0.0); + } + + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + let p = scene.resources.get_mut::().unwrap(); + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + let (ch_ent, (ch_tr, ch_component)) = scene + .world + .query_mut::<(&mut Transform, &mut CharacterBodyComponent)>() + .into_iter() + .next() + .expect("no character entity in world"); + + let query_pipeline = p.broad_phase.as_query_pipeline( + p.narrow_phase.query_dispatcher(), + &p.rigid_body_set, + &p.collider_set, + QueryFilter::default().exclude_rigid_body(ch_component.0), + ); + + self.vertical_velocity = (self.vertical_velocity + self.gravity * pctx.time_ctx.fixed_dt) + .max(self.max_fall_speed); + let mut total_displacement = self.desired_movement; + total_displacement.y += self.vertical_velocity * pctx.time_ctx.fixed_dt; + + let corrected_movement = self.character_controller.move_shape( + pctx.time_ctx.fixed_dt, + &query_pipeline, + &*self.character_collider.shape, + &Isometry3::from(self.last_position), + vector![total_displacement.x, total_displacement.y, total_displacement.z], + |_| {}, + ); + + // update character rigid body with the new translation. + if let Some(body) = p.get_rigid_body_mut(ch_component.0) { + self.last_position = vector![ + self.last_position.x + corrected_movement.translation.x, + self.last_position.y + corrected_movement.translation.y, + self.last_position.z + corrected_movement.translation.z, + ]; + body.set_next_kinematic_position(Isometry3::from(self.last_position)); + ch_tr.translation = Physics::rapier_translation_to_glam(&self.last_position); + // reset vertical velocity if grounded + if corrected_movement.grounded { + self.vertical_velocity = 0.0; + } + } + + self.desired_movement = Vec3::ZERO; + } +} + +impl KinematicCharacterController { + pub fn front(&self) -> Vec3 { + let yaw_rad = self.yaw.to_radians(); + let pitch_rad = self.pitch.to_radians(); + Vec3::new( + yaw_rad.cos() * pitch_rad.cos(), + pitch_rad.sin(), + yaw_rad.sin() * pitch_rad.cos(), + ).normalize() + } + + fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == CameraMode::Kinematic + } +} diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index 0ab4645..f975aff 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,3 +1,7 @@ mod physics; +mod kinematic_character_controller; +mod keybinds; pub use physics::PhysicsSystem; +pub use kinematic_character_controller::KinematicCharacterController; +pub use keybinds::KeybindsSystem; diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs index c5d4807..a77c75c 100644 --- a/game/src/systems/physics.rs +++ b/game/src/systems/physics.rs @@ -6,20 +6,22 @@ use raidillon_engine::system::System; use raidillon_physics::Physics; use raidillon_platform::PlatformContext; +/// Do physics calculations and apply to world. #[derive(Default)] pub struct PhysicsSystem; impl System for PhysicsSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let p = Physics::default(); - res.insert(p); + scene.resources.insert(p); } fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); - let physics = res.get_mut::().expect("Physics missing"); + let physics = scene.resources.get_mut::().expect("Physics missing"); physics.step(pctx.time_ctx.fixed_dt); + // apply calculations to dynamic bodies let mut query = scene.world.query::<(&mut Transform, &RigidBodyComponent)>(); for (_ent, (tr, rb_component)) in query.iter() { if let Some(body) = physics.get_rigid_body(rb_component.0) { diff --git a/physics/src/physics.rs b/physics/src/physics.rs index 009984c..5eff699 100644 --- a/physics/src/physics.rs +++ b/physics/src/physics.rs @@ -5,12 +5,12 @@ use raidillon_ecs::Transform; /// Tiny wrapper around rapier3d. pub struct Physics { - rigid_body_set: RigidBodySet, - collider_set: ColliderSet, + pub rigid_body_set: RigidBodySet, + pub collider_set: ColliderSet, physics_pipeline: PhysicsPipeline, island_manager: IslandManager, - broad_phase: DefaultBroadPhase, - narrow_phase: NarrowPhase, + pub broad_phase: DefaultBroadPhase, + pub narrow_phase: NarrowPhase, impulse_joint_set: ImpulseJointSet, multibody_joint_set: MultibodyJointSet, ccd_solver: CCDSolver, From 96a59b68fa23ddbf927e3d5540f7b2fe8efb8b22 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:35:55 +0300 Subject: [PATCH 14/38] Move debug_camera to the game crate, activate FPSDebugCameraSystem --- engine/src/systems/mod.rs | 1 - game/src/main.rs | 3 ++- .../fps_camera.rs => game/src/systems/debug_camera.rs | 8 ++++---- game/src/systems/mod.rs | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) rename engine/src/systems/fps_camera.rs => game/src/systems/debug_camera.rs (96%) diff --git a/engine/src/systems/mod.rs b/engine/src/systems/mod.rs index 8489571..e69de29 100644 --- a/engine/src/systems/mod.rs +++ b/engine/src/systems/mod.rs @@ -1 +0,0 @@ -pub mod fps_camera; diff --git a/game/src/main.rs b/game/src/main.rs index a5a3d9c..99eb0e2 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -17,7 +17,7 @@ use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; -use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use systems::debug_camera::FPSDebugCameraSystem; use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; @@ -123,6 +123,7 @@ fn main() { engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); // engine.system_manager.add::(); diff --git a/engine/src/systems/fps_camera.rs b/game/src/systems/debug_camera.rs similarity index 96% rename from engine/src/systems/fps_camera.rs rename to game/src/systems/debug_camera.rs index 5ff9cc7..13cb971 100644 --- a/engine/src/systems/fps_camera.rs +++ b/game/src/systems/debug_camera.rs @@ -1,4 +1,4 @@ -use crate::system::{System, SystemContext}; +use raidillon_engine::system::{System, SystemContext}; use glam::{Quat, Vec3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::{ElementState, Event, MouseButton, WindowEvent}; @@ -6,8 +6,8 @@ use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_assets::model_path; use raidillon_platform::{Camera, PlatformContext}; -use crate::input::InputState; -use crate::resources::EngineResources; +use raidillon_engine::InputState; +use raidillon_engine::EngineResources; use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; @@ -29,7 +29,7 @@ impl Default for FPSDebugCameraSystem { position: Vec3::new(0.0, 0.0, 2.0), yaw: -90.0, pitch: 0.0, - speed: 3.0, + speed: 8.0, sensitivity: 0.1, } } diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index f975aff..2a387b1 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,6 +1,7 @@ mod physics; mod kinematic_character_controller; mod keybinds; +pub mod debug_camera; pub use physics::PhysicsSystem; pub use kinematic_character_controller::KinematicCharacterController; From ded85dcd72c9e70adf6301249807215efb02122c Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:36:38 +0300 Subject: [PATCH 15/38] Optimize imports of kinematic_character_controller.rs --- game/src/systems/kinematic_character_controller.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index 4608e67..e3fc099 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -1,21 +1,16 @@ use glam::{Quat, Vec3}; -use hecs::Entity; -use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, QueryPipeline, RigidBodyBuilder}; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder}; use rapier3d::prelude::vector; use raidillon_core::scene::Scene; use raidillon_engine::{EngineResources, InputState}; use raidillon_engine::system::System; use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; -use rapier3d::math::Isometry; use rapier3d::na::{Isometry3, Vector3}; use winit::event::DeviceEvent::MouseMotion; -use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::event::Event; use winit::keyboard::KeyCode; -use winit::window::CursorGrabMode; -use raidillon_core::DebugUIBuffer; use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; use raidillon_ecs::Transform; -use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; use raidillon_physics::Physics; use raidillon_platform::{Camera, PlatformContext}; From e88ce258ce43ca48fcf82bbde46aea742a1e33b1 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:37:49 +0300 Subject: [PATCH 16/38] Tweak keybinds.rs to make camera mode toggle smoother --- game/src/systems/keybinds.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index 6bc3e70..e701dad 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -4,17 +4,22 @@ use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; use raidillon_engine::{EngineResources, InputState}; use raidillon_engine::system::System; -use raidillon_platform::Camera; +use raidillon_platform::{Camera, PlatformContext}; #[derive(Default)] -pub struct KeybindsSystem; +pub struct KeybindsSystem { + camera_toggle_held: bool, +} impl System for KeybindsSystem { fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let input = res.get::().unwrap(); - if input.key_held(KeyCode::F5) { + if self.camera_toggle_held { return } self.toggle_camera_mode(scene); + self.camera_toggle_held = true; + } else { + self.camera_toggle_held = false; } } From ce24354f3b71ecf597f4f9800ff33305e8f77f88 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 18:14:12 +0300 Subject: [PATCH 17/38] egui support - New Resource, EguiQueue: Utility to queue egui ui builders - New Rendering System, EguiRenderer: Utilizes a modified egui_glium library to render egui UI - Adjusted RenderingSystem trait and RenderingContext structure to provide event_loop and egui_queue. - Various minor adjusments --- Cargo.lock | 770 ++++++++++++++++++++++---- core/Cargo.toml | 3 +- core/src/egui_queue.rs | 22 + core/src/engine.rs | 3 - core/src/lib.rs | 2 + engine/Cargo.toml | 3 +- engine/src/engine.rs | 5 - game/Cargo.toml | 1 + game/src/main.rs | 32 +- glium_platform/Cargo.toml | 7 +- glium_platform/src/platform.rs | 17 +- glium_platform/src/render/basic.rs | 4 +- glium_platform/src/render/debug_ui.rs | 75 --- glium_platform/src/render/egui.rs | 46 ++ glium_platform/src/render/mod.rs | 3 +- glium_platform/src/render/skybox.rs | 3 +- glium_platform/src/system.rs | 11 +- platform/src/context.rs | 3 + 18 files changed, 806 insertions(+), 204 deletions(-) create mode 100644 core/src/egui_queue.rs delete mode 100644 glium_platform/src/render/debug_ui.rs create mode 100644 glium_platform/src/render/egui.rs diff --git a/Cargo.lock b/Cargo.lock index 91f50f1..3e9b382 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -59,7 +59,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.1", + "bitflags 2.10.0", "cc", "cesu8", "jni", @@ -94,6 +94,26 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.3", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -165,9 +185,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block2" @@ -186,9 +206,23 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "byteorder" @@ -214,7 +248,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "log", "polling", "rustix 0.38.44", @@ -222,18 +256,43 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "calloop" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" +dependencies = [ + "bitflags 2.10.0", + "polling", + "rustix 1.0.8", + "slab", + "tracing", +] + [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "calloop", + "calloop 0.13.0", "rustix 0.38.44", "wayland-backend", "wayland-client", ] +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.3", + "rustix 1.0.8", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cc" version = "1.2.31" @@ -278,6 +337,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c00d31b1d19317b4777ec879192d3745bd97d05262b4b19cb1dda284b9d22f19" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "combine" version = "4.6.7" @@ -307,6 +375,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -320,7 +398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "foreign-types", "libc", @@ -333,7 +411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] @@ -395,8 +473,19 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", + "bitflags 2.10.0", + "objc2 0.6.3", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -426,12 +515,84 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "ecolor" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084980ebede2fb1ad6c4f54285b3e489052ef2b6aa4016e4c19349417adc75c5" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "egui" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75645894de4ca1695ab3ab7201c7953bb95c1725aafeefa6822dc901ad2a81b" +dependencies = [ + "ahash", + "bitflags 2.10.0", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", + "smallvec", + "unicode-segmentation", +] + +[[package]] +name = "egui-winit" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4772ed5f16fa8ec2ba295e58f62b58ee83fcf49e67ec13d2b7ddf4e9a2dea34e" +dependencies = [ + "arboard", + "bytemuck", + "egui", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_glium" +version = "0.31.1" +source = "git+https://github.com/reo6/egui_glium.git#66dbadcb16b3e6c827847eaa51a0ca9bd296d768" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "egui-winit", + "glium", + "log", + "raw-window-handle", + "winit", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "emath" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e561352ae95c22ad179fb56c38d6e6eecd86cf4925cf5c70e738dd01df9b620" +dependencies = [ + "bytemuck", +] + [[package]] name = "ena" version = "0.14.3" @@ -441,6 +602,30 @@ dependencies = [ "log", ] +[[package]] +name = "epaint" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a909ce8cee63e8350fb0c251ad39277a5b24f19add60787e84a3b3ab3f2bd83a" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", + "profiling", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9649446c23368ae138716910e3e28143995691b598fbb9de16b42b0722cbcc" + [[package]] name = "equivalent" version = "1.0.2" @@ -457,6 +642,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "exr" version = "1.73.0" @@ -472,6 +663,26 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -536,6 +747,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -673,9 +893,9 @@ checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" [[package]] name = "glium" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a3028d1f135b5395e6e4336916b424bc5dd2b38c6e378ce2704e4b8f4a617ed" +checksum = "8cf52ce4f5ce1501bb056627f35484a819e812d1d93f0f3da413676421b1bbe0" dependencies = [ "backtrace", "fnv", @@ -733,7 +953,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cfg_aliases", "cgl", "dispatch2", @@ -741,7 +961,7 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "objc2 0.6.1", + "objc2 0.6.3", "objc2-app-kit 0.3.1", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -867,6 +1087,108 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "image" version = "0.25.8" @@ -879,6 +1201,7 @@ dependencies = [ "moxcms", "num-traits", "png", + "tiff", "zune-core", "zune-jpeg", ] @@ -896,16 +1219,6 @@ dependencies = [ "parking_lot", ] -[[package]] -name = "imgui-glium-renderer" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "781e53a393214a132c89c62e6e4b817645ad38832d3e334b2ad543f2c3df5106" -dependencies = [ - "glium", - "imgui", -] - [[package]] name = "imgui-sys" version = "0.12.0" @@ -918,16 +1231,6 @@ dependencies = [ "mint", ] -[[package]] -name = "imgui-winit-support" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7fcccfa9efab56c94274c0fec9939bb14149342b49e6a425883a5b7dda6a3f" -dependencies = [ - "imgui", - "winit", -] - [[package]] name = "indexmap" version = "2.10.0" @@ -1038,7 +1341,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "libc", "redox_syscall 0.5.17", ] @@ -1056,20 +1359,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] -name = "lock_api" -version = "0.4.13" +name = "litemap" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "matrixmultiply" @@ -1180,7 +1488,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "jni-sys", "log", "ndk-sys", @@ -1204,6 +1512,12 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "num-bigint" version = "0.4.6" @@ -1304,9 +1618,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ "objc2-encode", ] @@ -1317,7 +1631,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "libc", "objc2 0.5.2", @@ -1333,9 +1647,10 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", + "bitflags 2.10.0", + "objc2 0.6.3", "objc2-core-foundation", + "objc2-core-graphics", "objc2-foundation 0.3.1", ] @@ -1345,7 +1660,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-core-location", @@ -1369,7 +1684,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1377,13 +1692,26 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "dispatch2", - "objc2 0.6.1", + "objc2 0.6.3", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] @@ -1422,7 +1750,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "dispatch", "libc", @@ -1435,8 +1763,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", "objc2-core-foundation", ] @@ -1458,7 +1797,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1470,7 +1809,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1493,7 +1832,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-cloud-kit", @@ -1525,7 +1864,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "objc2 0.5.2", "objc2-core-location", @@ -1576,9 +1915,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1586,15 +1925,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall 0.5.17", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -1605,7 +1944,7 @@ checksum = "017be73f24c8ca8b10f9727616e5cb9af82b98488cc6d5eea468e727ffa780ca" dependencies = [ "approx", "arrayvec", - "bitflags 2.9.1", + "bitflags 2.10.0", "downcast-rs 2.0.2", "either", "ena", @@ -1675,7 +2014,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "crc32fast", "fdeflate", "flate2", @@ -1696,6 +2035,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -1742,6 +2090,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" @@ -1774,6 +2128,7 @@ version = "0.1.0" name = "raidillon_core" version = "0.1.0" dependencies = [ + "egui", "hecs", "imgui", "indexmap", @@ -1794,6 +2149,7 @@ dependencies = [ name = "raidillon_engine" version = "0.1.0" dependencies = [ + "egui", "glam 0.30.8", "hecs", "indexmap", @@ -1808,6 +2164,7 @@ dependencies = [ name = "raidillon_game" version = "0.1.0" dependencies = [ + "egui", "glam 0.30.8", "hecs", "raidillon_assets", @@ -1826,14 +2183,13 @@ name = "raidillon_glium" version = "0.1.0" dependencies = [ "anyhow", + "egui", + "egui_glium", "exr", "glam 0.30.8", "glium", "gltf", "image", - "imgui", - "imgui-glium-renderer", - "imgui-winit-support", "indexmap", "raidillon_assets", "raidillon_core", @@ -1871,7 +2227,7 @@ dependencies = [ "approx", "arrayvec", "bit-vec", - "bitflags 2.9.1", + "bitflags 2.10.0", "downcast-rs 2.0.2", "log", "nalgebra", @@ -1924,7 +2280,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", ] [[package]] @@ -1962,7 +2318,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1975,7 +2331,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.9.4", @@ -2033,7 +2389,7 @@ dependencies = [ "ab_glyph", "log", "memmap2", - "smithay-client-toolkit", + "smithay-client-toolkit 0.19.2", "tiny-skia", ] @@ -2112,9 +2468,9 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.1", - "calloop", - "calloop-wayland-source", + "bitflags 2.10.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", "cursor-icon", "libc", "log", @@ -2131,6 +2487,44 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.10.0", + "calloop 0.14.3", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.0.8", + "thiserror 2.0.17", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + [[package]] name = "smol_str" version = "0.2.2" @@ -2187,6 +2581,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2227,6 +2632,20 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -2252,6 +2671,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -2275,6 +2704,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-core", ] @@ -2309,12 +2739,29 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "urlencoding" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "version_check" version = "0.9.5" @@ -2431,7 +2878,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "rustix 1.0.8", "wayland-backend", "wayland-scanner", @@ -2443,7 +2890,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cursor-icon", "wayland-backend", ] @@ -2465,19 +2912,45 @@ version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-scanner", ] +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-protocols-plasma" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2490,7 +2963,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2540,6 +3013,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2 0.6.3", + "objc2-foundation 0.3.1", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "wide" version = "0.7.33" @@ -2565,6 +3060,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.45.0" @@ -2653,7 +3154,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -2853,13 +3354,13 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.1", + "bitflags 2.10.0", "block2", "bytemuck", - "calloop", + "calloop 0.13.0", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", @@ -2878,7 +3379,7 @@ dependencies = [ "redox_syscall 0.4.1", "rustix 0.38.44", "sctk-adwaita", - "smithay-client-toolkit", + "smithay-client-toolkit 0.19.2", "smol_str", "tracing", "unicode-segmentation", @@ -2911,9 +3412,15 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", ] +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "x11-dl" version = "2.21.0" @@ -2958,7 +3465,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "dlib", "log", "once_cell", @@ -2977,6 +3484,29 @@ version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.26" @@ -2997,6 +3527,60 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zune-core" version = "0.4.12" diff --git a/core/Cargo.toml b/core/Cargo.toml index fc6b73f..a8d650d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -8,4 +8,5 @@ hecs = "0.10.5" indexmap = "2.10.0" raidillon_assets = { path = "../asset" } winit = "0.30.12" -imgui = "0.12.0" \ No newline at end of file +imgui = "0.12.0" +egui = "0.33.2" \ No newline at end of file diff --git a/core/src/egui_queue.rs b/core/src/egui_queue.rs new file mode 100644 index 0000000..fbbb789 --- /dev/null +++ b/core/src/egui_queue.rs @@ -0,0 +1,22 @@ +pub struct EguiQueue { + pub queue_vec: Vec>, +} + +impl EguiQueue { + pub fn new() -> Self { + Self { queue_vec: Vec::new() } + } + + pub fn queue(&mut self, func: impl FnOnce(&egui::Context) + Send + 'static) { + self.queue_vec.push(Box::new(func)); + } + + pub fn clear(&mut self) { + self.queue_vec.clear() + } + + pub fn run(&mut self, ctx: &egui::Context) { + self.queue_vec.drain(..).for_each(|func| func(ctx)); + self.clear(); + } +} diff --git a/core/src/engine.rs b/core/src/engine.rs index b70a945..1308e20 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -1,5 +1,3 @@ -use std::cell::RefCell; -use std::rc::Rc; use crate::DebugUIBuffer; use crate::scene::Scene; @@ -14,5 +12,4 @@ pub trait EngineTrait { fn current_scene(&self) -> &Scene; fn get_debug_ui_buffer(&self) -> &DebugUIBuffer; fn reset_debug_ui_buffer(&mut self); - // fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Self::Scene, &DebugUIBuffer); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 238d5a6..61f5286 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -3,5 +3,7 @@ pub mod debug_ui; pub mod time; pub mod utils; pub mod scene; +mod egui_queue; pub use debug_ui::*; +pub use egui_queue::EguiQueue; diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 79a2de9..1264629 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -11,4 +11,5 @@ raidillon_ecs = { path = "../ecs" } winit = "0.30.12" hecs = "0.10.5" indexmap = "2.10.0" -glam = "0.30.8" \ No newline at end of file +glam = "0.30.8" +egui = "0.33.2" \ No newline at end of file diff --git a/engine/src/engine.rs b/engine/src/engine.rs index a5c8a1d..398920c 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -94,9 +94,4 @@ impl EngineTrait for Engine { fn reset_debug_ui_buffer(&mut self) { self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); } - - // fn scene_and_debug_ui_buffer_mut(&mut self) -> (&mut Scene, &DebugUIBuffer) { - // let (sm, dub) = (&mut self.scene_manager, self.scene_manager.current().resources.get::().unwrap()); - // (sm.current_mut(), dub) - // } } diff --git a/game/Cargo.toml b/game/Cargo.toml index 9579d22..f88ea43 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -19,3 +19,4 @@ glam = "0.30.5" winit = "0.30.12" rapier3d = "0.30.1" hecs = "0.10.5" +egui = "0.33.2" \ No newline at end of file diff --git a/game/src/main.rs b/game/src/main.rs index 99eb0e2..42ab2d3 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -16,7 +16,7 @@ use raidillon_glium::GliumPlatform; use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; -use raidillon_core::DebugUIBuffer; +use raidillon_core::{DebugUIBuffer, EguiQueue}; use systems::debug_camera::FPSDebugCameraSystem; use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; @@ -102,18 +102,30 @@ impl System for RenderingTestSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - let input = res.get::().unwrap(); - let dbg_ui = scene.resources.get_mut::().unwrap(); - - dbg_ui.text("Hello World!".to_owned()); - dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); - dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); - dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); + let ( + pctx, + input, + ) = res.get_many_mut::<( + PlatformContext, + InputState, + )>().unwrap(); + let mut egui_queue = pctx.egui_queue.borrow_mut(); + let time_ctx = pctx.time_ctx.clone(); + let mut character_pos = Vec3::ZERO; for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { - dbg_ui.text(format!("Character POS: {}", tr.translation)); + character_pos = tr.translation; } + egui_queue.queue(move |egui_ctx| { + egui::Window::new("Debug").show(egui_ctx, |ui| { + ui.label("Hello World!"); + ui.label(format!("Frame Delta: {}", time_ctx.frame_dt)); + ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt)); + ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt)); + ui.label(format!("Character POS: {}", character_pos)); + }); + }); + } } diff --git a/glium_platform/Cargo.toml b/glium_platform/Cargo.toml index 6e51be6..109fc57 100644 --- a/glium_platform/Cargo.toml +++ b/glium_platform/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] anyhow = "1.0.98" glam = "0.30.5" -glium = { version = "0.35.0", features = ["glutin_backend", "simple_window_builder"] } +glium = { version = "0.36.0", features = ["glutin_backend", "simple_window_builder"] } gltf = { version = "1.4.1", features = ["import", "utils", "KHR_texture_transform"] } raidillon_platform = { path = "../platform" } raidillon_core = { path = "../core" } @@ -15,8 +15,7 @@ raidillon_ecs = { path = "../ecs" } raidillon_engine = { path = "../engine" } winit = "0.30.12" indexmap = "2.10.0" -imgui = "0.12.0" -imgui-winit-support = "0.13.0" -imgui-glium-renderer = "0.13.0" exr = "1.73.0" image = { version = "0.25.8", default-features = false, features = ["exr"] } +egui = "0.33.2" +egui_glium = { version = "0.31.1", git = "https://github.com/reo6/egui_glium.git" } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index db8e56e..efc1543 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -14,11 +14,11 @@ use raidillon_assets::ModelManagerRef; use raidillon_core::engine::EngineTrait; use raidillon_core::time; use raidillon_core::time::Time; -use crate::render::debug_ui::ImguiBridge; -use crate::render::{BasicMeshRenderingSystem, SkyboxRenderingSystem}; +use crate::render::{BasicMeshRenderingSystem, EguiRenderer, SkyboxRenderingSystem}; use crate::GliumAssetManager; use glam::Vec3; use winit::event::DeviceEvent::MouseMotion; +use raidillon_core::EguiQueue; pub struct GliumPlatform> { event_loop: EventLoop<()>, @@ -28,6 +28,7 @@ pub struct GliumPlatform> { asset_manager: ModelManagerRef, engine: E, time: time::Time, + egui_queue: Rc>, } impl> Platform for GliumPlatform { @@ -50,9 +51,11 @@ impl> Platform for GliumPlatfor let window = Arc::new(Mutex::new(window)); // Install rendering systems in order - rendering_system_manager.add::(&display, window.clone()); - rendering_system_manager.add::(&display, window.clone()); - rendering_system_manager.add::(&display, window.clone()); + rendering_system_manager.add::(&display, window.clone(), &event_loop); + rendering_system_manager.add::(&display, window.clone(), &event_loop); + rendering_system_manager.add::(&display, window.clone(), &event_loop); + + let egui_queue = Rc::new(RefCell::new(EguiQueue::new())); Self { event_loop, @@ -62,6 +65,7 @@ impl> Platform for GliumPlatfor asset_manager, engine, time, + egui_queue, } } @@ -77,6 +81,7 @@ impl> Platform for GliumPlatfor frame_height: h as f32, time_ctx: self.construct_time_ctx(), window: self.window.clone(), + egui_queue: self.egui_queue.clone(), }; self.engine.initialize(ctx.clone()); let _ = &self.event_loop.run(move |event, el| { @@ -105,9 +110,11 @@ impl> Platform for GliumPlatfor let mut context = RenderingContext { scene, target: &mut target, + display: &self.display, asset_manager: self.asset_manager.clone(), window: self.window.clone(), debug_ui_buffer, + egui_queue: self.egui_queue.clone(), env_light_dir: Vec3::new(0.0, -1.0, 0.0), }; diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index 663b2dd..a55d865 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -9,6 +9,8 @@ use raidillon_assets::include_shader; pub use raidillon_platform::Camera; use glam::Vec3; use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction}; +use winit::event::Event; +use winit::event_loop::EventLoop; use raidillon_ecs::{Transform, ModelID}; use raidillon_ecs::components::ModelHandle; use crate::model::Model; @@ -21,7 +23,7 @@ pub struct BasicMeshRenderingSystem { } impl RenderingSystem for BasicMeshRenderingSystem { - fn initialize(display: &Display, _window: Arc>) -> Self { + fn initialize(display: &Display, _window: Arc>, event_loop: &EventLoop<()>) -> Self { const VERT_SRC: &str = include_shader!("gl_textured.vert"); const FRAG_SRC: &str = include_shader!("gl_textured.frag"); diff --git a/glium_platform/src/render/debug_ui.rs b/glium_platform/src/render/debug_ui.rs deleted file mode 100644 index ac4a06e..0000000 --- a/glium_platform/src/render/debug_ui.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::sync::{Arc, Mutex}; -use std::time::Instant; -use glium::Display; -use glium::glutin::surface::WindowSurface; -use imgui::{Context as ImguiContext}; -use imgui_winit_support::{HiDpiMode, WinitPlatform}; -use imgui_glium_renderer::Renderer as ImguiGliumRenderer; -use winit::window::Window; -use winit::event::Event; -use glium::Frame; -use crate::RenderingSystem; -use crate::system::RenderingContext; - -pub struct ImguiBridge { - imgui: ImguiContext, - platform: WinitPlatform, - renderer: ImguiGliumRenderer, - last_frame: Instant, - rendered_this_frame: bool, -} - -impl RenderingSystem for ImguiBridge { - fn handle_event(&mut self, window: Arc>, event: Event<()>) { - let window = window.lock().unwrap(); - self.platform.handle_event(self.imgui.io_mut(), &*window, &event); - } - - fn prepare_frame(&mut self, window: Arc>) { - self.rendered_this_frame = false; - let now = Instant::now(); - self.imgui.io_mut().update_delta_time(now - self.last_frame); - self.last_frame = now; - let window = window.lock().unwrap(); - self.platform - .prepare_frame(self.imgui.io_mut(), &*window) - .expect("Failed to prepare frame"); - } - - fn render(&mut self, ctx: &mut RenderingContext) { - if self.rendered_this_frame { return; } - self.rendered_this_frame = true; - - let ui = self.imgui.frame(); - ctx.debug_ui_buffer.write_buffer(&ui); - - { - let window = ctx.window.lock().unwrap(); - self.platform.prepare_render(&ui, &*window); - } - let draw_data = self.imgui.render(); - if draw_data.total_vtx_count == 0 && draw_data.total_idx_count == 0 { - return; - } - - self.renderer.render(ctx.target, draw_data).expect("imgui rendering failed"); - } - - fn initialize(display: &Display, window: Arc>) -> Self { - let mut imgui = ImguiContext::create(); - imgui.set_ini_filename(None); - let mut platform = WinitPlatform::new(&mut imgui); - let window = window.lock().unwrap(); - platform.attach_window(imgui.io_mut(), &*window, HiDpiMode::Default); - imgui.fonts().add_font(&[imgui::FontSource::DefaultFontData { config: None }]); - let renderer = ImguiGliumRenderer::new(&mut imgui, display).unwrap(); - - Self { - imgui, - platform, - renderer, - last_frame: Instant::now(), - rendered_this_frame: false, - } - } -} diff --git a/glium_platform/src/render/egui.rs b/glium_platform/src/render/egui.rs new file mode 100644 index 0000000..e754ba3 --- /dev/null +++ b/glium_platform/src/render/egui.rs @@ -0,0 +1,46 @@ +use std::sync::{Arc, Mutex}; +use egui::ViewportId; +use glium::{Display, Frame}; +use glium::glutin::surface::WindowSurface; +use winit::window::Window; +use crate::RenderingSystem; +use crate::system::RenderingContext; +use egui_glium::EguiGlium; +use winit::event::{Event, WindowEvent}; +use winit::event_loop::EventLoop; + +pub struct EguiRenderer { + egui_glium: EguiGlium, +} + +impl RenderingSystem for EguiRenderer { + fn initialize(display: &Display, window: Arc>, event_loop: &EventLoop<()>) -> Self + where + Self: Sized, + { + let window = window.lock().unwrap(); + let egui_glium = EguiGlium::new(ViewportId::ROOT, &display, &window, &event_loop); + + Self { egui_glium } + } + + fn render(&mut self, ctx: &mut RenderingContext) { + let window = ctx.window.lock().unwrap(); + + self.egui_glium.run(&window, |egui_ctx| { + ctx.egui_queue.borrow_mut().run(egui_ctx); + }); + + self.egui_glium.paint(ctx.display, ctx.target); + } + + fn handle_event(&mut self, window: Arc>, event: Event<()>) { + let window = window.lock().unwrap(); + match event { + Event::WindowEvent { event, .. } => { + let _ = self.egui_glium.on_event(&window, &event); + } + _ => {}, + } + } +} diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index f49e308..fdfa1ea 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,6 +1,7 @@ mod basic; -pub mod debug_ui; mod skybox; +mod egui; pub use basic::BasicMeshRenderingSystem; pub use skybox::SkyboxRenderingSystem; +pub use egui::EguiRenderer; diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs index ac22210..94832d0 100644 --- a/glium_platform/src/render/skybox.rs +++ b/glium_platform/src/render/skybox.rs @@ -8,6 +8,7 @@ use glium::index::PrimitiveType; use glium::texture::{RawImage2d, SrgbTexture2d, Texture2d}; use glium::uniform; use glam::{Mat4, Vec2, Vec3}; +use winit::event_loop::EventLoop; use raidillon_assets::include_shader; use crate::system::RenderingContext; use crate::RenderingSystem; @@ -109,7 +110,7 @@ impl SkyboxRenderingSystem { } impl RenderingSystem for SkyboxRenderingSystem { - fn initialize(display: &Display, _window: Arc>) -> Self { + fn initialize(display: &Display, _window: Arc>, event_loop: &EventLoop<()>) -> Self { const VERT_SRC: &str = include_shader!("skybox.vert"); const FRAG_SRC: &str = include_shader!("skybox.frag"); let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap(); diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 51a292f..5d7f197 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -6,16 +6,19 @@ use indexmap::IndexMap; use glium::{Display, Frame}; use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; -use raidillon_core::{define_typemap, DebugUIBuffer}; +use raidillon_core::{define_typemap, DebugUIBuffer, EguiQueue}; use raidillon_core::scene::Scene; use glam::Vec3; +use winit::event_loop::EventLoop; pub struct RenderingContext<'a> { pub scene: &'a Scene, pub target: &'a mut Frame, pub window: Arc>, + pub display: &'a Display, pub asset_manager: ModelManagerRef, pub debug_ui_buffer: &'a DebugUIBuffer, + pub egui_queue: Rc>, pub env_light_dir: Vec3, } @@ -30,7 +33,7 @@ pub trait RenderingSystem { } fn prepare_frame(&mut self, _window: Arc>) {} fn render(&mut self, ctx: &mut RenderingContext); - fn initialize(display: &Display, window: Arc>) -> Self + fn initialize(display: &Display, window: Arc>, event_loop: &EventLoop<()>) -> Self where Self: Sized; } @@ -48,11 +51,11 @@ impl RenderingSystemManager { } } - pub fn add(&mut self, display: &Display, window: Arc>) + pub fn add(&mut self, display: &Display, window: Arc>, event_loop: &EventLoop<()>) where R: RenderingSystem + 'static, { - let system = R::initialize(display, window); + let system = R::initialize(display, window, event_loop); self.systems.insert(TypeId::of::(), Box::new(system)); } diff --git a/platform/src/context.rs b/platform/src/context.rs index 892b69e..b78ea94 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -1,6 +1,8 @@ +use std::{cell::RefCell, rc::Rc}; use std::sync::{Arc, Mutex}; use winit::event::Event; use raidillon_assets::ModelManagerRef; +use raidillon_core::EguiQueue; #[derive(Clone)] pub struct PlatformContext { @@ -10,6 +12,7 @@ pub struct PlatformContext { pub frame_height: f32, pub time_ctx: TimeContext, pub window: Arc>, + pub egui_queue: Rc>, } #[derive(Clone)] From 4b97bd98d2a99eb0c5e1c8cf35d35f81ec3f1e0f Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 18:22:09 +0300 Subject: [PATCH 18/38] Fix #9, add tests to typemap.rs --- core/src/utils/typemap.rs | 116 ++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/core/src/utils/typemap.rs b/core/src/utils/typemap.rs index bd28a66..c04c2c9 100644 --- a/core/src/utils/typemap.rs +++ b/core/src/utils/typemap.rs @@ -101,7 +101,7 @@ macro_rules! define_typemap { { type Output<'a> = (&'a mut A,); fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; Some((a,)) @@ -133,8 +133,8 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::()]; if ids[0] == ids[1] { return None; } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -170,9 +170,9 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; if ids[0] == ids[1] || ids[0] == ids[2] || ids[1] == ids[2] { return None; } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -212,10 +212,10 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -259,11 +259,11 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -311,12 +311,12 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -368,13 +368,13 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -430,14 +430,14 @@ macro_rules! define_typemap { fn get_from_mut<'a>(map: &'a mut HashMap>) -> Option> { let ids = [TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::(), TypeId::of::()]; for i in 0..ids.len() { for j in (i+1)..ids.len() { if ids[i] == ids[j] { return None; } } } - let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; - let ptr_h = { map.get_mut(&TypeId::of::()).map(|v| v as *mut dyn Any) }?; + let ptr_a = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_b = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_c = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_d = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_e = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_f = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_g = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; + let ptr_h = { map.get_mut(&TypeId::of::()).map(|v| v.as_mut() as *mut dyn Any) }?; unsafe { let a = (&mut *ptr_a).downcast_mut::()?; let b = (&mut *ptr_b).downcast_mut::()?; @@ -498,3 +498,47 @@ macro_rules! define_typemap { // Self::new() // } // } + +#[cfg(test)] +mod tests { + use crate::define_typemap; + + define_typemap!(TestMap,); + + #[test] + fn get_test() { + let mut tm = TestMap::new(); + tm.insert::(42); + let v = tm.get::().expect("value should be present"); + assert_eq!(*v, 42); + } + + #[test] + fn get_many_test() { + let mut tm = TestMap::new(); + tm.insert::(1); + tm.insert::(-2); + tm.insert::(3.5); + + let (a, b, c) = tm + .get_many::<(u32, i32, f32)>() + .expect("all values should be present"); + assert_eq!((*a, *b, *c), (1, -2, 3.5)); + } + + #[test] + fn get_many_mut_test() { + let mut tm = TestMap::new(); + + tm.insert::(-2); + tm.insert::(1); + tm.insert::(3.5); + + let (a, b, c) = tm + .get_many_mut::<(i32, u32, f32)>() + .expect("all values should be present"); + *a = 4; + *b = 5; + *c = 6.5; + } +} From 13aefcc86fb7e45afc9534b6cfac36147b3c056a Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 18:34:48 +0300 Subject: [PATCH 19/38] Remove imgui from the entire project --- Cargo.lock | 38 -------------------------- core/Cargo.toml | 1 - core/src/debug_ui.rs | 50 ---------------------------------- core/src/engine.rs | 3 -- core/src/lib.rs | 2 -- core/src/scene.rs | 7 ++--- engine/src/engine.rs | 11 +------- engine/src/system.rs | 2 -- game/src/main.rs | 2 +- game/src/systems/keybinds.rs | 16 +++++++---- glium_platform/src/platform.rs | 6 +--- glium_platform/src/system.rs | 3 +- 12 files changed, 16 insertions(+), 125 deletions(-) delete mode 100644 core/src/debug_ui.rs diff --git a/Cargo.lock b/Cargo.lock index 3e9b382..877142c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,12 +331,6 @@ dependencies = [ "libc", ] -[[package]] -name = "chlorine" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d31b1d19317b4777ec879192d3745bd97d05262b4b19cb1dda284b9d22f19" - [[package]] name = "clipboard-win" version = "5.4.1" @@ -1206,31 +1200,6 @@ dependencies = [ "zune-jpeg", ] -[[package]] -name = "imgui" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8addafa5cecf0515812226e806913814e02ce38d10215778082af5174abe5669" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "imgui-sys", - "mint", - "parking_lot", -] - -[[package]] -name = "imgui-sys" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead193f9f4b60398e8b8f4ab1483e2321640d87aeebdaa3e5f44c55633ccd804" -dependencies = [ - "cc", - "cfg-if", - "chlorine", - "mint", -] - [[package]] name = "indexmap" version = "2.10.0" @@ -1423,12 +1392,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "mint" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" - [[package]] name = "moxcms" version = "0.7.5" @@ -2130,7 +2093,6 @@ version = "0.1.0" dependencies = [ "egui", "hecs", - "imgui", "indexmap", "raidillon_assets", "winit", diff --git a/core/Cargo.toml b/core/Cargo.toml index a8d650d..9b3f82f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -8,5 +8,4 @@ hecs = "0.10.5" indexmap = "2.10.0" raidillon_assets = { path = "../asset" } winit = "0.30.12" -imgui = "0.12.0" egui = "0.33.2" \ No newline at end of file diff --git a/core/src/debug_ui.rs b/core/src/debug_ui.rs deleted file mode 100644 index df3b9f1..0000000 --- a/core/src/debug_ui.rs +++ /dev/null @@ -1,50 +0,0 @@ -pub enum UICommand { - Text(String), - Separator, - SliderF32 { label: String, min: f32, max: f32, value: std::rc::Rc> }, -} - -pub struct DebugUIBuffer { - cmds: Vec, -} - -impl DebugUIBuffer { - pub fn new() -> DebugUIBuffer { - DebugUIBuffer { cmds: vec![] } - } - - // Commands - pub fn text(&mut self, text: String) { - self.cmds.push(UICommand::Text(text)); - } - - pub fn separator(&mut self) { - self.cmds.push(UICommand::Separator); - } - - pub fn slider_f32>(&mut self, label: T, min: f32, max: f32, value: std::rc::Rc>) { - self.cmds.push(UICommand::SliderF32 { label: label.into(), min, max, value }); - } - // End of commands - - pub fn write_buffer(&self, ui: &imgui::Ui) { - for cmd in &self.cmds { - match cmd { - UICommand::Text(s) => { - ui.text(s); - } - UICommand::Separator => { - ui.separator(); - } - UICommand::SliderF32 { label, min, max, value } => { - let mut v = value.borrow_mut(); - ui.slider_config(label.as_str(), *min, *max).build(&mut *v); - } - } - } - } - - pub fn reset_buffer(&mut self) { - self.cmds = vec![]; - } -} diff --git a/core/src/engine.rs b/core/src/engine.rs index 1308e20..5890063 100644 --- a/core/src/engine.rs +++ b/core/src/engine.rs @@ -1,4 +1,3 @@ -use crate::DebugUIBuffer; use crate::scene::Scene; pub trait EngineTrait { @@ -10,6 +9,4 @@ pub trait EngineTrait { fn handle_event(&mut self, platform_context: Self::PlatformCtx); fn current_scene_mut(&mut self) -> &mut Scene; fn current_scene(&self) -> &Scene; - fn get_debug_ui_buffer(&self) -> &DebugUIBuffer; - fn reset_debug_ui_buffer(&mut self); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 61f5286..759393d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,9 +1,7 @@ pub mod engine; -pub mod debug_ui; pub mod time; pub mod utils; pub mod scene; mod egui_queue; -pub use debug_ui::*; pub use egui_queue::EguiQueue; diff --git a/core/src/scene.rs b/core/src/scene.rs index 1a075bf..2f748c4 100644 --- a/core/src/scene.rs +++ b/core/src/scene.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use crate::{define_typemap, DebugUIBuffer}; +use crate::{define_typemap}; pub struct Scene { pub title: String, @@ -23,10 +23,7 @@ impl Scene { s } - pub fn load_default_resources(&mut self) { - let dbg = DebugUIBuffer::new(); - self.resources.insert(dbg); - } + pub fn load_default_resources(&mut self) {} } impl Scene {} diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 398920c..ec9841f 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use raidillon_core::scene::{Scene, SceneManager}; use crate::system::{SystemContext, SystemManager}; use raidillon_platform::PlatformContext; -use raidillon_core::{define_typemap, DebugUIBuffer}; +use raidillon_core::{define_typemap}; use raidillon_core::engine::EngineTrait; use crate::input::InputState; use crate::resources::EngineResources; @@ -50,7 +50,6 @@ impl EngineTrait for Engine { /// Update the engine fn frame_update(&mut self, platform_context: PlatformContext) { - self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); self.resources.insert(platform_context); for system in self.system_manager.systems.values_mut() { @@ -86,12 +85,4 @@ impl EngineTrait for Engine { fn current_scene(&self) -> &Scene { self.scene_manager.current() } - - fn get_debug_ui_buffer(&self) -> &DebugUIBuffer { - self.scene_manager.current().resources.get::().unwrap() - } - - fn reset_debug_ui_buffer(&mut self) { - self.scene_manager.current_mut().resources.get_mut::().unwrap().reset_buffer(); - } } diff --git a/engine/src/system.rs b/engine/src/system.rs index 55cccb1..420da1b 100644 --- a/engine/src/system.rs +++ b/engine/src/system.rs @@ -1,6 +1,5 @@ use indexmap::IndexMap; use raidillon_core::scene::Scene; -use raidillon_core::DebugUIBuffer; use raidillon_platform::PlatformContext; use std::any::TypeId; use std::cell::RefCell; @@ -11,7 +10,6 @@ use crate::resources::EngineResources; pub struct SystemContext<'a> { pub scene: &'a mut Scene, pub platform_context: PlatformContext, - pub debug_ui_buffer: Rc>, pub input_state: Rc>, } diff --git a/game/src/main.rs b/game/src/main.rs index 42ab2d3..e81d77c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -16,7 +16,7 @@ use raidillon_glium::GliumPlatform; use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; -use raidillon_core::{DebugUIBuffer, EguiQueue}; +use raidillon_core::{EguiQueue}; use systems::debug_camera::FPSDebugCameraSystem; use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index e701dad..10bbbe8 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -1,5 +1,4 @@ use winit::keyboard::KeyCode; -use raidillon_core::DebugUIBuffer; use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; use raidillon_engine::{EngineResources, InputState}; @@ -24,16 +23,21 @@ impl System for KeybindsSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let dbg_ui = scene.resources.get_mut::().unwrap(); - - dbg_ui.text("F5 to switch camera".to_owned()); - + let pctx = res.get_mut::().unwrap(); let mut q = scene.world.query::<(&Camera, &CameraMode)>(); let (cam_ent, (cam, cam_mode)) = q .iter() .next() .unwrap(); - dbg_ui.text(format!("Camera Mode: {:?}", cam_mode)); + + let cam_mode_str = format!("Camera Mode: {:?}", cam_mode); + + pctx.egui_queue.borrow_mut().queue(move |egui_ctx| { + egui::Window::new("Camera").show(egui_ctx, |ui| { + ui.label("F5 to switch camera"); + ui.label(cam_mode_str); + }); + }); } } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index efc1543..1b1ba08 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -103,17 +103,13 @@ impl> Platform for GliumPlatfor WindowEvent::RedrawRequested => { let mut target = self.display.draw(); target.clear_color_and_depth((0.1, 0.1, 0.15, 1.0), 1.0); - let (scene, debug_ui_buffer) = ( - self.engine.current_scene(), - self.engine.get_debug_ui_buffer(), - ); + let scene = self.engine.current_scene(); let mut context = RenderingContext { scene, target: &mut target, display: &self.display, asset_manager: self.asset_manager.clone(), window: self.window.clone(), - debug_ui_buffer, egui_queue: self.egui_queue.clone(), env_light_dir: Vec3::new(0.0, -1.0, 0.0), }; diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 5d7f197..d94e9c3 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -6,7 +6,7 @@ use indexmap::IndexMap; use glium::{Display, Frame}; use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; -use raidillon_core::{define_typemap, DebugUIBuffer, EguiQueue}; +use raidillon_core::{define_typemap, EguiQueue}; use raidillon_core::scene::Scene; use glam::Vec3; use winit::event_loop::EventLoop; @@ -17,7 +17,6 @@ pub struct RenderingContext<'a> { pub window: Arc>, pub display: &'a Display, pub asset_manager: ModelManagerRef, - pub debug_ui_buffer: &'a DebugUIBuffer, pub egui_queue: Rc>, pub env_light_dir: Vec3, } From db1b427e2ae2f905f1b0413953370d97fb027f9a Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 19 Oct 2025 17:40:51 +0300 Subject: [PATCH 20/38] Physics Support - NEW CRATE: raidillon_physics. - Added new models to be able to test the physics support. - Added a new system "PhysicsSystem" to apply physics calculations to the ECS world. - NEW COMPONENT: RigidBodyComponent --- Cargo.lock | 521 +++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + assets/models/plane.glb | 3 + assets/models/sphere.glb | 3 + ecs/Cargo.toml | 1 + ecs/src/components.rs | 3 + game/Cargo.toml | 2 + game/src/main.rs | 75 +++--- game/src/systems/mod.rs | 3 + game/src/systems/physics.rs | 34 +++ physics/Cargo.toml | 9 + physics/src/lib.rs | 3 + physics/src/physics.rs | 82 ++++++ 13 files changed, 697 insertions(+), 43 deletions(-) create mode 100644 assets/models/plane.glb create mode 100644 assets/models/sphere.glb create mode 100644 game/src/systems/mod.rs create mode 100644 game/src/systems/physics.rs create mode 100644 physics/Cargo.toml create mode 100644 physics/src/lib.rs create mode 100644 physics/src/physics.rs diff --git a/Cargo.lock b/Cargo.lock index e1b6adb..525e6ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.6.0" @@ -64,7 +70,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -79,6 +85,15 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -130,6 +145,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bit_field" version = "0.10.3" @@ -198,7 +219,7 @@ dependencies = [ "polling", "rustix 0.38.44", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -393,12 +414,33 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "dpi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -455,6 +497,18 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.5.0" @@ -521,6 +575,96 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + [[package]] name = "glam" version = "0.30.8" @@ -659,6 +803,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -673,6 +826,30 @@ name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] [[package]] name = "hecs" @@ -784,7 +961,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -849,6 +1026,12 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.9" @@ -888,6 +1071,16 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.5" @@ -938,6 +1131,49 @@ dependencies = [ "pxfm", ] +[[package]] +name = "nalgebra" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" +dependencies = [ + "approx", + "glam 0.14.0", + "glam 0.15.2", + "glam 0.16.0", + "glam 0.17.3", + "glam 0.18.0", + "glam 0.19.0", + "glam 0.20.5", + "glam 0.21.3", + "glam 0.22.0", + "glam 0.23.0", + "glam 0.24.2", + "glam 0.25.0", + "glam 0.27.0", + "glam 0.28.0", + "glam 0.29.3", + "glam 0.30.8", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ndk" version = "0.9.0" @@ -950,7 +1186,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -968,6 +1204,56 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -975,6 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1269,6 +1556,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -1301,6 +1597,40 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parry3d" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "017be73f24c8ca8b10f9727616e5cb9af82b98488cc6d5eea468e727ffa780ca" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.9.1", + "downcast-rs 2.0.2", + "either", + "ena", + "foldhash 0.2.0", + "hashbrown 0.16.0", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rstar", + "simba", + "slab", + "smallvec", + "spade", + "static_assertions", + "thiserror 2.0.17", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1384,6 +1714,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "pxfm" version = "0.1.24" @@ -1436,15 +1785,16 @@ dependencies = [ name = "raidillon_ecs" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", + "rapier3d", ] [[package]] name = "raidillon_engine" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "hecs", "indexmap", "raidillon_assets", @@ -1457,13 +1807,15 @@ dependencies = [ name = "raidillon_game" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", "raidillon_core", "raidillon_ecs", "raidillon_engine", "raidillon_glium", + "raidillon_physics", "raidillon_platform", + "rapier3d", "winit", ] @@ -1473,7 +1825,7 @@ version = "0.1.0" dependencies = [ "anyhow", "exr", - "glam", + "glam 0.30.8", "glium", "gltf", "image", @@ -1489,22 +1841,62 @@ dependencies = [ "winit", ] +[[package]] +name = "raidillon_physics" +version = "0.1.0" +dependencies = [ + "glam 0.30.8", + "raidillon_ecs", + "rapier3d", +] + [[package]] name = "raidillon_platform" version = "0.1.0" dependencies = [ - "glam", + "glam 0.30.8", "raidillon_assets", "raidillon_core", "winit", ] +[[package]] +name = "rapier3d" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd27b8eb36d0833fa0f2aea40164fabfad0fc34b9932286ae9e84f3452f5364" +dependencies = [ + "approx", + "arrayvec", + "bit-vec", + "bitflags 2.9.1", + "downcast-rs 2.0.2", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "parry3d", + "profiling", + "rustc-hash", + "simba", + "static_assertions", + "thiserror 2.0.17", + "wide", +] + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon-core" version = "1.13.0" @@ -1533,12 +1925,35 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "robust" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" + +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless", + "num-traits", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "0.38.44" @@ -1577,6 +1992,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1649,6 +2073,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -1681,7 +2118,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.44", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -1701,12 +2138,36 @@ dependencies = [ "serde", ] +[[package]] +name = "spade" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a14e31a007e9f85c32784b04f89e6e194bb252a4d41b4a8ccd9e77245d901c8c" +dependencies = [ + "hashbrown 0.15.4", + "num-traits", + "robust", + "smallvec", +] + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strict-num" version = "0.1.1" @@ -1730,7 +2191,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", ] [[package]] @@ -1744,6 +2214,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1808,6 +2289,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1929,7 +2416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", - "downcast-rs", + "downcast-rs 1.2.1", "rustix 1.0.8", "scoped-tls", "smallvec", @@ -2051,6 +2538,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 23332d0..dd544ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ "game", "ecs", "engine", + "physics", ] diff --git a/assets/models/plane.glb b/assets/models/plane.glb new file mode 100644 index 0000000..e755152 --- /dev/null +++ b/assets/models/plane.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b54f00f327a5edc76c03f83d4efa2a4400215d7ca9c961b38537e7f836bca710 +size 1240 diff --git a/assets/models/sphere.glb b/assets/models/sphere.glb new file mode 100644 index 0000000..ea99045 --- /dev/null +++ b/assets/models/sphere.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78583ec562098f7fa4eaf95c6e21b7d1ced53416eb4c71351e246d53f93aa0a3 +size 24744 diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml index 0e1efb5..a47173d 100644 --- a/ecs/Cargo.toml +++ b/ecs/Cargo.toml @@ -6,3 +6,4 @@ edition = "2024" [dependencies] glam = "0.30.5" raidillon_assets = { path = "../asset" } +rapier3d = "0.30.1" diff --git a/ecs/src/components.rs b/ecs/src/components.rs index 758f0f4..fa30cc2 100644 --- a/ecs/src/components.rs +++ b/ecs/src/components.rs @@ -15,3 +15,6 @@ impl Transform { } pub struct ModelHandle(pub ModelID); + +#[derive(Copy, Clone)] +pub struct RigidBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); diff --git a/game/Cargo.toml b/game/Cargo.toml index 484e434..9bb72c4 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -13,6 +13,8 @@ raidillon_platform = { path = "../platform" } raidillon_assets = { path = "../asset" } raidillon_ecs = { path = "../ecs" } raidillon_engine = { path = "../engine" } +raidillon_physics = { path = "../physics" } raidillon_glium = { path = "../glium_platform", optional = true } glam = "0.30.5" winit = "0.30.12" +rapier3d = "0.30.1" diff --git a/game/src/main.rs b/game/src/main.rs index ad230cf..8ddce6a 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,11 +1,14 @@ +mod systems; use std::fmt::format; use glam::{Quat, Vec3}; +use rapier3d::dynamics::RigidBodyType; +use rapier3d::prelude::ColliderBuilder; use raidillon_engine::{Engine, system::System, EngineResources}; use raidillon_engine::system::SystemContext; use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::ModelHandle; +use raidillon_ecs::components::{ModelHandle, RigidBodyComponent}; use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] @@ -15,9 +18,11 @@ use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_physics::Physics; +use crate::systems::PhysicsSystem; -const TEST_GLTF: &str = "pink-monkey.gltf"; - +const TEST_GLTF: &str = "sphere.glb"; +const PLANE_GLTF: &str = "plane.glb"; const MAIN_SCENE_ID: &str = "main_scene"; #[derive(Default)] @@ -40,28 +45,45 @@ impl System for UpdateAspectRatioSystem { } #[derive(Default)] -struct RenderingTestSystem { - rotation_speed: std::rc::Rc>, -} +struct RenderingTestSystem; impl System for RenderingTestSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); + let pctx = res.get::().expect("PlatformContext missing").clone(); + let physics = res.get_mut::().expect("Physics missing"); - self.rotation_speed = std::rc::Rc::new(std::cell::RefCell::new(5.0)); - - let mut am = pctx.asset_manager.borrow_mut(); - - am.load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); - - scene.world.spawn(( - Transform { - translation: Vec3::new(0.0, 0.0, 0.0), - rotation: Quat::IDENTITY, - scale: Vec3::new(1.0, 1.0, 1.0), - }, - ModelHandle(TEST_GLTF), - )); + // Spawn Sphere + { + let tr = Transform { + translation: Vec3::new(0.0, 5.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(1.0, 1.0, 1.0), + }; + let collider = ColliderBuilder::ball(1.0).build(); + let rb_handle = physics.add_rigid_body(RigidBodyType::Dynamic, tr, collider); + pctx.asset_manager.borrow_mut().load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); + scene.world.spawn(( + tr, + ModelHandle(TEST_GLTF), + RigidBodyComponent(rb_handle), + )); + } + // Spawn Plane + { + let tr = Transform { + translation: Vec3::new(0.0, 0.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(10.0, 1.0, 10.0), + }; + let collider = ColliderBuilder::cuboid(10.0, 0.01, 10.0).build(); + let rb_handle = physics.add_rigid_body(RigidBodyType::Fixed, tr, collider); + pctx.asset_manager.borrow_mut().load_gltf(PLANE_GLTF, &model_path(PLANE_GLTF)); + scene.world.spawn(( + tr, + ModelHandle(PLANE_GLTF), + RigidBodyComponent(rb_handle), + )); + } } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { @@ -72,22 +94,13 @@ impl System for RenderingTestSystem { dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); - dbg_ui.slider_f32("Rotation Speed", -10.0, 10.0, self.rotation_speed.clone()); } - - fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - - scene.world.query_mut::<(&mut Transform, &ModelHandle)>().into_iter().for_each(|(_, (t, _))| { - t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * pctx.time_ctx.fixed_dt); - }); - } - } fn main() { let mut engine = Engine::new(); // Define systems + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs new file mode 100644 index 0000000..0ab4645 --- /dev/null +++ b/game/src/systems/mod.rs @@ -0,0 +1,3 @@ +mod physics; + +pub use physics::PhysicsSystem; diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs new file mode 100644 index 0000000..c5d4807 --- /dev/null +++ b/game/src/systems/physics.rs @@ -0,0 +1,34 @@ +use raidillon_core::scene::Scene; +use raidillon_ecs::components::RigidBodyComponent; +use raidillon_ecs::Transform; +use raidillon_engine::EngineResources; +use raidillon_engine::system::System; +use raidillon_physics::Physics; +use raidillon_platform::PlatformContext; + +#[derive(Default)] +pub struct PhysicsSystem; + +impl System for PhysicsSystem { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let p = Physics::default(); + res.insert(p); + } + + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().expect("PlatformContext missing").clone(); + let physics = res.get_mut::().expect("Physics missing"); + physics.step(pctx.time_ctx.fixed_dt); + + let mut query = scene.world.query::<(&mut Transform, &RigidBodyComponent)>(); + for (_ent, (tr, rb_component)) in query.iter() { + if let Some(body) = physics.get_rigid_body(rb_component.0) { + let pos = body.position(); + let translation = Physics::rapier_translation_to_glam(&pos.translation.vector); + let rotation = Physics::rapier_rotation_to_glam(&pos.rotation); + tr.translation = translation; + tr.rotation = rotation; + } + } + } +} diff --git a/physics/Cargo.toml b/physics/Cargo.toml new file mode 100644 index 0000000..dc34e98 --- /dev/null +++ b/physics/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "raidillon_physics" +version = "0.1.0" +edition = "2024" + +[dependencies] +rapier3d = "0.30.1" +raidillon_ecs = { path = "../ecs" } +glam = "0.30.8" \ No newline at end of file diff --git a/physics/src/lib.rs b/physics/src/lib.rs new file mode 100644 index 0000000..a5dafc6 --- /dev/null +++ b/physics/src/lib.rs @@ -0,0 +1,3 @@ +mod physics; + +pub use crate::physics::Physics; diff --git a/physics/src/physics.rs b/physics/src/physics.rs new file mode 100644 index 0000000..009984c --- /dev/null +++ b/physics/src/physics.rs @@ -0,0 +1,82 @@ +use glam::{Quat, Vec3}; +use rapier3d::na::{Quaternion, UnitQuaternion}; +use rapier3d::prelude::*; +use raidillon_ecs::Transform; + +/// Tiny wrapper around rapier3d. +pub struct Physics { + rigid_body_set: RigidBodySet, + collider_set: ColliderSet, + physics_pipeline: PhysicsPipeline, + island_manager: IslandManager, + broad_phase: DefaultBroadPhase, + narrow_phase: NarrowPhase, + impulse_joint_set: ImpulseJointSet, + multibody_joint_set: MultibodyJointSet, + ccd_solver: CCDSolver, + gravity: Vector, + integration_parameters: IntegrationParameters, +} + +impl Default for Physics { + fn default() -> Self { + Self { + gravity: vector![0.0, -9.81, 0.0], + rigid_body_set: Default::default(), + collider_set: Default::default(), + physics_pipeline: Default::default(), + island_manager: Default::default(), + broad_phase: Default::default(), + narrow_phase: Default::default(), + impulse_joint_set: Default::default(), + multibody_joint_set: Default::default(), + ccd_solver: Default::default(), + integration_parameters: Default::default(), + } + } +} + +impl Physics { + pub fn step(&mut self, dt: f32) { + self.integration_parameters.dt = dt; + self.physics_pipeline.step( + &self.gravity, + &self.integration_parameters, + &mut self.island_manager, + &mut self.broad_phase, + &mut self.narrow_phase, + &mut self.rigid_body_set, + &mut self.collider_set, + &mut self.impulse_joint_set, + &mut self.multibody_joint_set, + &mut self.ccd_solver, + &(), + &(), + ); + } + + pub fn add_rigid_body(&mut self, kind: RigidBodyType, transform: Transform, collider: Collider) -> RigidBodyHandle { + let rb = RigidBodyBuilder::new(kind) + .translation(vector![transform.translation.x, transform.translation.y, transform.translation.z]) + .build(); + let rb_handle = self.rigid_body_set.insert(rb); + self.collider_set.insert_with_parent(collider, rb_handle, &mut self.rigid_body_set); + rb_handle + } + + pub fn get_rigid_body(&self, handle: RigidBodyHandle) -> Option<&RigidBody> { + self.rigid_body_set.get(handle) + } + + pub fn get_rigid_body_mut(&mut self, handle: RigidBodyHandle) -> Option<&mut RigidBody> { + self.rigid_body_set.get_mut(handle) + } + + pub fn rapier_translation_to_glam(v: &Vector) -> Vec3 { + Vec3::new(v.x, v.y, v.z) + } + + pub fn rapier_rotation_to_glam(r: &UnitQuaternion) -> Quat { + Quat::from_xyzw(r.i, r.j, r.k, r.w) + } +} From f503c70a9ba89dc2730a9de5ec0761cb569ef9f0 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 26 Oct 2025 18:29:59 +0300 Subject: [PATCH 21/38] MASSIVE Kinematic Character Controller Update - NEW kinematic character controller powered by rapier3d at kinematic_character_controller.rs - NEW camera modes. The ability to switch between the free debug camera and new character controller. - NEW keybinds system to support the camera mode swap --- Cargo.lock | 2 + ecs/src/components.rs | 10 + engine/Cargo.toml | 1 + engine/src/lib.rs | 1 + engine/src/systems/fps_camera.rs | 29 +-- game/Cargo.toml | 1 + game/src/main.rs | 35 ++- game/src/systems/keybinds.rs | 52 +++++ .../systems/kinematic_character_controller.rs | 200 ++++++++++++++++++ game/src/systems/mod.rs | 4 + game/src/systems/physics.rs | 6 +- physics/src/physics.rs | 8 +- 12 files changed, 323 insertions(+), 26 deletions(-) create mode 100644 game/src/systems/keybinds.rs create mode 100644 game/src/systems/kinematic_character_controller.rs diff --git a/Cargo.lock b/Cargo.lock index 525e6ad..91f50f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1799,6 +1799,7 @@ dependencies = [ "indexmap", "raidillon_assets", "raidillon_core", + "raidillon_ecs", "raidillon_platform", "winit", ] @@ -1808,6 +1809,7 @@ name = "raidillon_game" version = "0.1.0" dependencies = [ "glam 0.30.8", + "hecs", "raidillon_assets", "raidillon_core", "raidillon_ecs", diff --git a/ecs/src/components.rs b/ecs/src/components.rs index fa30cc2..3c20ec7 100644 --- a/ecs/src/components.rs +++ b/ecs/src/components.rs @@ -18,3 +18,13 @@ pub struct ModelHandle(pub ModelID); #[derive(Copy, Clone)] pub struct RigidBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); + +#[derive(Copy, Clone)] +pub struct CharacterBodyComponent(pub rapier3d::dynamics::RigidBodyHandle); + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +pub enum CameraMode { + #[default] + Kinematic, + Debug, +} diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 98202e4..79a2de9 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" raidillon_assets = { path = "../asset" } raidillon_core = { path = "../core" } raidillon_platform = { path = "../platform" } +raidillon_ecs = { path = "../ecs" } winit = "0.30.12" hecs = "0.10.5" indexmap = "2.10.0" diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 9a87556..a7ad724 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -6,3 +6,4 @@ mod resources; pub use crate::engine::Engine; pub use crate::resources::EngineResources; +pub use input::InputState; diff --git a/engine/src/systems/fps_camera.rs b/engine/src/systems/fps_camera.rs index 0d98577..5ff9cc7 100644 --- a/engine/src/systems/fps_camera.rs +++ b/engine/src/systems/fps_camera.rs @@ -9,6 +9,7 @@ use raidillon_platform::{Camera, PlatformContext}; use crate::input::InputState; use crate::resources::EngineResources; use raidillon_core::scene::Scene; +use raidillon_ecs::components::CameraMode; pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), @@ -35,20 +36,10 @@ impl Default for FPSDebugCameraSystem { } impl System for FPSDebugCameraSystem { - fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get::().unwrap(); - scene.world.spawn((Camera { - eye: Vec3::new(0.0, 0.0, 2.0), - center: Vec3::ZERO, - up: Vec3::Y, - fovy: 60_f32.to_radians(), - aspect: pctx.frame_width / pctx.frame_height, - znear: 0.1, - zfar: 100.0, - },)); - } - fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } let pctx = res.get::().unwrap(); let event2 = pctx.current_event.clone(); match event2 { @@ -92,6 +83,9 @@ impl System for FPSDebugCameraSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); if self.mouse_enabled { @@ -134,4 +128,13 @@ impl FPSDebugCameraSystem { yaw_rad.sin() * pitch_rad.cos(), ).normalize() } + + fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == CameraMode::Debug + } } diff --git a/game/Cargo.toml b/game/Cargo.toml index 9bb72c4..9579d22 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -18,3 +18,4 @@ raidillon_glium = { path = "../glium_platform", optional = true } glam = "0.30.5" winit = "0.30.12" rapier3d = "0.30.1" +hecs = "0.10.5" diff --git a/game/src/main.rs b/game/src/main.rs index 8ddce6a..a5a3d9c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,14 +1,14 @@ mod systems; use std::fmt::format; use glam::{Quat, Vec3}; -use rapier3d::dynamics::RigidBodyType; +use rapier3d::dynamics::{CoefficientCombineRule, RigidBodyType}; use rapier3d::prelude::ColliderBuilder; -use raidillon_engine::{Engine, system::System, EngineResources}; +use raidillon_engine::{Engine, system::System, EngineResources, InputState}; use raidillon_engine::system::SystemContext; use raidillon_platform::{Platform, Camera, PlatformContext}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::{ModelHandle, RigidBodyComponent}; +use raidillon_ecs::components::{CameraMode, CharacterBodyComponent, ModelHandle, RigidBodyComponent}; use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] @@ -18,8 +18,9 @@ use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; -use crate::systems::PhysicsSystem; +use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; const TEST_GLTF: &str = "sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; @@ -50,7 +51,7 @@ struct RenderingTestSystem; impl System for RenderingTestSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); - let physics = res.get_mut::().expect("Physics missing"); + let physics = scene.resources.get_mut::().expect("Physics missing"); // Spawn Sphere { @@ -59,7 +60,10 @@ impl System for RenderingTestSystem { rotation: Quat::IDENTITY, scale: Vec3::new(1.0, 1.0, 1.0), }; - let collider = ColliderBuilder::ball(1.0).build(); + let collider = ColliderBuilder::ball(1.0) + .restitution(0.7) + .restitution_combine_rule(CoefficientCombineRule::Max) + .build(); let rb_handle = physics.add_rigid_body(RigidBodyType::Dynamic, tr, collider); pctx.asset_manager.borrow_mut().load_gltf(TEST_GLTF, &model_path(TEST_GLTF)); scene.world.spawn(( @@ -84,16 +88,32 @@ impl System for RenderingTestSystem { RigidBodyComponent(rb_handle), )); } + + scene.world.spawn((Camera { + eye: Vec3::new(0.0, 2.0, 3.0), + center: Vec3::ZERO, + up: Vec3::Y, + fovy: 60_f32.to_radians(), + aspect: pctx.frame_width / pctx.frame_height, + znear: 0.1, + zfar: 100.0}, + CameraMode::default(), + )); } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().unwrap(); + let input = res.get::().unwrap(); let dbg_ui = scene.resources.get_mut::().unwrap(); dbg_ui.text("Hello World!".to_owned()); dbg_ui.text(format!("Frame Delta: {}", pctx.time_ctx.frame_dt)); dbg_ui.text(format!("Fixed Delta: {}", pctx.time_ctx.fixed_dt)); dbg_ui.text(format!("FPS: {}", 1.0 / pctx.time_ctx.frame_dt)); + + for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { + dbg_ui.text(format!("Character POS: {}", tr.translation)); + } } } @@ -101,7 +121,8 @@ fn main() { let mut engine = Engine::new(); // Define systems engine.system_manager.add::(); - engine.system_manager.add::(); + engine.system_manager.add::(); + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); // engine.system_manager.add::(); diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs new file mode 100644 index 0000000..6bc3e70 --- /dev/null +++ b/game/src/systems/keybinds.rs @@ -0,0 +1,52 @@ +use winit::keyboard::KeyCode; +use raidillon_core::DebugUIBuffer; +use raidillon_core::scene::Scene; +use raidillon_ecs::components::CameraMode; +use raidillon_engine::{EngineResources, InputState}; +use raidillon_engine::system::System; +use raidillon_platform::Camera; + +#[derive(Default)] +pub struct KeybindsSystem; + +impl System for KeybindsSystem { + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let input = res.get::().unwrap(); + + if input.key_held(KeyCode::F5) { + self.toggle_camera_mode(scene); + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let dbg_ui = scene.resources.get_mut::().unwrap(); + + dbg_ui.text("F5 to switch camera".to_owned()); + + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + dbg_ui.text(format!("Camera Mode: {:?}", cam_mode)); + } +} + +impl KeybindsSystem { + fn toggle_camera_mode(&mut self, scene: &mut Scene) { + let q = scene.world.query_mut::<(&mut Camera, &mut CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .into_iter() + .next() + .unwrap(); + + match *cam_mode { + CameraMode::Kinematic => { + *cam_mode = CameraMode::Debug; + } + CameraMode::Debug => { + *cam_mode = CameraMode::Kinematic; + } + } + } +} diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs new file mode 100644 index 0000000..4608e67 --- /dev/null +++ b/game/src/systems/kinematic_character_controller.rs @@ -0,0 +1,200 @@ +use glam::{Quat, Vec3}; +use hecs::Entity; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, QueryPipeline, RigidBodyBuilder}; +use rapier3d::prelude::vector; +use raidillon_core::scene::Scene; +use raidillon_engine::{EngineResources, InputState}; +use raidillon_engine::system::System; +use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; +use rapier3d::math::Isometry; +use rapier3d::na::{Isometry3, Vector3}; +use winit::event::DeviceEvent::MouseMotion; +use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::keyboard::KeyCode; +use winit::window::CursorGrabMode; +use raidillon_core::DebugUIBuffer; +use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; +use raidillon_ecs::Transform; +use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use raidillon_physics::Physics; +use raidillon_platform::{Camera, PlatformContext}; + +#[derive(Default)] +pub struct KinematicCharacterController { + character_controller: RapierKinematicCharacterController, + character_collider: ColliderBuilder, + + desired_movement: Vec3, + last_position: Vector3, + yaw: f32, + pitch: f32, + speed: f32, + sensitivity: f32, + mouse_delta: (f64, f64), + + vertical_velocity: f32, + gravity: f32, + max_fall_speed: f32, +} + +impl System for KinematicCharacterController { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + // create the rigid body, add it to the body set + let p = scene.resources.get_mut::().expect("Physics missing"); + let rb = RigidBodyBuilder::kinematic_position_based().build(); + let rb_handle = p.rigid_body_set.insert(rb); + self.character_collider = ColliderBuilder::capsule_z(1.5, 1.0); + p.collider_set.insert_with_parent(self.character_collider.build(), rb_handle, &mut p.rigid_body_set); + let tr = Transform { + translation: Vec3::new(0.0, 2.0, 3.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(1.0, 1.0, 1.0), + }; + self.last_position = vector![ + tr.translation.x, + tr.translation.y, + tr.translation.z, + ]; + scene.world.spawn(( + tr, + CharacterBodyComponent(rb_handle), + )); + + self.speed = 5.0; + self.sensitivity = 0.1; + self.gravity = -9.81; + self.max_fall_speed = -50.0; + self.vertical_velocity = 0.0; + } + + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + + let pctx = res.get::().unwrap(); + let event2 = pctx.current_event.clone(); + match event2 { + Event::DeviceEvent { device_id, event } => { + match event { + MouseMotion { delta } => { + self.mouse_delta.0 += delta.0; + self.mouse_delta.1 += delta.1; + }, + _ => {} + } + }, + _ => {}, + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + self.yaw += (self.mouse_delta.0 as f32) * self.sensitivity; + self.pitch -= (self.mouse_delta.1 as f32) * self.sensitivity; + self.pitch = self.pitch.clamp(-89.0, 89.0); + + let front = self.front(); + let right_vec = front.cross(Vec3::Y).normalize(); + + if input.key_held(KeyCode::KeyW) { + self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed; + } + + let pos = Physics::rapier_translation_to_glam(&self.last_position); + + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + // INTERPOLATION NEEDED. + camera.eye = pos; + camera.center = pos + front; + }); + self.mouse_delta = (0.0, 0.0); + } + + fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if !self.is_camera_mode_valid(scene) { + return + } + let p = scene.resources.get_mut::().unwrap(); + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + let (ch_ent, (ch_tr, ch_component)) = scene + .world + .query_mut::<(&mut Transform, &mut CharacterBodyComponent)>() + .into_iter() + .next() + .expect("no character entity in world"); + + let query_pipeline = p.broad_phase.as_query_pipeline( + p.narrow_phase.query_dispatcher(), + &p.rigid_body_set, + &p.collider_set, + QueryFilter::default().exclude_rigid_body(ch_component.0), + ); + + self.vertical_velocity = (self.vertical_velocity + self.gravity * pctx.time_ctx.fixed_dt) + .max(self.max_fall_speed); + let mut total_displacement = self.desired_movement; + total_displacement.y += self.vertical_velocity * pctx.time_ctx.fixed_dt; + + let corrected_movement = self.character_controller.move_shape( + pctx.time_ctx.fixed_dt, + &query_pipeline, + &*self.character_collider.shape, + &Isometry3::from(self.last_position), + vector![total_displacement.x, total_displacement.y, total_displacement.z], + |_| {}, + ); + + // update character rigid body with the new translation. + if let Some(body) = p.get_rigid_body_mut(ch_component.0) { + self.last_position = vector![ + self.last_position.x + corrected_movement.translation.x, + self.last_position.y + corrected_movement.translation.y, + self.last_position.z + corrected_movement.translation.z, + ]; + body.set_next_kinematic_position(Isometry3::from(self.last_position)); + ch_tr.translation = Physics::rapier_translation_to_glam(&self.last_position); + // reset vertical velocity if grounded + if corrected_movement.grounded { + self.vertical_velocity = 0.0; + } + } + + self.desired_movement = Vec3::ZERO; + } +} + +impl KinematicCharacterController { + pub fn front(&self) -> Vec3 { + let yaw_rad = self.yaw.to_radians(); + let pitch_rad = self.pitch.to_radians(); + Vec3::new( + yaw_rad.cos() * pitch_rad.cos(), + pitch_rad.sin(), + yaw_rad.sin() * pitch_rad.cos(), + ).normalize() + } + + fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == CameraMode::Kinematic + } +} diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index 0ab4645..f975aff 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,3 +1,7 @@ mod physics; +mod kinematic_character_controller; +mod keybinds; pub use physics::PhysicsSystem; +pub use kinematic_character_controller::KinematicCharacterController; +pub use keybinds::KeybindsSystem; diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs index c5d4807..a77c75c 100644 --- a/game/src/systems/physics.rs +++ b/game/src/systems/physics.rs @@ -6,20 +6,22 @@ use raidillon_engine::system::System; use raidillon_physics::Physics; use raidillon_platform::PlatformContext; +/// Do physics calculations and apply to world. #[derive(Default)] pub struct PhysicsSystem; impl System for PhysicsSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let p = Physics::default(); - res.insert(p); + scene.resources.insert(p); } fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); - let physics = res.get_mut::().expect("Physics missing"); + let physics = scene.resources.get_mut::().expect("Physics missing"); physics.step(pctx.time_ctx.fixed_dt); + // apply calculations to dynamic bodies let mut query = scene.world.query::<(&mut Transform, &RigidBodyComponent)>(); for (_ent, (tr, rb_component)) in query.iter() { if let Some(body) = physics.get_rigid_body(rb_component.0) { diff --git a/physics/src/physics.rs b/physics/src/physics.rs index 009984c..5eff699 100644 --- a/physics/src/physics.rs +++ b/physics/src/physics.rs @@ -5,12 +5,12 @@ use raidillon_ecs::Transform; /// Tiny wrapper around rapier3d. pub struct Physics { - rigid_body_set: RigidBodySet, - collider_set: ColliderSet, + pub rigid_body_set: RigidBodySet, + pub collider_set: ColliderSet, physics_pipeline: PhysicsPipeline, island_manager: IslandManager, - broad_phase: DefaultBroadPhase, - narrow_phase: NarrowPhase, + pub broad_phase: DefaultBroadPhase, + pub narrow_phase: NarrowPhase, impulse_joint_set: ImpulseJointSet, multibody_joint_set: MultibodyJointSet, ccd_solver: CCDSolver, From 0af3b41293ef5663cb8752feaff53ae6fed347c5 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:35:55 +0300 Subject: [PATCH 22/38] Move debug_camera to the game crate, activate FPSDebugCameraSystem --- engine/src/systems/mod.rs | 1 - game/src/main.rs | 3 ++- .../fps_camera.rs => game/src/systems/debug_camera.rs | 8 ++++---- game/src/systems/mod.rs | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) rename engine/src/systems/fps_camera.rs => game/src/systems/debug_camera.rs (96%) diff --git a/engine/src/systems/mod.rs b/engine/src/systems/mod.rs index 8489571..e69de29 100644 --- a/engine/src/systems/mod.rs +++ b/engine/src/systems/mod.rs @@ -1 +0,0 @@ -pub mod fps_camera; diff --git a/game/src/main.rs b/game/src/main.rs index a5a3d9c..99eb0e2 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -17,7 +17,7 @@ use winit::event::{ElementState, Event, WindowEvent}; use winit::event::DeviceEvent::MouseMotion; use winit::keyboard::{KeyCode, PhysicalKey}; use raidillon_core::DebugUIBuffer; -use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; +use systems::debug_camera::FPSDebugCameraSystem; use raidillon_glium::RenderingSystem; use raidillon_physics::Physics; use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; @@ -123,6 +123,7 @@ fn main() { engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); + engine.system_manager.add::(); engine.system_manager.add::(); engine.system_manager.add::(); // engine.system_manager.add::(); diff --git a/engine/src/systems/fps_camera.rs b/game/src/systems/debug_camera.rs similarity index 96% rename from engine/src/systems/fps_camera.rs rename to game/src/systems/debug_camera.rs index 5ff9cc7..13cb971 100644 --- a/engine/src/systems/fps_camera.rs +++ b/game/src/systems/debug_camera.rs @@ -1,4 +1,4 @@ -use crate::system::{System, SystemContext}; +use raidillon_engine::system::{System, SystemContext}; use glam::{Quat, Vec3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::{ElementState, Event, MouseButton, WindowEvent}; @@ -6,8 +6,8 @@ use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_assets::model_path; use raidillon_platform::{Camera, PlatformContext}; -use crate::input::InputState; -use crate::resources::EngineResources; +use raidillon_engine::InputState; +use raidillon_engine::EngineResources; use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; @@ -29,7 +29,7 @@ impl Default for FPSDebugCameraSystem { position: Vec3::new(0.0, 0.0, 2.0), yaw: -90.0, pitch: 0.0, - speed: 3.0, + speed: 8.0, sensitivity: 0.1, } } diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index f975aff..2a387b1 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,6 +1,7 @@ mod physics; mod kinematic_character_controller; mod keybinds; +pub mod debug_camera; pub use physics::PhysicsSystem; pub use kinematic_character_controller::KinematicCharacterController; From 445d90b1a5ff2ca191c6a593a7026bb34207858a Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:36:38 +0300 Subject: [PATCH 23/38] Optimize imports of kinematic_character_controller.rs --- game/src/systems/kinematic_character_controller.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index 4608e67..e3fc099 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -1,21 +1,16 @@ use glam::{Quat, Vec3}; -use hecs::Entity; -use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, QueryPipeline, RigidBodyBuilder}; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder}; use rapier3d::prelude::vector; use raidillon_core::scene::Scene; use raidillon_engine::{EngineResources, InputState}; use raidillon_engine::system::System; use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; -use rapier3d::math::Isometry; use rapier3d::na::{Isometry3, Vector3}; use winit::event::DeviceEvent::MouseMotion; -use winit::event::{ElementState, Event, MouseButton, WindowEvent}; +use winit::event::Event; use winit::keyboard::KeyCode; -use winit::window::CursorGrabMode; -use raidillon_core::DebugUIBuffer; use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; use raidillon_ecs::Transform; -use raidillon_engine::systems::fps_camera::FPSDebugCameraSystem; use raidillon_physics::Physics; use raidillon_platform::{Camera, PlatformContext}; From c431ebf74522cdf6cee8d034c5f4d9ac5f2c75b3 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 29 Oct 2025 21:37:49 +0300 Subject: [PATCH 24/38] Tweak keybinds.rs to make camera mode toggle smoother --- game/src/systems/keybinds.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index 6bc3e70..e701dad 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -4,17 +4,22 @@ use raidillon_core::scene::Scene; use raidillon_ecs::components::CameraMode; use raidillon_engine::{EngineResources, InputState}; use raidillon_engine::system::System; -use raidillon_platform::Camera; +use raidillon_platform::{Camera, PlatformContext}; #[derive(Default)] -pub struct KeybindsSystem; +pub struct KeybindsSystem { + camera_toggle_held: bool, +} impl System for KeybindsSystem { fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { let input = res.get::().unwrap(); - if input.key_held(KeyCode::F5) { + if self.camera_toggle_held { return } self.toggle_camera_mode(scene); + self.camera_toggle_held = true; + } else { + self.camera_toggle_held = false; } } From 82f3b277325bc48bb53688b9220232596fe73f14 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 18:42:45 +0300 Subject: [PATCH 25/38] Regenerate cargo.lock --- Cargo.lock | 558 +++++++++++++++++++++++++---------------------------- 1 file changed, 268 insertions(+), 290 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 877142c..7791e88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,15 +14,15 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -81,9 +81,9 @@ checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "approx" @@ -104,10 +104,10 @@ dependencies = [ "image", "log", "objc2 0.6.3", - "objc2-app-kit 0.3.1", + "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", "windows-sys 0.60.2", @@ -146,9 +146,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -156,7 +156,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -238,9 +238,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "calloop" @@ -264,7 +264,7 @@ checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" dependencies = [ "bitflags 2.10.0", "polling", - "rustix 1.0.8", + "rustix 1.1.2", "slab", "tracing", ] @@ -288,17 +288,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ "calloop 0.14.3", - "rustix 1.0.8", + "rustix 1.1.2", "wayland-backend", "wayland-client", ] [[package]] name = "cc" -version = "1.2.31" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -312,9 +313,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -628,12 +629,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -644,9 +645,9 @@ checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "exr" -version = "1.73.0" +version = "1.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" dependencies = [ "bit_field", "half", @@ -687,10 +688,16 @@ dependencies = [ ] [[package]] -name = "flate2" -version = "1.1.2" +name = "find-msvc-tools" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -752,31 +759,31 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.4.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "libc", - "windows-targets 0.48.5", + "rustix 1.1.2", + "windows-link", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasip2", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gl_generator" @@ -881,9 +888,9 @@ checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" [[package]] name = "glam" -version = "0.30.8" +version = "0.30.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" +checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46" [[package]] name = "glium" @@ -956,9 +963,9 @@ dependencies = [ "glutin_wgl_sys", "libloading", "objc2 0.6.3", - "objc2-app-kit 0.3.1", + "objc2-app-kit 0.3.2", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", "raw-window-handle", "wayland-sys", @@ -1009,12 +1016,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1037,9 +1045,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -1185,9 +1193,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.8" +version = "0.25.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" dependencies = [ "bytemuck", "byteorder-lite", @@ -1196,18 +1204,18 @@ dependencies = [ "num-traits", "png", "tiff", - "zune-core", - "zune-jpeg", + "zune-core 0.5.0", + "zune-jpeg 0.5.5", ] [[package]] name = "indexmap" -version = "2.10.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.16.0", ] [[package]] @@ -1246,9 +1254,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom", "libc", @@ -1256,9 +1264,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1284,18 +1292,18 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link", ] [[package]] @@ -1306,13 +1314,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", ] [[package]] @@ -1323,9 +1331,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1360,15 +1368,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -1394,9 +1402,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.5" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +checksum = "0fbdd3d7436f8b5e892b8b7ea114271ff0fa00bc5acae845d53b07d498616ef6" dependencies = [ "num-traits", "pxfm", @@ -1424,7 +1432,7 @@ dependencies = [ "glam 0.27.0", "glam 0.28.0", "glam 0.29.3", - "glam 0.30.8", + "glam 0.30.9", "matrixmultiply", "nalgebra-macros", "num-complex", @@ -1543,9 +1551,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1553,9 +1561,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1606,15 +1614,15 @@ dependencies = [ [[package]] name = "objc2-app-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.10.0", "objc2 0.6.3", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", ] [[package]] @@ -1722,9 +1730,9 @@ dependencies = [ [[package]] name = "objc2-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.10.0", "objc2 0.6.3", @@ -1836,9 +1844,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1851,9 +1859,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "orbclient" -version = "0.3.48" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +checksum = "247ad146e19b9437f8604c21f8652423595cf710ad108af40e77d3ae6e96b827" dependencies = [ "libredox", ] @@ -1894,16 +1902,16 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] name = "parry3d" -version = "0.25.1" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017be73f24c8ca8b10f9727616e5cb9af82b98488cc6d5eea468e727ffa780ca" +checksum = "e99471b7b6870f7fe406d5611dd4b4c9b07aa3e5436b1d27e1515f9832bb0c6b" dependencies = [ "approx", "arrayvec", @@ -1935,9 +1943,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project" @@ -1986,16 +1994,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.8", - "windows-sys 0.60.2", + "rustix 1.1.2", + "windows-sys 0.61.2", ] [[package]] @@ -2009,18 +2017,18 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -2046,9 +2054,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" dependencies = [ "num-traits", ] @@ -2070,9 +2078,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2102,7 +2110,7 @@ dependencies = [ name = "raidillon_ecs" version = "0.1.0" dependencies = [ - "glam 0.30.8", + "glam 0.30.9", "raidillon_assets", "rapier3d", ] @@ -2112,7 +2120,7 @@ name = "raidillon_engine" version = "0.1.0" dependencies = [ "egui", - "glam 0.30.8", + "glam 0.30.9", "hecs", "indexmap", "raidillon_assets", @@ -2127,7 +2135,7 @@ name = "raidillon_game" version = "0.1.0" dependencies = [ "egui", - "glam 0.30.8", + "glam 0.30.9", "hecs", "raidillon_assets", "raidillon_core", @@ -2148,7 +2156,7 @@ dependencies = [ "egui", "egui_glium", "exr", - "glam 0.30.8", + "glam 0.30.9", "glium", "gltf", "image", @@ -2165,7 +2173,7 @@ dependencies = [ name = "raidillon_physics" version = "0.1.0" dependencies = [ - "glam 0.30.8", + "glam 0.30.9", "raidillon_ecs", "rapier3d", ] @@ -2174,7 +2182,7 @@ dependencies = [ name = "raidillon_platform" version = "0.1.0" dependencies = [ - "glam 0.30.8", + "glam 0.30.9", "raidillon_assets", "raidillon_core", "winit", @@ -2238,9 +2246,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.10.0", ] @@ -2289,22 +2297,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -2357,18 +2365,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2377,14 +2395,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -2414,9 +2433,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -2462,7 +2481,7 @@ dependencies = [ "libc", "log", "memmap2", - "rustix 1.0.8", + "rustix 1.1.2", "thiserror 2.0.17", "wayland-backend", "wayland-client", @@ -2498,11 +2517,11 @@ dependencies = [ [[package]] name = "spade" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a14e31a007e9f85c32784b04f89e6e194bb252a4d41b4a8ccd9e77245d901c8c" +checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", "num-traits", "robust", "smallvec", @@ -2534,9 +2553,9 @@ checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -2605,7 +2624,7 @@ dependencies = [ "half", "quick-error", "weezl", - "zune-jpeg", + "zune-jpeg 0.4.21", ] [[package]] @@ -2645,18 +2664,31 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ "winnow", ] @@ -2691,9 +2723,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -2703,13 +2735,14 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2741,45 +2774,32 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -2790,9 +2810,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2800,22 +2820,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -2828,7 +2848,7 @@ checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs 1.2.1", - "rustix 1.0.8", + "rustix 1.1.2", "scoped-tls", "smallvec", "wayland-sys", @@ -2841,7 +2861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.10.0", - "rustix 1.0.8", + "rustix 1.1.2", "wayland-backend", "wayland-scanner", ] @@ -2863,7 +2883,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.2", "wayland-client", "xcursor", ] @@ -2957,9 +2977,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -2986,7 +3006,7 @@ dependencies = [ "log", "ndk-context", "objc2 0.6.3", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "url", "web-sys", ] @@ -3009,19 +3029,13 @@ dependencies = [ [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -3061,7 +3075,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", ] [[package]] @@ -3079,21 +3102,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3112,19 +3120,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -3133,12 +3141,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3147,9 +3149,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -3157,12 +3159,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3171,9 +3167,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -3181,12 +3177,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3195,9 +3185,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -3207,9 +3197,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -3217,12 +3207,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3231,9 +3215,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -3241,12 +3225,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3255,9 +3233,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -3265,12 +3243,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3279,9 +3251,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -3289,12 +3261,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3303,9 +3269,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" @@ -3361,21 +3327,18 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.10.0", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -3396,24 +3359,24 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", "libloading", "once_cell", - "rustix 0.38.44", + "rustix 1.1.2", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xcursor" @@ -3442,9 +3405,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] name = "yoke" @@ -3471,18 +3434,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -3549,6 +3512,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +[[package]] +name = "zune-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -3560,9 +3529,18 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" dependencies = [ - "zune-core", + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e" +dependencies = [ + "zune-core 0.5.0", ] From 47c3b2b11163e0842948ff351d2335f6abfa9cc5 Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 16 Nov 2025 19:32:56 +0300 Subject: [PATCH 26/38] New crate: raidillon_app, refactor game/main.rs to use the new crate --- Cargo.lock | 21 ++++-- Cargo.toml | 2 +- app/Cargo.toml | 16 +++++ app/src/lib.rs | 45 +++++++++++++ app/src/prelude.rs | 43 ++++++++++++ core/src/scene.rs | 2 +- game/Cargo.toml | 12 +--- game/src/main.rs | 65 +++++-------------- game/src/systems/debug_camera.rs | 8 +-- game/src/systems/keybinds.rs | 6 +- .../systems/kinematic_character_controller.rs | 8 +-- game/src/systems/physics.rs | 8 +-- 12 files changed, 140 insertions(+), 96 deletions(-) create mode 100644 app/Cargo.toml create mode 100644 app/src/lib.rs create mode 100644 app/src/prelude.rs diff --git a/Cargo.lock b/Cargo.lock index 7791e88..ac3f862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2091,6 +2091,19 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "raidillon_app" +version = "0.1.0" +dependencies = [ + "raidillon_assets", + "raidillon_core", + "raidillon_ecs", + "raidillon_engine", + "raidillon_glium", + "raidillon_physics", + "raidillon_platform", +] + [[package]] name = "raidillon_assets" version = "0.1.0" @@ -2137,13 +2150,7 @@ dependencies = [ "egui", "glam 0.30.9", "hecs", - "raidillon_assets", - "raidillon_core", - "raidillon_ecs", - "raidillon_engine", - "raidillon_glium", - "raidillon_physics", - "raidillon_platform", + "raidillon_app", "rapier3d", "winit", ] diff --git a/Cargo.toml b/Cargo.toml index dd544ab..3741214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,5 @@ members = [ "game", "ecs", "engine", - "physics", + "physics", "app", ] diff --git a/app/Cargo.toml b/app/Cargo.toml new file mode 100644 index 0000000..081dae0 --- /dev/null +++ b/app/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "raidillon_app" +version = "0.1.0" +edition = "2024" + +[dependencies] +raidillon_engine = { path = "../engine" } +raidillon_platform = { path = "../platform" } +raidillon_assets = { path = "../asset" } +raidillon_ecs = { path = "../ecs" } +raidillon_physics = { path = "../physics" } +raidillon_glium = { path = "../glium_platform", optional = true } +raidillon_core = { path = "../core" } + +[features] +glium = ["raidillon_glium"] diff --git a/app/src/lib.rs b/app/src/lib.rs new file mode 100644 index 0000000..a5b875a --- /dev/null +++ b/app/src/lib.rs @@ -0,0 +1,45 @@ +pub mod prelude; + +pub use prelude::*; + +pub struct App { + pub engine: Option, +} + +impl App { + pub fn new() -> Self { + let engine = Engine::new(); + Self { engine: Some(engine) } + } + + pub fn add_system(&mut self) -> &mut Self { + self.engine.as_mut().unwrap().system_manager.add::(); + self + } + + pub fn add_scene(&mut self, id: SceneID, scene: Scene) -> &mut Self { + self.engine.as_mut().unwrap().scene_manager.add_scene(id, scene); + self + } + + pub fn set_active_scene(&mut self, id: SceneID) -> &mut Self { + self.engine.as_mut().unwrap().scene_manager.set_active_scene(id); + self + } + + pub fn run(&mut self, title: String, width: u32, height: u32) { + #[cfg(feature = "glium")] + { + let platform = GliumPlatform::initialize( + self.engine.take().unwrap(), + title, + width, + height, + ); + platform.run(); + } + + #[cfg(not(any(feature = "glium")))] + compile_error!("No platform feature enabled."); + } +} diff --git a/app/src/prelude.rs b/app/src/prelude.rs new file mode 100644 index 0000000..1304175 --- /dev/null +++ b/app/src/prelude.rs @@ -0,0 +1,43 @@ +pub use raidillon_engine::{ + Engine, + system::System, + EngineResources, + InputState, + system::SystemContext, +}; + +pub use raidillon_platform::{ + Platform, + Camera, + PlatformContext, + TimeContext, +}; + +pub use raidillon_assets::{ + ModelManagerRef, + model_path, +}; + +pub use raidillon_ecs::{ + components::{ + CameraMode, + CharacterBodyComponent, + ModelHandle, + RigidBodyComponent, + Transform, + }, +}; + +pub use raidillon_physics::Physics; + +pub use raidillon_core::{ + scene::{Scene, SceneID}, + EguiQueue, + engine::EngineTrait, +}; + +#[cfg(feature = "glium")] +pub use raidillon_glium::{ + GliumPlatform, + RenderingSystem, +}; diff --git a/core/src/scene.rs b/core/src/scene.rs index 2f748c4..e586be5 100644 --- a/core/src/scene.rs +++ b/core/src/scene.rs @@ -40,7 +40,7 @@ impl AsMut for Scene { } } -type SceneID = &'static str; +pub type SceneID = &'static str; pub struct SceneManager { scenes: HashMap, diff --git a/game/Cargo.toml b/game/Cargo.toml index 480aea4..0a28217 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -3,18 +3,8 @@ name = "raidillon_game" version = "0.1.0" edition = "2024" -[features] -default = ["glium"] -glium = ["raidillon_glium"] - [dependencies] -raidillon_core = { path = "../core" } -raidillon_platform = { path = "../platform" } -raidillon_assets = { path = "../asset" } -raidillon_ecs = { path = "../ecs" } -raidillon_engine = { path = "../engine" } -raidillon_physics = { path = "../physics" } -raidillon_glium = { path = "../glium_platform", optional = true } +raidillon_app = { path = "../app", features = ["glium"] } glam = "0.30.5" winit = "0.30.12" rapier3d = "0.30.1" diff --git a/game/src/main.rs b/game/src/main.rs index e81d77c..939e4dd 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,25 +1,11 @@ +use raidillon_app::prelude::*; + mod systems; -use std::fmt::format; use glam::{Quat, Vec3}; use rapier3d::dynamics::{CoefficientCombineRule, RigidBodyType}; use rapier3d::prelude::ColliderBuilder; -use raidillon_engine::{Engine, system::System, EngineResources, InputState}; -use raidillon_engine::system::SystemContext; -use raidillon_platform::{Platform, Camera, PlatformContext}; -use raidillon_assets::model_path; -use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::{CameraMode, CharacterBodyComponent, ModelHandle, RigidBodyComponent}; -use raidillon_ecs::Transform; -use raidillon_core::scene::Scene; -#[cfg(feature = "glium")] -use raidillon_glium::GliumPlatform; -use winit::event::{ElementState, Event, WindowEvent}; -use winit::event::DeviceEvent::MouseMotion; -use winit::keyboard::{KeyCode, PhysicalKey}; -use raidillon_core::{EguiQueue}; +use winit::event::{Event, WindowEvent}; use systems::debug_camera::FPSDebugCameraSystem; -use raidillon_glium::RenderingSystem; -use raidillon_physics::Physics; use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; const TEST_GLTF: &str = "sphere.glb"; @@ -46,9 +32,9 @@ impl System for UpdateAspectRatioSystem { } #[derive(Default)] -struct RenderingTestSystem; +struct MainSystem; -impl System for RenderingTestSystem { +impl System for MainSystem { fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { let pctx = res.get::().expect("PlatformContext missing").clone(); let physics = scene.resources.get_mut::().expect("Physics missing"); @@ -130,35 +116,14 @@ impl System for RenderingTestSystem { } fn main() { - let mut engine = Engine::new(); - // Define systems - engine.system_manager.add::(); - engine.system_manager.add::(); - engine.system_manager.add::(); - engine.system_manager.add::(); - engine.system_manager.add::(); - engine.system_manager.add::(); - // engine.system_manager.add::(); - - // Set up the scene - let main_scene = Scene::new( - MAIN_SCENE_ID.to_owned(), - None, - ); - engine.scene_manager.add_scene(MAIN_SCENE_ID, main_scene); - engine.scene_manager.set_active_scene(MAIN_SCENE_ID); - - #[cfg(feature = "glium")] - { - let platform = GliumPlatform::initialize( - engine, - "Raidillon".to_string(), - 2560, - 1080, - ); - platform.run() - }; - - #[cfg(not(any(feature = "glium")))] - compile_error!("No platform feature enabled."); + raidillon_app::App::new() + .add_system::() + .add_system::() + .add_system::() + .add_system::() + .add_system::() + .add_system::() + .add_scene(MAIN_SCENE_ID, Scene::new(MAIN_SCENE_ID.to_owned(), None)) + .set_active_scene(MAIN_SCENE_ID) + .run("Raidillon".to_string(), 2560, 1080); } diff --git a/game/src/systems/debug_camera.rs b/game/src/systems/debug_camera.rs index 13cb971..959af5c 100644 --- a/game/src/systems/debug_camera.rs +++ b/game/src/systems/debug_camera.rs @@ -1,15 +1,9 @@ -use raidillon_engine::system::{System, SystemContext}; use glam::{Quat, Vec3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::{ElementState, Event, MouseButton, WindowEvent}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; -use raidillon_assets::model_path; -use raidillon_platform::{Camera, PlatformContext}; -use raidillon_engine::InputState; -use raidillon_engine::EngineResources; -use raidillon_core::scene::Scene; -use raidillon_ecs::components::CameraMode; +use raidillon_app::prelude::*; pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index 10bbbe8..d8df7b7 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -1,9 +1,5 @@ use winit::keyboard::KeyCode; -use raidillon_core::scene::Scene; -use raidillon_ecs::components::CameraMode; -use raidillon_engine::{EngineResources, InputState}; -use raidillon_engine::system::System; -use raidillon_platform::{Camera, PlatformContext}; +use raidillon_app::prelude::*; #[derive(Default)] pub struct KeybindsSystem { diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index e3fc099..34de5b4 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -1,18 +1,12 @@ use glam::{Quat, Vec3}; use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder}; use rapier3d::prelude::vector; -use raidillon_core::scene::Scene; -use raidillon_engine::{EngineResources, InputState}; -use raidillon_engine::system::System; use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; use rapier3d::na::{Isometry3, Vector3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::Event; use winit::keyboard::KeyCode; -use raidillon_ecs::components::{CameraMode, CharacterBodyComponent}; -use raidillon_ecs::Transform; -use raidillon_physics::Physics; -use raidillon_platform::{Camera, PlatformContext}; +use raidillon_app::prelude::*; #[derive(Default)] pub struct KinematicCharacterController { diff --git a/game/src/systems/physics.rs b/game/src/systems/physics.rs index a77c75c..f660526 100644 --- a/game/src/systems/physics.rs +++ b/game/src/systems/physics.rs @@ -1,10 +1,4 @@ -use raidillon_core::scene::Scene; -use raidillon_ecs::components::RigidBodyComponent; -use raidillon_ecs::Transform; -use raidillon_engine::EngineResources; -use raidillon_engine::system::System; -use raidillon_physics::Physics; -use raidillon_platform::PlatformContext; +use raidillon_app::prelude::*; /// Do physics calculations and apply to world. #[derive(Default)] From b17a7636d8daf649e63fc5f6f394afce937a8756 Mon Sep 17 00:00:00 2001 From: reo Date: Sat, 22 Nov 2025 14:15:53 +0300 Subject: [PATCH 27/38] Implement MenuSystem, reimplement the mouse grab and menu system, add a new common module in the game systems for common functions, various other fixes --- game/src/main.rs | 35 +++---- game/src/systems/common.rs | 34 +++++++ game/src/systems/debug_camera.rs | 95 +++++-------------- game/src/systems/keybinds.rs | 36 ++++--- .../systems/kinematic_character_controller.rs | 85 +++++++---------- game/src/systems/menu.rs | 81 ++++++++++++++++ game/src/systems/mod.rs | 3 + 7 files changed, 219 insertions(+), 150 deletions(-) create mode 100644 game/src/systems/common.rs create mode 100644 game/src/systems/menu.rs diff --git a/game/src/main.rs b/game/src/main.rs index 939e4dd..582e2d5 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -6,7 +6,8 @@ use rapier3d::dynamics::{CoefficientCombineRule, RigidBodyType}; use rapier3d::prelude::ColliderBuilder; use winit::event::{Event, WindowEvent}; use systems::debug_camera::FPSDebugCameraSystem; -use crate::systems::{KeybindsSystem, KinematicCharacterController, PhysicsSystem}; +use crate::systems::common::should_draw_menu; +use crate::systems::{KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem}; const TEST_GLTF: &str = "sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; @@ -96,22 +97,23 @@ impl System for MainSystem { InputState, )>().unwrap(); - let mut egui_queue = pctx.egui_queue.borrow_mut(); - let time_ctx = pctx.time_ctx.clone(); - let mut character_pos = Vec3::ZERO; - for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { - character_pos = tr.translation; - } - egui_queue.queue(move |egui_ctx| { - egui::Window::new("Debug").show(egui_ctx, |ui| { - ui.label("Hello World!"); - ui.label(format!("Frame Delta: {}", time_ctx.frame_dt)); - ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt)); - ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt)); - ui.label(format!("Character POS: {}", character_pos)); + if should_draw_menu(scene) { + let mut egui_queue = pctx.egui_queue.borrow_mut(); + let time_ctx = pctx.time_ctx.clone(); + let mut character_pos = Vec3::ZERO; + for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { + character_pos = tr.translation; + } + egui_queue.queue(move |egui_ctx| { + egui::Window::new("Debug").show(egui_ctx, |ui| { + ui.label("Hello World!"); + ui.label(format!("Frame Delta: {}", time_ctx.frame_dt)); + ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt)); + ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt)); + ui.label(format!("Character POS: {}", character_pos)); + }); }); - }); - + } } } @@ -121,6 +123,7 @@ fn main() { .add_system::() .add_system::() .add_system::() + .add_system::() .add_system::() .add_system::() .add_scene(MAIN_SCENE_ID, Scene::new(MAIN_SCENE_ID.to_owned(), None)) diff --git a/game/src/systems/common.rs b/game/src/systems/common.rs new file mode 100644 index 0000000..8863f2e --- /dev/null +++ b/game/src/systems/common.rs @@ -0,0 +1,34 @@ +use glam::Vec3; +use raidillon_app::prelude::*; +use crate::systems::menu::MenuState; + +pub fn is_camera_mode_valid(scene: &mut Scene, mode: CameraMode) -> bool { + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + *cam_mode == mode +} + +pub fn is_mouse_look_enabled(scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&MenuState)>(); + let (_ent, mode) = q.iter().next().unwrap(); + *mode == MenuState::Closed +} + +pub fn camera_front(yaw: f32, pitch: f32) -> Vec3 { + let yaw_rad = yaw.to_radians(); + let pitch_rad = pitch.to_radians(); + Vec3::new( + yaw_rad.cos() * pitch_rad.cos(), + pitch_rad.sin(), + yaw_rad.sin() * pitch_rad.cos(), + ).normalize() +} + +pub fn should_draw_menu(scene: &mut Scene) -> bool { + let mut q = scene.world.query::<(&MenuState)>(); + let (_ent, mode) = q.iter().next().unwrap(); + *mode == MenuState::Open +} diff --git a/game/src/systems/debug_camera.rs b/game/src/systems/debug_camera.rs index 959af5c..b687e08 100644 --- a/game/src/systems/debug_camera.rs +++ b/game/src/systems/debug_camera.rs @@ -5,9 +5,11 @@ use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorGrabMode; use raidillon_app::prelude::*; +use crate::systems::common::{camera_front, is_camera_mode_valid, is_mouse_look_enabled}; +use crate::systems::menu::MenuState; + pub struct FPSDebugCameraSystem { mouse_delta: (f64, f64), - mouse_enabled: bool, position: Vec3, yaw: f32, pitch: f32, @@ -19,7 +21,6 @@ impl Default for FPSDebugCameraSystem { fn default() -> Self { Self { mouse_delta: Default::default(), - mouse_enabled: Default::default(), position: Vec3::new(0.0, 0.0, 2.0), yaw: -90.0, pitch: 0.0, @@ -31,7 +32,7 @@ impl Default for FPSDebugCameraSystem { impl System for FPSDebugCameraSystem { fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { + if !(is_camera_mode_valid(scene, CameraMode::Debug) && is_mouse_look_enabled(scene)) { return } let pctx = res.get::().unwrap(); @@ -46,89 +47,43 @@ impl System for FPSDebugCameraSystem { _ => {} } }, - Event::WindowEvent { event, .. } => match event { - WindowEvent::MouseInput { state, button, .. } => { - if button == MouseButton::Right { - // blood and tear - let window = pctx.window.lock().unwrap(); - match state { - ElementState::Pressed => { - if window - .set_cursor_grab(CursorGrabMode::Confined) - .or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)) - .is_ok() - { - window.set_cursor_visible(false); - self.mouse_enabled = true; - } - } - ElementState::Released => { - let _ = window.set_cursor_grab(CursorGrabMode::None); - window.set_cursor_visible(true); - self.mouse_enabled = false; - } - } - } - } - _ => {}, - }, _ => {}, } } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { - return - } let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); - if self.mouse_enabled { + if is_mouse_look_enabled(scene) { self.yaw += self.mouse_delta.0 as f32 * self.sensitivity; self.pitch -= self.mouse_delta.1 as f32 * self.sensitivity; self.pitch = self.pitch.clamp(-89.0, 89.0); } - let front = self.front(); + let front = camera_front(self.yaw, self.pitch); let right_vec = front.cross(Vec3::Y).normalize(); - if input.key_held(KeyCode::KeyW) { - self.position += front * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyS) { - self.position -= front * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyA) { - self.position -= right_vec * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyD) { - self.position += right_vec * pctx.time_ctx.frame_dt * self.speed; + if is_mouse_look_enabled(scene) { + if input.key_held(KeyCode::KeyW) { + self.position += front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.position -= front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.position -= right_vec * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.position += right_vec * pctx.time_ctx.frame_dt * self.speed; + } } - scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { - camera.eye = self.position; - camera.center = self.position + front; - }); + if is_camera_mode_valid(scene, CameraMode::Debug) { + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + camera.eye = self.position; + camera.center = self.position + front; + }); + } self.mouse_delta = (0.0, 0.0); } } - -impl FPSDebugCameraSystem { - pub fn front(&self) -> Vec3 { - let yaw_rad = self.yaw.to_radians(); - let pitch_rad = self.pitch.to_radians(); - Vec3::new( - yaw_rad.cos() * pitch_rad.cos(), - pitch_rad.sin(), - yaw_rad.sin() * pitch_rad.cos(), - ).normalize() - } - - fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { - let mut q = scene.world.query::<(&Camera, &CameraMode)>(); - let (cam_ent, (cam, cam_mode)) = q - .iter() - .next() - .unwrap(); - *cam_mode == CameraMode::Debug - } -} diff --git a/game/src/systems/keybinds.rs b/game/src/systems/keybinds.rs index d8df7b7..5519969 100644 --- a/game/src/systems/keybinds.rs +++ b/game/src/systems/keybinds.rs @@ -1,6 +1,8 @@ use winit::keyboard::KeyCode; use raidillon_app::prelude::*; +use crate::systems::{common::should_draw_menu, menu::MenuState}; + #[derive(Default)] pub struct KeybindsSystem { camera_toggle_held: bool, @@ -19,21 +21,31 @@ impl System for KeybindsSystem { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - let pctx = res.get_mut::().unwrap(); - let mut q = scene.world.query::<(&Camera, &CameraMode)>(); - let (cam_ent, (cam, cam_mode)) = q - .iter() - .next() - .unwrap(); + if should_draw_menu(scene) { + let pctx = res.get_mut::().unwrap(); + let mut q = scene.world.query::<(&Camera, &CameraMode)>(); + let (cam_ent, (cam, cam_mode)) = q + .iter() + .next() + .unwrap(); + let cam_mode_str = format!("Camera Mode: {:?}", cam_mode); - let cam_mode_str = format!("Camera Mode: {:?}", cam_mode); + let mut q = scene.world.query::<(&MenuState)>(); + let (_ent, menu_state) = q + .iter() + .next() + .unwrap(); - pctx.egui_queue.borrow_mut().queue(move |egui_ctx| { - egui::Window::new("Camera").show(egui_ctx, |ui| { - ui.label("F5 to switch camera"); - ui.label(cam_mode_str); + let menu_state_str = format!("Menu State: {:?}", menu_state); + + pctx.egui_queue.borrow_mut().queue(move |egui_ctx| { + egui::Window::new("Camera").show(egui_ctx, |ui| { + ui.label("F5 to switch camera"); + ui.label(cam_mode_str); + ui.label(menu_state_str) + }); }); - }); + } } } diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index 34de5b4..41561a5 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -8,6 +8,9 @@ use winit::event::Event; use winit::keyboard::KeyCode; use raidillon_app::prelude::*; +use crate::systems::common::{camera_front, is_camera_mode_valid, is_mouse_look_enabled}; +use crate::systems::menu::MenuState; + #[derive(Default)] pub struct KinematicCharacterController { character_controller: RapierKinematicCharacterController, @@ -57,7 +60,7 @@ impl System for KinematicCharacterController { } fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { + if !is_camera_mode_valid(scene, CameraMode::Kinematic) { return } @@ -78,45 +81,44 @@ impl System for KinematicCharacterController { } fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { - return - } - let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); - - self.yaw += (self.mouse_delta.0 as f32) * self.sensitivity; - self.pitch -= (self.mouse_delta.1 as f32) * self.sensitivity; - self.pitch = self.pitch.clamp(-89.0, 89.0); - - let front = self.front(); + let front = camera_front(self.yaw, self.pitch); let right_vec = front.cross(Vec3::Y).normalize(); - if input.key_held(KeyCode::KeyW) { - self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyS) { - self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyA) { - self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed; - } - if input.key_held(KeyCode::KeyD) { - self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed; + if is_camera_mode_valid(scene, CameraMode::Kinematic) && is_mouse_look_enabled(scene) { + let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); + + self.yaw += (self.mouse_delta.0 as f32) * self.sensitivity; + self.pitch -= (self.mouse_delta.1 as f32) * self.sensitivity; + self.pitch = self.pitch.clamp(-89.0, 89.0); + + if input.key_held(KeyCode::KeyW) { + self.desired_movement += front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyS) { + self.desired_movement -= front * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyA) { + self.desired_movement -= right_vec * pctx.time_ctx.frame_dt * self.speed; + } + if input.key_held(KeyCode::KeyD) { + self.desired_movement += right_vec * pctx.time_ctx.frame_dt * self.speed; + } } - let pos = Physics::rapier_translation_to_glam(&self.last_position); + if is_camera_mode_valid(scene, CameraMode::Kinematic) { + let pos = Physics::rapier_translation_to_glam(&self.last_position); + + scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { + // INTERPOLATION NEEDED. + camera.eye = pos; + camera.center = pos + front; + }); + } - scene.world.query_mut::<&mut Camera>().into_iter().for_each(|(_, camera)| { - // INTERPOLATION NEEDED. - camera.eye = pos; - camera.center = pos + front; - }); self.mouse_delta = (0.0, 0.0); } fn fixed_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { - if !self.is_camera_mode_valid(scene) { - return - } let p = scene.resources.get_mut::().unwrap(); let (pctx, input) = res.get_many::<(PlatformContext, InputState)>().unwrap(); @@ -166,24 +168,3 @@ impl System for KinematicCharacterController { self.desired_movement = Vec3::ZERO; } } - -impl KinematicCharacterController { - pub fn front(&self) -> Vec3 { - let yaw_rad = self.yaw.to_radians(); - let pitch_rad = self.pitch.to_radians(); - Vec3::new( - yaw_rad.cos() * pitch_rad.cos(), - pitch_rad.sin(), - yaw_rad.sin() * pitch_rad.cos(), - ).normalize() - } - - fn is_camera_mode_valid(&self, scene: &mut Scene) -> bool { - let mut q = scene.world.query::<(&Camera, &CameraMode)>(); - let (cam_ent, (cam, cam_mode)) = q - .iter() - .next() - .unwrap(); - *cam_mode == CameraMode::Kinematic - } -} diff --git a/game/src/systems/menu.rs b/game/src/systems/menu.rs new file mode 100644 index 0000000..60350b7 --- /dev/null +++ b/game/src/systems/menu.rs @@ -0,0 +1,81 @@ +use egui::Id; +use raidillon_app::prelude::*; +use winit::{dpi::{LogicalPosition, Position}, keyboard::KeyCode, window::CursorGrabMode}; + +#[derive(Default)] +pub struct MenuSystem { + escape_key_held: bool, + /// Unoptimal solution to fix windows event delay + times_ran_initial_win_event: u32, +} + +#[derive(Default, Eq, PartialEq, Debug)] +pub enum MenuState { + Open, + #[default] + Closed, +} + +impl System for MenuSystem { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + scene.world.spawn((MenuState::Closed,)); + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + // Windows won't register some events in the first miliseconds after initialization + if self.times_ran_initial_win_event < 20 { + let window = res.get::().unwrap().window.lock().unwrap(); + window.set_cursor_grab(CursorGrabMode::Confined).or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)); + window.set_cursor_visible(false); + self.times_ran_initial_win_event += 1 + } + + let mut egui_queue = res.get::().unwrap().egui_queue.borrow_mut(); + egui_queue.queue(|egui_ctx| { + egui::Area::new(Id::new("esc to pause")) + .anchor(egui::Align2::RIGHT_TOP, [-10.0, 10.0]) + .show(egui_ctx, |ui| { + ui.label( + egui::RichText::new("ESC to pause").size(24.0).color(egui::Color32::BLACK) + ); + }); + }); + } + + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { + // The menu is toggled by pressing the escape key + let input = res.get::().unwrap(); + if input.key_held(KeyCode::Escape) { + if self.escape_key_held { return } + self.toggle_menu(res, scene); + self.escape_key_held = true; + } else { + self.escape_key_held = false; + } + } +} + +impl MenuSystem { + fn toggle_menu(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let q = scene.world.query_mut::<(&mut MenuState)>(); + let (menu_ent, menu_state) = q + .into_iter() + .next() + .unwrap(); + + let window = res.get::().unwrap().window.lock().unwrap(); + + match *menu_state { + MenuState::Open => { + *menu_state = MenuState::Closed; + window.set_cursor_grab(CursorGrabMode::Confined).or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)); + window.set_cursor_visible(false); + }, + MenuState::Closed => { + *menu_state = MenuState::Open; + window.set_cursor_grab(CursorGrabMode::None); + window.set_cursor_visible(true); + }, + } + } +} diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index 2a387b1..2f23ed5 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,8 +1,11 @@ mod physics; mod kinematic_character_controller; mod keybinds; +mod menu; pub mod debug_camera; +pub mod common; pub use physics::PhysicsSystem; pub use kinematic_character_controller::KinematicCharacterController; pub use keybinds::KeybindsSystem; +pub use menu::MenuSystem; From f5a16213fae1b05339b7372270e944a1c07a1535 Mon Sep 17 00:00:00 2001 From: reo Date: Tue, 9 Dec 2025 17:52:17 +0300 Subject: [PATCH 28/38] Add Settings with fullscreen and windowed options, a config file (settings.toml) to persist settings, fix a bug in platform code where innner window size wasn't updated on resize, various other tweaks --- Cargo.lock | 32 +++++++++ app/src/prelude.rs | 1 + game/src/main.rs | 35 +++++----- game/src/systems/display_settings.rs | 92 ++++++++++++++++++++++++ game/src/systems/mod.rs | 2 + glium_platform/src/platform.rs | 26 ++++++- platform/Cargo.toml | 2 + platform/src/context.rs | 4 +- platform/src/lib.rs | 1 + platform/src/settings.rs | 101 +++++++++++++++++++++++++++ 10 files changed, 277 insertions(+), 19 deletions(-) create mode 100644 game/src/systems/display_settings.rs create mode 100644 platform/src/settings.rs diff --git a/Cargo.lock b/Cargo.lock index ac3f862..d36cc29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2192,6 +2192,8 @@ dependencies = [ "glam 0.30.9", "raidillon_assets", "raidillon_core", + "serde", + "toml", "winit", ] @@ -2413,6 +2415,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2669,6 +2680,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.7.3" @@ -2699,6 +2725,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + [[package]] name = "tracing" version = "0.1.41" diff --git a/app/src/prelude.rs b/app/src/prelude.rs index 1304175..1171d0f 100644 --- a/app/src/prelude.rs +++ b/app/src/prelude.rs @@ -11,6 +11,7 @@ pub use raidillon_platform::{ Camera, PlatformContext, TimeContext, + settings::{Settings, WindowMode}, }; pub use raidillon_assets::{ diff --git a/game/src/main.rs b/game/src/main.rs index 582e2d5..b49445c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -7,7 +7,9 @@ use rapier3d::prelude::ColliderBuilder; use winit::event::{Event, WindowEvent}; use systems::debug_camera::FPSDebugCameraSystem; use crate::systems::common::should_draw_menu; -use crate::systems::{KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem}; +use crate::systems::{ + DisplaySettings, KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem +}; const TEST_GLTF: &str = "sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; @@ -97,23 +99,21 @@ impl System for MainSystem { InputState, )>().unwrap(); - if should_draw_menu(scene) { - let mut egui_queue = pctx.egui_queue.borrow_mut(); - let time_ctx = pctx.time_ctx.clone(); - let mut character_pos = Vec3::ZERO; - for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { - character_pos = tr.translation; - } - egui_queue.queue(move |egui_ctx| { - egui::Window::new("Debug").show(egui_ctx, |ui| { - ui.label("Hello World!"); - ui.label(format!("Frame Delta: {}", time_ctx.frame_dt)); - ui.label(format!("Fixed Delta: {}", time_ctx.fixed_dt)); - ui.label(format!("FPS: {}", 1.0 / time_ctx.frame_dt)); - ui.label(format!("Character POS: {}", character_pos)); - }); - }); + let mut egui_queue = pctx.egui_queue.borrow_mut(); + let time_ctx = pctx.time_ctx.clone(); + let mut character_pos = Vec3::ZERO; + for (_ent, (tr, ch_component)) in scene.world.query::<(&Transform, &CharacterBodyComponent)>().iter() { + character_pos = tr.translation; } + egui_queue.queue(move |egui_ctx| { + egui::Window::new("Debug").show(egui_ctx, |ui| { + ui.label("Hello World!"); + ui.label(format!("Frame Delta: {:.3}", time_ctx.frame_dt)); + ui.label(format!("Fixed Delta: {:.3}", time_ctx.fixed_dt)); + ui.label(format!("FPS: {:.3}", 1.0 / time_ctx.frame_dt)); + ui.label(format!("Character POS: {character_pos:.3}")); + }); + }); } } @@ -124,6 +124,7 @@ fn main() { .add_system::() .add_system::() .add_system::() + .add_system::() .add_system::() .add_system::() .add_scene(MAIN_SCENE_ID, Scene::new(MAIN_SCENE_ID.to_owned(), None)) diff --git a/game/src/systems/display_settings.rs b/game/src/systems/display_settings.rs new file mode 100644 index 0000000..6384d15 --- /dev/null +++ b/game/src/systems/display_settings.rs @@ -0,0 +1,92 @@ +use std::sync::{Arc, Mutex}; +use raidillon_app::prelude::*; +use crate::systems::common::should_draw_menu; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum SettingsTab { + Display, +} + +impl Default for SettingsTab { + fn default() -> Self { + SettingsTab::Display + } +} + +#[derive(Clone, Default)] +struct DisplaySettingsUiState { + selected_fullscreen_mode: WindowMode, + active_tab: SettingsTab, +} + +#[derive(Default)] +pub struct DisplaySettings { + ui_state: Arc>, +} + +impl System for DisplaySettings { + fn load_world(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get_mut::().unwrap(); + + // sync the settings with UI state once + if let (Ok(settings_handle), Ok(mut state)) = (pctx.settings.read(), self.ui_state.lock()) { + state.selected_fullscreen_mode = settings_handle.display_settings.fullscreen_mode; + } + } + + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + if should_draw_menu(scene) { + let pctx = res.get_mut::().unwrap(); + let settings = pctx.settings.clone(); + let ui_state = self.ui_state.clone(); + + pctx.egui_queue.borrow_mut().queue(move |egui_ctx| { + egui::Window::new("Settings").default_open(false).show(egui_ctx, |ui| { + let mut state = ui_state.lock().unwrap(); + + ui.horizontal(|ui| { + ui.selectable_value(&mut state.active_tab, SettingsTab::Display, "Display Settings"); + }); + ui.separator(); + + match state.active_tab { + SettingsTab::Display => { + ui.label("Window Mode"); + egui::ComboBox::from_id_salt("window_mode") + .selected_text(window_mode_label(state.selected_fullscreen_mode)) + .show_ui(ui, |ui| { + for mode in [ + WindowMode::Windowed, + WindowMode::BorderlessFullscreen, + ] { + ui.selectable_value( + &mut state.selected_fullscreen_mode, + mode, + window_mode_label(mode), + ); + } + }); + + ui.add_space(8.0); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.button("Apply").clicked() { + if let Ok(mut settings_handle) = settings.write() { + settings_handle.display_settings.fullscreen_mode = state.selected_fullscreen_mode; + settings_handle.display_settings.dirty = true; + } + } + }); + } + } + }); + }); + } + } +} + +fn window_mode_label(mode: WindowMode) -> &'static str { + match mode { + WindowMode::Windowed => "Windowed", + WindowMode::BorderlessFullscreen => "Borderless Fullscreen", + } +} diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index 2f23ed5..bcf77ca 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -4,8 +4,10 @@ mod keybinds; mod menu; pub mod debug_camera; pub mod common; +mod display_settings; pub use physics::PhysicsSystem; pub use kinematic_character_controller::KinematicCharacterController; pub use keybinds::KeybindsSystem; pub use menu::MenuSystem; +pub use display_settings::DisplaySettings; diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 1b1ba08..bb1e192 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,6 +1,6 @@ use std::cell::RefCell; use std::rc::Rc; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use raidillon_platform::{Platform, PlatformContext, TimeContext}; use glium::backend::glutin::Display; use glium::backend::glutin::SimpleWindowBuilder; @@ -19,6 +19,7 @@ use crate::GliumAssetManager; use glam::Vec3; use winit::event::DeviceEvent::MouseMotion; use raidillon_core::EguiQueue; +use raidillon_platform::settings::{Settings, default_config_path}; pub struct GliumPlatform> { event_loop: EventLoop<()>, @@ -29,6 +30,7 @@ pub struct GliumPlatform> { engine: E, time: time::Time, egui_queue: Rc>, + settings: Arc>, } impl> Platform for GliumPlatform { @@ -57,6 +59,12 @@ impl> Platform for GliumPlatfor let egui_queue = Rc::new(RefCell::new(EguiQueue::new())); + let settings = Arc::new( + RwLock::new( + Settings::load_or_default(default_config_path()).unwrap() + ) + ); + Self { event_loop, window, @@ -66,6 +74,7 @@ impl> Platform for GliumPlatfor engine, time, egui_queue, + settings, } } @@ -82,9 +91,18 @@ impl> Platform for GliumPlatfor time_ctx: self.construct_time_ctx(), window: self.window.clone(), egui_queue: self.egui_queue.clone(), + settings: self.settings.clone(), }; self.engine.initialize(ctx.clone()); + self.settings.read().unwrap().display_settings.apply(&*self.window.lock().unwrap()); + let _ = &self.event_loop.run(move |event, el| { + let settings_handle = self.settings.read().unwrap(); + if settings_handle.display_settings.dirty { + settings_handle.display_settings.apply(&*self.window.lock().unwrap()); + } + drop(settings_handle); + self.rendering_system_manager .systems .values_mut() @@ -96,8 +114,14 @@ impl> Platform for GliumPlatfor match event { Event::WindowEvent { event, .. } => match event { + WindowEvent::Resized(size) => { + if size.width > 0 && size.height > 0 { + self.display.resize((size.width, size.height)); + } + }, WindowEvent::CloseRequested => { // TODO: Run uninitialize on renderer and engine + self.settings.read().unwrap().save_to_file(default_config_path()); el.exit(); }, WindowEvent::RedrawRequested => { diff --git a/platform/Cargo.toml b/platform/Cargo.toml index 3b42bac..1b7b5b2 100644 --- a/platform/Cargo.toml +++ b/platform/Cargo.toml @@ -8,3 +8,5 @@ winit = "0.30.12" raidillon_core = { path = "../core" } raidillon_assets = { path = "../asset" } glam = "0.30.5" +serde = "1.0.228" +toml = "0.9.8" diff --git a/platform/src/context.rs b/platform/src/context.rs index b78ea94..fdbe087 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -1,8 +1,9 @@ use std::{cell::RefCell, rc::Rc}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use winit::event::Event; use raidillon_assets::ModelManagerRef; use raidillon_core::EguiQueue; +use crate::settings::Settings; #[derive(Clone)] pub struct PlatformContext { @@ -13,6 +14,7 @@ pub struct PlatformContext { pub time_ctx: TimeContext, pub window: Arc>, pub egui_queue: Rc>, + pub settings: Arc>, } #[derive(Clone)] diff --git a/platform/src/lib.rs b/platform/src/lib.rs index 8875d35..94c467b 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -2,6 +2,7 @@ pub mod platform; mod camera; mod event; pub mod context; +pub mod settings; pub use platform::Platform; pub use camera::Camera; diff --git a/platform/src/settings.rs b/platform/src/settings.rs new file mode 100644 index 0000000..f6d092f --- /dev/null +++ b/platform/src/settings.rs @@ -0,0 +1,101 @@ +use winit::dpi::LogicalSize; +use winit::window::{Fullscreen, Window}; +use serde::{Serialize, Deserialize}; +use std::error::Error; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +pub fn default_config_path() -> PathBuf { + let exe_path = std::env::current_exe().unwrap(); + let exe_dir = exe_path + .parent() + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "executable has no parent")).unwrap(); + + exe_dir.join("settings.toml") +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +pub enum WindowMode { + BorderlessFullscreen, + #[default] + Windowed, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct Settings { + pub display_settings: DisplaySettings, +} + +impl Settings { + pub fn load_from_file(path: impl AsRef) -> Result> { + let path = path.as_ref(); + let text = fs::read_to_string(path)?; + let settings: Settings = toml::from_str(&text)?; + Ok(settings) + } + + pub fn save_to_file(&self, path: impl AsRef) -> Result<(), Box> { + let path = path.as_ref(); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + let toml_str = toml::to_string_pretty(self)?; + fs::write(path, toml_str)?; + Ok(()) + } + + pub fn load_or_default(path: impl AsRef) -> Result> { + let path = path.as_ref(); + + match fs::read_to_string(path) { + Ok(text) => { + let settings: Settings = toml::from_str(&text)?; + Ok(settings) + } + Err(err) if err.kind() == io::ErrorKind::NotFound => { + let settings = Settings::default(); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + let toml_str = toml::to_string_pretty(&settings)?; + fs::write(path, toml_str)?; + Ok(settings) + } + Err(err) => Err(Box::new(err)), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct DisplaySettings { + pub fullscreen_mode: WindowMode, + #[serde(skip)] + pub dirty: bool, +} + +impl Default for DisplaySettings { + fn default() -> Self { + Self { + fullscreen_mode: WindowMode::Windowed, + dirty: false, + } + } +} + +impl DisplaySettings { + pub fn apply(&self, window: &Window) { + // apply fullscreen mode + match self.fullscreen_mode { + WindowMode::BorderlessFullscreen => { + let monitor = window.current_monitor().or_else(|| window.primary_monitor()); + window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); + } + WindowMode::Windowed => { + window.set_fullscreen(None); + }, + } + } +} From 8041c7e01d88a1997f2ca3c1ad4727b04ec12aa8 Mon Sep 17 00:00:00 2001 From: reo Date: Sat, 13 Dec 2025 18:23:52 +0300 Subject: [PATCH 29/38] Fix awkward mouse conflict issues between egui and the engine --- game/src/main.rs | 5 +++++ game/src/systems/menu.rs | 6 +++++- glium_platform/src/platform.rs | 8 +++++++- glium_platform/src/render/egui.rs | 32 +++++++++++++++++++++++++++++-- glium_platform/src/system.rs | 2 ++ platform/src/context.rs | 4 ++++ 6 files changed, 53 insertions(+), 4 deletions(-) diff --git a/game/src/main.rs b/game/src/main.rs index b49445c..0f84666 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -106,6 +106,11 @@ impl System for MainSystem { character_pos = tr.translation; } egui_queue.queue(move |egui_ctx| { + // disable text selection on all labels. + egui_ctx.style_mut(|style| { + style.interaction.selectable_labels = false; + }); + egui::Window::new("Debug").show(egui_ctx, |ui| { ui.label("Hello World!"); ui.label(format!("Frame Delta: {:.3}", time_ctx.frame_dt)); diff --git a/game/src/systems/menu.rs b/game/src/systems/menu.rs index 60350b7..8a4b0d5 100644 --- a/game/src/systems/menu.rs +++ b/game/src/systems/menu.rs @@ -42,6 +42,7 @@ impl System for MenuSystem { }); } + fn handle_event(&mut self, res: &mut EngineResources, scene: &mut Scene) { // The menu is toggled by pressing the escape key let input = res.get::().unwrap(); @@ -63,18 +64,21 @@ impl MenuSystem { .next() .unwrap(); - let window = res.get::().unwrap().window.lock().unwrap(); + let pctx = res.get::().unwrap(); + let window = pctx.window.lock().unwrap(); match *menu_state { MenuState::Open => { *menu_state = MenuState::Closed; window.set_cursor_grab(CursorGrabMode::Confined).or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked)); window.set_cursor_visible(false); + pctx.should_egui_receive_input_events.set(false); }, MenuState::Closed => { *menu_state = MenuState::Open; window.set_cursor_grab(CursorGrabMode::None); window.set_cursor_visible(true); + pctx.should_egui_receive_input_events.set(true); }, } } diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index bb1e192..b67ef77 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::{RefCell, Cell}; use std::rc::Rc; use std::sync::{Arc, Mutex, RwLock}; use raidillon_platform::{Platform, PlatformContext, TimeContext}; @@ -31,6 +31,8 @@ pub struct GliumPlatform> { time: time::Time, egui_queue: Rc>, settings: Arc>, + /// Used for [`raidillon_platform::context::PlatformContext::should_egui_receive_input_events`] + should_egui_receive_input_events: Rc>, } impl> Platform for GliumPlatform { @@ -64,6 +66,7 @@ impl> Platform for GliumPlatfor Settings::load_or_default(default_config_path()).unwrap() ) ); + let should_egui_receive_input_events = Rc::new(Cell::new(false)); Self { event_loop, @@ -75,6 +78,7 @@ impl> Platform for GliumPlatfor time, egui_queue, settings, + should_egui_receive_input_events, } } @@ -92,6 +96,7 @@ impl> Platform for GliumPlatfor window: self.window.clone(), egui_queue: self.egui_queue.clone(), settings: self.settings.clone(), + should_egui_receive_input_events: self.should_egui_receive_input_events.clone(), }; self.engine.initialize(ctx.clone()); self.settings.read().unwrap().display_settings.apply(&*self.window.lock().unwrap()); @@ -136,6 +141,7 @@ impl> Platform for GliumPlatfor window: self.window.clone(), egui_queue: self.egui_queue.clone(), env_light_dir: Vec3::new(0.0, -1.0, 0.0), + should_egui_receive_input_events: self.should_egui_receive_input_events.clone(), }; self.rendering_system_manager diff --git a/glium_platform/src/render/egui.rs b/glium_platform/src/render/egui.rs index e754ba3..3cd0583 100644 --- a/glium_platform/src/render/egui.rs +++ b/glium_platform/src/render/egui.rs @@ -8,9 +8,12 @@ use crate::system::RenderingContext; use egui_glium::EguiGlium; use winit::event::{Event, WindowEvent}; use winit::event_loop::EventLoop; +use std::cell::Cell; +use std::rc::Rc; pub struct EguiRenderer { egui_glium: EguiGlium, + should_egui_receive_input_events: Option>>, } impl RenderingSystem for EguiRenderer { @@ -21,10 +24,14 @@ impl RenderingSystem for EguiRenderer { let window = window.lock().unwrap(); let egui_glium = EguiGlium::new(ViewportId::ROOT, &display, &window, &event_loop); - Self { egui_glium } + Self { egui_glium: egui_glium, should_egui_receive_input_events: None } } fn render(&mut self, ctx: &mut RenderingContext) { + if self.should_egui_receive_input_events.is_none() { + self.should_egui_receive_input_events = Some(ctx.should_egui_receive_input_events.clone()); + } + let window = ctx.window.lock().unwrap(); self.egui_glium.run(&window, |egui_ctx| { @@ -38,7 +45,28 @@ impl RenderingSystem for EguiRenderer { let window = window.lock().unwrap(); match event { Event::WindowEvent { event, .. } => { - let _ = self.egui_glium.on_event(&window, &event); + let should_egui_receive_input_events = match self.should_egui_receive_input_events.as_ref() { + Some(v) => v.get(), + None => true, + }; + + let should_send_event = if should_egui_receive_input_events { + true + } else { + !matches!(event, + WindowEvent::KeyboardInput { .. } | + WindowEvent::ModifiersChanged(_) | + WindowEvent::CursorMoved { .. } | + WindowEvent::MouseInput { .. } | + WindowEvent::MouseWheel { .. } | + WindowEvent::Touch(_) | + WindowEvent::Ime(_) + ) + }; + + if should_send_event { + let _ = self.egui_glium.on_event(&window, &event); + } } _ => {}, } diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index d94e9c3..92fd553 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -10,6 +10,7 @@ use raidillon_core::{define_typemap, EguiQueue}; use raidillon_core::scene::Scene; use glam::Vec3; use winit::event_loop::EventLoop; +use std::cell::Cell; pub struct RenderingContext<'a> { pub scene: &'a Scene, @@ -19,6 +20,7 @@ pub struct RenderingContext<'a> { pub asset_manager: ModelManagerRef, pub egui_queue: Rc>, pub env_light_dir: Vec3, + pub should_egui_receive_input_events: Rc> } /// The internal "rendering system" trait of glium_platform. diff --git a/platform/src/context.rs b/platform/src/context.rs index fdbe087..0990527 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -1,3 +1,4 @@ +use std::cell::Cell; use std::{cell::RefCell, rc::Rc}; use std::sync::{Arc, Mutex, RwLock}; use winit::event::Event; @@ -15,6 +16,9 @@ pub struct PlatformContext { pub window: Arc>, pub egui_queue: Rc>, pub settings: Arc>, + /// Sets whether or not egui will receive input events. + /// Added to prevent the mouse state conflict between the engine and egui. + pub should_egui_receive_input_events: Rc>, } #[derive(Clone)] From 73692b710e5814bd519b4e30fec10f14b97d32e8 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 15:53:54 +0300 Subject: [PATCH 30/38] Add debug wireframe rendering support --- app/src/prelude.rs | 3 + assets/shaders/debug_wireframe.frag | 9 +++ assets/shaders/debug_wireframe.vert | 14 ++++ game/src/main.rs | 4 +- game/src/systems/mod.rs | 2 + game/src/systems/physics_debug.rs | 35 +++++++++ glium_platform/src/platform.rs | 14 +++- glium_platform/src/render/debug_wireframe.rs | 80 ++++++++++++++++++++ glium_platform/src/render/mod.rs | 2 + glium_platform/src/system.rs | 2 + platform/src/context.rs | 61 ++++++++++++++- platform/src/lib.rs | 2 +- 12 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 assets/shaders/debug_wireframe.frag create mode 100644 assets/shaders/debug_wireframe.vert create mode 100644 game/src/systems/physics_debug.rs create mode 100644 glium_platform/src/render/debug_wireframe.rs diff --git a/app/src/prelude.rs b/app/src/prelude.rs index 1171d0f..fc9cdc1 100644 --- a/app/src/prelude.rs +++ b/app/src/prelude.rs @@ -11,6 +11,9 @@ pub use raidillon_platform::{ Camera, PlatformContext, TimeContext, + DebugWireframes, + DebugWireframesRef, + DebugWireframeVertex, settings::{Settings, WindowMode}, }; diff --git a/assets/shaders/debug_wireframe.frag b/assets/shaders/debug_wireframe.frag new file mode 100644 index 0000000..96d53b9 --- /dev/null +++ b/assets/shaders/debug_wireframe.frag @@ -0,0 +1,9 @@ +#version 330 core + +in vec4 v_color; + +out vec4 frag_color; + +void main() { + frag_color = v_color; +} diff --git a/assets/shaders/debug_wireframe.vert b/assets/shaders/debug_wireframe.vert new file mode 100644 index 0000000..7a53438 --- /dev/null +++ b/assets/shaders/debug_wireframe.vert @@ -0,0 +1,14 @@ +#version 330 core + +in vec3 position; +in vec4 color; + +uniform mat4 view; +uniform mat4 projection; + +out vec4 v_color; + +void main() { + v_color = color; + gl_Position = projection * view * vec4(position, 1.0); +} diff --git a/game/src/main.rs b/game/src/main.rs index 0f84666..3e1f318 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -8,7 +8,8 @@ use winit::event::{Event, WindowEvent}; use systems::debug_camera::FPSDebugCameraSystem; use crate::systems::common::should_draw_menu; use crate::systems::{ - DisplaySettings, KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem + DisplaySettings, KeybindsSystem, KinematicCharacterController, MenuSystem, PhysicsSystem, + PhysicsDebugSystem, }; const TEST_GLTF: &str = "sphere.glb"; @@ -125,6 +126,7 @@ impl System for MainSystem { fn main() { raidillon_app::App::new() .add_system::() + .add_system::() .add_system::() .add_system::() .add_system::() diff --git a/game/src/systems/mod.rs b/game/src/systems/mod.rs index bcf77ca..08c54ab 100644 --- a/game/src/systems/mod.rs +++ b/game/src/systems/mod.rs @@ -1,4 +1,5 @@ mod physics; +mod physics_debug; mod kinematic_character_controller; mod keybinds; mod menu; @@ -7,6 +8,7 @@ pub mod common; mod display_settings; pub use physics::PhysicsSystem; +pub use physics_debug::PhysicsDebugSystem; pub use kinematic_character_controller::KinematicCharacterController; pub use keybinds::KeybindsSystem; pub use menu::MenuSystem; diff --git a/game/src/systems/physics_debug.rs b/game/src/systems/physics_debug.rs new file mode 100644 index 0000000..33364d2 --- /dev/null +++ b/game/src/systems/physics_debug.rs @@ -0,0 +1,35 @@ +use raidillon_app::prelude::*; + +/// renders aabb wireframes for all physics colliders +#[derive(Default)] +pub struct PhysicsDebugSystem; + +impl System for PhysicsDebugSystem { + fn frame_update(&mut self, res: &mut EngineResources, scene: &mut Scene) { + let pctx = res.get::().expect("PlatformContext missing").clone(); + + let mut debug_wireframes = pctx.debug_wireframes.borrow_mut(); + if !debug_wireframes.enabled { + return; + } + + let physics = match scene.resources.get::() { + Some(p) => p, + None => return, + }; + + let color = [1.0, 0.0, 0.0, 1.0]; + + for (_, collider) in physics.collider_set.iter() { + let aabb = collider.compute_aabb(); + let min = aabb.mins; + let max = aabb.maxs; + + debug_wireframes.add_box( + [min.x, min.y, min.z], + [max.x, max.y, max.z], + color, + ); + } + } +} diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index b67ef77..8f00d92 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,7 +1,7 @@ use std::cell::{RefCell, Cell}; use std::rc::Rc; use std::sync::{Arc, Mutex, RwLock}; -use raidillon_platform::{Platform, PlatformContext, TimeContext}; +use raidillon_platform::{Platform, PlatformContext, TimeContext, DebugWireframes, DebugWireframesRef}; use glium::backend::glutin::Display; use glium::backend::glutin::SimpleWindowBuilder; use glium::glutin::surface::WindowSurface; @@ -14,7 +14,7 @@ use raidillon_assets::ModelManagerRef; use raidillon_core::engine::EngineTrait; use raidillon_core::time; use raidillon_core::time::Time; -use crate::render::{BasicMeshRenderingSystem, EguiRenderer, SkyboxRenderingSystem}; +use crate::render::{BasicMeshRenderingSystem, DebugWireframeRenderingSystem, EguiRenderer, SkyboxRenderingSystem}; use crate::GliumAssetManager; use glam::Vec3; use winit::event::DeviceEvent::MouseMotion; @@ -31,7 +31,7 @@ pub struct GliumPlatform> { time: time::Time, egui_queue: Rc>, settings: Arc>, - /// Used for [`raidillon_platform::context::PlatformContext::should_egui_receive_input_events`] + debug_wireframes: DebugWireframesRef, should_egui_receive_input_events: Rc>, } @@ -57,6 +57,7 @@ impl> Platform for GliumPlatfor // Install rendering systems in order rendering_system_manager.add::(&display, window.clone(), &event_loop); rendering_system_manager.add::(&display, window.clone(), &event_loop); + rendering_system_manager.add::(&display, window.clone(), &event_loop); rendering_system_manager.add::(&display, window.clone(), &event_loop); let egui_queue = Rc::new(RefCell::new(EguiQueue::new())); @@ -66,6 +67,7 @@ impl> Platform for GliumPlatfor Settings::load_or_default(default_config_path()).unwrap() ) ); + let debug_wireframes = Rc::new(RefCell::new(DebugWireframes::new())); let should_egui_receive_input_events = Rc::new(Cell::new(false)); Self { @@ -78,6 +80,7 @@ impl> Platform for GliumPlatfor time, egui_queue, settings, + debug_wireframes, should_egui_receive_input_events, } } @@ -96,6 +99,7 @@ impl> Platform for GliumPlatfor window: self.window.clone(), egui_queue: self.egui_queue.clone(), settings: self.settings.clone(), + debug_wireframes: self.debug_wireframes.clone(), should_egui_receive_input_events: self.should_egui_receive_input_events.clone(), }; self.engine.initialize(ctx.clone()); @@ -140,6 +144,7 @@ impl> Platform for GliumPlatfor asset_manager: self.asset_manager.clone(), window: self.window.clone(), egui_queue: self.egui_queue.clone(), + debug_wireframes: self.debug_wireframes.clone(), env_light_dir: Vec3::new(0.0, -1.0, 0.0), should_egui_receive_input_events: self.should_egui_receive_input_events.clone(), }; @@ -149,6 +154,9 @@ impl> Platform for GliumPlatfor .values_mut() .for_each(|system| system.render(&mut context)); + // clear debug wireframes after rendering + self.debug_wireframes.borrow_mut().clear(); + target.finish().unwrap(); } _ => {}, diff --git a/glium_platform/src/render/debug_wireframe.rs b/glium_platform/src/render/debug_wireframe.rs new file mode 100644 index 0000000..2f259e9 --- /dev/null +++ b/glium_platform/src/render/debug_wireframe.rs @@ -0,0 +1,80 @@ +use std::sync::{Arc, Mutex}; +use glium::{Display, Program, Surface, VertexBuffer, implement_vertex}; +use glium::glutin::surface::WindowSurface; +use glium::index::PrimitiveType; +use glium::uniform; +use winit::event_loop::EventLoop; +use raidillon_assets::include_shader; +use crate::system::RenderingContext; +use crate::RenderingSystem; +pub use raidillon_platform::Camera; + +#[derive(Copy, Clone)] +struct DebugVertex { + position: [f32; 3], + color: [f32; 4], +} + +implement_vertex!(DebugVertex, position, color); + +/// renders debug wireframes from the shared buffer +pub struct DebugWireframeRenderingSystem { + program: Program, + params: glium::DrawParameters<'static>, +} + +impl RenderingSystem for DebugWireframeRenderingSystem { + fn initialize(display: &Display, _window: Arc>, _event_loop: &EventLoop<()>) -> Self { + const VERT_SRC: &str = include_shader!("debug_wireframe.vert"); + const FRAG_SRC: &str = include_shader!("debug_wireframe.frag"); + + let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap(); + + let params = glium::DrawParameters { + depth: glium::Depth { + test: glium::draw_parameters::DepthTest::IfLess, + write: false, + ..Default::default() + }, + line_width: Some(1.0), + ..Default::default() + }; + + Self { program, params } + } + + fn render(&mut self, ctx: &mut RenderingContext) { + let debug_wireframes = ctx.debug_wireframes.borrow(); + + if !debug_wireframes.enabled || debug_wireframes.vertices.is_empty() { + return; + } + + let cam = match ctx.scene.world.query::<&Camera>().iter().next() { + Some((_, cam)) => *cam, + None => return, + }; + + let vertices: Vec = debug_wireframes.vertices.iter() + .map(|v| DebugVertex { position: v.position, color: v.color }) + .collect(); + + let vbuf = match VertexBuffer::new(ctx.display, &vertices) { + Ok(vb) => vb, + Err(_) => return, + }; + + let uniforms = uniform! { + view: cam.view().to_cols_array_2d(), + projection: cam.projection().to_cols_array_2d(), + }; + + ctx.target.draw( + &vbuf, + glium::index::NoIndices(PrimitiveType::LinesList), + &self.program, + &uniforms, + &self.params, + ).ok(); + } +} diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index fdfa1ea..22aa047 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,7 +1,9 @@ mod basic; mod skybox; mod egui; +mod debug_wireframe; pub use basic::BasicMeshRenderingSystem; pub use skybox::SkyboxRenderingSystem; pub use egui::EguiRenderer; +pub use debug_wireframe::DebugWireframeRenderingSystem; diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 92fd553..cf5f945 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -8,6 +8,7 @@ use glium::glutin::surface::WindowSurface; use raidillon_assets::ModelManagerRef; use raidillon_core::{define_typemap, EguiQueue}; use raidillon_core::scene::Scene; +use raidillon_platform::DebugWireframesRef; use glam::Vec3; use winit::event_loop::EventLoop; use std::cell::Cell; @@ -19,6 +20,7 @@ pub struct RenderingContext<'a> { pub display: &'a Display, pub asset_manager: ModelManagerRef, pub egui_queue: Rc>, + pub debug_wireframes: DebugWireframesRef, pub env_light_dir: Vec3, pub should_egui_receive_input_events: Rc> } diff --git a/platform/src/context.rs b/platform/src/context.rs index 0990527..31bd6cb 100644 --- a/platform/src/context.rs +++ b/platform/src/context.rs @@ -6,6 +6,62 @@ use raidillon_assets::ModelManagerRef; use raidillon_core::EguiQueue; use crate::settings::Settings; +/// a single debug wireframe vertex with position and color +#[derive(Clone, Copy)] +pub struct DebugWireframeVertex { + pub position: [f32; 3], + pub color: [f32; 4], +} + +/// shared buffer for debug wireframe rendering +#[derive(Clone, Default)] +pub struct DebugWireframes { + pub vertices: Vec, + pub enabled: bool, +} + +impl DebugWireframes { + pub fn new() -> Self { + Self { vertices: Vec::new(), enabled: true } + } + + pub fn clear(&mut self) { + self.vertices.clear(); + } + + /// add a single line segment + pub fn add_line(&mut self, start: [f32; 3], end: [f32; 3], color: [f32; 4]) { + self.vertices.push(DebugWireframeVertex { position: start, color }); + self.vertices.push(DebugWireframeVertex { position: end, color }); + } + + /// add a wireframe box from min/max corners + pub fn add_box(&mut self, min: [f32; 3], max: [f32; 3], color: [f32; 4]) { + let [x0, y0, z0] = min; + let [x1, y1, z1] = max; + + // bottom face edges + self.add_line([x0, y0, z0], [x1, y0, z0], color); + self.add_line([x1, y0, z0], [x1, y0, z1], color); + self.add_line([x1, y0, z1], [x0, y0, z1], color); + self.add_line([x0, y0, z1], [x0, y0, z0], color); + + // top face edges + self.add_line([x0, y1, z0], [x1, y1, z0], color); + self.add_line([x1, y1, z0], [x1, y1, z1], color); + self.add_line([x1, y1, z1], [x0, y1, z1], color); + self.add_line([x0, y1, z1], [x0, y1, z0], color); + + // vertical edges + self.add_line([x0, y0, z0], [x0, y1, z0], color); + self.add_line([x1, y0, z0], [x1, y1, z0], color); + self.add_line([x1, y0, z1], [x1, y1, z1], color); + self.add_line([x0, y0, z1], [x0, y1, z1], color); + } +} + +pub type DebugWireframesRef = Rc>; + #[derive(Clone)] pub struct PlatformContext { pub current_event: Event<()>, @@ -16,8 +72,9 @@ pub struct PlatformContext { pub window: Arc>, pub egui_queue: Rc>, pub settings: Arc>, - /// Sets whether or not egui will receive input events. - /// Added to prevent the mouse state conflict between the engine and egui. + /// shared debug wireframe buffer + pub debug_wireframes: DebugWireframesRef, + /// sets whether or not egui will receive input events pub should_egui_receive_input_events: Rc>, } diff --git a/platform/src/lib.rs b/platform/src/lib.rs index 94c467b..ef88e08 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -6,4 +6,4 @@ pub mod settings; pub use platform::Platform; pub use camera::Camera; -pub use context::{PlatformContext, TimeContext}; +pub use context::{PlatformContext, TimeContext, DebugWireframes, DebugWireframesRef, DebugWireframeVertex}; From 2a255affe4bcec7d814a44ba69dc2f5a81f2c5ba Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 17:08:51 +0300 Subject: [PATCH 31/38] Fix the skybox visual artifact bug #10 --- assets/shaders/skybox.frag | 6 +++++- glium_platform/src/render/skybox.rs | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/assets/shaders/skybox.frag b/assets/shaders/skybox.frag index 864be50..ce28e95 100644 --- a/assets/shaders/skybox.frag +++ b/assets/shaders/skybox.frag @@ -9,15 +9,19 @@ uniform sampler2D equirect; const vec2 inv_atan = vec2(0.15915494309, 0.31830988618); vec2 sample_spherical_map(vec3 v) { - vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + vec2 uv = vec2(atan(v.z, v.x), asin(clamp(v.y, -1.0, 1.0))); uv *= inv_atan; uv += 0.5; + uv.x = fract(uv.x); return uv; } void main() { vec2 uv = sample_spherical_map(normalize(direction)); uv.y = 1.0 - uv.y; + vec2 size = vec2(textureSize(equirect, 0)); + float epsY = 0.5 / max(size.y, 1.0); + uv.y = clamp(uv.y, epsY, 1.0 - epsY); vec3 color = texture(equirect, uv).rgb; frag_color = vec4(color, 1.0); } diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs index 94832d0..2c9b9d9 100644 --- a/glium_platform/src/render/skybox.rs +++ b/glium_platform/src/render/skybox.rs @@ -7,6 +7,7 @@ use glium::glutin::surface::WindowSurface; use glium::index::PrimitiveType; use glium::texture::{RawImage2d, SrgbTexture2d, Texture2d}; use glium::uniform; +use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction}; use glam::{Mat4, Vec2, Vec3}; use winit::event_loop::EventLoop; use raidillon_assets::include_shader; @@ -132,10 +133,15 @@ impl RenderingSystem for SkyboxRenderingSystem { let mut view = cam.view(); // remove translation from view matrix (only orientation) view.col_mut(3).x = 0.0; view.col_mut(3).y = 0.0; view.col_mut(3).z = 0.0; + let mut sampler = self.equirect_srgb.sampled(); + sampler = sampler.wrap_function(SamplerWrapFunction::Repeat); + sampler = sampler.minify_filter(MinifySamplerFilter::Linear); + sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear); + let uniforms = uniform! { view: view.to_cols_array_2d(), projection: cam.projection().to_cols_array_2d(), - equirect: &self.equirect_srgb, + equirect: sampler, }; let params = glium::DrawParameters { depth: glium::Depth { test: glium::draw_parameters::DepthTest::IfLessOrEqual, write: false, ..Default::default() }, ..Default::default() }; ctx.target.draw(&self.quad_vb, &self.quad_ib, &self.program, &uniforms, ¶ms).ok(); From b50a60755ae9890d3438f34aa87396492556640d Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 22:16:41 +0300 Subject: [PATCH 32/38] Add exclusive fullscreen --- game/src/systems/display_settings.rs | 2 + platform/Cargo.toml | 2 +- platform/src/settings.rs | 67 +++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/game/src/systems/display_settings.rs b/game/src/systems/display_settings.rs index 6384d15..5d8455c 100644 --- a/game/src/systems/display_settings.rs +++ b/game/src/systems/display_settings.rs @@ -58,6 +58,7 @@ impl System for DisplaySettings { for mode in [ WindowMode::Windowed, WindowMode::BorderlessFullscreen, + WindowMode::ExclusiveFullscreen, ] { ui.selectable_value( &mut state.selected_fullscreen_mode, @@ -88,5 +89,6 @@ fn window_mode_label(mode: WindowMode) -> &'static str { match mode { WindowMode::Windowed => "Windowed", WindowMode::BorderlessFullscreen => "Borderless Fullscreen", + WindowMode::ExclusiveFullscreen => "Exclusive Fullscreen", } } diff --git a/platform/Cargo.toml b/platform/Cargo.toml index 1b7b5b2..16fec56 100644 --- a/platform/Cargo.toml +++ b/platform/Cargo.toml @@ -8,5 +8,5 @@ winit = "0.30.12" raidillon_core = { path = "../core" } raidillon_assets = { path = "../asset" } glam = "0.30.5" -serde = "1.0.228" +serde = { version = "1.0.228", features = ["derive"] } toml = "0.9.8" diff --git a/platform/src/settings.rs b/platform/src/settings.rs index f6d092f..a901137 100644 --- a/platform/src/settings.rs +++ b/platform/src/settings.rs @@ -1,4 +1,4 @@ -use winit::dpi::LogicalSize; +use winit::monitor::{MonitorHandle, VideoModeHandle}; use winit::window::{Fullscreen, Window}; use serde::{Serialize, Deserialize}; use std::error::Error; @@ -19,6 +19,7 @@ pub fn default_config_path() -> PathBuf { #[serde(rename_all = "snake_case")] pub enum WindowMode { BorderlessFullscreen, + ExclusiveFullscreen, #[default] Windowed, } @@ -93,9 +94,73 @@ impl DisplaySettings { let monitor = window.current_monitor().or_else(|| window.primary_monitor()); window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } + WindowMode::ExclusiveFullscreen => { + let monitor = window.current_monitor().or_else(|| window.primary_monitor()); + match monitor { + Some(monitor) => { + if let Some(video_mode) = pick_best_video_mode(&monitor) { + window.set_fullscreen(Some(Fullscreen::Exclusive(video_mode))); + } else { + // fallback to borderless + window.set_fullscreen(Some(Fullscreen::Borderless(Some(monitor)))); + } + } + None => { + // no monitor info, fallback to windowed + window.set_fullscreen(None); + } + } + } WindowMode::Windowed => { window.set_fullscreen(None); }, } } } + +fn pick_best_video_mode(monitor: &MonitorHandle) -> Option { + let target_size = monitor.size(); + + let mut best_native: Option = None; + let mut best_any: Option = None; + + for mode in monitor.video_modes() { + if mode.size() == target_size { + let replace = match best_native.as_ref() { + None => true, + Some(best) => { + (mode.refresh_rate_millihertz(), mode.bit_depth()) + > (best.refresh_rate_millihertz(), best.bit_depth()) + } + }; + if replace { + best_native = Some(mode.clone()); + } + } + + let replace = match best_any.as_ref() { + None => true, + Some(best) => is_better_video_mode(&mode, best), + }; + if replace { + best_any = Some(mode); + } + } + + best_native.or(best_any) +} + +fn is_better_video_mode(a: &VideoModeHandle, b: &VideoModeHandle) -> bool { + let a_size = a.size(); + let b_size = b.size(); + let a_area = u64::from(a_size.width) * u64::from(a_size.height); + let b_area = u64::from(b_size.width) * u64::from(b_size.height); + + match a_area.cmp(&b_area) { + std::cmp::Ordering::Greater => true, + std::cmp::Ordering::Less => false, + std::cmp::Ordering::Equal => { + (a.refresh_rate_millihertz(), a.bit_depth()) > (b.refresh_rate_millihertz(), b.bit_depth()) + } + } +} From 6ac8e8f503ab6c825b14b303141cedc6cbb9e5da Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 22:44:44 +0300 Subject: [PATCH 33/38] Update kinematic character controller to apply forces to dynamic bodies the character collides with --- .../systems/kinematic_character_controller.rs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index 41561a5..fd031c5 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -1,7 +1,7 @@ use glam::{Quat, Vec3}; -use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder}; +use rapier3d::prelude::{nalgebra, ColliderBuilder, QueryFilter, RigidBodyBuilder, RigidBodyType}; use rapier3d::prelude::vector; -use rapier3d::control::KinematicCharacterController as RapierKinematicCharacterController; +use rapier3d::control::{CharacterCollision, KinematicCharacterController as RapierKinematicCharacterController}; use rapier3d::na::{Isometry3, Vector3}; use winit::event::DeviceEvent::MouseMotion; use winit::event::Event; @@ -53,7 +53,7 @@ impl System for KinematicCharacterController { )); self.speed = 5.0; - self.sensitivity = 0.1; + self.sensitivity = 0.05; self.gravity = -9.81; self.max_fall_speed = -50.0; self.vertical_velocity = 0.0; @@ -141,13 +141,14 @@ impl System for KinematicCharacterController { let mut total_displacement = self.desired_movement; total_displacement.y += self.vertical_velocity * pctx.time_ctx.fixed_dt; + let mut collisions: Vec = Vec::new(); let corrected_movement = self.character_controller.move_shape( pctx.time_ctx.fixed_dt, &query_pipeline, &*self.character_collider.shape, &Isometry3::from(self.last_position), vector![total_displacement.x, total_displacement.y, total_displacement.z], - |_| {}, + |collision| collisions.push(collision), ); // update character rigid body with the new translation. @@ -165,6 +166,22 @@ impl System for KinematicCharacterController { } } + // apply impulses to dynamic bodies the character collided with + let character_push_force = 50.0; + for collision in collisions { + if let Some(collider) = p.collider_set.get(collision.handle) { + if let Some(rb_handle) = collider.parent() { + if let Some(rb) = p.rigid_body_set.get_mut(rb_handle) { + if rb.body_type() == RigidBodyType::Dynamic { + let push_direction = -collision.hit.normal1.into_inner(); + let impulse = push_direction * character_push_force * pctx.time_ctx.fixed_dt; + rb.apply_impulse(impulse, true); + } + } + } + } + } + self.desired_movement = Vec3::ZERO; } } From 98d2a9dacb0a813b03ed273d5ae83d2dcc543486 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 23:08:32 +0300 Subject: [PATCH 34/38] Add checkered sphere model --- assets/models/checkered-sphere.glb | 3 +++ game/src/main.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 assets/models/checkered-sphere.glb diff --git a/assets/models/checkered-sphere.glb b/assets/models/checkered-sphere.glb new file mode 100644 index 0000000..ad55283 --- /dev/null +++ b/assets/models/checkered-sphere.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36498b406df94ee6990c72a932082101fe0427af52260893e299e4d1a44de084 +size 1796992 diff --git a/game/src/main.rs b/game/src/main.rs index 3e1f318..a619cb3 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -12,7 +12,7 @@ use crate::systems::{ PhysicsDebugSystem, }; -const TEST_GLTF: &str = "sphere.glb"; +const TEST_GLTF: &str = "checkered-sphere.glb"; const PLANE_GLTF: &str = "plane.glb"; const MAIN_SCENE_ID: &str = "main_scene"; From a51aeb23bd71d00d8f9ab5207f6382fbe2e2f9d4 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 17 Dec 2025 20:56:02 +0300 Subject: [PATCH 35/38] Fix character collider shape --- game/src/systems/kinematic_character_controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/src/systems/kinematic_character_controller.rs b/game/src/systems/kinematic_character_controller.rs index fd031c5..b27450a 100644 --- a/game/src/systems/kinematic_character_controller.rs +++ b/game/src/systems/kinematic_character_controller.rs @@ -35,7 +35,7 @@ impl System for KinematicCharacterController { let p = scene.resources.get_mut::().expect("Physics missing"); let rb = RigidBodyBuilder::kinematic_position_based().build(); let rb_handle = p.rigid_body_set.insert(rb); - self.character_collider = ColliderBuilder::capsule_z(1.5, 1.0); + self.character_collider = ColliderBuilder::capsule_y(1.0, 1.0); p.collider_set.insert_with_parent(self.character_collider.build(), rb_handle, &mut p.rigid_body_set); let tr = Transform { translation: Vec3::new(0.0, 2.0, 3.0), From 71e991db779c4486d934f324c3e25b4bea7332e6 Mon Sep 17 00:00:00 2001 From: reo Date: Mon, 15 Dec 2025 23:34:51 +0300 Subject: [PATCH 36/38] Improve lighting - No specular on surfaces facing away from the light source - Transform the light direction to view space to ensure that all lighting calculations happen in a consistent coordinate space. - Other lighting tweaks --- assets/shaders/gl_textured.frag | 33 ++++++++++++++++++----------- glium_platform/src/render/basic.rs | 8 +++++-- glium_platform/src/render/skybox.rs | 2 +- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/assets/shaders/gl_textured.frag b/assets/shaders/gl_textured.frag index b264f77..abfa702 100644 --- a/assets/shaders/gl_textured.frag +++ b/assets/shaders/gl_textured.frag @@ -6,28 +6,37 @@ in vec3 v_position; out vec4 frag_color; -uniform vec3 u_light; +uniform vec3 u_light; // direction TO the light (normalized) uniform sampler2D tex; -uniform vec3 color; // base colour factor (acts as solid colour when no texture) +uniform vec3 color; // base colour factor (acts as solid colour when no texture) void main() { // Combine base texture (or constant white) with colour factor supplied by CPU. vec3 base_col = texture(tex, v_tex).rgb * color; - vec3 ambient_color = base_col * 0.2; - vec3 diffuse_color = base_col * 0.6; - vec3 specular_color = vec3(1.0); + vec3 N = normalize(v_normal); + vec3 L = normalize(u_light); - // u_light is the direction **from the light towards the fragment**. - float diffuse = max(dot(normalize(v_normal), normalize(u_light)), 0.0); + // Classic Blinn-Phong lighting + // Ambient: always present + vec3 ambient = base_col * 0.15; - vec3 camera_dir = normalize(-v_position); - vec3 half_dir = normalize(normalize(u_light) + camera_dir); - float specular = pow(max(dot(half_dir, normalize(v_normal)), 0.0), 16.0); + // Diffuse: N dot L, clamped + float NdotL = max(dot(N, L), 0.0); + vec3 diffuse = base_col * NdotL * 0.7; - vec3 result = ambient_color + diffuse * diffuse_color + specular * specular_color; + // Specular: only on surfaces facing the light (NdotL > 0) + float specular = 0.0; + if (NdotL > 0.0) { + vec3 V = normalize(-v_position); // view direction (camera at origin in view space) + vec3 H = normalize(L + V); // half-vector + float NdotH = max(dot(N, H), 0.0); + specular = pow(NdotH, 32.0) * 0.5; // tighter highlight, moderated intensity + } - // Convert from linear to sRGB for display (approximate γ-correction) + vec3 result = ambient + diffuse + vec3(specular); + + // Convert from linear to sRGB for display (approximate gamma correction) result = pow(result, vec3(1.0 / 2.2)); frag_color = vec4(result, 1.0); diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index a55d865..3ed144b 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -59,7 +59,11 @@ impl RenderingSystem for BasicMeshRenderingSystem { }; // Use HDR-derived environment light direction if provided, otherwise default to downward - let light_dir: Vec3 = if ctx.env_light_dir.length_squared() > 0.0 { ctx.env_light_dir.normalize() } else { Vec3::new(0.0, -1.0, 0.0) }; + let light_dir_world: Vec3 = if ctx.env_light_dir.length_squared() > 0.0 { ctx.env_light_dir.normalize() } else { Vec3::new(0.0, -1.0, 0.0) }; + + // Transform light direction to view space (normals/positions are in view space) + let view_mat3 = glam::Mat3::from_mat4(cam.view()); + let light_dir_view = (view_mat3 * light_dir_world).normalize(); let asset_manager = ctx.asset_manager.borrow(); @@ -90,7 +94,7 @@ impl RenderingSystem for BasicMeshRenderingSystem { model: tr.matrix().to_cols_array_2d(), view: cam.view().to_cols_array_2d(), projection: cam.projection().to_cols_array_2d(), - u_light: [light_dir.x, light_dir.y, light_dir.z], + u_light: [light_dir_view.x, light_dir_view.y, light_dir_view.z], tex: sampler, color: [c[0], c[1], c[2]], uv_offset: [mat.uv_offset.x, mat.uv_offset.y], diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs index 2c9b9d9..d11683b 100644 --- a/glium_platform/src/render/skybox.rs +++ b/glium_platform/src/render/skybox.rs @@ -119,7 +119,7 @@ impl RenderingSystem for SkyboxRenderingSystem { // Load EXR from assets/exr let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let path = std::path::Path::new(manifest_dir).join("../assets/exr/qwantani_sunset_puresky_2k.exr"); + let path = std::path::Path::new(manifest_dir).join("../assets/exr/citrus_orchard_road_puresky_4k.exr"); let (equirect_srgb, light_dir) = Self::load_hdr_equirect_and_analyze(display, &path); Self { program, quad_vb, quad_ib, equirect_srgb, light_dir } } From 0f2209a5d536994541166ae0d4111848d06d1831 Mon Sep 17 00:00:00 2001 From: reo Date: Wed, 31 Dec 2025 22:34:05 +0300 Subject: [PATCH 37/38] Update the gltf loader to support multiple meshes in one file --- glium_platform/src/assets.rs | 8 +- glium_platform/src/gltf_loader.rs | 151 ++++++++++++++++------------- glium_platform/src/render/basic.rs | 56 +++++------ 3 files changed, 115 insertions(+), 100 deletions(-) diff --git a/glium_platform/src/assets.rs b/glium_platform/src/assets.rs index 1b3b4e1..c8cb1d4 100644 --- a/glium_platform/src/assets.rs +++ b/glium_platform/src/assets.rs @@ -12,7 +12,7 @@ use raidillon_assets::model_manager::ModelID; /// Glium platform asset manager implementation. pub struct GliumAssetManager { - pub models: HashMap, + pub models: HashMap>, facade: Box, } @@ -28,8 +28,8 @@ impl GliumAssetManager { impl ModelManager for GliumAssetManager { fn load_gltf(&mut self, id: ModelID, path: &Path) { - let model = load_gltf(path, self.facade.as_ref()).unwrap(); - self.models.insert(id, model); + let models = load_gltf(path, self.facade.as_ref()).unwrap(); + self.models.insert(id, models); } fn unload_model(&mut self, id: ModelID) { @@ -48,6 +48,6 @@ impl ModelManager for GliumAssetManager { // } fn get_model(&self, id: &ModelID) -> Option<&dyn Any> { - self.models.get(id).map(|model| model as &dyn Any) + self.models.get(id).map(|models| models as &dyn Any) } } diff --git a/glium_platform/src/gltf_loader.rs b/glium_platform/src/gltf_loader.rs index e41bbfb..fe4ec37 100644 --- a/glium_platform/src/gltf_loader.rs +++ b/glium_platform/src/gltf_loader.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use glium::{backend::Facade, IndexBuffer, VertexBuffer}; use glium::index::PrimitiveType; use std::{fmt::Debug, path::Path}; @@ -8,31 +8,16 @@ use glium::uniforms::{SamplerWrapFunction, MinifySamplerFilter, MagnifySamplerFi use gltf::image::Format as GltfFormat; use glam::Vec2; -/// Load a glTF 2.0 file from disk and upload the first primitive to the GPU. -pub fn load_gltf

(path: P, facade: &dyn Facade) -> Result +/// Load a glTF 2.0 file from disk and upload all primitives to the GPU. +/// +/// Returns one [`Model`] per glTF primitive (across all meshes). +pub fn load_gltf

(path: P, facade: &dyn Facade) -> Result> where P: AsRef + Debug, { // -- parse the asset & bring buffer blobs into memory -- let (doc, buffers, images) = gltf::import(path.as_ref()).context("failed to import glTF file")?; - // -- grab the very first mesh / primitive -- - let mesh = doc.meshes().next().context("glTF has no meshes")?; - let primitive = mesh.primitives().next().context("mesh has no primitives")?; - - // ---------- MATERIAL ---------- - let mut mat = Material::default(); - - let mat_idx = primitive.material().index().context("primitive has no material")?; - let material = doc.materials().nth(mat_idx).unwrap(); - let pbr = material.pbr_metallic_roughness(); - - // Factors -------------------------------------------------- - mat.base_color_factor = pbr.base_color_factor(); - mat.metal_factor = pbr.metallic_factor(); - mat.roughness_factor = pbr.roughness_factor(); - mat.emissive_factor = material.emissive_factor(); - // Helper to update sampler settings from glTF sampler fn update_sampler(mat: &mut Material, t: &gltf::texture::Texture<'_>) { let sampler_info = t.sampler(); @@ -64,64 +49,92 @@ where } } - // Base-color texture (sRGB) - if let Some(info) = pbr.base_color_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.base_color = Some(glium_srgb_texture(facade, &images[view])?); - } + let mut out: Vec = Vec::new(); - // Metallic-Roughness (linear) - if let Some(info) = pbr.metallic_roughness_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.metallic_roughness = Some(glium_linear_texture(facade, &images[view])?); - } + for mesh in doc.meshes() { + for primitive in mesh.primitives() { + // ---------- MATERIAL ---------- + let mut mat = Material::default(); + let material = primitive.material(); + let pbr = material.pbr_metallic_roughness(); - // Normal map (linear) - if let Some(info) = primitive.material().normal_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.normal = Some(glium_linear_texture(facade, &images[view])?); - } + // Factors -------------------------------------------------- + mat.base_color_factor = pbr.base_color_factor(); + mat.metal_factor = pbr.metallic_factor(); + mat.roughness_factor = pbr.roughness_factor(); + mat.emissive_factor = material.emissive_factor(); - // Occlusion (linear) - if let Some(info) = primitive.material().occlusion_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.occlusion = Some(glium_linear_texture(facade, &images[view])?); - } + // Base-color texture (sRGB) + if let Some(info) = pbr.base_color_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.base_color = Some(glium_srgb_texture(facade, &images[view])?); + } - // Emissive (sRGB) - if let Some(info) = primitive.material().emissive_texture() { - update_sampler(&mut mat, &info.texture()); - let view = info.texture().source().index(); - mat.emissive = Some(glium_srgb_texture(facade, &images[view])?); - } + // Metallic-Roughness (linear) + if let Some(info) = pbr.metallic_roughness_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.metallic_roughness = Some(glium_linear_texture(facade, &images[view])?); + } - // KHR_texture_transform - if let Some(tex) = pbr.base_color_texture() { - if let Some(xform) = tex.texture_transform() { - mat.uv_offset = Vec2::new(xform.offset()[0], xform.offset()[1]); - mat.uv_scale = Vec2::new(xform.scale()[0], xform.scale()[1]); + // Normal map (linear) + if let Some(info) = material.normal_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.normal = Some(glium_linear_texture(facade, &images[view])?); + } + + // Occlusion (linear) + if let Some(info) = material.occlusion_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.occlusion = Some(glium_linear_texture(facade, &images[view])?); + } + + // Emissive (sRGB) + if let Some(info) = material.emissive_texture() { + update_sampler(&mut mat, &info.texture()); + let view = info.texture().source().index(); + mat.emissive = Some(glium_srgb_texture(facade, &images[view])?); + } + + // KHR_texture_transform (base color only, for now) + if let Some(tex) = pbr.base_color_texture() { + if let Some(xform) = tex.texture_transform() { + mat.uv_offset = Vec2::new(xform.offset()[0], xform.offset()[1]); + mat.uv_scale = Vec2::new(xform.scale()[0], xform.scale()[1]); + } + } + + // ---- Vertex/index data ---- + let reader = primitive.reader(|buf| Some(&buffers[buf.index()].0)); + + let positions: Vec<[f32; 3]> = reader.read_positions().context("missing POSITION")?.collect(); + let normals: Vec<[f32; 3]> = reader.read_normals().context("missing NORMAL")?.collect(); + let tex_coords: Vec<[f32; 2]> = reader + .read_tex_coords(0) + .map(|tc| tc.into_f32().collect()) + .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]); + let indices: Vec = reader.read_indices().context("missing indices")?.into_u32().collect(); + + // Interleave + let vertices: Vec = (0..positions.len()) + .map(|i| Vertex { position: positions[i], normal: normals[i], tex_coords: tex_coords[i] }) + .collect(); + + let vbuf = VertexBuffer::immutable(facade, &vertices)?; + let ibuf = IndexBuffer::immutable(facade, PrimitiveType::TrianglesList, &indices)?; + + out.push(Model { mesh: Mesh { vbuf, ibuf }, material: mat }); } } - // ---- Vertex/index data ---- - let reader = primitive.reader(|buf| Some(&buffers[buf.index()].0)); + if out.is_empty() { + bail!("glTF has no mesh primitives"); + } - let positions: Vec<[f32; 3]> = reader.read_positions().context("missing POSITION")?.collect(); - let normals: Vec<[f32; 3]> = reader.read_normals().context("missing NORMAL")?.collect(); - let tex_coords: Vec<[f32; 2]> = reader.read_tex_coords(0).map(|tc| tc.into_f32().collect()).unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]); - let indices: Vec = reader.read_indices().context("missing indices")?.into_u32().collect(); - - // Interleave - let vertices: Vec = (0..positions.len()).map(|i| Vertex { position: positions[i], normal: normals[i], tex_coords: tex_coords[i] }).collect(); - - let vbuf = VertexBuffer::immutable(facade, &vertices)?; - let ibuf = IndexBuffer ::immutable(facade, PrimitiveType::TrianglesList, &indices)?; - - Ok(Model { mesh: Mesh { vbuf, ibuf }, material: mat }) + Ok(out) } /// Linear-space texture (RGBA8) from glTF image data. diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index 3ed144b..6553cc0 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -73,41 +73,43 @@ impl RenderingSystem for BasicMeshRenderingSystem { _ => continue, }; - let model = match model.downcast_ref::() { - Some(model) => model, + let models = match model.downcast_ref::>() { + Some(models) => models, None => continue, }; - let mesh = &model.mesh; - let mat = &model.material; + for model in models { + let mesh = &model.mesh; + let mat = &model.material; - let tex_ref: &SrgbTexture2d = mat.base_color.as_ref().unwrap_or(&self.white_tex); + let tex_ref: &SrgbTexture2d = mat.base_color.as_ref().unwrap_or(&self.white_tex); - let mut sampler = tex_ref.sampled(); - sampler = sampler.wrap_function(SamplerWrapFunction::Repeat); - sampler = sampler.minify_filter(MinifySamplerFilter::Linear); - sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear); + let mut sampler = tex_ref.sampled(); + sampler = sampler.wrap_function(SamplerWrapFunction::Repeat); + sampler = sampler.minify_filter(MinifySamplerFilter::Linear); + sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear); - let c = mat.base_color_factor; + let c = mat.base_color_factor; - let uniforms = uniform! { - model: tr.matrix().to_cols_array_2d(), - view: cam.view().to_cols_array_2d(), - projection: cam.projection().to_cols_array_2d(), - u_light: [light_dir_view.x, light_dir_view.y, light_dir_view.z], - tex: sampler, - color: [c[0], c[1], c[2]], - uv_offset: [mat.uv_offset.x, mat.uv_offset.y], - uv_scale: [mat.uv_scale.x, mat.uv_scale.y], - }; + let uniforms = uniform! { + model: tr.matrix().to_cols_array_2d(), + view: cam.view().to_cols_array_2d(), + projection: cam.projection().to_cols_array_2d(), + u_light: [light_dir_view.x, light_dir_view.y, light_dir_view.z], + tex: sampler, + color: [c[0], c[1], c[2]], + uv_offset: [mat.uv_offset.x, mat.uv_offset.y], + uv_scale: [mat.uv_scale.x, mat.uv_scale.y], + }; - ctx.target.draw( - &mesh.vbuf, - &mesh.ibuf, - &self.program, - &uniforms, - &self.params, - ).unwrap(); + ctx.target.draw( + &mesh.vbuf, + &mesh.ibuf, + &self.program, + &uniforms, + &self.params, + ).unwrap(); + } } } } From a3a235692126b78d7cfdf3bace5f3d1d60f9772a Mon Sep 17 00:00:00 2001 From: Emre Osmanoglu Date: Fri, 3 Apr 2026 01:03:25 +0300 Subject: [PATCH 38/38] Add license --- LICENSE | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +.