From 34e9785fdcf512a83a2c6e746e64dd6d93889af6 Mon Sep 17 00:00:00 2001 From: Chris Conway Date: Tue, 28 Feb 2023 00:33:03 -0500 Subject: [PATCH] Multiple fixes to various parts. Basic examples/tests implemented in C++ and BP --- Config/DefaultRealtimeMeshComponent.ini | 5 +- .../RealtimeMeshSimple.uasset | Bin 0 -> 197344 bytes RealtimeMeshComponent.uplugin | 4 +- .../Private/RealtimeMesh.cpp | 2 +- .../Private/RealtimeMeshActor.cpp | 9 + .../Private/RealtimeMeshComponent.cpp | 6 +- .../Private/RealtimeMeshLibrary.cpp | 185 +++++++++++++ .../Private/RealtimeMeshSimple.cpp | 254 +++++++++++------- .../RealtimeMeshComponentProxy.cpp | 1 + .../RealtimeMeshSectionGroupProxy.cpp | 28 +- .../RenderProxy/RealtimeMeshSectionProxy.cpp | 3 +- .../Public/Data/RealtimeMeshConfig.h | 22 +- .../Public/Data/RealtimeMeshDataBuilder.h | 4 + .../Public/RealtimeMesh.h | 18 +- .../Public/RealtimeMeshActor.h | 3 +- .../Public/RealtimeMeshComponent.h | 8 +- .../Public/RealtimeMeshCore.h | 6 +- .../Public/RealtimeMeshLibrary.h | 25 +- .../Public/RealtimeMeshSimple.h | 96 ++++--- .../RenderProxy/RealtimeMeshGPUBuffer.h | 15 ++ .../RealtimeMeshSectionGroupProxy.h | 3 + .../RenderProxy/RealtimeMeshSectionProxy.h | 2 +- .../Private/RealtimeMeshEditorSubsystem.cpp | 4 - .../RealtimeMeshBasicUsageActor.cpp | 58 ++++ .../RealtimeMeshLatentUpdateTestActor.cpp | 66 +++++ .../RealtimeMeshUpdateTestActor.cpp | 63 +++++ .../RealtimeMeshBasicUsageActor.h | 19 ++ .../RealtimeMeshLatentUpdateTestActor.h | 45 ++++ .../RealtimeMeshUpdateTestActor.h | 39 +++ .../RealtimeMeshTests.Build.cs | 3 +- 30 files changed, 805 insertions(+), 191 deletions(-) create mode 100644 Content/CoreExamples/RealtimeMeshSimple/RealtimeMeshSimple.uasset create mode 100644 Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshBasicUsageActor.cpp create mode 100644 Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshLatentUpdateTestActor.cpp create mode 100644 Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshUpdateTestActor.cpp create mode 100644 Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshBasicUsageActor.h create mode 100644 Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshLatentUpdateTestActor.h create mode 100644 Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshUpdateTestActor.h diff --git a/Config/DefaultRealtimeMeshComponent.ini b/Config/DefaultRealtimeMeshComponent.ini index 4531c86..9cb787d 100644 --- a/Config/DefaultRealtimeMeshComponent.ini +++ b/Config/DefaultRealtimeMeshComponent.ini @@ -1,3 +1,6 @@ [CoreRedirects] +ClassRedirects=(OldName="/Script/RealtimeMeshEditor.ActorFactoryRealtimeMesh",NewName="/Script/RealtimeMeshEditor.RealtimeMeshActorFactory") -+PropertyRedirects=(OldName="/Script/RealtimeMeshComponent.RealtimeMeshSectionKey.SectionKey",NewName="/Script/RealtimeMeshComponent.RealtimeMeshSectionKey.SectionIndex") \ No newline at end of file ++PropertyRedirects=(OldName="/Script/RealtimeMeshComponent.RealtimeMeshSectionKey.SectionKey",NewName="/Script/RealtimeMeshComponent.RealtimeMeshSectionKey.SectionIndex") ++StructRedirects=(OldName="/Script/RealtimeMeshComponent.RealtimeMeshSimpleVertexData",NewName="/Script/RealtimeMeshComponent.RealtimeMeshSimpleMeshData") ++ClassRedirects=(OldName="/Script/RealtimeMeshTests.BasicUsageTest",NewName="/Script/RealtimeMeshTests.RealtimeMeshBasicUsageActor") ++FunctionRedirects=(OldName="/Script/RealtimeMeshComponent.RealtimeMeshSimple.UpdateMeshSection",NewName="/Script/RealtimeMeshComponent.RealtimeMeshSimple.UpdateSectionMesh") \ No newline at end of file diff --git a/Content/CoreExamples/RealtimeMeshSimple/RealtimeMeshSimple.uasset b/Content/CoreExamples/RealtimeMeshSimple/RealtimeMeshSimple.uasset new file mode 100644 index 0000000000000000000000000000000000000000..38d9f0cd8a98a1b96f7d03a325b3efabe37a9cc0 GIT binary patch literal 197344 zcmeEP2Y?gR)}B~Ev12djE}{rh(l^0IDlB!S*@f(GV57Uql4J!`6nuic`z&BD@7eK* zz3a0)d+)uA{aK%t|9tn($xJ4b>?CAy@n>LmX3j0=-gD1A?cSL@@V9{%{crj5<u{KtTMhWQ2)?5&QE2K0|!{_bJVZ+zirZw}gV z6N2sjz`=FDj{LC4H^(Zs95r*`&TA2D!a=X^H?rQ@d-i^_Z$D|-?d|IjY~faYrX4%J z`MS4X*y5U(AN_2D-3T`SrMZb~1LHsbV((Gc>~iaE_X5`UrAZguJAUV@qCIQRe)gD~ zV}l6xq4U)nHyD4y4!0fk%ypNI`rX@$V0&eMduCqv#?U*PopDyCrq7l;5iEG{;#Z?% z@9B5db-jWvl=lLP zLiH$nM5z)wV{|UrZ`)(jnCXOYUlMgMSj$S z2B1Z2DvSCIxAmsGU3Z-v@$5Pw-Wrc(;)6Xy>>^!JzPfBck#s5~j@j*3pMaZ33ICgG zs6#4~6dLU1=>6ODjC&jh(f!Z~@mOm%(H0*Y&$NV6ZS6#vWOisMm5xVd$J*LkTmoYu@U8M479eUW`&6|*+eR-EdTF@pKlIuD3+X* znlYm_J|&S!G$vXTRA&5=0c&g^6EoS&#FkidYL@crr5A54OQhP`sGGr##>Un-wJI5J zR%Q;_Y0MU)gg#MJ*&0{&?zj8y;Iu}fkycbqom!s^$Fs3SYo;NVjJGPAWQWZZ$fK!r zQ+#}TY-UGX>9PEtcQ!^1BV(EIhsD$BL^Dt;8y@h=2H@*pBAH6J#ac7UR`!kV6fLRG zjEyCd4Y5pS0ud>mj!|osD~?)pBr%=Rh}86<@n%B81ZL_!<2mMc5T5cdsz?&0o0Sn8 z+h12J=Ok(cck|hvuIBY+!G?}>Jlc_LQeJ&%`~tPa#CUc}thEE}<`Pq${qO^b4MN4B zZD(Hk<+cEgiO+~N%^4SuH&04Ybu;1<4~@4@h)>Tbr(J*GZW!nwRo0$PB(ozr63xmp zJ-3;>wH{iZ%*NBnSnK3We0;KXjN-9l-j+Hn)~f9A#G zJbzBRK-f4j-a0*$BC^fSDvj?Q`7cnbabioVqqRAdj>od`P^z_+rU_AN*-xkMxUQN! z3!HfKo8RxT4FE*$k%<{C4e5B3$T}&OoI%rt`gQ4{gT%-drBdnUjN&@#e^+dV zN{AaeVy)D-oK#V+J0$%)9x2Eq6emk^6|LA_wPuhL$TIY(06K{e3&{R zp6y5{1q&*7{;|%2{`#!4e~=>+<6>a)vhR|xG@DPo<77;v9C%WkbP6>`dGVUQ4`L#P#*Yh66*~InpPU0E zhUE0b3}u}UQtyDdh{Ne|DoK7#xbHOkwe|FAdfEHx&c8kd5y0h41*`YL$M=-&l9g)$ zZOr6_O9eF~+Hg8HOGuWp|NZvQ4K1`P|u_>O6Pe@Vg?>_mWwb1~zL@q&;8rE>nN8lJW zQR3YAk?&pLt7cNqGccGx{krHhnX5G$qh6+xBpn`{bi|s_FRG#vymt8CKMtZ~j;#MV zZ|sKPn@F>0DTyXQ?RRFK9)VH=BgdP=siqDn;`-$DlycY9jekWyWYYM?Lr4HL#Ih~Q z(}9cUi85mn)5&URBVm-J;t+p#uhsk?v`t2NAsiU1Q|1}bHv!M`S9cG zp<%2`MRPMsc)%`ufOZVlntbK{zio~JBZyPeG-AzCrYu}%-$@{YC@*Y@%Z_>CF!W74 zgc^QnfhIJU#L73}bH4?#>ytc%mA{|=!$>e?eX_kH3*~s%FH@Gn#lc9t*!$+q(Lpj# z9Nt_99~|(>Cg>nReL3JQ{r`CgCK3UKCBw>H^nQ=!y?)0~QDUyZl`nWlL3@mLg6F2H z^1Y)en@@MC;iNoM&fm1m7T6RX_;IJbT=i%%EdA(j0SIT|LH^ZL9#?5oN;k-mgHGj7>Fn zw8qIni)9ms#f6w@?|V{A)Jf7d0ir2a&DiS=uwODp+SK1Cav9o(#^(Ba%(v@|62SN@ zW{Qk*#XtVL6okRpvl^Y&Z|Zkw9$0 z=+5|TOFSKDf{}9H7YF<*Kpk0{ep5%pv&x9(wnd_H0fG}Kw9NW#XO2Zj#tSee>LVMx z*$2A5A=Y$gY({)cstKkSja4cM`e)i>B>8%0=Nu0@OJ0#|u+>{v9)NZVX&X;ZNvU)XAaO8!9Zt}J7=vebQBj6+^%f(ztbdE=^sxbFD093S9TkG&&e1;GB{e5 zDUV$}1l*+;)=JMk|91j>3_UPsg(;D{J{tzLq)*jbC$Ke6xTx1y2vUG5(tJPu+Li$sji+j!R_|)8|0bC_NXy zc&4mIb2Bc__B~PLB(q4P)n>QF7GVq~#tw@&Pf5gQjh{X}LtJ{~f4&Ik;QOaiZGpB_ z2MMF8Tb%M3CeXyDmUvrCS$@|^v!LIkS)^y36UTr3H_Tg=@BwAqgO@JAKSpMrNMB`+t7|$7IN9RHj~b$|zZ9wxgX{ zSqR}X)-8V<^iQ?*JXw|9 z9!U4Ypi7D5SEgs*p;2OJZqV}cPMA}`tekG?^H$)+NxIGyOiG?bED`FDNLaW zk99mG!XB(Z1@PK)j(Qz!XP~C~zdI=`)EcD-PWK2O5BxeW!MHO+67?#rK*sr%3g`9f%mXjA1#8Z_EJpGOa>4wUA zG*kVn1<{Kv?Vmnz*C*^aGL7PEF8X|etR9*w5iI(Ltj}BLY50p zPNqqrM4FXBYn{6eEC{Kh2S2mpeh@UEjGhp`k9=8%GdZ%SZ02`uAYxYxVe3G(-3Kgg zAB=jX7S7I^eTH~KOhLKsjQ9*_4b|10nr8&dhFo3q@XJGrWyukZJ&7@i#x%)WXg8Q`ap0@M7iOpV@HCGRDV@jD|Pch7#j5% zHr7-&<`!MI*)Zv;3d=(3gQ=sP%Iy!Fw*h8zB-zoXO!@bM6l|D?euz{JkaLrtId9)% zAUK4hCu&m;y3%$K7?^vjq`$r61T;nUCPHdh4VTLM5naq8cT$VE=6)&KW42 zKM3q zP2u?pYtNfL^K6qOia#sqr#A^g(&~H@WwTHw3T8A!!xaCf4;_o%usW+J351mhA&O$8 zm)F{@_w{I;mVw-~>97#@Td-(5kdl1><->V5p9N{I&awFDFKrlf)yz~fBaYu3O;De6 z#V#ClMI0FPBPkhozW);cPKK1C{CfJO{%ehvmFjn$yUvC0A{3#=pz;d!6JfKXAG-Dc zX$l!VcjEE?3|i{P^vo-+MHH3n_6J`MVIr76C()FVTQyHbg9JEV>-1gL<7Q!N2m?Us zHD&bX#~u%QP7FqE}94|cou+&8yAm4a??BAfU6u-(pwp*+DJ$l5kOkR{%XxZw& z>iucz6&JI7KU&Q9lf``hvzYH^i}`-BnD1AM`F^vQ?{^FNUM4#KU@_ksSj(iN)Oeu2 zH7(?OiSm6Ut1DR_@_k}4-=`MyeQhzH&0;^+x0r7ui}~)c(7qRmE>~F0*TVu{)YsEu zzAG)_y~;wq7YOe+7V~XTqJ7`0b)X;Lm8g$-{X5F{f`xpaQ@*<_!Z^mOA!$#MTBJ zW^zcM?_H6r^L&h>v--Hro#p$$BHj;8@^M+7v*72?OXTA@i+o>{$j5W`dCJH4X8qVu zr8DICXD0PAt@Qc+u$XUSweHUFZelUtTNdy_e=N3;5A<8d0$$`>*J8eZSj78Ji}|j$ zm~TCk_I*q3R*Q=Z%Vzi$8{rQf*fzum?zAU{YFAF9YV+zvj{g!2@{n`)QZvIUd_xs} zJ@)4sI9Ks2=QP9hjSdg;eXmvni@jbg_}+nBLvb9&6f3w`e*o8OY6Sxit`4q`b^ThC z>V}R)&A`>BqF8fk*Wto20ar#v(LSx~aG`&|)uE#JAl7xb5MTnXQ7Vdko4O9y+A1!d zR+lw*9WF#t(631 zu!`%NJGu@R&#y~yLr3b>uEWJT#Sa6Aj*Z{!I$W$%PKCgyqyD3=!^Jw~3KiEYpLZQD z)+x2Pq2q{eyABt45a(k2q2q#|yABsvLXWF%4b1NH4iZN>TbfJiB0QLhc@I$Wq3 zxQ45`a&u?b;o|WcqvD$E?K)g64=-0yTso{exPCK?7uE!Lyna>3E4)W_aQ&*o1(IQ1 zfpJ}>il)5+)xq_P4i~zCwF$-*Qc-*o>^fYi8GOjN21dFL7xN+GY8z1OK9o8W;DcfMSn0Nm|w%`1WxEnz&D~3 z+Yqs14Q&9g?m}lRoqg%tkB&;_(YUeF0J&=fQVebGkL zhiA|jX&3{BqdlNI>O;L4JLJb0fcB^xaEuG;#xv4@7jVEpXV4pT1-$_S&1>icok4rF z9aoe=8gSImi86Qw9ODCe)~Z+V7@omrpld%mL06RBiB8a#`37x89v*k#LmKj-?|4Q( z@r*v>8D;Q{{-7-L9`2ckanF2%dyFO8fbz_jfa96@nV*^0`59vlI`cEe8{^B*7<>IQ z#vOdX>EHo=1`MZ7qs6HwgH)Fo74Lr)7c8XAFV43Ut-CbsP5&!UA*9~8jy8VF zi#{?VUq=-0CQ+JQ%!wT3^dHrrRMDPXVu)TS7T-ib3OFy9qJLiGIV+s3?ci z=dN?ueIl(sgLiDjH%od@T9iKP5*Nxanh1)wZwDTQ31<`k9whaEyXES$gxW~)@!eMH z-3a9nC8flbQS>*Yv?+%v)0LS@MnF51aY`e>8)0J$e zscWcZSF{a1)OyBKyOM<2M&^QSl?GutY@(*vNDOh$wYx1;*hH$Ok@}9FpvGG2H+qzy zyGa5zlTv|cEw#jo_JPY!CMve8m}gSm#o%kJc)34{jeEm==aM#3+A^)Yk=;NowYM1g zFwvufdfY;-%P5CaT4(7!4J|aM_&RE?cBM%g|+2U&Tp+Q%Bn5f%GV+sD` zJ`_u5{7hx@K5VA8*2p!?Gx;3Tt4e+2wvD2m1Zhk%)GLfjG5C5_!pk%RUi5edof$e) zG`*&ACb%QLn(N9kKNhT4dc=l1Z`oLjm7*}R@X+Q$hp zhwABVsg0Yex$^57Nqudn-p&;IgKOlxrCY``qfKd0B6;O`hLtYQ<6;~$txY!4xK_rl zkDALG{k8geBv_B|T&z?EGxts*`6cyjA!JLntr{*K;W5OuS)so=NNY8d_L@#ljg+7J zhn6?eSS5(w$P29oDZ+Ylni5rJQK=O1u$EWT`sS(3y;_SHQke#V49HUZVS~)f%|%TD z6ieT_fHFL*3+);kg()OwKvHOZHR@xY)dPq|Qlbo`a@!POs;tMXGf_9@4s?_mY+Dtd zY%%7!Mr&^27zze(J!T^|EjP+Gaw%j@+IZF~M=B#|9z&|L^kc3#igL5f#JpB4FTRDz zt<+}U6q@0hzBlJR^qr{~6s>^ev7692&@_h#&J59A3*D!by;a_|k#Q_Bnx$>0rfKwI z9OdyQ>(UN&pIdTcg&r zqJ3q{LoW-pQ7fHVYpKtyIio~DO@`?4O;a$YHVS}NXZ%c9k)m#HeGVV#EGL+_x_HQupp&2w}3Jsb- zq2G3?5;b@Rg9b@qnZUBpd=?{ofQq-Yq?xSdnoM>Kte9HT22y|4s?XqI`c^6F9B@;d zav+L<>ugGCqkpnY2VG%l)snt}|1Wb7C5;jhmQo9)w3Ej(gRV2FWJ+Me>_9n4&Nfl& z41?xGU6@6O)9BbJP+T3}Vp~vp=+KvikX=<=r76g%4b<)q>KXplQlF;_3bYU{;QQMs zlwF;cvwj1H8B}|rch`B2!IpZ)S`5|{MpNs{5KHBmqVSC1bJ@fQN-D^|e^uLywQh0S zOH0u#tp;J0Wr)`BD{K_XmQ=Em*KTPI!A@|H4dJ1GQL-SS#J3@`8~nn~ z7(q}s-Ph4SADvE-KS+OVR7$puxnHwt^>?f(8fXr{gUZm%(0XH|8m-jAdRku+c7Xoh zUzL*zdBAAJPbTTuth5p}c)n>6wAS43%yqckRAvLe9|_!lM)a1jqg=pNh>q<=oL zG>1_>CtX97!%L~KM{ShH%6PK1B4uO;aXTWzkPz?+Ug zyL+;wB?Vjm4a5PpWOpHM0(l{+sjVT{$cwcy-ptc3;!rQ~rGu^x!Pj0IqY!Zocp4`- zdYJedB94yGeV9^R^v_LD8~uue6?`1UKr9f}${~Ww4pEMvUepS|+k8#LrtD#<{>{}~ z`Ccu!4VIQMf?y+C)Jm(FN@0}_E~3;3n&%#(3^YTKp4>zaH&G@?R2w1a=%u*>n1g79 zbj;PgEa79lsE@7I1A3+(V$_JY=+!C&Q+aA&a{xbYx7oF0!2Na$& zwA?T{2U8!-EdFA)eL!u=ipmi7bl_2gcMvz!yla*)`qWxoI=B@7O3yHk)tGT;u~@B* z=wYR9j+;%U_GiTUCOi{pgwET=esi%lNWW&L&=JZ2MC3~DLV zFl?3BMdX(wr}lzb%=H$waA?uI`RiFDEcJtBe>2SzUVR&BsU&*>#b|3&>dUUDgXU#M zNNN3w(kQhaIWFbsBFOpoCp|&FX;UK-48)3N=`&oh^9d=mVQ^{daG*M&{^BZBky3fh8Lf`!8T+N9e>A zQo4mOF_yj*QRSSFtP?Eti=(22>p`&P!Rc5vV_vVPmc;2FxF$;R0IZh5Z-K33BWY=c zV_2F&?j}X7DML~Lm{>1NvQ!W2Q++*%Gs`_VHlmM}*66hhw?>m+Q!1`QTSeH%QXQTaFk}?A($o zSb8}tEl0#RB)>{Mkx^?eq!@FZ-1i20Wi7QtvuPTMPccUMaujk2|4LJxHIv+5P)B+O zaULIOCU_5sr6G<6KSKWXiKq#pzlafgDK$)22jMx)623woAm7@@`YWt`S+}(JH6H+x zr_OzVS(aL&S#y$p`P9Qa?VNhNUM9;T&H{UP4wb~I|EFKAU@4dHD`HM#9I>vh?SR}! zt%Xy0e+eS~9mHec7p{-2No_=5E8M|UDz-m_$4fs3 z+D?(VmT<6^Fv>i=C##oxdblS0z!~^gdPG=4L$4yHks_N590+-JbXhg*O|lcZob71T zFwasAY=`bcc(KoSQd;GHAs(}wGHY+TNYTViFJp*CfFIg(`KQiTG$GnLOac&0gkg&is`5y zK^M(0n{vFR@_MX@+n!K%DHoMEvFuWADshsfQp}?oMKLlbTPn-;lUCNIoMNdY@0FZF zd;n_z@s}2|QeY!WUs$t8;A;l8t;588KTEBn(5~bj%c!YwpPM-pF$P#)Y-8bS zQ_i;3PVOT?%OCYWZ>@<($l9ncwnQ;)gs?jtBjgYi zhgBM(C)lV`c0qo@dvj8XL*&IyhcJyDAU5R!6(8F~+FOKrj?z{mppUdYs8|7LC;O|F zqKB{{vNW^w9tqc)U&^LjXsMSRjmX~@&YsUS>V>{7HsvBqwKDBs`@^os4z~#Lu}8># z#9jdd^{^uZUT>Hn*z-_F&(bf3rm!g&Tf)ckkk`<#!@=0{ODxs2rRlbuP7d>pG6~|h# zj}R?JZs=23=7=MMrbuZMmIwAQBAU}AG#cLL1-0_`s@RmvEw!0xgjwjO{>s^jHC?Q4 zg3kcO?i=i{gQX((;=xOWKZ^Z&5rW&4D=guQs=4xMg#KytfW4@_wbM2^{Y=-Y zH56L0>;=Gw!EDCb@8JdOJT~PzOKs?(=8`KXkZjjmD#!jZByb^L*pwU8S_<0_UlafA zG}e&b=mAGP<8qCve$`Tb9(@eiFdkr-$KU%2g4D?Mt;~fL} zcKVHKDc0x3tOWMy!FO_vfZuN~p7JkCt>rx_daDHQ_R04P@aBbFi7mu@lcoB3h9cSx zFAwqq5pcx6p}PA5&11z@zM&_j4gA8Tmhf?Zip2zt{m|lpH(RQoV-I+{ z2R?HxNfDcJONC|8uUjiD3(mjIQdu4YShRRg4!$||Gf7_^@*TPmBLSH&_nyOd$4WZh zO~78(DCM^)x0i(vdw`*Fz4Q-j^1uiA3keLpf|Utro!}jb5tLi5k|0VTb?_Z3zQTFW z5K!>WqPg_)x|uX7VjRUJ++Ah$4crS`Lhf`$^cs7cvC|ngEu!9G$^oq^`wPngS`_b< z!Va-1cU!`@t(r^IIJGnZIh#M*#}HlQ8b0d~ zhUk3`-c163OIjt6X3#S7?KYcoucaP!)`t5m)xtcDy?YpmVf0tZ6S8-V^*Ma1&Mvv3DGM6JfPJSQb9K(F`hKrh|^K0O6kjirNv!gB1n&3{H2@6ZWlR zu0l50lz*3n5Az;=Aw~@n9$EmJ0qc&K{c;TmBLx2i_~bpt4|#3MLn=O=|Ju$CrlDDk zZLpxCG4f^(wJ8sm)hB5+BNhO@f>ZH-^E$ue92sxc*7>1}_}^ga zb>SNt1F8NeEVZQgC_QPZJdYA&5Jp4p#fB_^wJ6_}#3}(IM92Z3lxLTRMiG%xynkX- zo+=9;EE`xM(AMl3$}dB}FN75eZh*Clt9)YA|yyPYgQ2j)T;}9U^g{CgeGGf?)4EtUSaU&H^+kzGZ?KA;uiC4N?wT72^$?A9K&9yl4qu=k0#UQa#*mX$gP_ zG1H`92;GQT2s;{jRj!L*UoYZ!utA_xF+(x;U$%sgtvEfu8uuI{(gwdy<6N8aily4Q zH*$3cJ^*%i@f##^y%Us&wS$N(Mg}%1bLo4N&qmzj36dDmi&s@0$MA+ zzJ_ zl(SBmx4Q^$XLNZ-#ao;%(6u(@T{U0%lKRhEZR}j)y=RH)EF&Pfd+GK|adwVSK3KE6z=Fui!_W=7Sw+ zQ$8-M{fJqiO=t~bQBqT)y`Uj{2(&vw|DcheKamS#in|cu`lKv;pgw!xGFB$P2!Tii zJVrz`F#hmhp#?j>w1@P%BHg-0RZ<|1S*TXS>7-`r5h!4WE!3xwDmhjb@_7Wqc z^&!8{(52F%(fV#vzEtbcVuM?$5WKets~TE|*GBNR7ode-+~$a(@!JC6lTvA472szH ztM{Ju->S3G=l`X~fJIaKJE7PIfSpm=PH$etXcj%ecROwL^8~U!Yq`HxbF)8UzWTFy zCw0Qw)VJXqOSQ74mmjw^(mX0gNPZ9dzn1D{?v=a=j{&*{+6edXtD%t)Z^SC8Tnz}) zz5Jesd~*)_UTn&@W#NNPk!!P%0niSRZBiS+7KYxzEP-5wd_Wu*QcuRuAYH#J3m@u{ zs~cEnLT{jr zglxn!+J!bkZ`za}%EE{7g7<{6ldZ;DIA{zh1C0e5<1b_|yg~SW@CjuMz(zltE9XLK z?q+^LOc+ufF&RV?&_D15pjgerm_n+m#gW31MpHu|~Ja%d?1Sj;QTAj~;<=Fs{Y zuYk^SHx>FKZ87Lg@UxA6va}q0s1@@L^9(i%EI^DQ#t5r_&{429z)R9n!#u&aYhaIJ zZQ<9l@Bt6F8l#WV0EfU51dm{*p%3r}5KV*K1e}rrh+1Ke|5g@0P!O{X?M1&3(?f(0 zSGnc@2_|g}mO7Ae;3TxlMnB0`4m~kzv1)<{o7C@+Xt2^SnsRnvPNIkCk8Bl2SgsS; zls{B_E2@Ke2eaJ$rvIV>v~cOvBj`&7b~=aAJLR}{i2J_O{=W2m#lF-Nr1d2_$!CxH z>{XxZ)Muai>=)0|l^E?^#C~+t;ZQNy)hEUQ&!Dd|m!NvgplNB`mjo zz&waZi#1*;98ph>S4)p5wEgN4wMON9{eWndET@eq;s+AO^BvX>Lo^tVbZ0zdHm*Da zI~!N#x2~M2-2cCpG=vu@d#2IYtD8@9)cTUWXl?Gm{*?8V);aLXI3MF+?!oG^)F|+0 zO4FTFi|Zh+p)>x==Xh?5#_tSa&PF^^VlS^|L^(TaHRoXI2v4ChTs$+he!wq~b#YJX za^ubT;73=251vlBed88(M#Vz;054l&(|8wad<>H{_9fc!NR^fk*w-M-mgZgfyqu45 zFjjMG3BHglp^XEV;89@QJO_bFaSrK? z96@QED(6#vUHNI!KVcYpQ&^kCB-0>6&^In2`)!T^v?x}aOta_ek@PaJN2Y0cF$%@% zm1$aDGiq|%Wg7BIh(C|s8a1WQA)oWNi=8uyz?Zp8GM zTe&QJWrnfjtghrn43jxtVm0~?oXYq)KhK9s_?5v@A_Dg#G%>k$fxnFnfynNLXNAR)TwM-tVvLC!9fL56kN))MH?4D znd`L(9-u7gN{27C|<*N>Mk@1Z{pWM4KB7DR)K|&K6)~-Yt&N zsA<@8f&`EfX&6!3s54PFOQzy=o6SWzTAF;Zj*lt6vhWqo7#^Eaqg84Dv^ew1>7Sel z7>DxkY1XXdd#mFoBjuIgW2%>nuXElv<4>&T$(da)zRKo9<@i`PlskTv_OG(}CpB03 z^Ig9t!d?sX7)yO^4pwSSVDwpAXf#|=90v(2yZgrnAi@9t#s@Ut5Sk6PuC$xXA{(5h zFH;yH05`Ffp;;tmF#_b&tPF`4>(E?UTT`@ZDO!u~0x!>fa~nlVs+Mo1=EvOJ7( zrfU+|9AlwJSD96hRx&3`4Qh_Bm|d@}O=`0UJ{Ze=yD#f#^Xj#xrY8dRWFBXHwe(}!IAkPq8hOdwB z6S;Hs$}uw2T=)e})C)O=RS{?-)PqP3w+byR9i_)!A^7UhgOGagvAwzSlBdnF%QTe7 z{FC2XLJP2uO=?hRUd(Ls7H}H-7}e9&+IqSsWz1Hy&~oPD&SD2$Cu6#9zOJ4}tLKr~ z+_h@+VqdhhNwj%{Im2>2Ut2>O!4BzsCb7OLt*y5{*}%(c%;lP8kbhHcILcl)NaNp z*1bWwlW(;}^5^bt#0=#4h2&l{J-Rxova+Kp)-4izv4U;$Qu21SMN;^VAvj-eMg92} z2{eFF-W&Io6ki3%?@ckAM8;1cd0AgUtFdP2%sw#U__J)1a$*irep%BsmD?m3JC4g} zc8QsmMBG`Q}{ZrF!%xl%*Y1G|0P0ZGvt*^=& zW##H!grFVENGaoZMGA7O)Gh((VmEv2yixA_%Glb(WSnp(5dE+M~W%=V* zIX;d~l*ccX<72*FS@_t7C>I}F8Rd=N>Rp6$bDpxag3LW-y@kY{HD-*t2Pv(6d^JUC z6)qulojC^Zb+OuHn%rfMwg+8$mLnSXF@!l!fZ8l$crd7KiD}w7FvG1z;zx@KEUpv_n9SI0gvLV9@yUSsNM(p}qep~-g8ndMPw*^WBddnqm3 z_4_TXNp`NcxcueF3EPkyY3NKot|~GM{i%+;TKz5of0>E*Ih#pIPSb2ONJis4!t7v8 ztj!}cj~zLgM@!G6>U!+J%l7xGyi%4gzbaYvwki?wF@b(uQj38p5WBoa36U zcoSM_`03yWE)5x0I#NEEa>LfcY7*=})WxOcoHx^x>`kEMz=@b9d|K?-_;Y;;>lUsi zNlQzd)fO)ELszUOt+sIGm-xHa+p>^vg`T3hg{xU9rRPz#EnLh?)_XD%u=)#_!|0d6 z%zvckZ*J|{p8HnBCTv!X=BSCwl%w<(Eilc12oH_x_d}9yHJlovjMd}t^NvT zVXPaPywKy3`dxB~o__4pcIFl2^PIGEj64TRiY@vT#{s_9JYVMP>UnOczmQIVvK7vK zsMI&amkXI58Yfq?2WYKv6o8fEZ7bRXj3{dltp|Xz_rMr1@|E$D*>3K_+8M&qOVb&h zajuy^tL^dB{8{tpskURYv`)lG^H`UTo+2lYnpJOB<4J84m?GK;FobEOabM>&f^WYn zto*F@*5C(9X})TDYub+F(lctcx3=0_>n!2{x%gN5wh{C>>sw7TnR{#6H+)Oaqt)J8 z*Sv+twqs|yHQ#c^2(z73eO+$NTdST03O&SDkJcMN*}~DRlhwZ3|2JQ)y1taTwUwL{^rlc3&kJp~0LrptMf%DyzdG^`e{_Sd?rzXn-TxT4RtiGBx}_FiJ?IR%f6C0Xehh}rw#h{Sx4ml)dGC`(iA zE!9%;bam{Gtx(p1m9HtIBwM1|NC8@19Lz;siG!_RJr4AwdV4vfj}ALh!(p`93b`7k z*taT=um74+D)frEOS657S?)~V>d1cJW_~Z+Q?UBiMtQbq;{x90XhXh#$zxc0WKdqy z-z>4r-d@nW+rp7C*TJZp`J*dlq}e)zwkls7qcO71?~g)qNQt!i*GBb`QqYAt!%Tik zZbU9AKdsVJi^mlotLoz8nNl7;?QPz2`^Ph@Tzouh%EiZXer4ffuX1JKW8JW_@DTVM3(v*&Pw+vVm1}dffdEEszeB+oi|rRgIDyI(4_8J{hctj zoHdK2n5D?FqO?6y-6#S0ooB4MjiRL%(|Fx4%8OnVN_yB@EFU#V!Tm!De=Co3N$I4; zfjYB)wE8vq#PyfLxcb*d<=PWuMq#8Gqlu9K^|e_9AB^RGzFp5csq{QTd7hiqjyLm| z7v}uB(}8QM}D8pLnJ)ovYo0O7W~JS{8Jxj*eaZg^=ofAq4V(y%9~y6kE|^ zdGzPWn68_ztLM?`U%ab)T@3SxZ7*#e0m^b+udOv5-uX;oeN$Rnn?>P(ooB3O<(gR< z<+YtX#jb68^?z*?F+i3dUEOw;srhu5GDcc`a_^Ufpk>m}kWfY0L{XG+v25b7cqrA@ zo=V1(*^IIV0UgR_^f%br5pPc?l37ElUD=#cC&Xi|*+g4>Y&_F4G11oE8lM_cw$c|H z5l_a`v246K)EdiVl%AA^^l=?+6XNZyi6$g9q*LwjbT$!}i0#Vyl%pY*rl!bJb@VLm z`VXDhluoo~hw2gMS~f%v4K!s_>Hf-kRCH{rxuZ2cF4h)THl;^>F4<@SmboUxTLm#1 zV%ZkTt0=wcl=P`$i$rIlt+5#yWnHS@;q*X8S=pM%+c~Q<(uWPAS zZmd2h)N8@zqTPSqHsTsJ{6^=cC;s^8c2~Y>s34*~Hx!gq0cWV!)70xHG!S`tIUx^d zcxYCiA6BnhbGm-+)nM|~sEq7Xb3CqIhj0l&k`3r-bRyFh&rWP=iMPcRK*RCrv5wYk z-u*>tvFFumj-YFcAyth+ zsaCxfTzGs6+IOs)HL6~jw3vqCSeyQ4(;abzd&0OlXKrpGJ7+4&fwzcC6{TzroP9`e z!{tjxgcct#@Pmgo-SNb-Iq(q`GdJ;(YH%O~lsJ0P--+?2Y$BDc36Qdr$7;5dbokTR z?pAd`%4WMi+&c#D8oFcPZ_jV__%)wju;&W1-J{iDyWVPdggRul8<9?Rw6A78l{70` z;<;74EVs8)pPBWAT`)ogvJ5&%ea;#RU#J=~EZ>Cz%9$A0qGsA#EnIM!phCF7j_UJ$ z1vw1>SV)sI@pLGa%#iFCQcW6~%D>dCTqj!&ZyBn{Jzl(t|FMTBA9(+Q7r+0t=biKJ zze!DLQm;DoF>NoVi?QWwvUS>Wn75I`$WR`Ujgii1q8V0suGO4x3V=US&R|Mm}q1iSjsA#T{kTD2Ay76yrn%N}e-k zYEbGeMYf@GrV?sH2DT1$4S?Ot?Pi*oHNf1S=oBl+VuBU64rApJGQ$dU#$26riW%%| zf?2QbK;RgiI5381i90z~FR1qzOJ`p?ccil)oxSNCOy{O_Zb9d+bZ$eZk4^`j808*x_N3EJC!CO- z=p0JtR&;_={pm#A1L?%vhBAPG59@yjosegKI-xW$(>-)L>4ZFR(FyAt8~|~~Jt~wD zEtHCbM;3DfM^PE^M_u#H=RY+pa$VcRZ}+|6^QZO1EQ^q6Hx8z{h*0cIfIf73>D->q z9q1fHXAPaZ(OE|)A{p?yz-U$M*v~J#@zndho{ijf@5T?WGy2Fg^c@2&aTL<;B$=Sh zRIl71ND}4bVu{m;(@!mycp0(UiNz9srvz{fj>6V}Z*UZ*94`};ht+F9y()XqO?@(v zO~hIghsSGTs@Ge?5gjSHV#(&339(qEft_p}fjYv|I*xwL6ixRt`6tSPBF{}o$N2nxHLUa>K$BA|| znN%D75%Im6HbfhmW7*iCnwB`xxFeHD&Y+=aiMLV^uckH8n2x3A)J*S43NxyFl7D~U zygkmJIyiJy_OVlEPu>0HDv;bxa-}kox33J6*E7i@8p&gl&>7Xmo|VY95IbjTQnQjZ z44eT6s3wuA$+qzDWU`g<`Lpx(+UmQdTi2g*=A*q|Np8DY6^LGv&r1rGCrc4MTqQn- z#1;p1-Nbm7D4Cs=s%ea65=}L2kQgLoGUc=HDSunK#QXTwp>x;$eY>rH*yz@-reqE2 z=AshgSrYL@#q*6-q9lw7ae$<%#K=jB8o{~Fn#R;@3h$RKf7cl|^r@Po&KQ5nweycZ z__o$ht3gfD$wh@hNosCPny|~M2{TF@{pc^tUsVdo2`?y`BXIdNUHtBC542u&z=)fU z4lbCz*`}9Qfu^UEeN!1t-z~+V^N8Tp&}ks0IBMvxN>gW^fX&n-k~M<7<g9?^&0Uj>q%PsV>yl0Q(2zY(=4DuchNl1A$W z23hl|Ys{@Z<<(IqC62sn=KpS+ZzQ_$nFNOLtlYvW^AN|z70~V)nh}WwIhcOaXuyqqGvet#Vj-u z#f&5=QS2@<^q--4!pyf3owd_k67WFEw=aG2*gSZpd` z#z!$xj~>wSF4evxQWRys;o_3JCWiBnNqE8EZ}utxBW@C>RG1x2~>#&UWo1c9Fx$V}ia3TzbIPHW2tKwHk0FI0P_#uI=9Bb`Di@<|@-u zPL$I#OaCqQmu=wazhHUPt_KvL+kfoAW1!CW@A1jnigNIn-^4vo9=v}ER!8N?REpJe zc{EmMP_Fe1(6f4CI8{uC#_~)b2`~7|EL@PF0ytfF1zlJ$qaRE>P?VW7*GLBF8_A8+ z8rwCEM1z<}y7|Z;(Z->=Ji7TP$Dj%TB!%LyiNFpsOYm5)hX}#(Oo$rhnw98y(>fHaJiBqhclHDIfgu@ zBFB)Y7c~_}o><~Ms>orbn4_E?P&mFUa`d^jd!6WOK2mHdIWY>HSIi7O zp!DEzu}%h$5D2P^+VyRv!5e#UAqqBZP;R^TdEV7th-W%#ULz1qbSnih(Q0oN#c~D5 z9z0dFge`YaP1bd#nh}fgQ0pkI&$ZoKlu!dRp3qM%P^AU=_wVr`RdMkCJsQaXSZlqB zwt4eZ3_6#LAn#F)oPcH7-gF+k|G`ngs{f+0TkKDzm37w0ITojJcDjC`mZp5U_sU5|d0Ew)jp60wbPor-N_>P&3w@)Q-@x;#b2Hu6ZZ&C>=DF+h4iff2=Q zpUbTW6v{$Yvie+wc73ig78p5zo>@pv3;AUkSzYKyAKz0)?>>6= z$Xk!AUH;H3JKSIfdqP(@uWz=l@0*WsZ~gmuA2fb?-nhk2&%J5K1*f2iqFYA9M?>emwdjEZf4nt`f=1Y#sEiK5tUc*wv~yglIW88lCdJY-;#vJ|Zcxt;lrSpGqmGq6)c*U90ktO{HTvSVOFnw% z);~h%gAuj_HLFKnA8f|);bw5|T<3BC3pWAbyZuM+Es^^&FRXd?XIq|T2Aihl$0axPJ?NoVWOlqsQKqWtdK@Z9 zUWz8<<;s7^V*u1-j~j79RJT7HrnaE+piMZwln5NZ%$DzbPqe?lMxsEvfl^e zj;LGP3}z~IwH~en**lC{df^ir-FL|u$Bei>@YVjqk}p9*b}E9qg%2mt|(nE2prU=*uKMl zdwZH|)S06j?)~t}S4R@a5|wU>Ja){7+iyAc#34TpdFz(T8$l%_Y!)h0j~u5DCBT^R z;nDFq20}<+%|!81Lewv;LYlCE)e` zSU3BB!^R&t`focY-(72+hNF?$%9PmhyPeitcb|vCr+LnO<@TQIybV$qVY{I+^~jL| zn|A3nkm-pT22$`gahYg|B)RYyW2u-Vr~s1{2fE^$_MPvkd0Pi=+AsY|%RjFZ!4bf> zA8^IbJ?=Oh0*Y!K)FV$MO!=N-Po_&csVGBfh?!EMc%?gvx1zIR2l^3|6i#H? zTVo<+Zp{(&W_dPcfaU^Q`EWH{AX;8axPtaNyEhc|xm})Uz!z}1{dRY_&TsenBKCl@ z&Myo($qZr}y9p{|m7bs@;1Bz~ZoAKE_XX@;x8LV+_O1wY`5!86Vy9&CL+ zzDIr|Q<}ksa{;<`69#ZOoo=7U8Faf{j*!pqbNWLepWEm65Hm!*!MX|u~3;;R$yK!nq=SvBI_e+<5h2%-*tE}>V1)fHa-Rs|DMQK}}A z>>z&FMI|VY{D)MRj>eijo(D8MU?zs=Bx7xfCeho8S^72tnM3N4Um*c2;SQr{LLElf zUW}2h-Gn>rKDQ(62!-7)d(`W9+Jn)cHxLMTgHew+;CI<8;Es?x6!wSRez!g7aJoam zu;1wmxr4r7sLo>#go5JjnEFi3ltd=c*cunI0cNAh9$HGTU@Fxbk0s@MJfIzO0q*mT zq6xZO2wqIW8}YdtPOJ(870EfQc=!=F}{~ z0$3SUSwNq*YB7P)-~baCVS9EzCUE(qp^!Hap_%Kh3%O_jy&-2f;EOt4k#ICviIEWW zMm;Wv%jFJ-d?AkyswZmq_(|nN>LT7~op|St=K9ze?O1N0Z|abLl5D1L?664aukk~X zY^xS)7;R3lh7q<$_hSvGuPz*NdF*bxJ?IKL>YOx_Nm7IY9=Dr@HCTashz5goK36#G zcDRDxI&awSi_oJ#8UW7({ca&2$R2Kr*NktE%_NBp3qmI!^l4RQ0(d7;dSNCm{1kb| zgHjbW@xYFYj(_u>pT=DGZK`9<^MBbAY+!`VXN+_eNYQ`j4%kUGVR~uJ=+wz~+H%R3 z+`8PpImT8@iIy^qMe{fVwD^cwz3!>GXrs_SdfYhs2hYX>G}!R8yCz&b z=ax|yKQQgSD_@+jy%}srUBMh(Y6v*7CDqZ|T%*1c5lXeTl5LSnR+%?oH;7N_fJvqc zCZQW~$*q;~iMJsVw>B-Ya+~KRHC~@cAeCwKBIfz_~i7jL+3qk@!@?JJuwL#vqGiA7B#=S)_|W! zE`IjfUY~v&T?Q%{VfsBeIgXA{Mvh#H?2~LkP)_u z#(U8f_Zi#uzj{V0aMrN<{hu${+zWaF{=z3?`yIE*-s7&_ZrD}P`g^`(_!s9qwo9+D zCwlT%2V5I{X6I)a{??S6zJKqL--4GvaJ45e?ADDKzQx(sJ2w6~G5++cPMAEWeg1rs5oE0#DD`KaRF|Ga4LPEsl3i+chr#nm| z@3jYLLBY3*#Ct1Y0;9o!I*hPx81HpQosme$<;A>p2Ezfn-4zN&{9eD$N8=R?Rlo$^ zP{1FsyMyjvou5RC!%OVtvj-zlcf{+A)~(v{-bz@*Xmg?tBdi<7dr5XXoKa_;+v%sZ zzz~^w_JBX+iv)ud%_j*_0c!+;QLjA|ak%O8@cu|N5)Aohb;w6c3v~g1U7&82iT75* z219=Q_Su1({#<|Z*~1pD)#KzS>M+9YCm0_&mltjxZCHxO+G-}mk~0bdeYzw|jxN4+ zP#s4XO{Hy+;y`yAYA^iZjDTluFh=R=zRzQMP_D?WZ?=-I#b_{EBsX2fmQo__;@dUO{f zofgCVQK!T0^+duRyN}GTusiCb%`_o<)Dwz|DWM;ItG=4k~KfIL1#E)bxtq2+el-7Z=`5BS27sE0P#L?iSY8+9a! zH!zHpRUUD<>~7i=M5-Fg{dKhd7N`pbXb~=KryX40NIs8DZpZf!BeSjXh#_|u@QB;j z6+B{;N#GG9?1ApbBSAmeZysOJ9SGXPv>(gw^hAR`QnFsZomNsSmm@)s*YEaWYo9wx z0UuY$=ZgmXbk_MoQL-52@_n+os2D-$I!}z$n`iC<4sm(Af1NB^X0ShRxth&j%tPZ?pa)iZ zXk4;J4~?+D#xk~>dRECYHYj3TvNnUQ^2^vB>S!g)*dT^+sm%VJ_=Zq$w}`Oz|yQF z4CxQn(f(Dpn_`f(Et$5cdn0}qz2snb*F`8&?fCPTu^pXRr5HTp+~UFJ0n}fS-^i3^ zu;IEw-Gl*L^zJ~|AMv|s_koj^u4&VOJroMMX{elzu(OhV40TaYozF*$l(bT1cLgG$ zusz@k(vr4=eB2QE1igq1xwn&qkAl5CtITUlm2ihqG@%Y7Y_IOe9U(6bUR@yIcGuAc za*9lZeKeAO+WX@6d%Xci1*;SuJH4z_XOBX_M56Sn5`|+)?9|!)k)Ssmbgu%-*p)DV zQ3s(8Bdi-PV+TSWH|;;6jYwX1l=dw|Xu&EFp}-df!vf*DN*Ev*^f+kmLfGvM(>p4( zj7|Jxr*N0U5v~h`)R=rI*4#L(^xziDJ z`so$6h{Hv%qgEgV{Iq+4USDQJjVGX0ri8_q1 zZn%u?rI(dlF50pd4mn)*x*+jHlr|>?L*Yo!=MrpG{)ES8S zz4Xo!F`eJ#wBaiVT9dJFg|j@omFEQ8{IK3C8H~V8xN%Xy zikD`@ZPuP{xQy)%*@M0)Nl35D?Q{8E?g%Y3*ZE!1pxaL`kXF99K>G&$K3c<~%|qmw z`MhoyEp)kp_6U7lz)|N2RT0zM#hyaMK1f`7(GV>rcTby|EtlMcuTuA>xR*$Qtwn+yUD0KyQ&c-3Ash ztSJfSuJ}#|L+&nEf$DsxLnS<76iKMV2zvl>N)+o)SNYOP)L*^=T zNS6r_qizC&7-8LT5t}wA`zdM{b-R3FkJk~kI|B5@ia>o4fbO+bsVClLoe;a6KX_vIruerog?O>^(LZPzDvGJ}=BG%Bh$ zevT2O5()fA-zJ6?yFRBD&a0s_9|WDb)N|Kay)Hju)RpO`@bA}7Y%qfzro)-`tce1pC@TY9b|9aO5l zzt`wDAL|%#?%IdV>9@`f+nd2=A+vhqIDIGqy3c2D(K;4==tAV^#}>R4yC`(Qzwd>G2lah)>LX^b`MQF+8Gr@L+{dttUr6d6-^zl9 zsN`E&puw~44=jw$-)G$2Z=boNA;p{_&E$qC_ePKm=u`KrqDc*l8^N8PY z2c-+Q;ffEd5Wd`@*XTK5CpUS96I9N@J$Cxz!SI0b3(h(6tkWKuDIAw#5(ITu&v3#I zJ{0v|?o#`~eJpA?VUJ1C?Ya0dm$LnrbtW%f^xV1i!TZ`y8UD(UL057U&?4A8I3NS= zoARP>zk5^Bg>6S2J!`Am#ZUbiVbhRVJ#v$2r5a9K6ZSp}k9~67i*JuR`>HkedDD@7 z1`R4T#z;pGjpfW>f1crlR^qVIlb`30-}On)UZWN%KOevR<8{AAPmHkg4Wyl^7E=SM z^B)K(>(@@uOS2Gv+qa zVrH;EPaB~JI56dmV*$989vU?gdT4~L;#vxVrV=d$QW!Oo8SGEfQYC*O!^+?rH4=Jf zgsogHwK27zA)RQeo{mDFjQYq7wpvFKp_DW6hYtQUh*EOJ2cdo(tRPO3kIGE-nyaOT zA{s??l%292Sga)2xNLXomILMldq26$n6vJ6eLR2G*@trbfP~}F+tB^5zuq5oJoECH zvm5ptx$k=S?P~^`hC=F*Q()qGstVQD&N;=kXhL4D{0D)81v`xCLpF^q`Y7#)oODIw zo_Fs5tXSRaY;L0;#>ll9%v5!uJuBfS*sy|P{5d><8t8FTr-c zWbeCP`L>@K>`t}3Sf0e7SN8LlD}NaeojGs8wLhJ6=u0Ec`})@-C%(5%Mpp zAgA@Hwt{SQ&X{|p=dw{}o%}?2)UhY8rym1WkkB+n1!)GGrlVDE=&8jk@9Xsg2-z=B zZn6C~^Pe7bSM51Vdz>Dbg+`TP0HmWgMm1>$GgXsX4_AV+#6p8{EYJfhJv1sy^w0>q z6P3|nU0DVww`m+QNWW*%o=zmQHPMb_QcTw*S4UBQQMlqOtmdy zYQfZO`nF$lq9fC53jJql0M*V9BGu6L{JD~fM(y?S_ zI{o%#uiRZB5mvrlMn8-?*bFwG^6`-qV1M4~BR9qDny2^ho6MEybkM-4zs+E#IvonL z)aql=h1p)cM#Pi!(_qVLN&3PjI5$DHG5Fa z4Ems9vL@RSugP?@(+{r3n`@dAZSf?27$`HOW*mNRwKX%SX7ZGbs4k0-Y$oY9>0BvAwcQH-om0InrzG*V^_Taep ze_cHOtZgT3)MM0dHD;!XnL0FsO%sHae{wThQ`{@zr_?d8j3x%!)0yH%Z;a*4V1K@8 zf_)3d{d`t?Xf#96LnG|}uW15m7)=v1*q>sWXhz5&Q!8b~O%rt0XquS8Rz=eU{V?hU zGuWzXnwXgr)MZOz&uE%}E=JgD(?qWpL0KzDp7jX1ZQPrON=j*_>X{~=Qk1UfMyx~B z&Tt=3QlAT!6`rNW7LA&GLnsa`RuV6Tl)bEwYPo*rTfW(R_;(~wy`a;AxRo~lCiwR28!Et-&*EB_&H=y#^H zcizAGnE{V~7dZ93mxIB3hCYVurG_5q=tm#lQ%CPUdiKa$kE>n&&?`IKUIT{ zh?ML`Xtw_Sybl^bJ#XCNr{~_Z1z8wVq7YY1lg$LD3HEng{vieV^EE{#>%TIm3b@r<}P`Q;s{pOEL!n3~GZ`@T! z-_Un?+oD!<+z6Y8%G4vDVLd98yHM6XGc-?s;P*c9oJSoieW?BS9Rq4lJZkjCZI^uX z&aHoh&<7)Ii4Id6{JBx}K#Um&X0X|JTzsJ5$rMJ3B19OW69sR?qA1>ZP)jgej70iHY#|YvJBEU!y+(>Z}N*dsz*a?P3^cb@*BC| zhVt2vWm&n7>m&1tMiK=JQElYJ<%8Vr1fk?xpvK z;8{OQeuVae|gH`<|F6SygmM&`;Vhr1V6gh-QTred6j#6QSU2;cHeL! zga2*P$Q@HIKW50vC08T{e182;82tR%t8Totyi4))8@?L<(P`BL_RHz($k(kLz194~ z!l|>5U2@8w82p84^A~)7$L%Gn-y5-Yt$tIFTsUQo4y$Vz%=EvU{L@NYq!4@)r;%tl zuGBCQTg&wj&(DI3l%#2ns(6JQN9RZ)4j}U!8Rt!T2mC)Fha3(1NuOgN&PCBv`6HzyC zYzZR#y0)QMg`7EQS_sH!daeu)I|T4BG+f}6TO2@Wx{9L2LPELvc4fq!-z4me+EN^+lzPyYR;gdOD1_9!D4{ZV*lB4WX(gn#6iY*( zUJ*wR<4j1=bxfQbgM$H05vRQ&7LSjwMxH+GD1t$*jWI-G#X*9isUvcafx;Xc-P{a| z%jzTMhyXn~Nt*LU8eVB_KD=gP++U>o7wl$}keMGDh|E0fWcEemt%S^y=c)LIQ)ETk zFl|I?(rp|q15#@^UsNxoN8{Wg(KdBOb~R12V*<{uR%4=tSUegIstH2LWkp~0QL{S` znjh+k&^+wa)<DDWKs+0&!T{Ea1H`0TOOk8DmZ;i z6iu8^hlpRYY^%Y^QVBTs;E0!@rHMpzSR6aOHKn5~k_Xi7HZc66Ef#IQw^9v!sk=}}KD*);6vFxataFJ`u_ zN$5wTa4n6(M98r|A=w8r$d^xZD&_k=ICH^g*7aJquOiE4pHI!k!Lg~%Ov%A z#;5F!d?pp_S(fMBhW&##y0SNey!FND+JH%NPIVt`#xz=^_v!X429I9y@!+}J`d`&y zTT$n9MAMcGrDO1)kJA+fTjf997@I}^1zywJ{3eIg7(Px{80?auft2m=|1dsno)vCG z9g*tHYmdTU`FBKL!$mD+DV;wUtfAaT6_bY~7#s*(#Ee2TAATV=wLuyFQ zT~4~$=swzc)ek3qwd}9=8y-c$yghOlY!sejDU*dS48ZB7vnTyL z=Z%b~GJj-{o|KQh5B}Gx;U{k>x+T_mTgBvSw$S7Ck)G~H@t@b$hrwDx>rQp2L~PCP zdJsIHw($m5QM|{)c3as>jp1#k!eF#J$WkU_^7FWYjknV*q(U!u-a;yh7Vs8QVKCwr zmg;Y8^BZYLQ8C^UDhzftYXI{4+mQFfJFUd5%M3|Edjo|ST1W97KVc7pJ&5O6%4l2G zOttAPl!-U^MGn=aK1p&xP}!XyAiu(lCg-;FeZgngNI-^Ql(Hwz!;?26=LI<_cPX8n z%p#FAPSyaJZ#ful@mMS1O{K8+u-|M&QfS3f=M z_Trge?>+b2Ek-YfFsd+f(e8%ptABgOsPF!<)}0xfar=rxp7}`_>^6ESNSVA?5+C#C)jiK@fN`t= z`Y>YU?mIbWman3SJ-lP>wPnrfRe%4~nQAYZv207}IEGC^E?cg4$93stWgC~eHSVM4 zGe`eo#*`bwU^~&V7%}Oy`KCs7EUNFOW!qo z`9VjO%zq=ceN(@Cej5h6B@Kfq0Y|E@A#gLQiJIiX*UA5z?XQ?+CPrc}t-RbO85s|8 zKQc;aNCq~O{J1};Bjfl2X`$3U)5Szn~(?dB+jUrpq$&%0N zo5#-$D#zMYh%EV00g9}S`wl;0^Y|CthnL>_Zolr&zr(&y)j@fF`WgOq{?+FbHoaZr zEIV@jm$wXxo)ZQuu=f#OSo4D~=9M*yj8xyfFKe&dH}$pwH*`Mg*>37xkB7lpqW95o zMu}|jdAA6HH#FSMpofrXw}H}+lUXG45Uz4gT^AR^F3*hJ_`zZbYc*nLSLl^H=69%KUG11W6ZR?3|KO4Dq;^dXRi>EI- zuK0zut9t>ISKHM0``okXmPg&?bN=$`{7WqO0^fy6pwqxVa;AO2o{qvwHx?_T!mkB^$z=Xq8T z70UX_@wnzauEJnMBbJ(*Mx+vNQV~5A+P0h`dQCwhdeIN!?3*@Q(*_Rcv-RWaXQZQ= zb=xoz5eBPZct)Oq>Sk~V2@|VSom}04DJQ8Nd??#6*gPsMNEvNw1`$oMH5(g@c1BRw ztWmyR78;6CxzW9Kt}yHAcRt*2$WxK>K4(4MBO|skW1%of90qG?7UD-V!eT=#MD5^1 zN`}E&l7-0Jh~HhVuWfX%FczB9z5o_FxL4OT7me-N|EW{jJ$c+2-s&(Z6gJ?6!CIPy z_(?*2Xx@izB2hc|aBE?(mSiE?#%U}Ik>sM3y#@4S@T2^zmbgrU{R1i7v#+6(RYj(j-dIZTS!Fi3U0p;N=7@EzMDg~uw)$Z=##?OL08(n+nymL&BI}zUjfNt`o z)p>em-}z~d(%SjU&9KtiMfS`24(rA_$+9qbwO+CetxA+r(7U|J;=zB$#If443{!l` zP;|wzTuav^4<{$P0a@xwZ6ixSg;Z~um4hsu1GsNz5kv~h>MK8I5BWI_p8hUl%+eM3 zIJvTi)9Ya(4|)s{t*lY_UD zS&Am`Fc2hHabz23rs2G2%hGL`d{$sxBkMQ^JfqMyq6An>5ry>!o+ufkid9~jjH#DJ zl^mmrqUesUnxbrqo+=0~jV-!h8nz?Cq0dP=@zs^u>dDeB(#)gE#DGpbS^TIabdTs$ zQeGJ^t%=%I6;(CaBa0ank<3yZM!=||fFKxEG|h!$j%n%eq1vJ@>b9o_q!3y^DVkkqy_Fv+#C%DC zH~|)%z%AKRMNhY4iU8*|&59ZL<$*c@Da1BXWG%#LCdp z6!`hMw(QwhLI$Le+DMT_3JSqRi61NWO@fnW&*T`x`>|*IJT-<}@Y>WpEDkk6!{Nj5 z;T2<+V_Amj>N@8q-UqZ6C~XV+uGM{o03+U661;y$)AzPS;57$AZI#Prrj%N5Au26 zHM7s%aqk}oUwh5vw{-gchz?BYN6)c^aBp1m?jB*Vd(#TvLDy)rpErZs1}Q^%w;8Ge zakbCc^dlruJHA`{ZI_<2wiT_Ix!RJatnM8K+nD}ts;j7;EIP6)$Tm8Qem*?%diD`4gU`!S4mzzS560-!)M1%w{=9v~|w3td^ z@`|;cWIL8&`RJ)rG%%vc8=t-U=0_>#jWgn}g5@KlQg-uaHFvD)O7M7X*#oL1}9Z*wp&2!%$)a`eF{(?}$!a6=s`1qne;>RwA?=Pmgi`g&0_ZpvwxP3tF}*r<8DnFB-w1kgLN|QaDFNCj&*K zv&)i2FSZC`e$->t9lCk{poMD3yU+Q!+b;<*Jd7#RG7%#o)SG2V(8Oq_A}E%uncyoE z!bo%wx{WRV1%IlOM2yfdVuY4NOnP67^!dly_s{-i@w`*-SoPsC`sq=pfL1ruRQYO$IWnt%)W)n2&*x7d4yC+ord%pL_$D@Bg{Gj9aA?)xl zri01E4#p}0VFwXo(orQ$doWYB5K7F_P02E_gV>Z}?40{%Maaui#`fOlAy~K_P7AN+^8$m(%}w;rNq_7T{3P+|D;ui0D z8r^9yuftw%OpalKDd@25vJ?T@VMBH-*VU3Mn7T!N8(9LG#uPFUrc2<-l3Fm*lS~)D z6D5arm#!+Hf?~^(2P;U&@nEYWW7-dmuO^G4oS}(mBMDCurpDk&vQySwie##YEQ&a# zoA)Y*`;ir+-T?&kZy_hM<4vZ*F!LdadiyKQ892d%NyUn^Qa%Uj8 zrt$)eDWn=8Mc&q3hAe2748??|Lo0zj2aG5%V^L##j%^DVPeZ0aD~` z-6d<9F6(e{Fa_0;1jABQBPJMZViwkZ|g1#&L*ydIWEQQ zw`|$K0UV-iAc((h3ZB69m~GZw*}a0P`FK+7jCGeJ;0zs8kR2CW>yCne`nrfM9C&DX zkXlZgbr(j7oFfL)Eb;`22184OIz)aFJcPaI=P9L{tP0q9RB=ubmWzfWLbIY+I#iXW zqQ^WzYw$a5Vl6;yYFfp#mptjZGYT|pbHx)chjkYWxgAr5$qHrwpdHR9!U zW3p;u4gnhq$x&fB2^#{>BD0fBy(S@MH?r;`H1L*OVKAngB+S&IMpZ4xHVp~d5%eYm zA#g3l^DOX-X29@7<&=wr8|qLgC1yy6O5Mx0A80%0?Pr>(cxU+8H$0k808TC9%1Oe!n%vF#9MNO!I=6k6LawC)3NUjGh7Wg zgKs!I#|_7bNsf#CaMuge4Y4$nl-L3~1Py z*_)WgF}sU(7a@VSL zOteaH1jYJHm!T`f`P=ZP02?Bj18h^VS#H{S=rRbgZPs0c)^$E^@rTS7o(z64U~7xr zr5Uy$h=u}1u?w?45z9^$x?>N1ryd={$}P=Xy~e89H+tEUo>w?Btq`|(=aq{&%p+9O zbaX*hWEI|TSm$|AFFH1Sf8pqaYl$nhjVwVoV;a9*V%=q%aDNfu&0)HRCU_WeTr9U? z9U)1ufR)=~clkuPGa$uOo+M0-!S|G%vhIRGmj}0MSfttruwv*qCOn2cN6pq`H`pi5?wnc!kSB#MbDEF2?yalu>A;~gNevpj>jorT0L2s zeRrN|Zg{fnymgnVAQFy(eR@IDC7BGn;HKu0$C0jUI-;-;A)Y*` z;ir+-U8*U;eMdB*CXh5JwH?J(4FRLRYQnBaU{Q+NVs~X5PcVg41Ek2`x(h?Q9fRX0 zG@-VFF(hWWifM_W=)f}7V}^EZ)?L}8U@EW%NRhX7mqlxO+kpkWquVjnhNqDU^_hoo zcJP>llXkL~wZ-nrCIwTvH9(5It-A~X9^k5HBa|?iX~R<iI!sj*Y~%){XtVCh zCIwUT@ub){n2Mz<^!Vvjj@VsLkZPg?ml6;q2KP@#i_ZxOEri$rh}cz#_%cWJQNVp9L>|TtP#sTCPYU zX*ag+A|&wEU12b$Ov^+Jd{Ptysgh&_eu?RD4K@(jK-XdNMss0V+KsKd2noD(R~U?` zws^!qPpDclLT-RpBqas|3LIJjbcrf_LzI+)ih5Ym>}_|k?jj`c)?HyRrfAAUjHMu~ zI6RuL*(1WY-$v9@#P6bb3pkPjK?PIlqB_LvF0s3O?%=Gu!eC78lZhA%RjPnvpK#QW zt0VXUHY~9nV*xD!JR)={U2{XYp|#HLWZgxW;H|sDU`z*-i5(9^yC&-hG~mV@Ma3V> zR$!O}GcRm|Amm#^8WTd9i8AZa-qaspik%!4Ax9ZX=}W;RB>0>2M%q*Daor@by9kB6 zbypaS=|}uJq^1M{W&s%0G}%JHQ$2i2j7?s>*m}ZJla_)H{?| z{XPY|B_Y9Ib|V8A{P61%awDmA32xBF`Puhh-r@AC*4gv^JpbwQzgba9x9H_u1<-xM z#A}BxIC1`q=UMj*S^niIvqn2%uvPRhlFttva8WD{t{M~f<@xZ^nsM>k^h@ea{^NYZ zKVvuiyZBRI!`U-@o`Jet4J0_v{nrI0UB#Eke&^)=-v>`z5#+2tvEYN{tOP#4Qxq<3y%Pxp+9C+&s^QVs}KWSV#F_hR*qu*b9VAoGR z_3r;nOZ@cUp<}5Yxp;%_QKEKS{qNYS^QV1y`c3CeJ>~`T-QqA<6EQzSssC;IlaB8c zuz`y`l=+J~*2DarVVL5vqJD}EySLP`It zpZk9Ojh|iDGYr;}%ufT*cUKQN;U(p?LGvTu-LT{1j$aZFHN^bXjt5GH2xYHa+yAM3 zzwLZcj{|3i!P=Oguo-Dd$v*|cJ*VU^i1`P<`fcAtLbo5;w%=>(pRVpb({E$)4`coq ze)7y825U*?r`WWGFh8|}XZ|o)8}s`-(UQ!+xV~vmHFi)NJ?+`HW%G)kJMRsc^6-bB zAGYqO&0)+hpxvcs_xtyIqohG|iifWLXvyXQVX&5D{sU3R2mfpo9r5f-gO|SXM$eJ6 zKD(UA)sW<;cC7W@S#{~Ro4tpXzkjs;*oBB+l?`iSe!^ypO8#y8)g$>AL;uof=AS$H z_2bQHUl-qT;Cr1b9$9x)81sv0^-;wKJoM7oFYS3xFS@+@{1=CZ!CI2}4?-O`PF}t0 zxr%9nS3M)F{^)}Ze&(P<*ZI|34;1}r!lXZk zF+Z6#?bkmt;MRqwmE3+^mn%zNoU$qm#xO+f%Utk2A2gYjzOKm^yu|#M%pbGm$GyHA zu;A7EI(@p``zw94A?Bxcbba`OZ|}LecEF8){Ni_;KRmiD47P_eKa*>XX8tDB_AKsF z5Azo!`KvbE^RrhzZST(f__2$Z9KO9DV^3lnHs_ad%}f3;7{d^~o5}pM353K-Z+dOd z!6>~D$xrRzC4U%f4`+TR*BZ_IO(^--)W`edL-`)M%QXud zSvkj!hk)5eGyjKw{r%%V+%T~C=H+Kxx$W`pv(kyNcR&Rl`QiUJZ|mq!=M7%4@t2Pr z`>Pud2!oAEqguwiCvstMC%yEvhwD}Y8Ndd9_@AH^Eu{nS|0wYVx6~DOulAI80_QU;R`-1H@xW8dzBv5u3kE%S*`mo4&%N`12_HOc4;SJq0(`dQ;&t&) z>Jj4Ag{Un*xcRqBx5v&Iu<+*{-t0PZh?6d6nGiS7@{0$)J#6JUFL^8X{&M5=3)`;? zgN+I>L|LlC_^@3s=nlnqMov5AiUX&gbNXYO25z`v&TW(F(+x2{wPW5F*RL7ocDQwoKUn9}whP)H_n&8+UPFF$>lJfn zK7aB4)HWW*I2*S(y6t+U1JUi(>ZhlcUVYZ!C*PZMSnt=L+(Pxpr)?)K`u$gr|D;>V zwCJ4G7q$C(F13w^HIESAb$B9NP|61%Jr|w%R>eUh``;oSw(#a}u8k2sc-S5;#2@vM zS-{@YW&dM!+3{e1d%=0Vx2`?#*y-Jaf(lrMZmUm-7ZqZ4uzb~j8vev?1xzwrBW zwho@G`D0K+%unqo{?qW4PuxGl{MBV^KV4QlyI{2w0 zE|0u?#>tmc8+aJwU@9|{KD-cmzRC9ni9VI`eIJ~;;4|xbt=m_TWwXzxdgRl#hwq$q zS-Z*=_Vf!6zj^%{a}KqQhqa8bZaO>>E-2-Lk5m6K^Vn@mtDN~AKDuq(+QYsfeDJVU zerY>x8Q-Qh3Gi5eupU~U+5{FfjaQqz{+c{fzxk@7#rIu4Y~=5c|9P0O{y7Tf)h1!E zQ9;8f%M=E?e#T7-^Fq`n)DB*45(aAtVND&EQWXDx_+gDDyz*Ab%|lNeaQ41WolWqD zIySX~SCNFlT2gI7bSp${LhayHBw?`T5#qZ}y@Y1^DIZ$%DiXp658Ew;c)C!+bk*O- zCWIX3qb3reX<2Q}#CYU*T+odH{Ecq8ZqUp*^o?V4-JqgFKTbC=Uyjh010`~>>D#+^ z)V%~Qa?wl2$19`N6{VA+qvPYChKr7^s)3TOJP|D|tF5Z(6YbqMGT47Fl8k#nPYuQM zk!u4eZHw3yrHMp5(YJr;gt%9OKbKb3T-4X}OBf${Ie4-M^8CK)zd-0C%Row<@E?B4 z73GQYs>)PTMxepelujL^y*s9rTvz{S)g|$oiqdK{r?Lu)$eLucPV}1@zpQjZbwwO1 zM?wJZU0PakQF-M!s3U8u0EGf`+_-p6AHR4Hc!4^zvNn;#S=H$OiI?HW_|i+LgwYjM zW&XQmtd=>xF}}REmcCI+U#mC~+Rk{i$AI$M9zp5JuN9%T@j6e&M6{|hIvNcKNK-k+ zSEMFepF57xG6q{s8};8I+pt2iP3WC+sl(|i4GmF zB&GY9^d3!JY&f1MfTA(HnxtsnU&b3rp(yxQX#$dUbNJaL3r(c@_;&!ImDC5L!$~fo zg(SDiD=HweqGQWz&?N9PI!yW4Nq)Dmh9WBkw`1dhvH6&!Z*XFK)x?T1eqngICO&#% zc|~m>>@*~M%ot}W*Ms-`K3HDkOP}Zjyn($AbW>mW z_>}|!>h?9pR8g-_R7J<2#$~k?M5L+-ejV*9JXSNYtQN8@dPZ4Id3CKX1^TAQ(iKP; zI&pMGd0AiP>zt}$pE?1%6^}I;j+zQTothdoXk?Og4)L$j*oX~rF5IM(vt71g(Y5 zt?C+#8qu*6D}6E&ht)~ZgHWH0dL)UQB5(dfXo4@P@D??qH4$3Fk*sLzxNRM`aG4g_ zKVYM_j$0XRo{mc^g4^+*QaAi}O*iH}RghB){j}qf z;3E4DtJjL)30AL@gWtakaz1Ovn^f=2$Cm`9&k1r`llkcs{Cz064!y~$PtWuS9;6vT zda4WmLeTU#$V!1yR?%NT^-a59l(KG~1|WH+Pm~6r4uV^ zGp^qWo@U6VF@#ciGRyuut<%6xXLUJk(A>9&u30qZKRc>}N2Uh(KJ1l&!9{v22L-UK QhM2gK{@~Ns_P*o)0cd2vlK=n! literal 0 HcmV?d00001 diff --git a/RealtimeMeshComponent.uplugin b/RealtimeMeshComponent.uplugin index 19ea448..888933b 100644 --- a/RealtimeMeshComponent.uplugin +++ b/RealtimeMeshComponent.uplugin @@ -11,14 +11,14 @@ "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/ad0b45a6a31e462aba7cf7c790a9c125", "SupportURL": "https://github.com/TriAxis-Games/RealtimeMeshComponent/issues", "EnabledByDefault": true, - "CanContainContent": false, + "CanContainContent": true, "IsBetaVersion": false, "Installed": false, "Modules": [ { "Name": "RealtimeMeshComponent", "Type": "Runtime", - "LoadingPhase": "PostConfigInit", + "LoadingPhase": "PostConfigInit" }, { "Name": "RealtimeMeshTests", diff --git a/Source/RealtimeMeshComponent/Private/RealtimeMesh.cpp b/Source/RealtimeMeshComponent/Private/RealtimeMesh.cpp index df7cbe0..5eaf176 100644 --- a/Source/RealtimeMeshComponent/Private/RealtimeMesh.cpp +++ b/Source/RealtimeMeshComponent/Private/RealtimeMesh.cpp @@ -198,7 +198,7 @@ void URealtimeMesh::Serialize(FArchive& Ar) { Super::Serialize(Ar); - if (!HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject)) + if (!IsTemplate()) { Ar.UsingCustomVersion(RealtimeMesh::FRealtimeMeshVersion::GUID); diff --git a/Source/RealtimeMeshComponent/Private/RealtimeMeshActor.cpp b/Source/RealtimeMeshComponent/Private/RealtimeMeshActor.cpp index aea714e..bb09044 100644 --- a/Source/RealtimeMeshComponent/Private/RealtimeMeshActor.cpp +++ b/Source/RealtimeMeshComponent/Private/RealtimeMeshActor.cpp @@ -18,6 +18,10 @@ ARealtimeMeshActor::ARealtimeMeshActor() //RealtimeMeshComponent->CollisionType = ECollisionTraceFlag::CTF_UseDefault; SetRootComponent(RealtimeMeshComponent); + +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION == 0 + RegisterWithGenerationManager(); +#endif } @@ -37,13 +41,18 @@ void ARealtimeMeshActor::OnConstruction(const FTransform& Transform) void ARealtimeMeshActor::PostLoad() { Super::PostLoad(); + +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 RegisterWithGenerationManager(); +#endif } void ARealtimeMeshActor::PostActorCreated() { Super::PostActorCreated(); +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 RegisterWithGenerationManager(); +#endif } void ARealtimeMeshActor::Destroyed() diff --git a/Source/RealtimeMeshComponent/Private/RealtimeMeshComponent.cpp b/Source/RealtimeMeshComponent/Private/RealtimeMeshComponent.cpp index 83a257d..72f4138 100644 --- a/Source/RealtimeMeshComponent/Private/RealtimeMeshComponent.cpp +++ b/Source/RealtimeMeshComponent/Private/RealtimeMeshComponent.cpp @@ -13,6 +13,7 @@ DECLARE_CYCLE_STAT(TEXT("RealtimeMeshComponent - Collision Data Received"), STAT_RealtimeMeshComponent_NewCollisionMeshReceived, STATGROUP_RealtimeMesh); DECLARE_CYCLE_STAT(TEXT("RealtimeMeshComponent - Create Scene Proxy"), STAT_RealtimeMeshComponent_CreateSceneProxy, STATGROUP_RealtimeMesh); +PRAGMA_DISABLE_OPTIMIZATION URealtimeMeshComponent::URealtimeMeshComponent() { @@ -51,12 +52,12 @@ void URealtimeMeshComponent::SetRealtimeMesh(URealtimeMesh* NewMesh) } } -URealtimeMesh* URealtimeMeshComponent::InitializeRealtimeMesh(TSubclassOf MeshClass, UObject* CustomOuter) +URealtimeMesh* URealtimeMeshComponent::InitializeRealtimeMesh(TSubclassOf MeshClass) { URealtimeMesh* NewMesh = nullptr; if (MeshClass) { - NewMesh = NewObject(IsValid(CustomOuter)? CustomOuter : this, MeshClass); + NewMesh = NewObject(IsValid(GetOuter())? GetOuter() : this, MeshClass); SetRealtimeMesh(NewMesh); } SetRealtimeMesh(NewMesh); @@ -255,3 +256,4 @@ void URealtimeMeshComponent::UpdateCollision() FNavigationSystem::UpdateComponentData(*this); } +PRAGMA_ENABLE_OPTIMIZATION \ No newline at end of file diff --git a/Source/RealtimeMeshComponent/Private/RealtimeMeshLibrary.cpp b/Source/RealtimeMeshComponent/Private/RealtimeMeshLibrary.cpp index b3f490d..910b781 100644 --- a/Source/RealtimeMeshComponent/Private/RealtimeMeshLibrary.cpp +++ b/Source/RealtimeMeshComponent/Private/RealtimeMeshLibrary.cpp @@ -1,8 +1,193 @@ // Copyright TriAxis Games, L.L.C. All Rights Reserved. #include "RealtimeMeshLibrary.h" +#include "RealtimeMeshSimple.h" FRealtimeMeshLODKey URealtimeMeshBlueprintFunctionLibrary::Conv_IntToRealtimeMeshLODKey(int32 LODIndex) { return FRealtimeMeshLODKey(LODIndex); } + +FRealtimeMeshLODKey URealtimeMeshBlueprintFunctionLibrary::MakeLODKey(int32 LODIndex) +{ + return FRealtimeMeshLODKey(LODIndex); +} + +FRealtimeMeshSectionGroupKey URealtimeMeshBlueprintFunctionLibrary::MakeSectionGroupKey(FRealtimeMeshLODKey LODKey, int32 SectionGroupIndex) +{ + return FRealtimeMeshSectionGroupKey(LODKey, SectionGroupIndex); +} + +FRealtimeMeshSectionKey URealtimeMeshBlueprintFunctionLibrary::MakeSectionKey(FRealtimeMeshSectionGroupKey SectionGroupKey, int32 SectionIndex) +{ + return FRealtimeMeshSectionKey(SectionGroupKey, SectionIndex); +} + +void URealtimeMeshBlueprintFunctionLibrary::BreakLODKey(const FRealtimeMeshLODKey& LODKey, int32& LODIndex) +{ + LODIndex = RealtimeMesh::FRealtimeMeshKeyHelpers::GetLODIndex(LODKey); +} + +FRealtimeMeshStreamRange URealtimeMeshBlueprintFunctionLibrary::MakeStreamRange(int32 VerticesLowerInclusive, + int32 VerticesUpperExclusive, int32 IndicesLowerInclusive, int32 IndicesUpperExclusive) +{ + return FRealtimeMeshStreamRange(VerticesLowerInclusive, VerticesUpperExclusive, IndicesLowerInclusive, IndicesUpperExclusive); +} + +static void ConvertQuadToTriangles(TArray& Triangles, int32 Vert0, int32 Vert1, int32 Vert2, int32 Vert3) +{ + Triangles.Add(Vert0); + Triangles.Add(Vert1); + Triangles.Add(Vert3); + + Triangles.Add(Vert1); + Triangles.Add(Vert2); + Triangles.Add(Vert3); +} + +FRealtimeMeshSimpleMeshData& URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector BoxRadius, FTransform BoxTransform, FRealtimeMeshSimpleMeshData& MeshData) +{ + // Generate verts + FVector BoxVerts[8]; + BoxVerts[0] = BoxTransform.TransformPosition(FVector(-BoxRadius.X, BoxRadius.Y, BoxRadius.Z)); + BoxVerts[1] = BoxTransform.TransformPosition(FVector(BoxRadius.X, BoxRadius.Y, BoxRadius.Z)); + BoxVerts[2] = BoxTransform.TransformPosition(FVector(BoxRadius.X, -BoxRadius.Y, BoxRadius.Z)); + BoxVerts[3] = BoxTransform.TransformPosition(FVector(-BoxRadius.X, -BoxRadius.Y, BoxRadius.Z)); + + BoxVerts[4] = BoxTransform.TransformPosition(FVector(-BoxRadius.X, BoxRadius.Y, -BoxRadius.Z)); + BoxVerts[5] = BoxTransform.TransformPosition(FVector(BoxRadius.X, BoxRadius.Y, -BoxRadius.Z)); + BoxVerts[6] = BoxTransform.TransformPosition(FVector(BoxRadius.X, -BoxRadius.Y, -BoxRadius.Z)); + BoxVerts[7] = BoxTransform.TransformPosition(FVector(-BoxRadius.X, -BoxRadius.Y, -BoxRadius.Z)); + + // Generate triangles (from quads) + const int32 StartVertex = MeshData.Positions.Num(); + const int32 NumVerts = 24; // 6 faces x 4 verts per face + const int32 NumIndices = 36; + + // Make sure the secondary arrays are the same length, zeroing them if necessary + MeshData.Normals.SetNumZeroed(StartVertex); + MeshData.Tangents.SetNumZeroed(StartVertex); + MeshData.UV0.SetNumZeroed(StartVertex); + + MeshData.Positions.Reserve(StartVertex + NumVerts); + MeshData.Normals.Reserve(StartVertex + NumVerts); + MeshData.Tangents.Reserve(StartVertex + NumVerts); + MeshData.UV0.Reserve(StartVertex + NumVerts); + MeshData.Triangles.Reserve(MeshData.Triangles.Num() + NumIndices); + + const auto WriteToNextFour = [](TArray& Array, const FVector& Value) + { + Array.Add(Value); + Array.Add(Value); + Array.Add(Value); + Array.Add(Value); + }; + + const auto WriteQuadPositions = [&MeshData](const FVector& VertA, const FVector& VertB, const FVector& VertC, const FVector& VertD) + { + MeshData.Positions.Add(VertA); + MeshData.Positions.Add(VertB); + MeshData.Positions.Add(VertC); + MeshData.Positions.Add(VertD); + }; + + WriteQuadPositions(BoxVerts[0], BoxVerts[1], BoxVerts[2], BoxVerts[3]); + WriteToNextFour(MeshData.Normals, BoxTransform.TransformVectorNoScale(FVector(0.0f, 0.0f, 1.0f))); + WriteToNextFour(MeshData.Tangents, BoxTransform.TransformVectorNoScale(FVector(0.0f, -1.0f, 0.0f))); + ConvertQuadToTriangles(MeshData.Triangles, StartVertex + 0, StartVertex + 1, StartVertex + 2, StartVertex + 3); + + WriteQuadPositions(BoxVerts[4], BoxVerts[0], BoxVerts[3], BoxVerts[7]); + WriteToNextFour(MeshData.Normals, BoxTransform.TransformVectorNoScale(FVector(-1.0, 0.0, 0.0))); + WriteToNextFour(MeshData.Tangents, BoxTransform.TransformVectorNoScale(FVector(0.0f, -1.0f, 0.0f))); + ConvertQuadToTriangles(MeshData.Triangles, StartVertex + 4, StartVertex + 5, StartVertex + 6, StartVertex + 7); + + WriteQuadPositions(BoxVerts[5], BoxVerts[1], BoxVerts[0], BoxVerts[4]); + WriteToNextFour(MeshData.Normals, BoxTransform.TransformVectorNoScale(FVector(0.0, 1.0, 0.0))); + WriteToNextFour(MeshData.Tangents, BoxTransform.TransformVectorNoScale(FVector(-1.0f, 0.0f, 0.0f))); + ConvertQuadToTriangles(MeshData.Triangles, StartVertex + 8, StartVertex + 9, StartVertex + 10, StartVertex + 11); + + WriteQuadPositions(BoxVerts[6], BoxVerts[2], BoxVerts[1], BoxVerts[5]); + WriteToNextFour(MeshData.Normals, BoxTransform.TransformVectorNoScale(FVector(1.0, 0.0, 0.0))); + WriteToNextFour(MeshData.Tangents, BoxTransform.TransformVectorNoScale(FVector(0.0f, 1.0f, 0.0f))); + ConvertQuadToTriangles(MeshData.Triangles, StartVertex + 12, StartVertex + 13, StartVertex + 14, StartVertex + 15); + + WriteQuadPositions(BoxVerts[7], BoxVerts[3], BoxVerts[2], BoxVerts[6]); + WriteToNextFour(MeshData.Normals, BoxTransform.TransformVectorNoScale(FVector(0.0, -1.0, 0.0))); + WriteToNextFour(MeshData.Tangents, BoxTransform.TransformVectorNoScale(FVector(1.0f, 0.0f, 0.0f))); + ConvertQuadToTriangles(MeshData.Triangles, StartVertex + 16, StartVertex + 17, StartVertex + 18, StartVertex + 19); + + WriteQuadPositions(BoxVerts[7], BoxVerts[6], BoxVerts[5], BoxVerts[4]); + WriteToNextFour(MeshData.Normals, BoxTransform.TransformVectorNoScale(FVector(0.0, 0.0, -1.0))); + WriteToNextFour(MeshData.Tangents, BoxTransform.TransformVectorNoScale(FVector(0.0f, 1.0f, 0.0f))); + ConvertQuadToTriangles(MeshData.Triangles, StartVertex + 20, StartVertex + 21, StartVertex + 22, StartVertex + 23); + + // UVs + for (int32 Index = 0; Index < 6; Index++) + { + MeshData.UV0.Add(FVector2D(0.0f, 0.0f)); + MeshData.UV0.Add(FVector2D(0.0f, 1.0f)); + MeshData.UV0.Add(FVector2D(1.0f, 1.0f)); + MeshData.UV0.Add(FVector2D(1.0f, 0.0f)); + } + + return MeshData; +} + +template +static void AppendVertexArrayIfContains(TArray& Destination, const TArray& Source, int32 VertexOffset, int32 FinalLength) +{ + if (Source.Num() > 0) + { + Destination.SetNumZeroed(VertexOffset); + Destination.Append(Source.GetData(), FMath::Min(FinalLength - VertexOffset, Source.Num())); + } +} + +static void AppendTransformedTangentArray(TArray& Destination, const TArray& Source, int32 VertexOffset, int32 FinalLength, const FTransform& Transform) +{ + if (Source.Num() > 0) + { + const int32 NumToCopy = FMath::Min(FinalLength - VertexOffset, Source.Num()); + Destination.SetNumZeroed(FinalLength); + for (int32 Index = 0; Index < NumToCopy; Index++) + { + Destination[VertexOffset + Index] = Transform.TransformVector(Source[Index]); + } + } +} + + +FRealtimeMeshSimpleMeshData& URealtimeMeshBlueprintFunctionLibrary::AppendMesh(FRealtimeMeshSimpleMeshData& TargetMeshData, const FRealtimeMeshSimpleMeshData& MeshDataToAdd, const FTransform& Transform) +{ + const int32 StartVertex = TargetMeshData.Positions.Num(); + + // Skip slower transform logic if transform == identity + if (Transform.Equals(FTransform::Identity)) + { + TargetMeshData.Positions.Append(MeshDataToAdd.Positions); + AppendVertexArrayIfContains(TargetMeshData.Normals, MeshDataToAdd.Normals, StartVertex, TargetMeshData.Positions.Num()); + AppendVertexArrayIfContains(TargetMeshData.Binormals, MeshDataToAdd.Binormals, StartVertex, TargetMeshData.Positions.Num()); + AppendVertexArrayIfContains(TargetMeshData.Tangents, MeshDataToAdd.Tangents, StartVertex, TargetMeshData.Positions.Num()); + } + else + { + TargetMeshData.Positions.Reserve(TargetMeshData.Positions.Num() + MeshDataToAdd.Positions.Num()); + for (int32 Index = 0; Index < MeshDataToAdd.Positions.Num(); Index++) + { + TargetMeshData.Positions.Add(Transform.TransformPosition(MeshDataToAdd.Positions[Index])); + } + AppendTransformedTangentArray(TargetMeshData.Normals, MeshDataToAdd.Normals, StartVertex, TargetMeshData.Positions.Num(), Transform); + AppendTransformedTangentArray(TargetMeshData.Binormals, MeshDataToAdd.Binormals, StartVertex, TargetMeshData.Positions.Num(), Transform); + AppendTransformedTangentArray(TargetMeshData.Tangents, MeshDataToAdd.Tangents, StartVertex, TargetMeshData.Positions.Num(), Transform); + } + + AppendVertexArrayIfContains(TargetMeshData.Colors, MeshDataToAdd.Colors, StartVertex, TargetMeshData.Positions.Num()); + AppendVertexArrayIfContains(TargetMeshData.LinearColors, MeshDataToAdd.LinearColors, StartVertex, TargetMeshData.Positions.Num()); + + AppendVertexArrayIfContains(TargetMeshData.UV0, MeshDataToAdd.UV0, StartVertex, TargetMeshData.Positions.Num()); + AppendVertexArrayIfContains(TargetMeshData.UV1, MeshDataToAdd.UV1, StartVertex, TargetMeshData.Positions.Num()); + AppendVertexArrayIfContains(TargetMeshData.UV2, MeshDataToAdd.UV2, StartVertex, TargetMeshData.Positions.Num()); + AppendVertexArrayIfContains(TargetMeshData.UV3, MeshDataToAdd.UV3, StartVertex, TargetMeshData.Positions.Num()); + + return TargetMeshData; +} diff --git a/Source/RealtimeMeshComponent/Private/RealtimeMeshSimple.cpp b/Source/RealtimeMeshComponent/Private/RealtimeMeshSimple.cpp index 6e5bb65..05cbfbe 100644 --- a/Source/RealtimeMeshComponent/Private/RealtimeMeshSimple.cpp +++ b/Source/RealtimeMeshComponent/Private/RealtimeMeshSimple.cpp @@ -14,151 +14,151 @@ namespace RealtimeMesh namespace RealtimeMeshSimpleInternal { template - TSharedRef BuildMeshData(FName ComponentName, const TArray& Triangles, - const FRealtimeMeshSimpleVertexData& Vertices, bool bRemoveDegenerates) + TSharedRef BuildMeshData(FName ComponentName, + const FRealtimeMeshSimpleMeshData& MeshData, bool bRemoveDegenerates) { const auto Builder = MakeShared(); // Build position buffer const auto PositionBuffer = Builder->CreateVertexStream(RealtimeMesh::FRealtimeMeshLocalVertexFactory::PositionStreamName); - PositionBuffer->Append(Vertices.Positions.Num(), [&Vertices](int32 Index) -> FVector3f { return FVector3f(Vertices.Positions[Index]); }); + PositionBuffer->Append(MeshData.Positions.Num(), [&MeshData](int32 Index) -> FVector3f { return FVector3f(MeshData.Positions[Index]); }); // Build tangents buffer { - const int32 TangentsToCopy = FMath::Min(Vertices.Positions.Num(), FMath::Max(Vertices.Tangents.Num(), Vertices.Normals.Num())); + const int32 TangentsToCopy = FMath::Min(MeshData.Positions.Num(), FMath::Max(MeshData.Tangents.Num(), MeshData.Normals.Num())); using TangentsBufferType = RealtimeMesh::FRealtimeMeshTangents; auto TangentsBuffer = Builder->CreateVertexStream(RealtimeMesh::FRealtimeMeshLocalVertexFactory::TangentsStreamName); - TangentsBuffer->template Append(TangentsToCopy, [&Vertices](int32 Index) -> TangentsBufferType + TangentsBuffer->template Append(TangentsToCopy, [&MeshData](int32 Index) -> TangentsBufferType { - const FVector Normal = Vertices.Normals.IsValidIndex(Index)? Vertices.Normals[Index] : FVector::ZAxisVector; - const FVector Tangent = Vertices.Tangents.IsValidIndex(Index)? Vertices.Tangents[Index] : FVector::XAxisVector; - const FVector Binormal = Vertices.Binormals.IsValidIndex(Index)? Vertices.Binormals[Index] : FVector::CrossProduct(Normal, Tangent); + const FVector Normal = MeshData.Normals.IsValidIndex(Index)? MeshData.Normals[Index] : FVector::ZAxisVector; + const FVector Tangent = MeshData.Tangents.IsValidIndex(Index)? MeshData.Tangents[Index] : FVector::XAxisVector; + const FVector Binormal = MeshData.Binormals.IsValidIndex(Index)? MeshData.Binormals[Index] : FVector::CrossProduct(Normal, Tangent); const float TangentYSign = GetBasisDeterminantSign(Tangent, Binormal, Normal); return TangentsBufferType(FVector4(Normal), FVector4(Tangent, TangentYSign)); }); - if (TangentsToCopy < Vertices.Positions.Num()) + if (TangentsToCopy < MeshData.Positions.Num()) { - TangentsBuffer->AddZeroed(Vertices.Positions.Num() - TangentsToCopy); + TangentsBuffer->AddZeroed(MeshData.Positions.Num() - TangentsToCopy); } } // Build color buffer from linear colors { const auto ColorBuffer = Builder->CreateVertexStream(RealtimeMesh::FRealtimeMeshLocalVertexFactory::ColorStreamName); - if (Vertices.LinearColors.Num() > 0) + if (MeshData.LinearColors.Num() > 0) { - ColorBuffer->Append(Vertices.Positions.Num(), [&Vertices](int32 Index) -> FColor + ColorBuffer->Append(MeshData.Positions.Num(), [&MeshData](int32 Index) -> FColor { - return Vertices.LinearColors.IsValidIndex(Index)? Vertices.LinearColors[Index].ToFColor(false) : FColor::White; + return MeshData.LinearColors.IsValidIndex(Index)? MeshData.LinearColors[Index].ToFColor(false) : FColor::White; }); } else { - ColorBuffer->Append(Vertices.Positions.Num(), [&Vertices](int32 Index) -> FColor + ColorBuffer->Append(MeshData.Positions.Num(), [&MeshData](int32 Index) -> FColor { - return Vertices.Colors.IsValidIndex(Index)? Vertices.Colors[Index] : FColor::White; + return MeshData.Colors.IsValidIndex(Index)? MeshData.Colors[Index] : FColor::White; }); } } // Build tex coord buffer { - if (Vertices.UV3.Num() > 0) + if (MeshData.UV3.Num() > 0) { - const int32 TexCoordsToCopy = FMath::Min(Vertices.Positions.Num(), FMath::Max(FMath::Max(Vertices.UV0.Num(), Vertices.UV1.Num()), FMath::Max(Vertices.UV2.Num(), Vertices.UV3.Num()))); + const int32 TexCoordsToCopy = FMath::Min(MeshData.Positions.Num(), FMath::Max(FMath::Max(MeshData.UV0.Num(), MeshData.UV1.Num()), FMath::Max(MeshData.UV2.Num(), MeshData.UV3.Num()))); using TexCoordBufferType = RealtimeMesh::FRealtimeMeshTexCoord; auto TexCoordBuffer = Builder->CreateVertexStream(RealtimeMesh::FRealtimeMeshLocalVertexFactory::TexCoordsStreamName); - TexCoordBuffer->template Append(TexCoordsToCopy, [&Vertices](int32 Index) -> TexCoordBufferType + TexCoordBuffer->template Append(TexCoordsToCopy, [&MeshData](int32 Index) -> TexCoordBufferType { TexCoordBufferType NewUVs; - NewUVs[0] = Vertices.UV0.IsValidIndex(Index)? FVector2f(Vertices.UV0[Index]) : FVector2f::ZeroVector; - NewUVs[1] = Vertices.UV1.IsValidIndex(Index)? FVector2f(Vertices.UV1[Index]) : FVector2f::ZeroVector; - NewUVs[2] = Vertices.UV2.IsValidIndex(Index)? FVector2f(Vertices.UV2[Index]) : FVector2f::ZeroVector; - NewUVs[3] = Vertices.UV3.IsValidIndex(Index)? FVector2f(Vertices.UV3[Index]) : FVector2f::ZeroVector; + NewUVs[0] = MeshData.UV0.IsValidIndex(Index)? FVector2f(MeshData.UV0[Index]) : FVector2f::ZeroVector; + NewUVs[1] = MeshData.UV1.IsValidIndex(Index)? FVector2f(MeshData.UV1[Index]) : FVector2f::ZeroVector; + NewUVs[2] = MeshData.UV2.IsValidIndex(Index)? FVector2f(MeshData.UV2[Index]) : FVector2f::ZeroVector; + NewUVs[3] = MeshData.UV3.IsValidIndex(Index)? FVector2f(MeshData.UV3[Index]) : FVector2f::ZeroVector; return NewUVs; }); - if (TexCoordsToCopy < Vertices.Positions.Num()) + if (TexCoordsToCopy < MeshData.Positions.Num()) { - TexCoordBuffer->AddZeroed(Vertices.Positions.Num() - TexCoordsToCopy); + TexCoordBuffer->AddZeroed(MeshData.Positions.Num() - TexCoordsToCopy); } } - else if (Vertices.UV2.Num() > 0) + else if (MeshData.UV2.Num() > 0) { - const int32 TexCoordsToCopy = FMath::Min(Vertices.Positions.Num(), FMath::Max(FMath::Max(Vertices.UV0.Num(), Vertices.UV1.Num()), Vertices.UV2.Num())); + const int32 TexCoordsToCopy = FMath::Min(MeshData.Positions.Num(), FMath::Max(FMath::Max(MeshData.UV0.Num(), MeshData.UV1.Num()), MeshData.UV2.Num())); using TexCoordBufferType = RealtimeMesh::FRealtimeMeshTexCoord; auto TexCoordBuffer = Builder->CreateVertexStream(RealtimeMesh::FRealtimeMeshLocalVertexFactory::TexCoordsStreamName); - TexCoordBuffer->template Append(TexCoordsToCopy, [&Vertices](int32 Index) -> TexCoordBufferType + TexCoordBuffer->template Append(TexCoordsToCopy, [&MeshData](int32 Index) -> TexCoordBufferType { TexCoordBufferType NewUVs; - NewUVs[0] = Vertices.UV0.IsValidIndex(Index)? FVector2f(Vertices.UV0[Index]) : FVector2f::ZeroVector; - NewUVs[1] = Vertices.UV1.IsValidIndex(Index)? FVector2f(Vertices.UV1[Index]) : FVector2f::ZeroVector; - NewUVs[2] = Vertices.UV2.IsValidIndex(Index)? FVector2f(Vertices.UV2[Index]) : FVector2f::ZeroVector; + NewUVs[0] = MeshData.UV0.IsValidIndex(Index)? FVector2f(MeshData.UV0[Index]) : FVector2f::ZeroVector; + NewUVs[1] = MeshData.UV1.IsValidIndex(Index)? FVector2f(MeshData.UV1[Index]) : FVector2f::ZeroVector; + NewUVs[2] = MeshData.UV2.IsValidIndex(Index)? FVector2f(MeshData.UV2[Index]) : FVector2f::ZeroVector; return NewUVs; }); - if (TexCoordsToCopy < Vertices.Positions.Num()) + if (TexCoordsToCopy < MeshData.Positions.Num()) { - TexCoordBuffer->AddZeroed(Vertices.Positions.Num() - TexCoordsToCopy); + TexCoordBuffer->AddZeroed(MeshData.Positions.Num() - TexCoordsToCopy); } } - else if (Vertices.UV1.Num() > 0) + else if (MeshData.UV1.Num() > 0) { - const int32 TexCoordsToCopy = FMath::Min(Vertices.Positions.Num(), FMath::Max(Vertices.UV0.Num(), Vertices.UV1.Num())); + const int32 TexCoordsToCopy = FMath::Min(MeshData.Positions.Num(), FMath::Max(MeshData.UV0.Num(), MeshData.UV1.Num())); using TexCoordBufferType = RealtimeMesh::FRealtimeMeshTexCoord; auto TexCoordBuffer = Builder->CreateVertexStream(RealtimeMesh::FRealtimeMeshLocalVertexFactory::TexCoordsStreamName); - TexCoordBuffer->template Append(TexCoordsToCopy, [&Vertices](int32 Index) -> TexCoordBufferType + TexCoordBuffer->template Append(TexCoordsToCopy, [&MeshData](int32 Index) -> TexCoordBufferType { TexCoordBufferType NewUVs; - NewUVs[0] = Vertices.UV0.IsValidIndex(Index)? FVector2f(Vertices.UV0[Index]) : FVector2f::ZeroVector; - NewUVs[1] = Vertices.UV1.IsValidIndex(Index)? FVector2f(Vertices.UV1[Index]) : FVector2f::ZeroVector; + NewUVs[0] = MeshData.UV0.IsValidIndex(Index)? FVector2f(MeshData.UV0[Index]) : FVector2f::ZeroVector; + NewUVs[1] = MeshData.UV1.IsValidIndex(Index)? FVector2f(MeshData.UV1[Index]) : FVector2f::ZeroVector; return NewUVs; }); - if (TexCoordsToCopy < Vertices.Positions.Num()) + if (TexCoordsToCopy < MeshData.Positions.Num()) { - TexCoordBuffer->AddZeroed(Vertices.Positions.Num() - TexCoordsToCopy); + TexCoordBuffer->AddZeroed(MeshData.Positions.Num() - TexCoordsToCopy); } } else { - const int32 TexCoordsToCopy = FMath::Min(Vertices.Positions.Num(), Vertices.UV0.Num()); + const int32 TexCoordsToCopy = FMath::Min(MeshData.Positions.Num(), MeshData.UV0.Num()); using TexCoordBufferType = RealtimeMesh::FRealtimeMeshTexCoord; auto TexCoordBuffer = Builder->CreateVertexStream(RealtimeMesh::FRealtimeMeshLocalVertexFactory::TexCoordsStreamName); - TexCoordBuffer->template Append(Vertices.Positions.Num(), [&Vertices](int32 Index) -> TexCoordBufferType + TexCoordBuffer->template Append(MeshData.Positions.Num(), [&MeshData](int32 Index) -> TexCoordBufferType { TexCoordBufferType NewUVs; - NewUVs[0] = Vertices.UV0.IsValidIndex(Index)? FVector2f(Vertices.UV0[Index]) : FVector2f::ZeroVector; + NewUVs[0] = MeshData.UV0.IsValidIndex(Index)? FVector2f(MeshData.UV0[Index]) : FVector2f::ZeroVector; return NewUVs; }); - if (TexCoordsToCopy < Vertices.Positions.Num()) + if (TexCoordsToCopy < MeshData.Positions.Num()) { - TexCoordBuffer->AddZeroed(Vertices.Positions.Num() - TexCoordsToCopy); + TexCoordBuffer->AddZeroed(MeshData.Positions.Num() - TexCoordsToCopy); } } } - const TArray* FinalTriangles = &Triangles; + const TArray* FinalTriangles = &MeshData.Triangles; TArray CleanTriangles; if (bRemoveDegenerates) { // Get triangle indices, clamping to vertex range - const int32 MaxIndex = Vertices.Positions.Num() - 1; - const auto GetTriIndices = [&Triangles, MaxIndex](int32 Idx) + const int32 MaxIndex = MeshData.Positions.Num() - 1; + const auto GetTriIndices = [&MeshData, MaxIndex](int32 Idx) { - return TTuple(FMath::Min(Triangles[Idx ], MaxIndex), - FMath::Min(Triangles[Idx + 1], MaxIndex), - FMath::Min(Triangles[Idx + 2], MaxIndex)); + return TTuple(FMath::Min(MeshData.Triangles[Idx ], MaxIndex), + FMath::Min(MeshData.Triangles[Idx + 1], MaxIndex), + FMath::Min(MeshData.Triangles[Idx + 2], MaxIndex)); }; - const int32 NumTriIndices = (Triangles.Num() / 3) * 3; // Ensure number of triangle indices is multiple of three + const int32 NumTriIndices = (MeshData.Triangles.Num() / 3) * 3; // Ensure number of triangle indices is multiple of three // Detect degenerate triangles, i.e. non-unique vertex indices within the same triangle - CleanTriangles.Reserve(Triangles.Num()); + CleanTriangles.Reserve(MeshData.Triangles.Num()); int32 NumDegenerateTriangles = 0; for (int32 IndexIdx = 0; IndexIdx < NumTriIndices; IndexIdx += 3) { @@ -180,7 +180,7 @@ namespace RealtimeMesh if (NumDegenerateTriangles > 0) { UE_LOG(LogTemp, Warning, TEXT("Detected %d degenerate triangle%s with non-unique vertex indices for created mesh section in '%s'; degenerate triangles will be dropped."), - NumDegenerateTriangles, NumDegenerateTriangles > 1 ? "s" : "", *ComponentName.ToString()); + NumDegenerateTriangles, NumDegenerateTriangles > 1 ? TEXT("s") : TEXT(""), *ComponentName.ToString()); } check(NumDegenerateTriangles == 0); @@ -199,43 +199,43 @@ namespace RealtimeMesh } template - inline TSharedRef BuildMeshData(FName ComponentName, const TArray& Triangles, - const FRealtimeMeshSimpleVertexData& Vertices, bool bRemoveDegenerates) + inline TSharedRef BuildMeshData(FName ComponentName, + const FRealtimeMeshSimpleMeshData& MeshData, bool bRemoveDegenerates) { - if (Vertices.Positions.Num() > TNumericLimits::Max()) + if (MeshData.Positions.Num() > TNumericLimits::Max()) { - return BuildMeshData(ComponentName, Triangles, Vertices, bRemoveDegenerates); + return BuildMeshData(ComponentName, MeshData, bRemoveDegenerates); } else { - return BuildMeshData(ComponentName, Triangles, Vertices, bRemoveDegenerates); + return BuildMeshData(ComponentName, MeshData, bRemoveDegenerates); } } template - inline TSharedRef BuildMeshData(FName ComponentName, const TArray& Triangles, const FRealtimeMeshSimpleVertexData& Vertices, + inline TSharedRef BuildMeshData(FName ComponentName, const FRealtimeMeshSimpleMeshData& MeshData, bool bRemoveDegenerates) { - if (Vertices.bUseHighPrecisionTexCoords) + if (MeshData.bUseHighPrecisionTexCoords) { - return BuildMeshData(ComponentName, Triangles, Vertices, bRemoveDegenerates); + return BuildMeshData(ComponentName, MeshData, bRemoveDegenerates); } else { - return BuildMeshData(ComponentName, Triangles, Vertices, bRemoveDegenerates); + return BuildMeshData(ComponentName, MeshData, bRemoveDegenerates); } } - inline TSharedRef BuildMeshData(FName ComponentName, const TArray& Triangles, - const FRealtimeMeshSimpleVertexData& Vertices, bool bRemoveDegenerates) + inline TSharedRef BuildMeshData(FName ComponentName, + const FRealtimeMeshSimpleMeshData& MeshData, bool bRemoveDegenerates) { - if (Vertices.bUseHighPrecisionTangents) + if (MeshData.bUseHighPrecisionTangents) { - return BuildMeshData(ComponentName, Triangles, Vertices, bRemoveDegenerates); + return BuildMeshData(ComponentName, MeshData, bRemoveDegenerates); } else { - return BuildMeshData(ComponentName, Triangles, Vertices, bRemoveDegenerates); + return BuildMeshData(ComponentName, MeshData, bRemoveDegenerates); } } } @@ -293,6 +293,17 @@ namespace RealtimeMesh } } + void FRealtimeMeshSectionSimple::UpdateStreamRange(const FRealtimeMeshStreamRange& InRange) + { + FRealtimeMeshSectionData::UpdateStreamRange(InRange); + + UpdateBounds(RecalculateBounds()); + if (const auto Mesh = MeshWeak.Pin()) + { + Mesh->MarkCollisionDirty(); + } + } + bool FRealtimeMeshSectionSimple::GetPhysicsTriMeshData(FTriMeshCollisionData* CollisionData, bool InUseAllTriData) { if (bShouldCreateMeshCollision) @@ -314,7 +325,8 @@ namespace RealtimeMesh const int32 StartVertexIndex = CollisionData->Vertices.Num(); // Copy in the vertices - CollisionData->Vertices.Append(PositionStream->GetArrayView()); + auto PositionsView = PositionStream->GetArrayView(); + CollisionData->Vertices.Append(PositionsView.GetData(), PositionsView.Num()); if (CollisionData->UVs.Num() < 1) { @@ -326,11 +338,21 @@ namespace RealtimeMesh { if (TexCoordsStream->GetLayout() == RealtimeMesh::GetRealtimeMeshBufferLayout()) { - CollisionData->UVs[0].Append(TexCoordsStream->GetArrayView().Left(PositionStream->Num())); + const auto TexCoordsView = TexCoordsStream->GetArrayView().Left(PositionStream->Num()); + CollisionData->UVs[0].SetNum(TexCoordsView.Num()); + for (int32 TexCoordIdx = 0; TexCoordIdx < TexCoordsView.Num(); TexCoordIdx++) + { + CollisionData->UVs[0][TexCoordIdx] = FVector2D(TexCoordsView[TexCoordIdx]); + } } else - { - CollisionData->UVs[0].Append(TexCoordsStream->GetArrayView().Left(PositionStream->Num())); + { + const auto TexCoordsView = TexCoordsStream->GetArrayView().Left(PositionStream->Num()); + CollisionData->UVs[0].SetNum(TexCoordsView.Num()); + for (int32 TexCoordIdx = 0; TexCoordIdx < TexCoordsView.Num(); TexCoordIdx++) + { + CollisionData->UVs[0][TexCoordIdx] = FVector2D(TexCoordsView[TexCoordIdx]); + } } NumRemainingTexCoords -= TexCoordsStream->Num(); } @@ -417,13 +439,13 @@ namespace RealtimeMesh { const auto Stream = SectionGroup->GetStream(FRealtimeMeshStreamKey( ERealtimeMeshStreamType::Vertex, FRealtimeMeshLocalVertexFactory::PositionStreamName)); - if (Stream && GetStreamRange().GetMaxVertex() < Stream->Num()) + if (Stream && GetStreamRange().NumVertices() > 0 && GetStreamRange().GetMaxVertex() < Stream->Num()) { auto TestLayout = GetRealtimeMeshBufferLayout(); if (Stream->GetLayout() == TestLayout) { - const FVector3f* Points = Stream->GetData(); - Bounds = FBoxSphereBounds3f(Points, Stream->Num()); + const FVector3f* Points = Stream->GetData() + GetStreamRange().GetMinVertex(); + Bounds = FBoxSphereBounds3f(Points, GetStreamRange().NumVertices()); } } } @@ -465,24 +487,24 @@ namespace RealtimeMesh FRealtimeMeshSectionGroup::CreateOrUpdateStream(StreamKey, UpdateData); } - void FRealtimeMeshSectionGroupSimple::SetStreamData(const FRealtimeMeshSimpleVertexData& Vertices, const TArray& Triangles) + void FRealtimeMeshSectionGroupSimple::SetStreamData(const FRealtimeMeshSimpleMeshData& MeshData) { - if (Vertices.Positions.Num() < 3) + if (MeshData.Positions.Num() < 3) { FMessageLog("RealtimeMesh").Error(FText::Format( LOCTEXT("RealtimeMeshSectionGroupSimple_SetStreamData_InvalidVertexCount", "Invalid vertex count {0} for mesh {1}"), - Vertices.Positions.Num(), FText::FromName(GetMeshName()))); + MeshData.Positions.Num(), FText::FromName(GetMeshName()))); return; } - if (Triangles.Num() < 3) + if (MeshData.Triangles.Num() < 3) { FMessageLog("RealtimeMesh").Error(FText::Format( LOCTEXT("RealtimeMeshSectionGroupSimple_SetStreamData_InvalidTriangleCount", "Invalid triangle count {0} for mesh {1}"), - Triangles.Num(), FText::FromName(GetMeshName()))); + MeshData.Triangles.Num(), FText::FromName(GetMeshName()))); return; } - const auto UpdateData = RealtimeMeshSimpleInternal::BuildMeshData(GetMeshName(), Triangles, Vertices, false /*RemoveDegenerates*/); + const auto UpdateData = RealtimeMeshSimpleInternal::BuildMeshData(GetMeshName(), MeshData, false /*RemoveDegenerates*/); SetAllStreams(UpdateData->GetBuffers()); } @@ -704,7 +726,7 @@ void URealtimeMeshSimple::Reset(bool bCreateNewMeshData) Super::Reset(bCreateNewMeshData); } -FRealtimeMeshSectionGroupKey URealtimeMeshSimple::CreateSectionGroup(FRealtimeMeshLODKey LODKey) +FRealtimeMeshSectionGroupKey URealtimeMeshSimple::CreateSectionGroup(const FRealtimeMeshLODKey& LODKey) { if (const auto LOD = GetMesh()->GetLOD(LODKey)) { @@ -715,14 +737,14 @@ FRealtimeMeshSectionGroupKey URealtimeMeshSimple::CreateSectionGroup(FRealtimeMe return FRealtimeMeshSectionGroupKey(); } -FRealtimeMeshSectionGroupKey URealtimeMeshSimple::CreateSectionGroupWithMesh(FRealtimeMeshLODKey LODKey, const FRealtimeMeshSimpleVertexData& Vertices, const TArray& Triangles) +FRealtimeMeshSectionGroupKey URealtimeMeshSimple::CreateSectionGroupWithMesh(const FRealtimeMeshLODKey& LODKey, const FRealtimeMeshSimpleMeshData& MeshData) { if (const auto LOD = MeshRef->GetLOD(LODKey)) { const FRealtimeMeshSectionGroupKey SectionGroupKey = LOD->CreateSectionGroup(); const auto SectionGroup = StaticCastSharedPtr(LOD->GetSectionGroup(SectionGroupKey)); check(SectionGroup); - SectionGroup->SetStreamData(Vertices, Triangles); + SectionGroup->SetStreamData(MeshData); return SectionGroupKey; } @@ -733,14 +755,13 @@ FRealtimeMeshSectionGroupKey URealtimeMeshSimple::CreateSectionGroupWithMesh(FRe return FRealtimeMeshSectionGroupKey(); } -void URealtimeMeshSimple::UpdateSectionGroupMesh(FRealtimeMeshSectionGroupKey SectionGroupKey, const FRealtimeMeshSimpleVertexData& Vertices, - const TArray& Triangles) +void URealtimeMeshSimple::UpdateSectionGroupMesh(const FRealtimeMeshSectionGroupKey& SectionGroupKey, const FRealtimeMeshSimpleMeshData& MeshData) { if (const auto LOD = MeshRef->GetLOD(SectionGroupKey.GetLODKey())) { if (const auto SectionGroup = LOD->GetSectionGroupAs(SectionGroupKey)) { - SectionGroup->SetStreamData(Vertices, Triangles); + SectionGroup->SetStreamData(MeshData); } else { @@ -757,7 +778,7 @@ void URealtimeMeshSimple::UpdateSectionGroupMesh(FRealtimeMeshSectionGroupKey Se } } -void URealtimeMeshSimple::RemoveSectionGroup(FRealtimeMeshSectionGroupKey SectionGroupKey) +void URealtimeMeshSimple::RemoveSectionGroup(const FRealtimeMeshSectionGroupKey& SectionGroupKey) { if (const auto LOD = GetMesh()->GetLOD(SectionGroupKey.GetLODKey())) { @@ -769,7 +790,7 @@ void URealtimeMeshSimple::RemoveSectionGroup(FRealtimeMeshSectionGroupKey Sectio } } -FRealtimeMeshSectionKey URealtimeMeshSimple::CreateMeshSectionInGroup(FRealtimeMeshSectionGroupKey SectionGroupKey, const FRealtimeMeshSectionConfig& Config, +FRealtimeMeshSectionKey URealtimeMeshSimple::CreateSectionInGroup(const FRealtimeMeshSectionGroupKey& SectionGroupKey, const FRealtimeMeshSectionConfig& Config, const FRealtimeMeshStreamRange& StreamRange, bool bShouldCreateCollision) { if (const auto LOD = GetMesh()->GetLOD(SectionGroupKey.GetLODKey())) @@ -795,15 +816,15 @@ FRealtimeMeshSectionKey URealtimeMeshSimple::CreateMeshSectionInGroup(FRealtimeM return FRealtimeMeshSectionKey(); } -FRealtimeMeshSectionKey URealtimeMeshSimple::CreateMeshSection(FRealtimeMeshLODKey LODKey, const FRealtimeMeshSectionConfig& Config, - const FRealtimeMeshSimpleVertexData& Vertices, const TArray& Triangles, bool bShouldCreateCollision) +FRealtimeMeshSectionKey URealtimeMeshSimple::CreateMeshSection(const FRealtimeMeshLODKey& LODKey, const FRealtimeMeshSectionConfig& Config, + const FRealtimeMeshSimpleMeshData& MeshData, bool bShouldCreateCollision) { if (const auto LOD = MeshRef->GetLOD(LODKey)) { const FRealtimeMeshSectionGroupKey SectionGroupKey = LOD->CreateSectionGroup(); const auto SectionGroup = StaticCastSharedPtr(LOD->GetSectionGroup(SectionGroupKey)); check(SectionGroup); - SectionGroup->SetStreamData(Vertices, Triangles); + SectionGroup->SetStreamData(MeshData); const FRealtimeMeshSectionKey SectionKey = SectionGroup->CreateSection(Config, SectionGroup->GetBaseRange()); const auto& Section = SectionGroup->GetSectionAs(SectionKey); @@ -821,7 +842,7 @@ FRealtimeMeshSectionKey URealtimeMeshSimple::CreateMeshSection(FRealtimeMeshLODK } } -void URealtimeMeshSimple::UpdateMeshSection(FRealtimeMeshSectionKey SectionKey, const FRealtimeMeshSimpleVertexData& Vertices, const TArray& Triangles) +void URealtimeMeshSimple::UpdateSectionMesh(const FRealtimeMeshSectionKey& SectionKey, const FRealtimeMeshSimpleMeshData& MeshData) { if (const auto LOD = MeshRef->GetLOD(SectionKey.GetLODKey())) { @@ -831,7 +852,7 @@ void URealtimeMeshSimple::UpdateMeshSection(FRealtimeMeshSectionKey SectionKey, { if (const auto Section = SectionGroup->GetSection(SectionKey)) { - SectionGroup->SetStreamData(Vertices, Triangles); + SectionGroup->SetStreamData(MeshData); Section->UpdateStreamRange(SectionGroup->GetBaseRange()); } else @@ -863,7 +884,40 @@ void URealtimeMeshSimple::UpdateMeshSection(FRealtimeMeshSectionKey SectionKey, } } -FRealtimeMeshSectionConfig URealtimeMeshSimple::GetSectionConfig(FRealtimeMeshSectionKey SectionKey) const +void URealtimeMeshSimple::UpdateSectionSegment(const FRealtimeMeshSectionKey& SectionKey, + const FRealtimeMeshStreamRange& StreamRange) +{ + if (const auto LOD = MeshRef->GetLOD(SectionKey.GetLODKey())) + { + if (const auto SectionGroup = LOD->GetSectionGroupAs(SectionKey.GetSectionGroupKey())) + { + if (const auto Section = SectionGroup->GetSection(SectionKey)) + { + Section->UpdateStreamRange(StreamRange); + } + else + { + FMessageLog("RealtimeMesh").Error( + FText::Format(LOCTEXT("UpdateSectionInvalid", "Attempted to update invalid section {0} in Mesh:{1}"), + FText::FromString(SectionKey.ToString()), FText::FromName(GetFName()))); + } + } + else + { + FMessageLog("RealtimeMesh").Error( + FText::Format(LOCTEXT("UpdateSectionInvalidSectionGroup", "Attempted to update section {0} in invalid section group in Mesh:{1}"), + FText::FromString(SectionKey.ToString()), FText::FromName(GetFName()))); + } + } + else + { + FMessageLog("RealtimeMesh").Error( + FText::Format(LOCTEXT("UpdateSectionInvalidLOD", "Attempted to update section in invalid LOD {0} in Mesh:{1}"), + FText::FromString(SectionKey.GetLODKey().ToString()), FText::FromName(GetFName()))); + } +} + +FRealtimeMeshSectionConfig URealtimeMeshSimple::GetSectionConfig(const FRealtimeMeshSectionKey& SectionKey) const { if (const auto LOD = GetMesh()->GetLOD(SectionKey.GetLODKey())) { @@ -890,7 +944,7 @@ FRealtimeMeshSectionConfig URealtimeMeshSimple::GetSectionConfig(FRealtimeMeshSe return FRealtimeMeshSectionConfig(); } -void URealtimeMeshSimple::UpdateSectionConfig(FRealtimeMeshSectionKey SectionKey, const FRealtimeMeshSectionConfig& Config) +void URealtimeMeshSimple::UpdateSectionConfig(const FRealtimeMeshSectionKey& SectionKey, const FRealtimeMeshSectionConfig& Config) { if (const auto LOD = GetMesh()->GetLOD(SectionKey.GetLODKey())) { @@ -916,7 +970,7 @@ void URealtimeMeshSimple::UpdateSectionConfig(FRealtimeMeshSectionKey SectionKey } } -bool URealtimeMeshSimple::IsSectionVisible(FRealtimeMeshSectionKey SectionKey) const +bool URealtimeMeshSimple::IsSectionVisible(const FRealtimeMeshSectionKey& SectionKey) const { if (const auto LOD = GetMesh()->GetLOD(SectionKey.GetLODKey())) { @@ -943,7 +997,7 @@ bool URealtimeMeshSimple::IsSectionVisible(FRealtimeMeshSectionKey SectionKey) c return false; } -void URealtimeMeshSimple::SetSectionVisibility(FRealtimeMeshSectionKey SectionKey, bool bIsVisible) +void URealtimeMeshSimple::SetSectionVisibility(const FRealtimeMeshSectionKey& SectionKey, bool bIsVisible) { if (const auto LOD = GetMesh()->GetLOD(SectionKey.GetLODKey())) { @@ -969,7 +1023,7 @@ void URealtimeMeshSimple::SetSectionVisibility(FRealtimeMeshSectionKey SectionKe } } -bool URealtimeMeshSimple::IsSectionCastingShadow(FRealtimeMeshSectionKey SectionKey) const +bool URealtimeMeshSimple::IsSectionCastingShadow(const FRealtimeMeshSectionKey& SectionKey) const { if (const auto LOD = GetMesh()->GetLOD(SectionKey.GetLODKey())) { @@ -996,7 +1050,7 @@ bool URealtimeMeshSimple::IsSectionCastingShadow(FRealtimeMeshSectionKey Section return false; } -void URealtimeMeshSimple::SetSectionCastShadow(FRealtimeMeshSectionKey SectionKey, bool bCastShadow) +void URealtimeMeshSimple::SetSectionCastShadow(const FRealtimeMeshSectionKey& SectionKey, bool bCastShadow) { if (const auto LOD = GetMesh()->GetLOD(SectionKey.GetLODKey())) { @@ -1022,7 +1076,7 @@ void URealtimeMeshSimple::SetSectionCastShadow(FRealtimeMeshSectionKey SectionKe } } -void URealtimeMeshSimple::RemoveSection(FRealtimeMeshSectionKey SectionKey) +void URealtimeMeshSimple::RemoveSection(const FRealtimeMeshSectionKey& SectionKey) { if (const auto LOD = GetMesh()->GetLOD(SectionKey.GetLODKey())) { diff --git a/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshComponentProxy.cpp b/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshComponentProxy.cpp index c4dd837..a3257be 100644 --- a/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshComponentProxy.cpp +++ b/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshComponentProxy.cpp @@ -57,6 +57,7 @@ namespace RealtimeMesh FRealtimeMeshComponentSceneProxy::~FRealtimeMeshComponentSceneProxy() { + check(true); } bool FRealtimeMeshComponentSceneProxy::CanBeOccluded() const diff --git a/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshSectionGroupProxy.cpp b/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshSectionGroupProxy.cpp index 1505644..3091d45 100644 --- a/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshSectionGroupProxy.cpp +++ b/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshSectionGroupProxy.cpp @@ -96,10 +96,14 @@ namespace RealtimeMesh { TRHIResourceUpdateBatcher Batcher; + TArray UpdatedStreams; for (const auto& Stream : InStreams) { + UpdatedStreams.Add(Stream->GetStreamKey()); CreateOrUpdateStreamImplementation(Batcher, Stream); } + + AlertSectionsOfStreamUpdates(UpdatedStreams, { }); MarkStateDirty(); } @@ -111,6 +115,8 @@ namespace RealtimeMesh Streams[Stream]->ReleaseUnderlyingResource(); Streams.Remove(Stream); } + + AlertSectionsOfStreamUpdates({ }, InStreams); MarkStateDirty(); } @@ -145,12 +151,13 @@ namespace RealtimeMesh { SectionMaterial = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy(); } + ensure(SectionMaterial); } #if RHI_RAYTRACING - Section->CreateMeshBatch(Params, GetVertexFactory().ToSharedRef(), SectionMaterial, bIsWireframe, bSupportsDithering, &RayTracingGeometry); + Section->CreateMeshBatch(Params, GetVertexFactory().ToSharedRef(), bIsWireframe? WireframeMaterial : SectionMaterial, bIsWireframe, bSupportsDithering, &RayTracingGeometry); #else - Section->CreateMeshBatch(Params, GetVertexFactory().ToSharedRef(), SectionMaterial, bIsWireframe, bSupportsDithering); + Section->CreateMeshBatch(Params, GetVertexFactory().ToSharedRef(), bIsWireframe? WireframeMaterial : SectionMaterial, bIsWireframe, bSupportsDithering); #endif } } @@ -192,7 +199,10 @@ namespace RealtimeMesh bHadSectionUpdates = true; } - UpdateRayTracingInfo(); + if (bHadSectionUpdates) + { + UpdateRayTracingInfo(); + } return bHadSectionUpdates; } @@ -237,7 +247,7 @@ namespace RealtimeMesh // TODO: Get better debug name Initializer.DebugName = TEXT("RealtimeMeshComponent"); Initializer.IndexBuffer = IndexStream->IndexBufferRHI; - Initializer.TotalPrimitiveCount = IndexStream->Num() / 3; + Initializer.TotalPrimitiveCount = 0; Initializer.GeometryType = RTGT_Triangles; Initializer.bFastBuild = true; Initializer.bAllowUpdate = false; @@ -255,6 +265,7 @@ namespace RealtimeMesh Segment.FirstPrimitive = Section->GetStreamRange().GetMinIndex() / 3; Segment.NumPrimitives = Section->GetStreamRange().NumPrimitives(3); Segment.bEnabled = true; + Initializer.TotalPrimitiveCount += Segment.NumPrimitives; Initializer.Segments.Add(Segment); } } @@ -303,4 +314,13 @@ namespace RealtimeMesh const int32 SectionIndex = FRealtimeMeshKeyHelpers::GetSectionIndex(SectionKey); Sections.Insert(SectionIndex, ClassFactory->CreateSectionProxy(ProxyWeak.Pin().ToSharedRef(), SectionKey, InitParams)); } + + void FRealtimeMeshSectionGroupProxy::AlertSectionsOfStreamUpdates(const TArray& AddedOrUpdatedStreams, + const TArray& RemovedStreams) + { + for (const auto& Section : Sections) + { + Section->OnStreamsUpdated(AddedOrUpdatedStreams, RemovedStreams); + } + } } diff --git a/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshSectionProxy.cpp b/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshSectionProxy.cpp index ad158b4..0b74246 100644 --- a/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshSectionProxy.cpp +++ b/Source/RealtimeMeshComponent/Private/RenderProxy/RealtimeMeshSectionProxy.cpp @@ -63,6 +63,7 @@ namespace RealtimeMesh MeshBatch.bDitheredLODTransition = !Params.bIsMovable && Params.LODMask.IsDithered() && bSupportsDithering; MeshBatch.bWireframe = bIsWireframe; + ensure(Material); MeshBatch.MaterialRenderProxy = Material; MeshBatch.ReverseCulling = Params.bIsLocalToWorldDeterminantNegative; @@ -81,7 +82,7 @@ namespace RealtimeMesh BatchElement.PrimitiveUniformBuffer = Params.UniformBuffer; BatchElement.IndexBuffer = &VertexFactory->GetIndexBuffer(bDepthOnly, bMatrixInverted, Params.ResourceSubmitter); - BatchElement.FirstIndex = 0; + BatchElement.FirstIndex = StreamRange.GetMinIndex(); BatchElement.NumPrimitives = StreamRange.NumPrimitives(REALTIME_MESH_NUM_INDICES_PER_PRIMITIVE); BatchElement.MinVertexIndex = StreamRange.GetMinVertex(); BatchElement.MaxVertexIndex = StreamRange.GetMaxVertex(); diff --git a/Source/RealtimeMeshComponent/Public/Data/RealtimeMeshConfig.h b/Source/RealtimeMeshComponent/Public/Data/RealtimeMeshConfig.h index 357c0c3..4a4d069 100644 --- a/Source/RealtimeMeshComponent/Public/Data/RealtimeMeshConfig.h +++ b/Source/RealtimeMeshComponent/Public/Data/RealtimeMeshConfig.h @@ -8,7 +8,8 @@ #define LOCTEXT_NAMESPACE "RealtimeMesh" -USTRUCT(BlueprintType) +USTRUCT(BlueprintType, meta=( + HasNativeMake="RealtimeMeshComponent.RealtimeMeshBlueprintFunctionLibrary.MakeStreamRange")) struct REALTIMEMESHCOMPONENT_API FRealtimeMeshStreamRange { GENERATED_BODY() @@ -178,14 +179,15 @@ struct REALTIMEMESHCOMPONENT_API FRealtimeMeshSectionConfig { GENERATED_BODY() public: - FRealtimeMeshSectionConfig() - : MaterialSlot(0) - , DrawType(ERealtimeMeshSectionDrawType::Static) + FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType InDrawType = ERealtimeMeshSectionDrawType::Static, int32 InMaterialSlot = 0) + : MaterialSlot(InMaterialSlot) + , DrawType(InDrawType) , bIsVisible(true) , bCastsShadow(true) , bIsMainPassRenderable(true) , bForceOpaque(false) { + } UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="RealtimeMesh|Section|Config") @@ -215,9 +217,9 @@ struct REALTIMEMESHCOMPONENT_API FRealtimeMeshLODConfig { GENERATED_BODY() public: - FRealtimeMeshLODConfig() + FRealtimeMeshLODConfig(float InScreenSize = 0.0f) : bIsVisible(true) - , ScreenSize(0.0f) + , ScreenSize(InScreenSize) { } @@ -253,7 +255,9 @@ namespace RealtimeMesh struct FRealtimeMeshKeyHelpers; } -USTRUCT(BlueprintType) +USTRUCT(BlueprintType, meta=( + HasNativeBreak="RealtimeMeshComponent.RealtimeMeshBlueprintFunctionLibrary.BreakLODKey", + HasNativeMake="RealtimeMeshComponent.RealtimeMeshBlueprintFunctionLibrary.MakeLODKey")) struct REALTIMEMESHCOMPONENT_API FRealtimeMeshLODKey { GENERATED_BODY() @@ -276,7 +280,7 @@ struct REALTIMEMESHCOMPONENT_API FRealtimeMeshLODKey friend FArchive& operator<<(FArchive& Ar, FRealtimeMeshLODKey& Key); }; -USTRUCT(BlueprintType) +USTRUCT(BlueprintType, meta=(HasNativeMake="RealtimeMeshComponent.RealtimeMeshBlueprintFunctionLibrary.MakeSectionGroupKey")) struct REALTIMEMESHCOMPONENT_API FRealtimeMeshSectionGroupKey : public FRealtimeMeshLODKey { GENERATED_BODY() @@ -315,7 +319,7 @@ struct REALTIMEMESHCOMPONENT_API FRealtimeMeshSectionGroupKey : public FRealtime static FRealtimeMeshSectionGroupKey Invalid; }; -USTRUCT(BlueprintType) +USTRUCT(BlueprintType, meta=(HasNativeMake="RealtimeMeshComponent.RealtimeMeshBlueprintFunctionLibrary.MakeSectionKey")) struct REALTIMEMESHCOMPONENT_API FRealtimeMeshSectionKey : public FRealtimeMeshSectionGroupKey { GENERATED_BODY() diff --git a/Source/RealtimeMeshComponent/Public/Data/RealtimeMeshDataBuilder.h b/Source/RealtimeMeshComponent/Public/Data/RealtimeMeshDataBuilder.h index fce2102..2475dac 100644 --- a/Source/RealtimeMeshComponent/Public/Data/RealtimeMeshDataBuilder.h +++ b/Source/RealtimeMeshComponent/Public/Data/RealtimeMeshDataBuilder.h @@ -4,6 +4,10 @@ #include "RealtimeMeshDataTypes.h" #include "RenderProxy/RealtimeMeshGPUBuffer.h" +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION == 0 +// Included for TMakeUnsigned in 5.0 +#include "Containers/RingBuffer.h" +#endif namespace RealtimeMesh { diff --git a/Source/RealtimeMeshComponent/Public/RealtimeMesh.h b/Source/RealtimeMeshComponent/Public/RealtimeMesh.h index a0a7649..f784704 100644 --- a/Source/RealtimeMeshComponent/Public/RealtimeMesh.h +++ b/Source/RealtimeMeshComponent/Public/RealtimeMesh.h @@ -48,22 +48,6 @@ class REALTIMEMESHCOMPONENT_API URealtimeMesh : public UObject, public FTickable UPROPERTY(Transient) TArray AsyncBodySetupQueue; - // UPROPERTY() - // TArray CollisionSource; - // - // // // Do we need to update our collision? - // // bool bCollisionIsDirty; - // // - // - // // - // - // // - // // Queue of pending collision cooks - // - // UPROPERTY(Transient) - // TUniquePtr> PendingSourceInfo; - - public: virtual RealtimeMesh::FRealtimeMeshRef GetMesh() const { @@ -120,7 +104,7 @@ class REALTIMEMESHCOMPONENT_API URealtimeMesh : public UObject, public FTickable UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - void SetupMaterialSlot(int32 MaterialSlot, FName SlotName, UMaterialInterface* InMaterial); + void SetupMaterialSlot(int32 MaterialSlot, FName SlotName, UMaterialInterface* InMaterial = nullptr); UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") int32 GetMaterialIndex(FName MaterialSlotName) const; diff --git a/Source/RealtimeMeshComponent/Public/RealtimeMeshActor.h b/Source/RealtimeMeshComponent/Public/RealtimeMeshActor.h index 9b6c256..ef6e39d 100644 --- a/Source/RealtimeMeshComponent/Public/RealtimeMeshActor.h +++ b/Source/RealtimeMeshComponent/Public/RealtimeMeshActor.h @@ -38,8 +38,9 @@ class REALTIMEMESHCOMPONENT_API ARealtimeMeshActor : public AActor * be rebuilt. GeneratedDynamicMeshActor BP subclasses should rebuild their * meshes on this event, instead of doing so directly from the Construction Script. */ - UFUNCTION(BlueprintImplementableEvent, CallInEditor, Category = "Events") + UFUNCTION(BlueprintNativeEvent, CallInEditor, Category = "Events") void OnGenerateMesh(); + virtual void OnGenerateMesh_Implementation() { } /** diff --git a/Source/RealtimeMeshComponent/Public/RealtimeMeshComponent.h b/Source/RealtimeMeshComponent/Public/RealtimeMeshComponent.h index 88a040d..90234ad 100644 --- a/Source/RealtimeMeshComponent/Public/RealtimeMeshComponent.h +++ b/Source/RealtimeMeshComponent/Public/RealtimeMeshComponent.h @@ -31,7 +31,13 @@ class REALTIMEMESHCOMPONENT_API URealtimeMeshComponent : public UMeshComponent void SetRealtimeMesh(URealtimeMesh* NewMesh); UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMeshComponent", meta=(DeterminesOutputType="MeshClass")) - URealtimeMesh* InitializeRealtimeMesh(TSubclassOf MeshClass, UObject* CustomOuter = nullptr); + URealtimeMesh* InitializeRealtimeMesh(TSubclassOf MeshClass); + + template + MeshType* InitializeRealtimeMesh(TSubclassOf MeshClass = MeshType::StaticClass()) + { + return CastChecked(InitializeRealtimeMesh(MeshClass)); + } /** Clears the geometry for ALL collision only sections */ UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMeshComponent") diff --git a/Source/RealtimeMeshComponent/Public/RealtimeMeshCore.h b/Source/RealtimeMeshComponent/Public/RealtimeMeshCore.h index 733f2b2..fff5c70 100644 --- a/Source/RealtimeMeshComponent/Public/RealtimeMeshCore.h +++ b/Source/RealtimeMeshComponent/Public/RealtimeMeshCore.h @@ -190,7 +190,11 @@ namespace RealtimeMesh class FRWScopeLockEx { public: - UE_NODISCARD_CTOR explicit FRWScopeLockEx(FRWLock& InLockObject,FRWScopeLockType InLockType) + +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + UE_NODISCARD_CTOR +#endif + explicit FRWScopeLockEx(FRWLock& InLockObject,FRWScopeLockType InLockType) : LockObject(InLockObject) , LockType(InLockType) { diff --git a/Source/RealtimeMeshComponent/Public/RealtimeMeshLibrary.h b/Source/RealtimeMeshComponent/Public/RealtimeMeshLibrary.h index c153ed9..adae508 100644 --- a/Source/RealtimeMeshComponent/Public/RealtimeMeshLibrary.h +++ b/Source/RealtimeMeshComponent/Public/RealtimeMeshLibrary.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "RealtimeMeshSimple.h" #include "Data/RealtimeMeshConfig.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "RealtimeMeshLibrary.generated.h" @@ -14,8 +15,30 @@ class REALTIMEMESHCOMPONENT_API URealtimeMeshBlueprintFunctionLibrary : public U GENERATED_BODY() public: - UFUNCTION(BlueprintPure, meta = (DisplayName = "LODIndex to LODKey", CompactNodeTitle = "->", BlueprintAutocast), Category = "RealtimeMesh|Key") + UFUNCTION(BlueprintPure, Category = "RealtimeMesh|Key", meta = (DisplayName = "LODIndex to LODKey", CompactNodeTitle = "->", BlueprintAutocast)) static FRealtimeMeshLODKey Conv_IntToRealtimeMeshLODKey(int32 LODIndex); + UFUNCTION(BlueprintPure, Category = "RealtimeMesh|Key") + static FRealtimeMeshLODKey MakeLODKey(int32 LODIndex); + + UFUNCTION(BlueprintPure, Category = "RealtimeMesh|Key") + static FRealtimeMeshSectionGroupKey MakeSectionGroupKey(FRealtimeMeshLODKey LODKey, int32 SectionGroupIndex); + + UFUNCTION(BlueprintPure, Category = "RealtimeMesh|Key") + static FRealtimeMeshSectionKey MakeSectionKey(FRealtimeMeshSectionGroupKey SectionGroupKey, int32 SectionIndex); + + UFUNCTION(BlueprintPure, Category = "RealtimeMesh|Key") + static void BreakLODKey(const FRealtimeMeshLODKey& LODKey, int32& LODIndex); + + UFUNCTION(BlueprintPure, Category = "RealtimeMesh|Stream") + static FRealtimeMeshStreamRange MakeStreamRange(int32 VerticesLowerInclusive = 0, int32 VerticesUpperExclusive = 0, int32 IndicesLowerInclusive = 0, int32 IndicesUpperExclusive = 0); + + /** Generate vertex and index buffer for a simple box, given the supplied dimensions. Normals, UVs and tangents are also generated for each vertex. */ + UFUNCTION(BlueprintCallable, Category = "RealtimeMesh|MeshGeneration") + static FRealtimeMeshSimpleMeshData& AppendBoxMesh(FVector BoxRadius, FTransform BoxTransform, UPARAM(Ref) FRealtimeMeshSimpleMeshData& MeshData); + + /** Generate vertex and index buffer for a simple box, given the supplied dimensions. Normals, UVs and tangents are also generated for each vertex. */ + UFUNCTION(BlueprintCallable, Category = "RealtimeMesh|MeshGeneration", meta=(AutoCreateRefTerm="Transform")) + static FRealtimeMeshSimpleMeshData& AppendMesh(UPARAM(Ref) FRealtimeMeshSimpleMeshData& TargetMeshData, const FRealtimeMeshSimpleMeshData& MeshDataToAdd, const FTransform& Transform); }; \ No newline at end of file diff --git a/Source/RealtimeMeshComponent/Public/RealtimeMeshSimple.h b/Source/RealtimeMeshComponent/Public/RealtimeMeshSimple.h index 1aadaaa..7504523 100644 --- a/Source/RealtimeMeshComponent/Public/RealtimeMeshSimple.h +++ b/Source/RealtimeMeshComponent/Public/RealtimeMeshSimple.h @@ -19,15 +19,13 @@ USTRUCT(BlueprintType) -struct REALTIMEMESHCOMPONENT_API FRealtimeMeshSimpleVertexData +struct REALTIMEMESHCOMPONENT_API FRealtimeMeshSimpleMeshData { GENERATED_BODY(); public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") - bool bUseHighPrecisionTangents; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") - bool bUseHighPrecisionTexCoords; + TArray Triangles; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") TArray Positions; @@ -38,26 +36,32 @@ struct REALTIMEMESHCOMPONENT_API FRealtimeMeshSimpleVertexData UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") TArray Tangents; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh", AdvancedDisplay) TArray Binormals; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") - TArray UV0; + TArray LinearColors; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") + TArray UV0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh", AdvancedDisplay) TArray UV1; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh", AdvancedDisplay) TArray UV2; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh", AdvancedDisplay) TArray UV3; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") - TArray LinearColors; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh") - TArray Colors; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh", AdvancedDisplay) + TArray Colors; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh", AdvancedDisplay) + bool bUseHighPrecisionTangents; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RealtimeMesh", AdvancedDisplay) + bool bUseHighPrecisionTexCoords; }; @@ -77,6 +81,7 @@ namespace RealtimeMesh void SetShouldCreateCollision(bool bNewShouldCreateMeshCollision); virtual void OnStreamsChanged(const TArray& AddedOrUpdatedStreams, const TArray& RemovedStreams) override; + virtual void UpdateStreamRange(const FRealtimeMeshStreamRange& InRange) override; virtual bool GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData); virtual bool ContainsPhysicsTriMeshData(bool InUseAllTriData) const; @@ -102,7 +107,7 @@ namespace RealtimeMesh virtual void CreateOrUpdateStream(FRealtimeMeshStreamKey StreamKey, const FRealtimeMeshDataStreamRef& InStream); - virtual void SetStreamData(const FRealtimeMeshSimpleVertexData& Vertices, const TArray& Triangles); + virtual void SetStreamData(const FRealtimeMeshSimpleMeshData& MeshData); virtual FRealtimeMeshSectionGroupProxyInitializationParametersRef GetInitializationParams() const override; @@ -182,49 +187,52 @@ class REALTIMEMESHCOMPONENT_API URealtimeMeshSimple : public URealtimeMesh virtual void Reset(bool bCreateNewMeshData) override; - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - FRealtimeMeshSectionGroupKey CreateSectionGroup(FRealtimeMeshLODKey LODKey); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta= (AutoCreateRefTerm = "LODKey")) + FRealtimeMeshSectionGroupKey CreateSectionGroup(const FRealtimeMeshLODKey& LODKey); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "Vertices, Triangles")) - FRealtimeMeshSectionGroupKey CreateSectionGroupWithMesh(FRealtimeMeshLODKey LODKey, const FRealtimeMeshSimpleVertexData& Vertices, const TArray& Triangles); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "LODKey, MeshData")) + FRealtimeMeshSectionGroupKey CreateSectionGroupWithMesh(const FRealtimeMeshLODKey& LODKey, const FRealtimeMeshSimpleMeshData& MeshData); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "Vertices, Triangles")) - void UpdateSectionGroupMesh(FRealtimeMeshSectionGroupKey SectionGroupKey, const FRealtimeMeshSimpleVertexData& Vertices, const TArray& Triangles); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionGroupKey, MeshData")) + void UpdateSectionGroupMesh(const FRealtimeMeshSectionGroupKey& SectionGroupKey, const FRealtimeMeshSimpleMeshData& MeshData); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - void RemoveSectionGroup(FRealtimeMeshSectionGroupKey SectionGroupKey); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionGroupKey")) + void RemoveSectionGroup(const FRealtimeMeshSectionGroupKey& SectionGroupKey); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "Config, Vertices, Triangles")) - FRealtimeMeshSectionKey CreateMeshSectionInGroup(FRealtimeMeshSectionGroupKey SectionGroupKey, const FRealtimeMeshSectionConfig& Config, + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionGroupKey, Config, StreamRange")) + FRealtimeMeshSectionKey CreateSectionInGroup(const FRealtimeMeshSectionGroupKey& SectionGroupKey, const FRealtimeMeshSectionConfig& Config, const FRealtimeMeshStreamRange& StreamRange, bool bShouldCreateCollision = false); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "Config, Vertices, Triangles")) - FRealtimeMeshSectionKey CreateMeshSection(FRealtimeMeshLODKey LODKey, const FRealtimeMeshSectionConfig& Config, const FRealtimeMeshSimpleVertexData& Vertices, - const TArray& Triangles, bool bShouldCreateCollision = false); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "LODKey, Config, MeshData")) + FRealtimeMeshSectionKey CreateMeshSection(const FRealtimeMeshLODKey& LODKey, const FRealtimeMeshSectionConfig& Config, const FRealtimeMeshSimpleMeshData& MeshData, + bool bShouldCreateCollision = false); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "Vertices, Triangles")) - void UpdateMeshSection(FRealtimeMeshSectionKey SectionKey, const FRealtimeMeshSimpleVertexData& Vertices, const TArray& Triangles); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionKey, MeshData")) + void UpdateSectionMesh(const FRealtimeMeshSectionKey& SectionKey, const FRealtimeMeshSimpleMeshData& MeshData); + + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionKey")) + void UpdateSectionSegment(const FRealtimeMeshSectionKey& SectionKey, const FRealtimeMeshStreamRange& StreamRange); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - FRealtimeMeshSectionConfig GetSectionConfig(FRealtimeMeshSectionKey SectionKey) const; + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionKey")) + FRealtimeMeshSectionConfig GetSectionConfig(const FRealtimeMeshSectionKey& SectionKey) const; - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - void UpdateSectionConfig(FRealtimeMeshSectionKey SectionKey, const FRealtimeMeshSectionConfig& Config); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionKey")) + void UpdateSectionConfig(const FRealtimeMeshSectionKey& SectionKey, const FRealtimeMeshSectionConfig& Config); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - bool IsSectionVisible(FRealtimeMeshSectionKey SectionKey) const; + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionKey")) + bool IsSectionVisible(const FRealtimeMeshSectionKey& SectionKey) const; - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - void SetSectionVisibility(FRealtimeMeshSectionKey SectionKey, bool bIsVisible); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionKey")) + void SetSectionVisibility(const FRealtimeMeshSectionKey& SectionKey, bool bIsVisible); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - bool IsSectionCastingShadow(FRealtimeMeshSectionKey SectionKey) const; + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionKey")) + bool IsSectionCastingShadow(const FRealtimeMeshSectionKey& SectionKey) const; - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - void SetSectionCastShadow(FRealtimeMeshSectionKey SectionKey, bool bCastShadow); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionKey")) + void SetSectionCastShadow(const FRealtimeMeshSectionKey& SectionKey, bool bCastShadow); - UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh") - void RemoveSection(FRealtimeMeshSectionKey SectionKey); + UFUNCTION(BlueprintCallable, Category = "Components|RealtimeMesh", meta = (AutoCreateRefTerm = "SectionKey")) + void RemoveSection(const FRealtimeMeshSectionKey& SectionKey); }; #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshGPUBuffer.h b/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshGPUBuffer.h index a7a8538..e003665 100644 --- a/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshGPUBuffer.h +++ b/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshGPUBuffer.h @@ -43,6 +43,8 @@ namespace RealtimeMesh { FRHIResourceCreateInfo CreateInfo(TEXT("RealtimeMeshBuffer-Temp"), Resource.Get()); CreateInfo.bWithoutNativeResource = Resource == nullptr || BufferLayout.GetStride() == 0 || GetNumElements() == 0; + +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 FRHIAsyncCommandList CommandList; if (StreamKey.IsVertexStream()) @@ -56,6 +58,19 @@ namespace RealtimeMesh Buffer = CommandList->CreateBuffer(Resource->GetResourceDataSize(), UsageFlags | BUF_IndexBuffer | BUF_ShaderResource, BufferLayout.GetStride(), ERHIAccess::SRVMask, CreateInfo); } +#else + if (StreamKey.IsVertexStream()) + { + Buffer = RHIAsyncCreateVertexBuffer(Resource->GetResourceDataSize(), UsageFlags | BUF_VertexBuffer | BUF_ShaderResource, + ERHIAccess::SRVMask, CreateInfo); + } + else + { + check(StreamKey.IsIndexStream()); + Buffer = RHIAsyncCreateIndexBuffer(BufferLayout.GetStride(), Resource->GetResourceDataSize(), UsageFlags | BUF_IndexBuffer | BUF_ShaderResource, + ERHIAccess::SRVMask, CreateInfo); + } +#endif } } diff --git a/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshSectionGroupProxy.h b/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshSectionGroupProxy.h index e6807c0..a801e31 100644 --- a/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshSectionGroupProxy.h +++ b/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshSectionGroupProxy.h @@ -69,6 +69,9 @@ namespace RealtimeMesh const FRealtimeMeshSectionGroupStreamUpdateDataRef& StreamData); void CreateSectionImplementation(FRealtimeMeshSectionKey SectionKey, const FRealtimeMeshSectionProxyInitializationParametersRef& InitParams); + + void AlertSectionsOfStreamUpdates(const TArray& AddedOrUpdatedStreams, + const TArray& RemovedStreams); }; } diff --git a/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshSectionProxy.h b/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshSectionProxy.h index 262cd98..059e985 100644 --- a/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshSectionProxy.h +++ b/Source/RealtimeMeshComponent/Public/RenderProxy/RealtimeMeshSectionProxy.h @@ -53,7 +53,7 @@ namespace RealtimeMesh void MarkStateDirty(); virtual bool HandleUpdates(bool bShouldForceUpdate); virtual void Reset(); - protected: + void OnStreamsUpdated(const TArray& AddedOrUpdatedStreams, const TArray& RemovedStreams); }; diff --git a/Source/RealtimeMeshEditor/Private/RealtimeMeshEditorSubsystem.cpp b/Source/RealtimeMeshEditor/Private/RealtimeMeshEditorSubsystem.cpp index b25c334..607a2d5 100644 --- a/Source/RealtimeMeshEditor/Private/RealtimeMeshEditorSubsystem.cpp +++ b/Source/RealtimeMeshEditor/Private/RealtimeMeshEditorSubsystem.cpp @@ -3,10 +3,6 @@ #include "RealtimeMeshEditorSubsystem.h" #include "RealtimeMeshActor.h" #include "Editor.h" -#include UE_INLINE_GENERATED_CPP_BY_NAME(RealtimeMeshEditorSubsystem) - - - bool URealtimeMeshEditorEngineSubsystem::bIsShuttingDown = false; diff --git a/Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshBasicUsageActor.cpp b/Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshBasicUsageActor.cpp new file mode 100644 index 0000000..24ef639 --- /dev/null +++ b/Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshBasicUsageActor.cpp @@ -0,0 +1,58 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "FunctionalTests/RealtimeMeshBasicUsageActor.h" + +#include "RealtimeMeshLibrary.h" +#include "RealtimeMeshSimple.h" + + +ARealtimeMeshBasicUsageActor::ARealtimeMeshBasicUsageActor() +{ +} + +void ARealtimeMeshBasicUsageActor::OnGenerateMesh_Implementation() +{ + Super::OnGenerateMesh_Implementation(); + + // Initialize the simple mesh + URealtimeMeshSimple* RealtimeMesh = GetRealtimeMeshComponent()->InitializeRealtimeMesh(); + + // This example create 3 rectangular prisms, one on each axis, with 2 of them grouped in the same vertex buffers, but with different sections + // This allows for setting up separate materials even if sections share a single set of buffers + + // Setup the two material slots + RealtimeMesh->SetupMaterialSlot(0, "PrimaryMaterial"); + RealtimeMesh->SetupMaterialSlot(1, "SecondaryMaterial"); + + { // Create a basic single section + FRealtimeMeshSimpleMeshData MeshData; + + // This just adds a simple box, you can instead create your own mesh data + URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector(100, 100, 200), FTransform::Identity, MeshData); + + // Create a single section, with its own dedicated section group + FRealtimeMeshSectionKey StaticSectionKey = RealtimeMesh->CreateMeshSection(0, + FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0), MeshData, true); + } + + { // Create a basic group with 2 sections + FRealtimeMeshSimpleMeshData MeshData; + + // This just adds two simple boxes, one after the other + URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector(200, 100, 100), FTransform::Identity, MeshData); + URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector(100, 200, 100), FTransform::Identity, MeshData); + + // Create a section group passing it our mesh data + FRealtimeMeshSectionGroupKey GroupKey = RealtimeMesh->CreateSectionGroupWithMesh(0, MeshData); + + // Create both sections on the same mesh data + FRealtimeMeshSectionKey SectionInGroupA = RealtimeMesh->CreateSectionInGroup(GroupKey, + FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0), + FRealtimeMeshStreamRange(0, 24, 0, 36), true); + + FRealtimeMeshSectionKey SectionInGroupB = RealtimeMesh->CreateSectionInGroup(GroupKey, + FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 1), + FRealtimeMeshStreamRange(24, 48, 36, 72), true); + } +} diff --git a/Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshLatentUpdateTestActor.cpp b/Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshLatentUpdateTestActor.cpp new file mode 100644 index 0000000..6244689 --- /dev/null +++ b/Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshLatentUpdateTestActor.cpp @@ -0,0 +1,66 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "FunctionalTests/RealtimeMeshLatentUpdateTestActor.h" + +#include "RealtimeMeshLibrary.h" +#include "RealtimeMeshSimple.h" + + +ARealtimeMeshLatentUpdateTestActor::ARealtimeMeshLatentUpdateTestActor() +{ + +} + + +void ARealtimeMeshLatentUpdateTestActor::OnGenerateMesh_Implementation() +{ + Super::OnGenerateMesh_Implementation(); + + // Initialize the simple mesh + RealtimeMesh = GetRealtimeMeshComponent()->InitializeRealtimeMesh(); + + // This example create 3 rectangular prisms, one on each axis, with 2 of them grouped in the same vertex buffers, but with different sections + // This allows for setting up separate materials even if sections share a single set of buffers. + // Here we do a latent mesh submission, so we create the mesh section group and sections first, and then apply the mesh data later + + FRealtimeMeshSimpleMeshData EmptyMeshData; + + // Create a single section, with its own dedicated section group + StaticSectionKey = RealtimeMesh->CreateMeshSection(0, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0), EmptyMeshData, true); + + // Create a section group passing it our mesh data + GroupKey = RealtimeMesh->CreateSectionGroupWithMesh(0, EmptyMeshData); + + // Create both sections on the same mesh data + SectionInGroupA = RealtimeMesh->CreateSectionInGroup(GroupKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0), FRealtimeMeshStreamRange(), true); + SectionInGroupB = RealtimeMesh->CreateSectionInGroup(GroupKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0), FRealtimeMeshStreamRange(), true); +} + +void ARealtimeMeshLatentUpdateTestActor::BeginPlay() +{ + Super::BeginPlay(); + + { // Create a basic single section + FRealtimeMeshSimpleMeshData MeshData; + + // This just adds a simple box, you can instead create your own mesh data + URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector(100, 100, 200), FTransform::Identity, MeshData); + + RealtimeMesh->UpdateSectionMesh(StaticSectionKey, MeshData); + + } + + { // Create a basic group with 2 sections + FRealtimeMeshSimpleMeshData MeshData; + + // This just adds two simple boxes, one after the other + URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector(200, 100, 100), FTransform::Identity, MeshData); + URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector(100, 200, 100), FTransform::Identity, MeshData); + + RealtimeMesh->UpdateSectionGroupMesh(GroupKey, MeshData); + RealtimeMesh->UpdateSectionSegment(SectionInGroupA, FRealtimeMeshStreamRange(0, 24, 0, 36)); + RealtimeMesh->UpdateSectionSegment(SectionInGroupB, FRealtimeMeshStreamRange(24, 48, 36, 72)); + + } +} diff --git a/Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshUpdateTestActor.cpp b/Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshUpdateTestActor.cpp new file mode 100644 index 0000000..8fdcbcc --- /dev/null +++ b/Source/RealtimeMeshTests/Private/FunctionalTests/RealtimeMeshUpdateTestActor.cpp @@ -0,0 +1,63 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "FunctionalTests/RealtimeMeshUpdateTestActor.h" + +#include "RealtimeMeshLibrary.h" +#include "RealtimeMeshSimple.h" + + +ARealtimeMeshUpdateTestActor::ARealtimeMeshUpdateTestActor() +{ + +} + + +void ARealtimeMeshUpdateTestActor::OnGenerateMesh_Implementation() +{ + Super::OnGenerateMesh_Implementation(); + + // Initialize the simple mesh + RealtimeMesh = GetRealtimeMeshComponent()->InitializeRealtimeMesh(); + + // This example create 3 rectangular prisms, one on each axis, with 2 of them grouped in the same vertex buffers, but with different sections + // This allows for setting up separate materials even if sections share a single set of buffers. + // Here we do a latent mesh submission, so we create the mesh section group and sections first, and then apply the mesh data later + + FRealtimeMeshSimpleMeshData EmptyMeshData; + + // Create a single section, with its own dedicated section group + StaticSectionKey = RealtimeMesh->CreateMeshSection(0, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0), EmptyMeshData, true); + + // Create a section group passing it our mesh data + GroupKey = RealtimeMesh->CreateSectionGroupWithMesh(0, EmptyMeshData); + + // Create both sections on the same mesh data + SectionInGroupA = RealtimeMesh->CreateSectionInGroup(GroupKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0), FRealtimeMeshStreamRange(), true); + SectionInGroupB = RealtimeMesh->CreateSectionInGroup(GroupKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0), FRealtimeMeshStreamRange(), true); + + + { // Create a basic single section + FRealtimeMeshSimpleMeshData MeshData; + + // This just adds a simple box, you can instead create your own mesh data + URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector(100, 100, 200), FTransform::Identity, MeshData); + + RealtimeMesh->UpdateSectionMesh(StaticSectionKey, MeshData); + + } + + { // Create a basic group with 2 sections + FRealtimeMeshSimpleMeshData MeshData; + + // This just adds two simple boxes, one after the other + URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector(200, 100, 100), FTransform::Identity, MeshData); + URealtimeMeshBlueprintFunctionLibrary::AppendBoxMesh(FVector(100, 200, 100), FTransform::Identity, MeshData); + + RealtimeMesh->UpdateSectionGroupMesh(GroupKey, MeshData); + RealtimeMesh->UpdateSectionSegment(SectionInGroupA, FRealtimeMeshStreamRange(0, 24, 0, 36)); + RealtimeMesh->UpdateSectionSegment(SectionInGroupB, FRealtimeMeshStreamRange(24, 48, 36, 72)); + + } + +} diff --git a/Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshBasicUsageActor.h b/Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshBasicUsageActor.h new file mode 100644 index 0000000..6fe5b6d --- /dev/null +++ b/Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshBasicUsageActor.h @@ -0,0 +1,19 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "RealtimeMeshActor.h" +#include "RealtimeMeshBasicUsageActor.generated.h" + +UCLASS() +class REALTIMEMESHTESTS_API ARealtimeMeshBasicUsageActor : public ARealtimeMeshActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + ARealtimeMeshBasicUsageActor(); + + virtual void OnGenerateMesh_Implementation() override; +}; diff --git a/Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshLatentUpdateTestActor.h b/Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshLatentUpdateTestActor.h new file mode 100644 index 0000000..dcadb90 --- /dev/null +++ b/Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshLatentUpdateTestActor.h @@ -0,0 +1,45 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "RealtimeMeshActor.h" +#include "RealtimeMeshLatentUpdateTestActor.generated.h" + +class URealtimeMeshSimple; + + +/* + * This is a test where the mesh structure is created on mesh generation, and the actual data is generated and applied later in BeginPlay + */ +UCLASS() +class REALTIMEMESHTESTS_API ARealtimeMeshLatentUpdateTestActor : public ARealtimeMeshActor +{ + GENERATED_BODY() + +public: + + UPROPERTY() + TObjectPtr RealtimeMesh; + + UPROPERTY() + FRealtimeMeshSectionKey StaticSectionKey; + + // Create a section group passing it our mesh data + UPROPERTY() + FRealtimeMeshSectionGroupKey GroupKey; + + // Create both sections on the same mesh data + UPROPERTY() + FRealtimeMeshSectionKey SectionInGroupA; + UPROPERTY() + FRealtimeMeshSectionKey SectionInGroupB; + + + // Sets default values for this actor's properties + ARealtimeMeshLatentUpdateTestActor(); + + virtual void OnGenerateMesh_Implementation() override; + + virtual void BeginPlay() override; +}; \ No newline at end of file diff --git a/Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshUpdateTestActor.h b/Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshUpdateTestActor.h new file mode 100644 index 0000000..1936dea --- /dev/null +++ b/Source/RealtimeMeshTests/Public/FunctionalTests/RealtimeMeshUpdateTestActor.h @@ -0,0 +1,39 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "RealtimeMeshActor.h" +#include "RealtimeMeshUpdateTestActor.generated.h" + +class URealtimeMeshSimple; +UCLASS() +class REALTIMEMESHTESTS_API ARealtimeMeshUpdateTestActor : public ARealtimeMeshActor +{ + GENERATED_BODY() + +public: + + UPROPERTY() + TObjectPtr RealtimeMesh; + + UPROPERTY() + FRealtimeMeshSectionKey StaticSectionKey; + + // Create a section group passing it our mesh data + UPROPERTY() + FRealtimeMeshSectionGroupKey GroupKey; + + // Create both sections on the same mesh data + UPROPERTY() + FRealtimeMeshSectionKey SectionInGroupA; + UPROPERTY() + FRealtimeMeshSectionKey SectionInGroupB; + + + // Sets default values for this actor's properties + ARealtimeMeshUpdateTestActor(); + + virtual void OnGenerateMesh_Implementation() override; + +}; \ No newline at end of file diff --git a/Source/RealtimeMeshTests/RealtimeMeshTests.Build.cs b/Source/RealtimeMeshTests/RealtimeMeshTests.Build.cs index 3f7a341..a7bd1df 100644 --- a/Source/RealtimeMeshTests/RealtimeMeshTests.Build.cs +++ b/Source/RealtimeMeshTests/RealtimeMeshTests.Build.cs @@ -11,7 +11,8 @@ public RealtimeMeshTests(ReadOnlyTargetRules Target) : base(Target) PublicDependencyModuleNames.AddRange( new string[] { - "Core", + "Core", + "RealtimeMeshComponent", } );