From 74407403fe5e641eb814098060b49e7ba7186740 Mon Sep 17 00:00:00 2001 From: Rexocrates <88537378+Rexocrates@users.noreply.github.com> Date: Sat, 7 Aug 2021 11:38:59 +0200 Subject: [PATCH] Commit of code --- .../MirrorAnimationSystem.uplugin | 36 + .../Resources/CreateMirrorTable_Icon.png | Bin 0 -> 18279 bytes MirrorAnimationSystem/Resources/Icon128.png | Bin 0 -> 20629 bytes .../Resources/MirrorAnimAsset_Icon.png | Bin 0 -> 18279 bytes .../Resources/MirrorTable_ClassIcon.png | Bin 0 -> 18735 bytes .../MirrorAnimationSystem.Build.cs | 54 ++ .../Private/AnimNode_Mirror.cpp | 153 ++++ .../Private/AnimNode_MirrorCS.cpp | 265 +++++++ .../Private/ExtCharacter.cpp | 78 ++ .../Private/ExtCharacterMovementComponent.cpp | 427 +++++++++++ .../Private/MASUtils.cpp | 129 ++++ .../Private/MirrorAnimationSystem.cpp | 25 + .../Private/MirrorTable.cpp | 8 + .../Public/AnimNode_Mirror.h | 34 + .../Public/AnimNode_MirrorCS.h | 57 ++ .../Public/ExtCharacter.h | 41 ++ .../Public/ExtCharacterMovementComponent.h | 28 + .../MirrorAnimationSystem/Public/MASUtils.h | 17 + .../Public/MirrorAnimationSystem.h | 15 + .../Public/MirrorTable.h | 49 ++ .../MirrorAnimationSystemDev.Build.cs | 59 ++ .../Private/MASFunctionLibrary.cpp | 686 ++++++++++++++++++ .../Private/MirrorAnimationSystemDev.cpp | 20 + .../Public/MASFunctionLibrary.h | 30 + .../Public/MirrorAnimationSystemDev.h | 15 + .../MirrorAnimationSystemEditor.Build.cs | 72 ++ .../Private/AnimGraphNode_Mirror.cpp | 34 + .../Private/AnimGraphNode_MirrorCS.cpp | 53 ++ .../Private/AssetTypeActions_MirrorTable.cpp | 56 ++ .../Private/ContentBrowserTools.cpp | 254 +++++++ .../Private/MirrorAnimAssetDialog.cpp | 230 ++++++ .../Private/MirrorAnimAssetSettings.cpp | 9 + .../Private/MirrorAnimationSystemEditor.cpp | 106 +++ .../Private/MirrorAnimationSystemStyle.cpp | 64 ++ .../Private/MirrorTableDetails.cpp | 239 ++++++ .../Private/MirrorTableFromSkeletonDialog.cpp | 288 ++++++++ .../MirrorTableFromSkeletonSettings.cpp | 9 + .../Public/AnimGraphNode_Mirror.h | 29 + .../Public/AnimGraphNode_MirrorCS.h | 51 ++ .../Public/AssetTypeActions_MirrorTable.h | 28 + .../Public/ContentBrowserTools.h | 15 + .../Public/MirrorAnimAssetDialog.h | 46 ++ .../Public/MirrorAnimAssetSettings.h | 34 + .../Public/MirrorAnimationSystemEditor.h | 55 ++ .../Public/MirrorAnimationSystemStyle.h | 29 + .../Public/MirrorTableDetails.h | 73 ++ .../Public/MirrorTableFromSkeletonDialog.h | 52 ++ .../Public/MirrorTableFromSkeletonSettings.h | 42 ++ 48 files changed, 4094 insertions(+) create mode 100644 MirrorAnimationSystem/MirrorAnimationSystem.uplugin create mode 100644 MirrorAnimationSystem/Resources/CreateMirrorTable_Icon.png create mode 100644 MirrorAnimationSystem/Resources/Icon128.png create mode 100644 MirrorAnimationSystem/Resources/MirrorAnimAsset_Icon.png create mode 100644 MirrorAnimationSystem/Resources/MirrorTable_ClassIcon.png create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/MirrorAnimationSystem.Build.cs create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_Mirror.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_MirrorCS.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacter.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacterMovementComponent.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MASUtils.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorAnimationSystem.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorTable.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_Mirror.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_MirrorCS.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacter.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacterMovementComponent.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MASUtils.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorAnimationSystem.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorTable.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemDev/MirrorAnimationSystemDev.Build.cs create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MASFunctionLibrary.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MirrorAnimationSystemDev.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MASFunctionLibrary.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MirrorAnimationSystemDev.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/MirrorAnimationSystemEditor.Build.cs create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_Mirror.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_MirrorCS.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AssetTypeActions_MirrorTable.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/ContentBrowserTools.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetDialog.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetSettings.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemEditor.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemStyle.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableDetails.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonDialog.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonSettings.cpp create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_Mirror.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_MirrorCS.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AssetTypeActions_MirrorTable.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/ContentBrowserTools.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetDialog.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetSettings.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemEditor.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemStyle.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableDetails.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonDialog.h create mode 100644 MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonSettings.h diff --git a/MirrorAnimationSystem/MirrorAnimationSystem.uplugin b/MirrorAnimationSystem/MirrorAnimationSystem.uplugin new file mode 100644 index 0000000..dddc8f3 --- /dev/null +++ b/MirrorAnimationSystem/MirrorAnimationSystem.uplugin @@ -0,0 +1,36 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "2.0", + "FriendlyName": "Mirror Animation System", + "Description": "Incorporates functionalities that allow the mirroring of animations with their root motion at runtime, as well as inside the editor.", + "Category": "Animation", + "CreatedBy": "Rexocrates", + "CreatedByURL": "https://github.com/Rexocrates", + "DocsURL": "https://github.com/Rexocrates/Mirror_Animation_System/blob/main/Mirror%20Animation%20System%20Documentation.pdf", + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/36b1fe6319794a25ab6dfffb82e1b29b", + "SupportURL": "https://github.com/Rexocrates/Mirror_Animation_System/issues", + "EngineVersion": "4.26.0", + "CanContainContent": false, + "Installed": true, + "Modules": [ + { + "Name": "MirrorAnimationSystem", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms": [ "Win64", "Win32", "PS4", "XboxOne", "Mac" ] + }, + { + "Name": "MirrorAnimationSystemDev", + "Type": "DeveloperTool", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms": [ "Win64", "Win32", "PS4", "XboxOne", "Mac" ] + }, + { + "Name": "MirrorAnimationSystemEditor", + "Type": "Editor", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms": [ "Win64", "Win32", "PS4", "XboxOne", "Mac" ] + } + ] +} \ No newline at end of file diff --git a/MirrorAnimationSystem/Resources/CreateMirrorTable_Icon.png b/MirrorAnimationSystem/Resources/CreateMirrorTable_Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c231a93e8c3f1d00dee427dcab1374dd84a60842 GIT binary patch literal 18279 zcmeI4&u`pB6vw@&Knbc;i36w{u(lw9!10f@vukU!QJZZ^B(_9pP*Vws&W>jjtGo7M zJIN;EfZ)Ud32{QGe*^yjNC+Xg0SAs;;Q*H&dO`?=H*3$V_ea8{DWIaU-OoM-zt7z}L~wYbzj8BClqa8}drA59&NGT~ z`Kw@KGu>>x=HaL}kNl`j<_En#cq>YMY0yXbHc8bs*$TqN^4-tBEUSUPSia%3j8=b{ zYzHd`F}Z$lZ37?N#;#vpYRuIK9t7x-6sdz=H%z?2VtE+X1Dh7>Wp(J1-d-$Uq7|x} ztu=KyiizsXFKF1T)YPgwZ#r(xvR_dx!(7k}S2u0VFg?Td3{%awa$^pxdhB<+_2$*Q zI{3F(-cHlLr|Wxrd-Hph`6%AfP1kjG!_qBF0}n0P4^uSI!sKEWgvDu+1jj)?4Wdw` zaZx+kNf*myS`pv!cJ=zaqAOa+#Oz;k9E=|Jrbq~=**cq zK-y|?>CEbSy_w1+y|N1}$cfBUCL8;GqOX%A+KDl_vI|#oan{8r={gy~6$l0HGXwRv zqclpkqay(F(??=VgU%5bLeCs2BZJ@w89kH9hGQ!0>N-&OmScj_DBg&oZX+A6YwYUP z=>~*5JhtfvSLt8}ehg)@hR9NsF zj$UsyOi~nLvf6Aw!F&+-9wrNwDy~*E|?EBEZCZ9lB!m7px(A+IJiwr-*hUhqC&8#ie8Tc zNQJ0d@S$z-p)#w3u<6>SMo0&$AhxB|h>bMUHk>x<5C_>cR>h1kQ&p`535>h_!gz#H zIZSrf$=l_j&hCape-6GHFl#WK68d6wfzjB19t!9$p)y9TBMo%nTqtaNl)T$#&) z>FyLmki{>^kFH~Hn}o;RC|RhX*U>?esFUuYm|WU|5h8LAXHmyxli}++eLQr7f1o20 zw0pX4FszD2knLzTsk&OtwQUWVepPdPf5FGdAbzFIy7~UPp#o{jvuZ5U{CC|j8pU`U zgi4?=Ml_Y`yP==Y2l*37O(V7Nn9R5r zV*HDv)F*Lpk_=P0O3W~+4_TW}ol~o|b_bbON9!QuYgHSb^KK2pECM59AlE?^A7(09 zx5_(nG*`%;lBbxKoVw@4=9u^0bJO$g|3Oa8W;f&WOg49&o|n><4NTCRSzftHvmx#F zzn_M}OcIueUg3UW3j@&y;e>oFi{{Z`DZthSC!4Bg7gvzaiXNSIL6FrlBh08aM!o>J zFzu}KXNsF5Wt0A!bUo4C8Lk(qmI0e? z(%)QKJUuDBdAziEdQv)4z7Ym#!^&%$C@!ezYCeN;9o}G(#)9KioVq?)JgHa<2Bf>$ z3}7Xh&=rxnkZUGS%oN@{N?16N3$xp+@UCgtomnZo$%2Brc9Z#E`f|C~+T&i=z-RBrXw3+(+W#C`1g2ON0{lk+?Vt5kuk< zp~QV8E{;OPkhnxBaUY3`qYyD9E)h!HN8;irL=1^bgcA3WxHt+CL*f#l#C;?#jzYwc zxI`#%ABl^j5HTb!5lY-g;^HVo42esG68Dj~I0_L%;u4|6eIzc9Ld1}`L@03|iHoBU zF(fV#O58`{;wVH6iA#hM_mQ|b3K2u%5~0L>Brc9Z#E`f|C~+T&i=z-RBrXw3+(+W# zC`627am|gsOF=^T7{wlZb>h#TKKdHISfS#T^_HR>yr3xWzON|1AHwlhMcFYF<+odk z;(ekhPe&iW_03g9Iis#NFKrA;%K0;Q|0rDm&xwtf{GuF|lv0WPFM%Jkx#SUU-KTLz zKu2q4ThSht&ANiK%vx?^zHLh^z^} zgXnSCe_&#Mr0p){O+SVK^>~MiiUt$YvZ9^)PzGy>hMj+2vdB{RJ3@^2PuF literal 0 HcmV?d00001 diff --git a/MirrorAnimationSystem/Resources/Icon128.png b/MirrorAnimationSystem/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..1ac305497d2b97e8044d54c388ea61acbd7a7c51 GIT binary patch literal 20629 zcmeI4c{r49+rVeWSj#R%F_u&^W-()#tTna@kzI>1GZ>7S%#6K4$WB^FrATEdOCC%1 zvQ$qgQfRTJq!db8P~S{y^7PdE&HKIY_kPEB95ctkoUZF!?)!IL=Xss?AGi3e*5<-O zazX$AK-kg(YsdVKVEqX2F@HB3;%+j(1gRD-Gyq_UBPDhB$s#m> zxGmJ$9v|w9*CQw!84BqKW0(%eL^=)%kBxC>()->tWys$hVKOp%7Ej z4FL4XNEi|cWokfaA^vn+Fw~!> zvf$*qA1slE4k9QG?C z0-kL{Wd!(ry)*(IM)V_+iT-pN(+=@VJ0?;qE4Jw`!;#6qw4~8Z1DSX5jmR%8Y4#yh zBFv6Rqc8&SMAJa#m8ksc;%Rg{;t#kSxMHgR(h=30LZ{HYDZc^8p8hRHI?3}l7s8tP ztr;Uf~gP@Enf1@%NC32?j?5spMJ;IOh< zZ2mXbSPGuON+71|g<3qI6OiRg@6)?p3D$L6cVaSMB$(a6ddh= z^CY5iDBbTNe&ObCp)5!==DrJAv>%zf@@ui%M%?|^(eD#}q_0JbiVL6-SvhT>{5`4J zRgPW6ST}U7-FxK(lNBUn_2pTT%Ut0z}=2p|f>7b#W zdU`rgBnqX?%qlnms_RL_6OeE%9k_?*_htT{F2i3c&Hvpp{Mf~KZ=Amukzfe>erMkY z{(~!rr}zgF1OAyLVx}sN*%T22Xoj8v6fy)yrTUTZI95W!0{sc!x(aq3gU~6EMa6c3 z`ywCz!FweT14#eOK3#N`Y)p{-r4Ren@FJhArX9oViJ0f}*V1Q* z(1-nP>}P#vR&V3S_}aVZAmPjp48i*S&2;hf&!#T_*mUvq&!#^t9sEgjL*%z;KMgOk zVm(#KU-`i1rz<8u_R_!OJj9s2qd$4{A%>`%r7lnervgC z=9}e0f-QRT{kotFqs-@O<|93f{Y<}Tt=awHzhC%qOaFe~Y>(U^Y!sXbTwI(`V(V~m zu~Bd$aB*=$iLJxM#YVx2z{SN0CAJP17aIj90v8u2l-N34Tx=Aa2wYs8P-5$Faj{Ww zB5-kWLW!-z#l=R!iNM9h2_?1;7Z)1^Cju82CzRMaTwH7voCsW8oKRxxaB;Cwa3XMV zaYBi$!^Oo$!HK}d#R(<04i^_21t$U*7ble1I$T_A6r2cLT%1s1>u_T0Du7p0A9EQ0GLbwK!%dOtJMqu5Noo;n%Dl}NmW>W4H4c-Pe)i+Cwz(*)GaQdnD5w7n#>zmb7{`o5U5 zqyAH4c^~c0GKaHmF-x`?MP&idn(oyFd&;lpH4gPe81Xh7crog@L-E|(+cxT*BYR$4 zhFYcD3QP9PMLPSlwc@si$??1lWbByUNcJF@TO7udVQ0@pJ4sfZj$cjdzZ}J zP;G>W86K9p7^bGo>jN5>bppMz+#8uS`3!4Px&29AmwFZ!(rBwuSE;vea&?c$O#mKTJJ=X(oCOkzR6(tKAi?x<5j&~c$)NvgX9kNJHbIStEB zFghO`xG|y0U&&bhgWKRi%RMHCCyx?c=1i0xB=haJz3J~Pl1W@vs#q;vz48Nmo{)B< z5GYA`nf26s#SFjWA)9=}0OY!hq_79r6OPgK1y>7w*gV*`ZrJc-o2vfM#s27$yW_3O zH3#jl3VUI;nYEKQ1wOeQn9pd|L8CCt^?waKk9WHiwibU0`IO|V$AR%b}Aa0OUa{D9{% zKtoO{RJ-<`x;wqj_o$$lflv(^q>2W1KS_=7^*RPny79R;E!@c$Hh48`7O4D5^FWb9 z6R!-9u1y5h_~@-}XYh4`j3zeMjx1PrB zF1rKRRdko|sBAvlzH4^#E5H-Vq^3%5xOb6YskF}0>b-Kog4O)b?T3XE-gKpC7&%)^ z?IXT-nD${PFmMY>qSc?rFOLd^7fT%7J5r7SdiA~Mr(af<7?586*-3JcIL`l>+El-- z$Z#L+q2#jYbGQ1&P{0yVk+Dy+M;LbMy63Az%O+2`zWaE-Yu|jk$=E9MSGg)K0VkEP zA^TKN%e#F*bxCfqkKO7gCPteckgV@{h@3BguO>^+HE8V*n3bx(n;%k@C?ZcL6$wrv zE9HRW#j-J7VZk2dYBc(@+7opnBfFzdiAqTqHu~*}im&|R{_cp2mm{t@?&)8$9oZfq zD?0;yk3_nOc|@YxO&CK7cN?C=e5l^7g$HiQ0UbyA>y8M@#6CU@Xd8I(C~?-bxj+^q zD}p$!%b>S1Ou~+=({wOhR_b-VzS-@MWjmF0jF0!v_&dm)&T*e^JTP6aZYkMiA(v-a zlXG9foko8?A%;2NeOaGk#{4RY_9bWS+>4I_jt})S%myB) zZ&6?Ms>1u=%ol6t@sm9IKuM2@blI@O6|TH~DetDfoZ7O-Oz73wnW64u(v_uB1lc1t zOUM0ZD(z=-E|uooS9jN4D-&5#QaiI`S=twR9?dpYA>^Q>eO%|9#fjMzOe4p=VIq} z*~SPKP4&p%fS+puQ(ZPa^zHaF>r{V--c;krLxo3=dwoFDwv{rI1DlI?*dJS-sHtl8 zQcl=h%Suq<`9qNyr~*6_z3%A2$s)w_ol&RzBXqfzxrjz%5k@2QQ`@j5gHNG zcQ$N+$eQwQA2PgruB4@M_f%{wBd=i4u2pT`QS$T&Kx!w9w^5;_SPc01QlRF9cZSH0 zIQKKka`4;%-L9gWkz6iPRisz>?jtGc#^pIY6=$p{YZP$c| z-*{t@?-9CRbl}|obA8J!;S$n@p8LMbpB3qGqbd#|1g9rT%fe-fFbpJfMLAB|ClWNB? zC9rNyu0r=CVYgP>PdOvxGq$RaKG{aSI3b!KDkRjsRxMr^{~8vdscp?}x1ndKm~)BP$Tgcv4V?A1AajjQ zX+5*Zo0?7}Oz7KW-CEv55<6clYrhOs_s32H&?%2Bd85kZC&YmW$eO5}xcTNh&|piS z=`{mq)sMoe5Ls1$Q1>aUewqzhYWp%!__7au4#k7Z2M=|f*lH_9mQ&G**ys4rywzvwGNV_tqs&xK9?x;w%nZ`})$9=rwh%OtEldT?|uvZ<;y;-*J zqYNHBYU`G)&CE_qb9YQIi5F7mdq#79x6#~I+4UF?(eog{hFt#T(3$Xfa_xS%DjD9Y z5$gnyAG(rSz=zYm*xu2uScmv_8A5L{IED9y$9u`|POBbn=Ghysjj> z2P%0?N7tM{Shx7>6&`rAsqPCQoibR$X5!A|%;vlHyDCTToSr{BkOjb_rA5ms^3r6? zj{u+~gRUH!eWz6rM2{i*stmfpa&KedaBlMxUr>^`D0x3XWqG9IcseA}?GiAm7WRIV z#=*CRAhS44dpqGzM`YfeY~c;o9AFp)lfF>W;|;_iBtgn=mR?4`YYrU&MhV9ZGebPt-IM$A=JBe zXP)BI+PxO1p4#%mW~A3(4jWsK^N7?Yw?0;DZCx#(?G`U~&lmV)k}p<#@P;kF_D#Nw zn*AZj-gpb4ppKBuX%~(Bl};T-l-dEY^2-bpylr(m1lXQ2z9{YG!NhLNC zxV;m6WPiQt`W2HserW*po5eljGw(jh3Um|-!u`kB+;^;dHTyE<#9dPbvL>Tea1%nB zu~X7rb-VUR|M5WNq}|J<`8!t#_8gv#S54+UBxJ{I+CpU=UFKXr9p9WSp9~adgr3o6 zY#oj-&lzj;1V3cmu-3%Khqi)g(b4YB@`=Fgyxew;>1S4dc(n!a8M{ilOMt88KD@RN z^}I4PwXr->f-!qS&DBQU;#cp%?tlOoZ2HyD(rU+~u;Z+hW zQS=;of3!R;W;mRbxe~X_D#Et(fo6{Oyd3sHrFqJ@6FoB{r`=$z3?retLw|iiq?Jdk z{{X@#W3;BdwagSW%3^(rtajspJ}FP?%7N`k1Rl#ly_#_KU5=eo8c(~yvV#0$ry!Vo z`rH|~>xPDkN<2IjyrE#FQekQ!GgCY(FE;gw3GMY00KQgq;dS0G`My4Cxc;o@ZPeZl zZ|yhDdGG{hrqIq=#?aie_#Ni=q(!~_sQIg+XEPL<8-IT_5k@OAdSK{%F#S%TT z5o%eZaePOdCEfX`PxHZ<@0ZZGjDEUWykiT!Et|B;($shFj$ZKfHoeT5FWPm_3gz}p zZ!>sPuhwLi2~4>+oX_)Ty1V?$_@}-r$DWZ(&Myy>`T&B-G;RwIH+@`k?Rpr`s%2F9 z`8xN+)XZq7^p6}PusiqS`0@wk1(thnCd|)VFz4|)38)hO zIHf`jycda2lD(BkMB2qmpyr-Q{6)qC7*(tJOv&cwcfjxpGAY?Go&5$65_QLpa&Z&?hPC biy}a^xI5Bf0=Ay@j{_}Dt+B-$+@t;r1{2X2 literal 0 HcmV?d00001 diff --git a/MirrorAnimationSystem/Resources/MirrorAnimAsset_Icon.png b/MirrorAnimationSystem/Resources/MirrorAnimAsset_Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bcf5a073c757203e13c7abe5942cefe70b6c611f GIT binary patch literal 18279 zcmeI4&5s;M6~JqD*PCRcKm>=N1c}xd1jy;_@0sb=>==93W>?BM)_PFZr=+I4X1C>; z?xDMPckELH7dXNJp$LipAP2+^A>;!>967)Ri9-ZJL_$F^im)B;*? zQ1xndUcY+pRn@PmURTYbzkl`0H=kR3aSb8#+|KsaE_^>vKTog1|1aGcWB7VzxP3iF z=-JQHPX)d8lP@52@wVUDOZM8YIBqyt$6nYa>!ZOCtPyIS9}Tg4gCtUy?EAq+^}S#G zsw(;3M)ftTt+t1owrwv zN>xftk{cV<3$#LMuYFb83?m|0>owWcjD}RV*EP#-=;q6ku4*+!wH3{jRn1XtN7bas zRc)<-)Qr5Iv%7V9QXSlFR1cD5=qSqZ@$vexu^vYIie}rkqUwsS%U~hLH-iL^IQ?ko^f*N2zoFfGS@Ti+#C{Rm&D;=Y^a++-Pw!? zPQs`YhW%DHU9aY?mo_))^sGx?d(98L@HqZ@Q<-%sCQcEJY~h5osHd#Ltgd%--7!o@ zH_xfMqpGDKndMB3HY_3!C%6>FfN?}=w;kiyPj0QMq z`2)O9lwq*nR0?IuGM&B7P7o(JaLLYA3l!_V?>U}j=$2>dGOnpuHVo5~ZEP5_(be3p zp?j`tW;ojIviqsfTcLYM7Z8Nba*Y?d5S~pWhcs#$(G5-33ASWSBXzl}Sy*nks*3AY z-P1ii6x-dpjQoIVp%g)tBZTY!e%3{ zVosP-Rc-q*%)6VV`3SQzO?KDFx2LsvLElf4)(}TAp=)}hnlEaWYcui$WTQrM#PTc&{!^wvri(hhIT*vVN2_CjkvQVkj;vjL@OOA0wF6_e$;aS63 z)M>Zb@J)q29{T>jVIX3B^kl=J>IO`6(~?b6x8;UynljeBx@>t~&2zCzJfoWr^T!(o z$TX^>H}XvL-y4Qv7P|*H*e6~~$$OS7K20N6GrX?VgX9ERN|&{+*_FF>mB?M&?h#^I znvU(fe~&wI@Qlzpy5<o%KH4oIkn#C_OPb+3#SA!P79?-AwN@^S$foeJMTJ(0y~3 zS1!|RNH<51-iAUBh9ja=qMjcNcpro_?o(NG5*>~L`Pty{w(42qO78QbhfjJS$ZDAr zrl>6^UjkfOXifHvaW=z#7_~!>w2Y=QPhE;h56TXlLtsywKD1g|Q#qxZwcn%XiT+`F zUa0FTT#82jPh88zv#!@4?piLMbuB!<75GUD4r*ybXVfJxdYaE*T!UY*NULU9hSgMN z)w3Q~{SoQ!ZTWDLjOmF;t4(}n3+6grU*uRSm^fz3tMpq_I-Hpne#wF#(G>P8T4|)& z3G9;>iXMIPu~|e>2}8jn2wXgrn2o^2Q1A!>7Y`+7BXBVkJc7W*Ly6f4Tnq(|AaL7Y`+7BXBVk zJc7W*Ly6f4Tnq(|@K9W9#d|480579BhPO`q=4ZDz;EffMyS>{+=+;*ddh@#o-5taC z-w--f5qkFxgq$BE^g{T<*MEH(p%rOo>q2K#LC>$ecfT_JGkxL3)b%;I`};&w`OJ$5 z-KijmmtJSbX#A%Ns!VPXT4GWme6bLxCF(q_f#%FA;QkMDbg7Egyfj`$XW(xc{vhDo z7JP_a8W(LWqE(jA@(bh2$wZTxl)LZ@lo0s*7X77?_qlLKXncYm{1s~bB`D9)Kw~f~ z2Y3?--hxjJK8r3|?ZwJT<$F)vTScq)&rAiVFY=nz-&?--%KNK#(8;)H0d2|oS;I%% zFMY830c*iIFn;g|hjKBseTgb^22`<36`U8feTUlS0e%ni{RP8E?bx+6UV_)S6jvUA zYR z2Tw1roUG)wV;bO(6N_S9mgeEVD#4^U{rW6)qqBmRZ|6!HAirqeUQTZ+=uWOg=y!z( n5_I96Gbbzgf}jg~JpTDlJ74|A+dsNbEq5+m*?Rlp8{hc|>3{^? literal 0 HcmV?d00001 diff --git a/MirrorAnimationSystem/Resources/MirrorTable_ClassIcon.png b/MirrorAnimationSystem/Resources/MirrorTable_ClassIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..bb2f063b89415a4781e6aab57fe6bebff066fb7f GIT binary patch literal 18735 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.Num(); + if (Number > 0) + { + USkeletalMeshComponent* SkelComp = Output.AnimInstanceProxy->GetSkelMeshComponent(); + + if (SkelComp != NULL) + { + + for (int i = 0; i < Number; i++) + { + + FMirrorBone CurrentBone = MirrorTable->MirrorBones[i]; + int BoneIndex = SkelComp->GetBoneIndex(CurrentBone.BoneName); + FCompactPoseBoneIndex CmpctBoneIndex(BoneIndex); + if (Output.Pose.IsValidIndex(CmpctBoneIndex)) + { + + FTransform BoneTransform = Output.Pose[CmpctBoneIndex]; + + BoneTransform.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis); + + if (CurrentBone.RotationOffset != FRotator::ZeroRotator) + { + FRotator BoneNewRotation = BoneTransform.Rotator(); + + BoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw; + BoneNewRotation.Roll += CurrentBone.RotationOffset.Roll; + BoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch; + + BoneTransform.SetRotation(FQuat(BoneNewRotation)); + } + + if (CurrentBone.IsTwinBone) + { + + int TwinBoneIndex = SkelComp->GetBoneIndex(CurrentBone.TwinBoneName); + FCompactPoseBoneIndex CmpctTwinBoneIndex(TwinBoneIndex); + + if (Output.Pose.IsValidIndex(CmpctTwinBoneIndex)) + { + + + FTransform TwinBoneTransform = Output.Pose[CmpctTwinBoneIndex]; + TwinBoneTransform.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis); + + if (CurrentBone.RotationOffset != FRotator::ZeroRotator) + { + FRotator TwinBoneNewRotation = TwinBoneTransform.Rotator(); + TwinBoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw; + TwinBoneNewRotation.Roll += CurrentBone.RotationOffset.Roll; + TwinBoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch; + TwinBoneTransform.SetRotation(FQuat(TwinBoneNewRotation)); + } + + TwinBoneTransform.SetScale3D(TwinBoneTransform.GetScale3D().GetAbs()); + BoneTransform.SetScale3D(BoneTransform.GetScale3D().GetAbs()); + + if (CurrentBone.MirrorTranslation) + { + //TwinBoneTransform.SetRotation(TwinBoneTransform.GetRotation()); + //BoneTransform.SetRotation(FQuat(BoneNewRotation)); + + + Output.Pose[CmpctBoneIndex] = TwinBoneTransform; + Output.Pose[CmpctBoneIndex].NormalizeRotation(); + + Output.Pose[CmpctTwinBoneIndex] = BoneTransform; + Output.Pose[CmpctTwinBoneIndex].NormalizeRotation(); + } + else + { + FVector TwinPos = TwinBoneTransform.GetLocation(); + FVector Pos = BoneTransform.GetLocation(); + + TwinBoneTransform.SetLocation(Pos); + BoneTransform.SetLocation(TwinPos); + + Output.Pose[CmpctBoneIndex] = TwinBoneTransform; + Output.Pose[CmpctBoneIndex].NormalizeRotation(); + + Output.Pose[CmpctTwinBoneIndex] = BoneTransform; + Output.Pose[CmpctTwinBoneIndex].NormalizeRotation(); + } + } + + } + + else + { + + BoneTransform.SetScale3D(BoneTransform.GetScale3D().GetAbs()); + + Output.Pose[CmpctBoneIndex] = BoneTransform; + //Output.Pose[CmpctBoneIndex].NormalizeRotation(); + } + } + } + } + } + } +} + + +void FAnimNode_Mirror::GatherDebugData(FNodeDebugData & DebugData) +{ + FString DebugLine = DebugData.GetNodeName(this); + + DebugData.AddDebugItem(DebugLine); + + BasePose.GatherDebugData(DebugData); +} \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_MirrorCS.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_MirrorCS.cpp new file mode 100644 index 0000000..2f82737 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_MirrorCS.cpp @@ -0,0 +1,265 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "AnimNode_MirrorCS.h" +#include "MirrorAnimationSystem.h" + +#include "AnimationRuntime.h" +#include "Animation/AnimInstanceProxy.h" + +#include "MASUtils.h" + +///////////////////////////////////////////////////// +// FAnimNode_MirrorCS + +FAnimNode_MirrorCS::~FAnimNode_MirrorCS() +{ + TwinPairs.Empty(); + NonTwinIDs.Empty(); + NonTwinFlipAxis.Empty(); +} + +FAnimNode_MirrorCS::FAnimNode_MirrorCS() : +CompletlySymmetrical(false) +{ + TwinPairs.Empty(); + NonTwinIDs.Empty(); + NonTwinFlipAxis.Empty(); +} + +void FAnimNode_MirrorCS::GatherDebugData(FNodeDebugData& DebugData) +{ + Super::GatherDebugData(DebugData); +} + +void FAnimNode_MirrorCS::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) +{ + check(OutBoneTransforms.Num() == 0); + + if(NonTwinIDs.Num()) + { + const bool DeltaStep = !CompletlySymmetrical; + + TArray NewCSTMs; NewCSTMs.SetNum(Output.Pose.GetPose().GetNumBones()); + + const USkeletalMeshComponent* SkelComp = Output.AnimInstanceProxy->GetSkelMeshComponent(); + const auto& RefSkeleton = SkelComp->SkeletalMesh->RefSkeleton; + + + FVector TwinMirrorScale = FVector(1.f); + FVector TargetAxis = FVector::ZeroVector; + if (MirrorAxis != EAxis::None) + { + TwinMirrorScale[MirrorAxis - 1] = -1.f; + TargetAxis[MirrorAxis - 1] = 1.f; + } + + FTransform TwinMirrorModTM(FQuat::Identity, FVector::ZeroVector, TwinMirrorScale); + + for (int32 i = 0; i < NonTwinIDs.Num(); i++) + { + FCompactPoseBoneIndex CmptBoneIndex(NonTwinIDs[i]); + + FTransform TM = Output.Pose.GetComponentSpaceTransform(CmptBoneIndex); + TM.Mirror(MirrorAxis, NonTwinFlipAxis[i]); + + OutBoneTransforms.Add(FBoneTransform(CmptBoneIndex, TM)); + NewCSTMs[NonTwinIDs[i]] = TM; + } + + for (int32 i = 0; i < TwinPairs.Num(); i++) + { + int32 BoneIndex = TwinPairs[i].X; + FCompactPoseBoneIndex CmptBoneIndex(BoneIndex); + + const int32 TwinBoneIndex = TwinPairs[i].Y; + + FCompactPoseBoneIndex TwinCmptBoneIndex(TwinBoneIndex); + + const FTransform RefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, BoneIndex); + const FTransform TwinRefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, TwinBoneIndex); + + const FTransform TM = Output.Pose.GetComponentSpaceTransform(CmptBoneIndex); + const FTransform TwinTM = Output.Pose.GetComponentSpaceTransform(TwinCmptBoneIndex); + + const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex); + const int32 TwinParentIndex = RefSkeleton.GetParentIndex(TwinBoneIndex); + + const bool SameParent = ParentIndex == TwinParentIndex; + + + + + // twin 1º + { + const FTransform MirrRef = RefTM * TwinMirrorModTM; + const FTransform Delta = TwinRefTM.GetRelativeTransform(MirrRef); + const FQuat DeltaQuat = Delta.GetRotation(); + + FTransform MirrTM = TM * TwinMirrorModTM; + + MirrTM.SetRotation(MirrTM.GetRotation() * DeltaQuat); + MirrTM.SetScale3D(TwinTM.GetScale3D()); + + if(DeltaStep) + { + if (SameParent) + { + FTransform RefBS = RefTM; + RefBS = RefBS * TwinMirrorModTM; + const FVector PosDelta = MirrTM.GetLocation() - RefBS.GetLocation(); + MirrTM.SetLocation(TwinRefTM.GetLocation() + PosDelta); + } + else + { + const FTransform& ParentTwinTM = NewCSTMs[RefSkeleton.GetParentIndex(TwinBoneIndex)]; + const FTransform& IParentTM = Output.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(ParentIndex)); + FTransform RefBS = RefSkeleton.GetRefBonePose()[BoneIndex] * IParentTM; + RefBS = RefBS * TwinMirrorModTM; + RefBS.SetRotation(RefBS.GetRotation() * DeltaQuat); + RefBS.SetScale3D(TwinTM.GetScale3D()); + + MirrTM = (MirrTM.GetRelativeTransform(RefBS) * RefSkeleton.GetRefBonePose()[TwinBoneIndex]) * ParentTwinTM; + } + } + + + OutBoneTransforms.Add(FBoneTransform(TwinCmptBoneIndex, MirrTM)); + NewCSTMs[TwinBoneIndex] = MirrTM; + } + + // twin 2º + { + FTransform TwinMirrRef = TwinRefTM * TwinMirrorModTM; + const FQuat TwinDeltaQuat = TwinMirrRef.GetRotation().Inverse() * RefTM.GetRotation(); + + FTransform TwinMirrTM = TwinTM * TwinMirrorModTM; + + TwinMirrTM.SetRotation(TwinMirrTM.GetRotation() * TwinDeltaQuat); + TwinMirrTM.SetScale3D(TM.GetScale3D()); + + if (DeltaStep) + { + if (SameParent) + { + FTransform TwinRefBS = TwinRefTM; + TwinRefBS = TwinRefBS * TwinMirrorModTM; + const FVector PosDelta = TwinMirrTM.GetLocation() - TwinRefBS.GetLocation(); + TwinMirrTM.SetLocation(RefTM.GetLocation() + PosDelta); + } + else + { + const FTransform& ParentTM = NewCSTMs[RefSkeleton.GetParentIndex(BoneIndex)]; + const FTransform& IParentTwinTM = Output.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(TwinParentIndex)); + FTransform TwinRefBS = RefSkeleton.GetRefBonePose()[TwinBoneIndex] * IParentTwinTM; + TwinRefBS = TwinRefBS * TwinMirrorModTM; + TwinRefBS.SetRotation(TwinRefBS.GetRotation() * TwinDeltaQuat); + TwinRefBS.SetScale3D(TM.GetScale3D()); + + TwinMirrTM = (TwinMirrTM.GetRelativeTransform(TwinRefBS) * RefSkeleton.GetRefBonePose()[BoneIndex]) * ParentTM; + } + } + + OutBoneTransforms.Add(FBoneTransform(CmptBoneIndex, TwinMirrTM)); + NewCSTMs[BoneIndex] = TwinMirrTM; + } + } + + } + + if (OutBoneTransforms.Num()) + { + OutBoneTransforms.Sort(FCompareBoneTransformIndex()); + } +} + +bool FAnimNode_MirrorCS::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) +{ + return true; +} + +void FAnimNode_MirrorCS::InitializeBoneReferences(const FBoneContainer& RequiredBones) +{ + Super::InitializeBoneReferences(RequiredBones); + + SetMirrorDataIfDirty(RequiredBones.GetReferenceSkeleton()); +} + +void FAnimNode_MirrorCS::Initialize_AnyThread(const FAnimationInitializeContext& Context) +{ + Super::Initialize_AnyThread(Context); +} + + +void FAnimNode_MirrorCS::SetMirrorDataIfDirty(const FReferenceSkeleton& RefSkeleton) +{ + if (RefSkeleton.GetNum() == LastBoneNum) return; + + const int32 NumBones = RefSkeleton.GetNum(); + LastBoneNum = NumBones; + TArray Already; Already.SetNumZeroed(NumBones); + + FVector TargetAxis = FVector::ZeroVector; + if (MirrorAxis != EAxis::None) + { + TargetAxis[MirrorAxis - 1] = 1.f; + } + + for (int32 BoneIndex = 0; BoneIndex < NumBones; BoneIndex++) + { + if (Already[BoneIndex] == true) continue; + + FName BoneName = RefSkeleton.GetBoneName(BoneIndex); + FName TwinBoneName; + if (FMASUtils::TwinSubstring(BoneName, Substring_A, Substring_B, TwinBoneName)) + { + const int32 TwinBoneIndex = RefSkeleton.FindBoneIndex(TwinBoneName); + if (TwinBoneIndex != INDEX_NONE) + { + Already[BoneIndex] = true; + Already[TwinBoneIndex] = true; + TwinPairs.Add(FIntPoint(BoneIndex, TwinBoneIndex)); + } + } + else + { + const FTransform RefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, BoneIndex); + + EAxis::Type WinnerAxis = EAxis::None; + float Max = -FLT_MAX; + + for (int32 Axis = 0; Axis < 3; Axis++) + { + if (Axis == 0) + { + const float Val = FMath::Abs(RefTM.GetRotation().GetAxisX() | TargetAxis); + if (Val > Max) + { + Max = Val; + WinnerAxis = EAxis::X; + } + } + else if (Axis == 1) + { + const float Val = FMath::Abs(RefTM.GetRotation().GetAxisY() | TargetAxis); + if (Val > Max) + { + Max = Val; + WinnerAxis = EAxis::Y; + } + } + else if (Axis == 2) + { + const float Val = FMath::Abs(RefTM.GetRotation().GetAxisZ() | TargetAxis); + if (Val > Max) + { + Max = Val; + WinnerAxis = EAxis::Z; + } + } + } + + NonTwinIDs.Add(BoneIndex); + NonTwinFlipAxis.Add(WinnerAxis); + + } + } +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacter.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacter.cpp new file mode 100644 index 0000000..bb3b784 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacter.cpp @@ -0,0 +1,78 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "ExtCharacter.h" +#include "MirrorAnimationSystem.h" + + + +// Sets default values +AExtCharacter::AExtCharacter(const FObjectInitializer& ObjectInitializer/* = FObjectInitializer::Get()*/) + : + Super(ObjectInitializer.SetDefaultSubobjectClass(ACharacter::CharacterMovementComponentName)) +{ + // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + +} + +// Called when the game starts or when spawned +void AExtCharacter::BeginPlay() +{ + Super::BeginPlay(); + +} + +// Called every frame +void AExtCharacter::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + +// Called to bind functionality to input +void AExtCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + +} + +void AExtCharacter::SetMirrorRootMotion(bool Mirror) +{ + UExtCharacterMovementComponent * ChMovComp = Cast(AExtCharacter::GetCharacterMovement()); + if (ChMovComp != NULL) + { + ChMovComp->MirrorRootMotion = Mirror; + } +} + +bool AExtCharacter::GetMirrorRootMotion() +{ + UExtCharacterMovementComponent * ChMovComp = Cast(AExtCharacter::GetCharacterMovement()); + if (ChMovComp != NULL) + { + return ChMovComp->MirrorRootMotion; + } + return false; +} + +void AExtCharacter::SetRootMotionMirrorAndFlipAxis(TEnumAsByte MirrorAxis, TEnumAsByte FlipAxis) +{ + UExtCharacterMovementComponent * ChMovComp = Cast(AExtCharacter::GetCharacterMovement()); + if (ChMovComp != NULL) + { + ChMovComp->MirrorAxis = MirrorAxis; + ChMovComp->FlipAxis = FlipAxis; + return; + } +} + +void AExtCharacter::GetRootMotionMirrorAndFlipAxis(TEnumAsByte& MirrorAxis, TEnumAsByte& FlipAxis) +{ + UExtCharacterMovementComponent * ChMovComp = Cast(AExtCharacter::GetCharacterMovement()); + if (ChMovComp != NULL) + { + MirrorAxis = ChMovComp->MirrorAxis; + FlipAxis = ChMovComp->FlipAxis; + return; + } +} + diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacterMovementComponent.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacterMovementComponent.cpp new file mode 100644 index 0000000..591f538 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacterMovementComponent.cpp @@ -0,0 +1,427 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= +Movement.cpp: Character movement implementation + +=============================================================================*/ +#include "ExtCharacterMovementComponent.h" + +#include "GameFramework/CharacterMovementComponent.h" +#include "EngineStats.h" +#include "Components/PrimitiveComponent.h" +#include "AI/NavigationSystemBase.h" +#include "AI/Navigation/NavigationDataInterface.h" +#include "UObject/Package.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PhysicsVolume.h" +#include "Components/SkeletalMeshComponent.h" +#include "Engine/NetDriver.h" +#include "DrawDebugHelpers.h" +#include "GameFramework/GameNetworkManager.h" +#include "GameFramework/Character.h" +#include "Components/CapsuleComponent.h" +#include "GameFramework/GameStateBase.h" +#include "Engine/Canvas.h" +#include "AI/Navigation/PathFollowingAgentInterface.h" +#include "AI/Navigation/AvoidanceManager.h" +#include "Components/BrushComponent.h" + +#include "Engine/DemoNetDriver.h" +#include "Engine/NetworkObjectList.h" + +//#include "Net/PerfCountersHelpers.h" +#include "ProfilingDebugging/CsvProfiler.h" + +DECLARE_CYCLE_STAT(TEXT("Char PerformMovement"), STAT_CharacterMovementPerformMovement, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char RootMotionSource Calculate"), STAT_CharacterMovementRootMotionSourceCalculate, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char RootMotionSource Apply"), STAT_CharacterMovementRootMotionSourceApply, STATGROUP_Character); + + +// Defines for build configs +#if DO_CHECK && !UE_BUILD_SHIPPING // Disable even if checks in shipping are enabled. +#define devCode( Code ) checkCode( Code ) +#else +#define devCode(...) +#endif +// CVars +namespace ExtCharacterMovementCVars +{ + static int32 ExtNetUseClientTimestampForReplicatedTransform = 1; + FAutoConsoleVariableRef ExtCVarNetUseClientTimestampForReplicatedTransform( + TEXT("p.ExtNetUseClientTimestampForReplicatedTransform"), + ExtNetUseClientTimestampForReplicatedTransform, + TEXT("If enabled, use client timestamp changes to track the replicated transform timestamp, otherwise uses server tick time as the timestamp.\n") + TEXT("Game session usually needs to be restarted if this is changed at runtime.\n") + TEXT("0: Disable, 1: Enable"), + ECVF_Default); +} + + +void UExtCharacterMovementComponent::PerformMovement(float DeltaSeconds) +{ + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementPerformMovement); + + const UWorld* MyWorld = GetWorld(); + if (!HasValidData() || MyWorld == nullptr) + { + return; + } + + // no movement if we can't move, or if currently doing physical simulation on UpdatedComponent + if (MovementMode == MOVE_None || UpdatedComponent->Mobility != EComponentMobility::Movable || UpdatedComponent->IsSimulatingPhysics()) + { + if (!CharacterOwner->bClientUpdating && !CharacterOwner->bServerMoveIgnoreRootMotion) + { + // Consume root motion + if (CharacterOwner->IsPlayingRootMotion() && CharacterOwner->GetMesh()) + { + TickCharacterPose(DeltaSeconds); + RootMotionParams.Clear(); + } + if (CurrentRootMotion.HasActiveRootMotionSources()) + { + CurrentRootMotion.Clear(); + } + } + // Clear pending physics forces + ClearAccumulatedForces(); + return; + } + + // Force floor update if we've moved outside of CharacterMovement since last update. + bForceNextFloorCheck |= (IsMovingOnGround() && UpdatedComponent->GetComponentLocation() != LastUpdateLocation); + + // Update saved LastPreAdditiveVelocity with any external changes to character Velocity that happened since last update. + if (CurrentRootMotion.HasAdditiveVelocity()) + { + const FVector Adjustment = (Velocity - LastUpdateVelocity); + CurrentRootMotion.LastPreAdditiveVelocity += Adjustment; + +#if ROOT_MOTION_DEBUG + if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1) + { + if (!Adjustment.IsNearlyZero()) + { + FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement HasAdditiveVelocity LastUpdateVelocityAdjustment LastPreAdditiveVelocity(%s) Adjustment(%s)"), + *CurrentRootMotion.LastPreAdditiveVelocity.ToCompactString(), *Adjustment.ToCompactString()); + RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString); + } + } +#endif + } + + FVector OldVelocity; + FVector OldLocation; + + // Scoped updates can improve performance of multiple MoveComponent calls. + { + FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + MaybeUpdateBasedMovement(DeltaSeconds); + + // Clean up invalid RootMotion Sources. + // This includes RootMotion sources that ended naturally. + // They might want to perform a clamp on velocity or an override, + // so we want this to happen before ApplyAccumulatedForces and HandlePendingLaunch as to not clobber these. + const bool bHasRootMotionSources = HasRootMotionSources(); + if (bHasRootMotionSources && !CharacterOwner->bClientUpdating && !CharacterOwner->bServerMoveIgnoreRootMotion) + { + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementRootMotionSourceCalculate); + + const FVector VelocityBeforeCleanup = Velocity; + CurrentRootMotion.CleanUpInvalidRootMotion(DeltaSeconds, *CharacterOwner, *this); + +#if ROOT_MOTION_DEBUG + if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1) + { + if (Velocity != VelocityBeforeCleanup) + { + const FVector Adjustment = Velocity - VelocityBeforeCleanup; + FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement CleanUpInvalidRootMotion Velocity(%s) VelocityBeforeCleanup(%s) Adjustment(%s)"), + *Velocity.ToCompactString(), *VelocityBeforeCleanup.ToCompactString(), *Adjustment.ToCompactString()); + RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString); + } + } +#endif + } + + OldVelocity = Velocity; + OldLocation = UpdatedComponent->GetComponentLocation(); + + ApplyAccumulatedForces(DeltaSeconds); + + // Update the character state before we do our movement + UpdateCharacterStateBeforeMovement(DeltaSeconds); + + if (MovementMode == MOVE_NavWalking && bWantsToLeaveNavWalking) + { + TryToLeaveNavWalking(); + } + + // Character::LaunchCharacter() has been deferred until now. + HandlePendingLaunch(); + ClearAccumulatedForces(); + +#if ROOT_MOTION_DEBUG + if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1) + { + if (OldVelocity != Velocity) + { + const FVector Adjustment = Velocity - OldVelocity; + FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement ApplyAccumulatedForces+HandlePendingLaunch Velocity(%s) OldVelocity(%s) Adjustment(%s)"), + *Velocity.ToCompactString(), *OldVelocity.ToCompactString(), *Adjustment.ToCompactString()); + RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString); + } + } +#endif + + // Update saved LastPreAdditiveVelocity with any external changes to character Velocity that happened due to ApplyAccumulatedForces/HandlePendingLaunch + if (CurrentRootMotion.HasAdditiveVelocity()) + { + const FVector Adjustment = (Velocity - OldVelocity); + CurrentRootMotion.LastPreAdditiveVelocity += Adjustment; + +#if ROOT_MOTION_DEBUG + if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1) + { + if (!Adjustment.IsNearlyZero()) + { + FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement HasAdditiveVelocity AccumulatedForces LastPreAdditiveVelocity(%s) Adjustment(%s)"), + *CurrentRootMotion.LastPreAdditiveVelocity.ToCompactString(), *Adjustment.ToCompactString()); + RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString); + } + } +#endif + } + + // Prepare Root Motion (generate/accumulate from root motion sources to be used later) + if (bHasRootMotionSources && !CharacterOwner->bClientUpdating && !CharacterOwner->bServerMoveIgnoreRootMotion) + { + // Animation root motion - If using animation RootMotion, tick animations before running physics. + if (CharacterOwner->IsPlayingRootMotion() && CharacterOwner->GetMesh()) + { + TickCharacterPose(DeltaSeconds); + + // Make sure animation didn't trigger an event that destroyed us + if (!HasValidData()) + { + return; + } + + // For local human clients, save off root motion data so it can be used by movement networking code. + if (CharacterOwner->IsLocallyControlled() && (CharacterOwner->GetLocalRole() == ROLE_AutonomousProxy) && CharacterOwner->IsPlayingNetworkedRootMotionMontage()) + { + CharacterOwner->ClientRootMotionParams = RootMotionParams; + } + } + + // Generates root motion to be used this frame from sources other than animation + { + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementRootMotionSourceCalculate); + CurrentRootMotion.PrepareRootMotion(DeltaSeconds, *CharacterOwner, *this, true); + } + + // For local human clients, save off root motion data so it can be used by movement networking code. + if (CharacterOwner->IsLocallyControlled() && (CharacterOwner->GetLocalRole() == ROLE_AutonomousProxy)) + { + CharacterOwner->SavedRootMotion = CurrentRootMotion; + } + } + + // Apply Root Motion to Velocity + if (CurrentRootMotion.HasOverrideVelocity() || HasAnimRootMotion()) + { + // Animation root motion overrides Velocity and currently doesn't allow any other root motion sources + if (HasAnimRootMotion()) + { + // Convert to world space (animation root motion is always local) + USkeletalMeshComponent* SkelMeshComp = CharacterOwner->GetMesh(); + if (SkelMeshComp) + { + if (MirrorRootMotion) + { + FTransform TmpRootMootTr = RootMotionParams.GetRootMotionTransform(); + TmpRootMootTr.Mirror(MirrorAxis, FlipAxis); + TmpRootMootTr.SetScale3D(TmpRootMootTr.GetScale3D().GetAbs()); + RootMotionParams.Set(ConvertLocalRootMotionToWorld(TmpRootMootTr)); + } + else + { + // Convert Local Space Root Motion to world space. Do it right before used by physics to make sure we use up to date transforms, as translation is relative to rotation. + RootMotionParams.Set(ConvertLocalRootMotionToWorld(RootMotionParams.GetRootMotionTransform())); + } + } + + // Then turn root motion to velocity to be used by various physics modes. + if (DeltaSeconds > 0.f) + { + AnimRootMotionVelocity = CalcAnimRootMotionVelocity(RootMotionParams.GetRootMotionTransform().GetTranslation(), DeltaSeconds, Velocity); + Velocity = ConstrainAnimRootMotionVelocity(AnimRootMotionVelocity, Velocity); + } + + UE_LOG(LogRootMotion, Log, TEXT("PerformMovement WorldSpaceRootMotion Translation: %s, Rotation: %s, Actor Facing: %s, Velocity: %s") + , *RootMotionParams.GetRootMotionTransform().GetTranslation().ToCompactString() + , *RootMotionParams.GetRootMotionTransform().GetRotation().Rotator().ToCompactString() + , *CharacterOwner->GetActorForwardVector().ToCompactString() + , *Velocity.ToCompactString() + ); + } + else + { + // We don't have animation root motion so we apply other sources + if (DeltaSeconds > 0.f) + { + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementRootMotionSourceApply); + + const FVector VelocityBeforeOverride = Velocity; + FVector NewVelocity = Velocity; + CurrentRootMotion.AccumulateOverrideRootMotionVelocity(DeltaSeconds, *CharacterOwner, *this, NewVelocity); + Velocity = NewVelocity; + +#if ROOT_MOTION_DEBUG + if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1) + { + if (VelocityBeforeOverride != Velocity) + { + FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement AccumulateOverrideRootMotionVelocity Velocity(%s) VelocityBeforeOverride(%s)"), + *Velocity.ToCompactString(), *VelocityBeforeOverride.ToCompactString()); + RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString); + } + } +#endif + } + } + } + +#if ROOT_MOTION_DEBUG + if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1) + { + FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement Velocity(%s) OldVelocity(%s)"), + *Velocity.ToCompactString(), *OldVelocity.ToCompactString()); + RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString); + } +#endif + + // NaN tracking + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("UCharacterMovementComponent::PerformMovement: Velocity contains NaN (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + // Clear jump input now, to allow movement events to trigger it for next update. + CharacterOwner->ClearJumpInput(DeltaSeconds); + NumJumpApexAttempts = 0; + + // change position + StartNewPhysics(DeltaSeconds, 0); + + if (!HasValidData()) + { + return; + } + + // Update character state based on change from movement + UpdateCharacterStateAfterMovement(DeltaSeconds); + + if ((bAllowPhysicsRotationDuringAnimRootMotion || !HasAnimRootMotion()) && !CharacterOwner->IsMatineeControlled()) + { + PhysicsRotation(DeltaSeconds); + } + + // Apply Root Motion rotation after movement is complete. + if (HasAnimRootMotion()) + { + const FQuat OldActorRotationQuat = UpdatedComponent->GetComponentQuat(); + const FQuat RootMotionRotationQuat = RootMotionParams.GetRootMotionTransform().GetRotation(); + if (!RootMotionRotationQuat.IsIdentity()) + { + const FQuat NewActorRotationQuat = RootMotionRotationQuat * OldActorRotationQuat; + MoveUpdatedComponent(FVector::ZeroVector, NewActorRotationQuat, true); + } + +#if !(UE_BUILD_SHIPPING) + // debug + if (false) + { + const FRotator OldActorRotation = OldActorRotationQuat.Rotator(); + const FVector ResultingLocation = UpdatedComponent->GetComponentLocation(); + const FRotator ResultingRotation = UpdatedComponent->GetComponentRotation(); + + // Show current position + DrawDebugCoordinateSystem(MyWorld, CharacterOwner->GetMesh()->GetComponentLocation() + FVector(0, 0, 1), ResultingRotation, 50.f, false); + + // Show resulting delta move. + DrawDebugLine(MyWorld, OldLocation, ResultingLocation, FColor::Red, false, 10.f); + + // Log details. + UE_LOG(LogRootMotion, Warning, TEXT("PerformMovement Resulting DeltaMove Translation: %s, Rotation: %s, MovementBase: %s"), //-V595 + *(ResultingLocation - OldLocation).ToCompactString(), *(ResultingRotation - OldActorRotation).GetNormalized().ToCompactString(), *GetNameSafe(CharacterOwner->GetMovementBase())); + + const FVector RMTranslation = RootMotionParams.GetRootMotionTransform().GetTranslation(); + const FRotator RMRotation = RootMotionParams.GetRootMotionTransform().GetRotation().Rotator(); + UE_LOG(LogRootMotion, Warning, TEXT("PerformMovement Resulting DeltaError Translation: %s, Rotation: %s"), + *(ResultingLocation - OldLocation - RMTranslation).ToCompactString(), *(ResultingRotation - OldActorRotation - RMRotation).GetNormalized().ToCompactString()); + } +#endif // !(UE_BUILD_SHIPPING) + + // Root Motion has been used, clear + RootMotionParams.Clear(); + } + + // consume path following requested velocity + bHasRequestedVelocity = false; + + OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity); + } // End scoped movement update + + // Call external post-movement events. These happen after the scoped movement completes in case the events want to use the current state of overlaps etc. + CallMovementUpdateDelegate(DeltaSeconds, OldLocation, OldVelocity); + + SaveBaseLocation(); + UpdateComponentVelocity(); + + const bool bHasAuthority = CharacterOwner && CharacterOwner->HasAuthority(); + + // If we move we want to avoid a long delay before replication catches up to notice this change, especially if it's throttling our rate. + if (bHasAuthority && UNetDriver::IsAdaptiveNetUpdateFrequencyEnabled() && UpdatedComponent) + { + UNetDriver* NetDriver = MyWorld->GetNetDriver(); + if (NetDriver && NetDriver->IsServer()) + { + FNetworkObjectInfo* NetActor = NetDriver->FindOrAddNetworkObjectInfo(CharacterOwner); + + if (NetActor && MyWorld->GetTimeSeconds() <= NetActor->NextUpdateTime && NetDriver->IsNetworkActorUpdateFrequencyThrottled(*NetActor)) + { + if (ShouldCancelAdaptiveReplication()) + { + NetDriver->CancelAdaptiveReplication(*NetActor); + } + } + } + } + + const FVector NewLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector; + const FQuat NewRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity; + + if (bHasAuthority && UpdatedComponent && !IsNetMode(NM_Client)) + { + const bool bLocationChanged = (NewLocation != LastUpdateLocation); + const bool bRotationChanged = (NewRotation != LastUpdateRotation); + if (bLocationChanged || bRotationChanged) + { + // Update ServerLastTransformUpdateTimeStamp. This is used by Linear smoothing on clients to interpolate positions with the correct delta time, + // so the timestamp should be based on the client's move delta (ServerAccumulatedClientTimeStamp), not the server time when receiving the RPC. + const bool bIsRemotePlayer = (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy); + const FNetworkPredictionData_Server_Character* ServerData = bIsRemotePlayer ? GetPredictionData_Server_Character() : nullptr; + if (bIsRemotePlayer && ServerData && ExtCharacterMovementCVars::ExtNetUseClientTimestampForReplicatedTransform) + { + ServerLastTransformUpdateTimeStamp = float(ServerData->ServerAccumulatedClientTimeStamp); + } + else + { + ServerLastTransformUpdateTimeStamp = MyWorld->GetTimeSeconds(); + } + } + } + + LastUpdateLocation = NewLocation; + LastUpdateRotation = NewRotation; + LastUpdateVelocity = Velocity; +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MASUtils.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MASUtils.cpp new file mode 100644 index 0000000..011e452 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MASUtils.cpp @@ -0,0 +1,129 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + + +#include "MASUtils.h" + +bool FMASUtils::TwinSubstring(const FName BoneName, const FString Substring_A, const FString Substring_B, FName& OutTwinBoneName) +{ + FString CurrentBoneString = BoneName.ToString(); + + int ResultA = CurrentBoneString.Find(Substring_A, ESearchCase::IgnoreCase, ESearchDir::FromEnd, -1); + int ResultB = CurrentBoneString.Find(Substring_B, ESearchCase::IgnoreCase, ESearchDir::FromEnd, -1); + + if ((ResultA != -1) || (ResultB != -1)) + { + FString TwinBoneString; + bool ValidSymmetry = true; + if (ResultB != -1) + { + + TwinBoneString = CurrentBoneString.Mid(0, ResultB); + TwinBoneString += Substring_A; + TwinBoneString += CurrentBoneString.Mid(ResultB + Substring_B.Len()); + + if ((!(abs(TwinBoneString.Len() - Substring_A.Len()) == abs(CurrentBoneString.Len() - Substring_B.Len()))) && ValidSymmetry) + { + ValidSymmetry = false; + } + } + else + { + + TwinBoneString = CurrentBoneString.Mid(0, ResultA); + TwinBoneString += Substring_B; + TwinBoneString += CurrentBoneString.Mid(ResultA + Substring_A.Len()); + + if ((!(abs(TwinBoneString.Len() - Substring_B.Len()) == abs(CurrentBoneString.Len() - Substring_A.Len()))) && ValidSymmetry) + { + ValidSymmetry = false; + } + } + + if (ValidSymmetry) + { + OutTwinBoneName = FName(*TwinBoneString); + return true; + } + } + + return false; +} + +void FMASUtils::CSMirrorSettings( + const FReferenceSkeleton& RefSkeleton, + const EAxis::Type MirrorAxis, const FString Substring_A, const FString Substring_B, + TArray& TwinPairs, TArray& NonTwinIDs, TArray& NonTwinFlipAxis) +{ + TwinPairs.Empty(); + NonTwinIDs.Empty(); + NonTwinFlipAxis.Empty(); + + const int32 NumBones = RefSkeleton.GetNum(); + TArray Already; Already.SetNumZeroed(NumBones); + + FVector TargetAxis = FVector::ZeroVector; + if (MirrorAxis != EAxis::None) + { + TargetAxis[MirrorAxis - 1] = 1.f; + } + + for (int32 BoneIndex = 0; BoneIndex < NumBones; BoneIndex++) + { + if (Already[BoneIndex] == true) continue; + + FName BoneName = RefSkeleton.GetBoneName(BoneIndex); + FName TwinBoneName; + if (FMASUtils::TwinSubstring(BoneName, Substring_A, Substring_B, TwinBoneName)) + { + const int32 TwinBoneIndex = RefSkeleton.FindBoneIndex(TwinBoneName); + if (TwinBoneIndex != INDEX_NONE) + { + Already[BoneIndex] = true; + Already[TwinBoneIndex] = true; + TwinPairs.Add(FIntPoint(BoneIndex, TwinBoneIndex)); + } + } + else + { + const FTransform RefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, BoneIndex); + + EAxis::Type WinnerAxis = EAxis::None; + float Max = -FLT_MAX; + + for (int32 Axis = 0; Axis < 3; Axis++) + { + if (Axis == 0) + { + const float Val = FMath::Abs(RefTM.GetRotation().GetAxisX() | TargetAxis); + if (Val > Max) + { + Max = Val; + WinnerAxis = EAxis::X; + } + } + else if (Axis == 1) + { + const float Val = FMath::Abs(RefTM.GetRotation().GetAxisY() | TargetAxis); + if (Val > Max) + { + Max = Val; + WinnerAxis = EAxis::Y; + } + } + else if (Axis == 2) + { + const float Val = FMath::Abs(RefTM.GetRotation().GetAxisZ() | TargetAxis); + if (Val > Max) + { + Max = Val; + WinnerAxis = EAxis::Z; + } + } + } + + NonTwinIDs.Add(BoneIndex); + NonTwinFlipAxis.Add(WinnerAxis); + + } + } +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorAnimationSystem.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorAnimationSystem.cpp new file mode 100644 index 0000000..2b53d81 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorAnimationSystem.cpp @@ -0,0 +1,25 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MirrorAnimationSystem.h" + +#define LOCTEXT_NAMESPACE "FMirrorAnimationSystemModule" + +void FMirrorAnimationSystemModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +#if WITH_EDITOR + FModuleManager::Get().LoadModule(TEXT("BlueprintGraph")); + //FModuleManager::Get().LoadModule(TEXT("AnimGraph")); + FModuleManager::Get().LoadModule(TEXT("MirrorAnimationSystemEditor")); +#endif +} + +void FMirrorAnimationSystemModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FMirrorAnimationSystemModule, MirrorAnimationSystem) \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorTable.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorTable.cpp new file mode 100644 index 0000000..d0ed2cc --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorTable.cpp @@ -0,0 +1,8 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "MirrorTable.h" +#include "MirrorAnimationSystem.h" + + + + + diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_Mirror.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_Mirror.h new file mode 100644 index 0000000..6804300 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_Mirror.h @@ -0,0 +1,34 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once +#include "Animation/AnimNodeBase.h" +#include "Animation/AnimInstanceProxy.h" +#include "MirrorTable.h" +#include "AnimNode_Mirror.generated.h" +/** +* +*/ +/*Runtime code for the AnimGraph Node Mirror Pose, +takes in a pose in local space and Mirrors each bone according to a Mirror Table*/ +USTRUCT(BlueprintInternalUseOnly) +struct MIRRORANIMATIONSYSTEM_API FAnimNode_Mirror : public FAnimNode_Base +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Links) + FPoseLink BasePose; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorTable, meta = (PinShownByDefault)) + UMirrorTable* MirrorTable; +public: + + FAnimNode_Mirror(); + + // FAnimNode_Base interface + virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override; + virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override; + virtual void Update_AnyThread(const FAnimationUpdateContext& Context) override; + virtual void Evaluate_AnyThread(FPoseContext & Output) override; + virtual void GatherDebugData(FNodeDebugData& DebugData) override; + // End of FAnimNode_Base interface +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_MirrorCS.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_MirrorCS.h new file mode 100644 index 0000000..960d3d6 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_MirrorCS.h @@ -0,0 +1,57 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "BoneContainer.h" +#include "BonePose.h" +#include "BoneControllers/AnimNode_SkeletalControlBase.h" + +#include "MirrorTable.h" + +#include "AnimNode_MirrorCS.generated.h" + + +// Component Space version of the Mirror Pose node. +USTRUCT(BlueprintInternalUseOnly) +struct MIRRORANIMATIONSYSTEM_API FAnimNode_MirrorCS : public FAnimNode_SkeletalControlBase +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorSettings) + TEnumAsByte MirrorAxis = EAxis::None; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorSettings) + bool CompletlySymmetrical = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorBones) + FString Substring_A; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorBones) + FString Substring_B; + + ~FAnimNode_MirrorCS(); + FAnimNode_MirrorCS(); + + // FAnimNode_Base interface + virtual void GatherDebugData(FNodeDebugData& DebugData) override; + // End of FAnimNode_Base interface + + // FAnimNode_SkeletalControlBase interface + virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) override; + virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override; + virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override; + // End of FAnimNode_SkeletalControlBase interface + +private: + // FAnimNode_SkeletalControlBase interface + virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override; + // End of FAnimNode_SkeletalControlBase interface + + + void SetMirrorDataIfDirty(const FReferenceSkeleton& RefSkeleton); + TArray TwinPairs; + TArray NonTwinIDs; + TArray NonTwinFlipAxis; + int32 LastBoneNum = 0; +}; + diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacter.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacter.h new file mode 100644 index 0000000..da390dc --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacter.h @@ -0,0 +1,41 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once + +#include "GameFramework/Character.h" +#include "ExtCharacterMovementComponent.h" +#include "ExtCharacter.generated.h" +/*Character class that merely implements ExtCharacterMovementComponent and some blueprint functions to set the Mirror Root Motion Parameters*/ +UCLASS() +class MIRRORANIMATIONSYSTEM_API AExtCharacter : public ACharacter +{ + GENERATED_BODY() + +public: + // Sets default values for this character's properties + AExtCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + +protected: + // Called when the game starts or when spawned + virtual void BeginPlay() override; + +public: + // Called every frame + virtual void Tick(float DeltaTime) override; + + // Called to bind functionality to input + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + + UFUNCTION(BlueprintCallable, Category = "Mirror Animation") + virtual void SetMirrorRootMotion(bool Mirror); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Mirror Animation") + virtual bool GetMirrorRootMotion(); + + UFUNCTION(BlueprintCallable, Category = "Mirror Animation") + virtual void SetRootMotionMirrorAndFlipAxis(TEnumAsByte MirrorAxis, TEnumAsByte FlipAxis); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Mirror Animation") + virtual void GetRootMotionMirrorAndFlipAxis(TEnumAsByte & MirrorAxis, TEnumAsByte & FlipAxis); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacterMovementComponent.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacterMovementComponent.h new file mode 100644 index 0000000..aab73ad --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacterMovementComponent.h @@ -0,0 +1,28 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "GameFramework/CharacterMovementComponent.h" +#include "ExtCharacterMovementComponent.generated.h" +/** + * + */ + +struct FRootMotionMovementParams; +/*CharacterMovementComponent Class just carries the Mirror Root Motion parameters and applies them when root motion is received from an animation*/ +UCLASS() +class MIRRORANIMATIONSYSTEM_API UExtCharacterMovementComponent : public UCharacterMovementComponent +{ + GENERATED_BODY() +protected: + virtual void PerformMovement(float DeltaSeconds) override; + +public: + UPROPERTY(Category = "Character Movement: Root Motion", EditAnywhere, BlueprintReadWrite) + bool MirrorRootMotion = false; + + UPROPERTY(Category = "Character Movement: Root Motion", EditAnywhere, BlueprintReadWrite) + TEnumAsByte MirrorAxis = EAxis::None; + + UPROPERTY(Category = "Character Movement: Root Motion", EditAnywhere, BlueprintReadWrite) + TEnumAsByte FlipAxis = EAxis::None; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MASUtils.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MASUtils.h new file mode 100644 index 0000000..0d3934a --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MASUtils.h @@ -0,0 +1,17 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +/** + * + */ +class MIRRORANIMATIONSYSTEM_API FMASUtils +{ +public: + static bool TwinSubstring(const FName BoneName, const FString Substring_A, const FString Substring_B, FName& OutTwinBoneName); + static void CSMirrorSettings(const FReferenceSkeleton& RefSkeleton, + const EAxis::Type MirrorAxis, const FString Substring_A, const FString Substring_B, + TArray& TwinPairs, TArray& NonTwinIDs, TArray& NonTwinFlipAxis); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorAnimationSystem.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorAnimationSystem.h new file mode 100644 index 0000000..877c712 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorAnimationSystem.h @@ -0,0 +1,15 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FMirrorAnimationSystemModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorTable.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorTable.h new file mode 100644 index 0000000..5725d4e --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorTable.h @@ -0,0 +1,49 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once + +#include "Engine/DataAsset.h" +#include "MirrorTable.generated.h" + +/** + * + */ +/*Struct that contains the setup data for Mirroring a single bone or a pair of bones*/ +USTRUCT(BlueprintType) +struct MIRRORANIMATIONSYSTEM_API FMirrorBone +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation") + FName BoneName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation") + TEnumAsByte MirrorAxis; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation") + TEnumAsByte FlipAxis; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation") + FRotator RotationOffset; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation") + bool IsTwinBone; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation") + FName TwinBoneName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation") + bool MirrorTranslation; +}; + +/*Data asset class that holds the MirrorBone's parameters for an entire skeleton, +this class is used for both the Mirror Pose Animgraph Node and when selecting a Mirror Table inside the Mirror AnimAsset dialog*/ +UCLASS(BlueprintType) +class MIRRORANIMATIONSYSTEM_API UMirrorTable : public UDataAsset +{ + GENERATED_BODY() +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorBoneSettings") + TArray MirrorBones; + +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/MirrorAnimationSystemDev.Build.cs b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/MirrorAnimationSystemDev.Build.cs new file mode 100644 index 0000000..2143f52 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/MirrorAnimationSystemDev.Build.cs @@ -0,0 +1,59 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class MirrorAnimationSystemDev : ModuleRules +{ + public MirrorAnimationSystemDev(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "MirrorAnimationSystem", + + // ... add private dependencies that you statically link with here ... + } + ); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MASFunctionLibrary.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MASFunctionLibrary.cpp new file mode 100644 index 0000000..5866750 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MASFunctionLibrary.cpp @@ -0,0 +1,686 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + + +#include "MASFunctionLibrary.h" + +#include "MirrorTable.h" + +#include "Animation/AnimSequence.h" + +#include "Misc/PackageName.h" + +#if WITH_EDITOR +#include "AssetToolsModule.h" +#include "AssetRegistryModule.h" +#include "Toolkits/AssetEditorManager.h" + +#include "Framework/Notifications/NotificationManager.h" +#include "Widgets/Notifications/SNotificationList.h" + +#endif + +#include "MASUtils.h" +#include "AnimationRuntime.h" + +#define LOCTEXT_NAMESPACE "MASLibrary" + +UMASFunctionLibrary::UMASFunctionLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UMASFunctionLibrary::BulkMirrorEditorOnly(const TArray SourceAnims, const UMirrorTable* MirrorTable, TArray & OutNewAnims) +{ + if (SourceAnims.Num() == 0) return; + if (MirrorTable == NULL) return; + +#if WITH_EDITOR + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + + for (int32 i = 0; i < SourceAnims.Num(); i++) + { + // Create the asset + FString Name; + FString PackageName; + + + FString Suffix = TEXT("_Mirrored"); + + AssetToolsModule.Get().CreateUniqueAssetName(SourceAnims[i]->GetOutermost()->GetName(), Suffix, /*out*/ PackageName, /*out*/ Name); + const FString PackagePath = FPackageName::GetLongPackagePath(PackageName); + + + UObject* NewAsset = AssetToolsModule.Get().DuplicateAsset(Name, PackagePath, SourceAnims[i]); + + if (NewAsset != NULL) + { + UAnimSequence* MirrorAnimSequence = Cast(NewAsset); + CreateMirrorSequenceFromAnimSequence(MirrorAnimSequence, MirrorTable); + + OutNewAnims.Add(MirrorAnimSequence); + + // Notify asset registry of new asset + FAssetRegistryModule::AssetCreated(MirrorAnimSequence); + + // Display notification so users can quickly access + if (GIsEditor) + { + FNotificationInfo Info(FText::Format(LOCTEXT("AnimationMirrored", "Successfully Mirrored Animation"), FText::FromString(MirrorAnimSequence->GetName()))); + Info.ExpireDuration = 8.0f; + Info.bUseLargeFont = false; + //Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { FAssetEditorManager::Get().OpenEditorForAssets(TArray({ MirrorAnimSequence })); }); + Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { GEditor->GetEditorSubsystem()->OpenEditorForAssets(TArray({ MirrorAnimSequence })); }); + + Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(MirrorAnimSequence->GetName())); + TSharedPtr Notification = FSlateNotificationManager::Get().AddNotification(Info); + if (Notification.IsValid()) + { + Notification->SetCompletionState(SNotificationItem::CS_Success); + } + } + } + } +#endif +} + +MIRRORANIMATIONSYSTEMDEV_API void UMASFunctionLibrary::BulkMirror_CS_EditorOnly( + const TArray SourceAnims, + const TEnumAsByte MirrorAxis, const FString Substring_A, const FString Substring_B, const bool Symmetrical, TArray& OutNewAnims) +{ + if (SourceAnims.Num() == 0) return; + if (MirrorAxis == EAxis::None) return; + +#if WITH_EDITOR + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + + for (int32 i = 0; i < SourceAnims.Num(); i++) + { + // Create the asset + FString Name; + FString PackageName; + + + FString Suffix = TEXT("_Mirrored"); + + AssetToolsModule.Get().CreateUniqueAssetName(SourceAnims[i]->GetOutermost()->GetName(), Suffix, /*out*/ PackageName, /*out*/ Name); + const FString PackagePath = FPackageName::GetLongPackagePath(PackageName); + + + UObject* NewAsset = AssetToolsModule.Get().DuplicateAsset(Name, PackagePath, SourceAnims[i]); + + if (NewAsset != NULL) + { + UAnimSequence* MirrorAnimSequence = Cast(NewAsset); + CreateMirrorSequenceFromAnimSequence_CS(MirrorAnimSequence, MirrorAxis, Substring_A, Substring_B, Symmetrical); + + OutNewAnims.Add(MirrorAnimSequence); + + // Notify asset registry of new asset + FAssetRegistryModule::AssetCreated(MirrorAnimSequence); + + // Display notification so users can quickly access + if (GIsEditor) + { + FNotificationInfo Info(FText::Format(LOCTEXT("AnimationMirrored", "Successfully Mirrored Animation"), FText::FromString(MirrorAnimSequence->GetName()))); + Info.ExpireDuration = 8.0f; + Info.bUseLargeFont = false; + //Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { FAssetEditorManager::Get().OpenEditorForAssets(TArray({ MirrorAnimSequence })); }); + Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { GEditor->GetEditorSubsystem()->OpenEditorForAssets(TArray({ MirrorAnimSequence })); }); + + Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(MirrorAnimSequence->GetName())); + TSharedPtr Notification = FSlateNotificationManager::Get().AddNotification(Info); + if (Notification.IsValid()) + { + Notification->SetCompletionState(SNotificationItem::CS_Success); + } + } + } + } +#endif +} + +#if WITH_EDITOR + +void UMASFunctionLibrary::CreateMirrorSequenceFromAnimSequence(UAnimSequence* MirrorSequence, const UMirrorTable* MirrorTable) +{ + //Check if it's valid + if ((MirrorSequence != NULL) && (MirrorTable != NULL) && (MirrorSequence->GetSkeleton() != NULL)) + { + //Make the duplicate that I will edit + //UAnimSequence* MirrorSequence = FromAnimSequence; + const auto& Skel = MirrorSequence->GetSkeleton()->GetReferenceSkeleton(); + + int NumMirrorBones = MirrorTable->MirrorBones.Num(); + + int NumFrames = MirrorSequence->GetNumberOfFrames(); + TArray SourceRawAnimDatas = MirrorSequence->GetRawAnimationData(); + const auto& TrackNames = MirrorSequence->GetAnimationTrackNames(); + + for (int i = 0; i < NumMirrorBones; i++) + { + FMirrorBone CurrentBone = MirrorTable->MirrorBones[i]; + + if (Skel.FindBoneIndex(CurrentBone.BoneName) == INDEX_NONE) + { + continue; + } + + if (CurrentBone.IsTwinBone) + { + if (Skel.FindBoneIndex(CurrentBone.TwinBoneName) == INDEX_NONE) + { + continue; + } + + int32 TrackIndex = TrackNames.IndexOfByKey(CurrentBone.BoneName); + int32 TwinTrackIndex = TrackNames.IndexOfByKey(CurrentBone.TwinBoneName); + + if (TrackIndex == INDEX_NONE && TwinTrackIndex == INDEX_NONE) + { + continue; + } + + TArray MirrorPosKeys; + TArray MirrorRotKeys; + TArray MirrorScaleKeys; + + TArray TwinMirrorPosKeys; + TArray TwinMirrorRotKeys; + TArray TwinMirrorScaleKeys; + + // Original Bone + if (TrackIndex != INDEX_NONE) + { + auto& MirroredRawTrack = SourceRawAnimDatas[TrackIndex]; + + for (int u = 0; u < NumFrames; u++) + { + FTransform MirrorTM; + + bool bSetPos = false; + bool bSetRot = false; + bool bSetScale = false; + + if (MirroredRawTrack.PosKeys.IsValidIndex(u)) + { + MirrorTM.SetTranslation(MirroredRawTrack.PosKeys[u]); + bSetPos = true; + } + if (MirroredRawTrack.RotKeys.IsValidIndex(u)) + { + MirrorTM.SetRotation(MirroredRawTrack.RotKeys[u]); + bSetRot = true; + } + if (MirroredRawTrack.ScaleKeys.IsValidIndex(u)) + { + MirrorTM.SetScale3D(MirroredRawTrack.ScaleKeys[u]); + bSetScale = true; + } + + MirrorTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis); + + FRotator BoneNewRotation = MirrorTM.Rotator(); + + BoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw; + BoneNewRotation.Roll += CurrentBone.RotationOffset.Roll; + BoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch; + + MirrorTM.SetRotation(FQuat(BoneNewRotation)); + MirrorTM.SetScale3D(MirrorTM.GetScale3D().GetAbs()); + MirrorTM.NormalizeRotation(); + + if (bSetPos) + { + MirrorPosKeys.Add(MirrorTM.GetTranslation()); + } + if (bSetRot) + { + MirrorRotKeys.Add(MirrorTM.GetRotation()); + } + if (bSetScale) + { + MirrorScaleKeys.Add(MirrorTM.GetScale3D()); + } + } + } + else + { + auto RefTM = Skel.GetRefBonePose()[Skel.FindBoneIndex(CurrentBone.BoneName)]; + + RefTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis); + + FRotator BoneNewRotation = RefTM.Rotator(); + + BoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw; + BoneNewRotation.Roll += CurrentBone.RotationOffset.Roll; + BoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch; + + RefTM.SetRotation(FQuat(BoneNewRotation)); + RefTM.SetScale3D(RefTM.GetScale3D().GetAbs()); + RefTM.NormalizeRotation(); + + MirrorPosKeys.Add(RefTM.GetTranslation()); + MirrorRotKeys.Add(RefTM.GetRotation()); + } + + // Twin Bone + if (TwinTrackIndex != INDEX_NONE) + { + auto& TwinMirroredRawTrack = SourceRawAnimDatas[TwinTrackIndex]; + + for (int u = 0; u < NumFrames; u++) + { + FTransform TwinMirrorTM; + + bool TwinbSetPos = false; + bool TwinbSetRot = false; + bool TwinbSetScale = false; + + if (TwinMirroredRawTrack.PosKeys.IsValidIndex(u)) + { + TwinMirrorTM.SetTranslation(TwinMirroredRawTrack.PosKeys[u]); + TwinbSetPos = true; + } + if (TwinMirroredRawTrack.RotKeys.IsValidIndex(u)) + { + TwinMirrorTM.SetRotation(TwinMirroredRawTrack.RotKeys[u]); + TwinbSetRot = true; + } + if (TwinMirroredRawTrack.ScaleKeys.IsValidIndex(u)) + { + TwinMirrorTM.SetScale3D(TwinMirroredRawTrack.ScaleKeys[u]); + TwinbSetScale = true; + } + + TwinMirrorTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis); + + FRotator TwinBoneNewRotation = TwinMirrorTM.Rotator(); + + TwinBoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw; + TwinBoneNewRotation.Roll += CurrentBone.RotationOffset.Roll; + TwinBoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch; + + TwinMirrorTM.SetRotation(FQuat(TwinBoneNewRotation)); + TwinMirrorTM.SetScale3D(TwinMirrorTM.GetScale3D().GetAbs()); + TwinMirrorTM.NormalizeRotation(); + + if (TwinbSetPos) + { + TwinMirrorPosKeys.Add(TwinMirrorTM.GetTranslation()); + } + if (TwinbSetRot) + { + TwinMirrorRotKeys.Add(TwinMirrorTM.GetRotation()); + } + if (TwinbSetScale) + { + TwinMirrorScaleKeys.Add(TwinMirrorTM.GetScale3D()); + } + } + } + else + { + auto RefTM = Skel.GetRefBonePose()[Skel.FindBoneIndex(CurrentBone.TwinBoneName)]; + + RefTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis); + + FRotator TwinBoneNewRotation = RefTM.Rotator(); + + TwinBoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw; + TwinBoneNewRotation.Roll += CurrentBone.RotationOffset.Roll; + TwinBoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch; + + RefTM.SetRotation(FQuat(TwinBoneNewRotation)); + RefTM.SetScale3D(RefTM.GetScale3D().GetAbs()); + RefTM.NormalizeRotation(); + + TwinMirrorPosKeys.Add(RefTM.GetTranslation()); + TwinMirrorRotKeys.Add(RefTM.GetRotation()); + } + + // Original Bone -> Twin Bone + { + FRawAnimSequenceTrack NewTrack; + + NewTrack.PosKeys = CurrentBone.MirrorTranslation ? MirrorPosKeys : TwinMirrorPosKeys; + NewTrack.RotKeys = MirrorRotKeys; + NewTrack.ScaleKeys = MirrorScaleKeys; + + MirrorSequence->AddNewRawTrack(CurrentBone.TwinBoneName, &NewTrack); + } + + // Twin Bone -> Original Bone + { + FRawAnimSequenceTrack NewTrack; + + NewTrack.PosKeys = CurrentBone.MirrorTranslation ? TwinMirrorPosKeys : MirrorPosKeys; + NewTrack.RotKeys = TwinMirrorRotKeys; + NewTrack.ScaleKeys = TwinMirrorScaleKeys; + + MirrorSequence->AddNewRawTrack(CurrentBone.BoneName, &NewTrack); + } + } + else + { + int32 TrackIndex = TrackNames.IndexOfByKey(CurrentBone.BoneName); + + if (TrackIndex == INDEX_NONE) + { + continue; + } + + FRawAnimSequenceTrack MirroredRawTrack = SourceRawAnimDatas[TrackIndex]; + + //MirrorAllFrames + TArray MirrorPosKeys; + TArray MirrorRotKeys; + TArray MirrorScaleKeys; + + for (int u = 0; u < NumFrames; u++) + { + //Mirror Transform + FTransform MirrorTM; + + bool bSetPos = false; + bool bSetRot = false; + bool bSetScale = false; + + if (MirroredRawTrack.PosKeys.IsValidIndex(u)) + { + MirrorTM.SetTranslation(MirroredRawTrack.PosKeys[u]); + bSetPos = true; + } + if (MirroredRawTrack.RotKeys.IsValidIndex(u)) + { + MirrorTM.SetRotation(MirroredRawTrack.RotKeys[u]); + bSetRot = true; + } + if (MirroredRawTrack.ScaleKeys.IsValidIndex(u)) + { + MirrorTM.SetScale3D(MirroredRawTrack.ScaleKeys[u]); + bSetScale = true; + } + + MirrorTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis); + + FRotator BoneNewRotation = MirrorTM.Rotator(); + + BoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw; + BoneNewRotation.Roll += CurrentBone.RotationOffset.Roll; + BoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch; + + MirrorTM.SetRotation(FQuat(BoneNewRotation)); + //MirrorTM.NormalizeRotation(); + MirrorTM.SetScale3D(MirrorTM.GetScale3D().GetAbs()); + + MirrorTM.NormalizeRotation(); + + //Setting it up Main + if (bSetPos) + { + MirrorPosKeys.Add(MirrorTM.GetTranslation()); + } + if (bSetRot) + { + MirrorRotKeys.Add(MirrorTM.GetRotation()); + } + if (bSetScale) + { + MirrorScaleKeys.Add(MirrorTM.GetScale3D()); + } + + ///////////////////////////////// + } + + MirroredRawTrack.PosKeys = MirrorPosKeys; + MirroredRawTrack.RotKeys = MirrorRotKeys; + MirroredRawTrack.ScaleKeys = MirrorScaleKeys; + + //Finally Setting it in the AnimSequence + + MirrorSequence->AddNewRawTrack(CurrentBone.BoneName, &MirroredRawTrack); + } + } + MirrorSequence->ClearBakedTransformData(); + MirrorSequence->RawCurveData.TransformCurves.Empty(); + MirrorSequence->bNeedsRebake = false; + MirrorSequence->MarkRawDataAsModified(); + MirrorSequence->OnRawDataChanged(); + MirrorSequence->MarkPackageDirty(); + } +} + + +static FTransform GetAnimBoneTM(UAnimSequence* AnimSeq, const int32 BoneTreeIndex, const float AnimTime) +{ + USkeleton* Skeleton = AnimSeq->GetSkeleton(); + //int32 BoneTreeIndex = Skeleton->GetSkeletonBoneIndexFromMeshBoneIndex(SkelMesh, BoneTreeIndex); + int32 BoneTrackIndex = Skeleton->GetRawAnimationTrackIndex(BoneTreeIndex, AnimSeq); + if (BoneTrackIndex == INDEX_NONE) + { + return Skeleton->GetReferenceSkeleton().GetRefBonePose()[BoneTreeIndex]; + } + FTransform BoneTM = FTransform::Identity; + AnimSeq->GetBoneTransform(BoneTM, BoneTrackIndex, AnimTime, true); + return BoneTM; +} + +static FTransform GetAnimBoneCSTM(UAnimSequence* AnimSeq, const int32 BoneTreeIndex, const float AnimTime) +{ + USkeleton* Skeleton = AnimSeq->GetSkeleton(); + const auto& RefSkeleton = Skeleton->GetReferenceSkeleton(); + FTransform BoneTMWS = GetAnimBoneTM(AnimSeq, BoneTreeIndex, AnimTime); + int32 CurrBone = BoneTreeIndex; + while (true) + { + const int32 Parent(RefSkeleton.GetParentIndex(CurrBone)); + if (Parent < 0) break; + else + { + + BoneTMWS = BoneTMWS * GetAnimBoneTM(AnimSeq, Parent, AnimTime); + + CurrBone = Parent; + } + } + return BoneTMWS; +} + +MIRRORANIMATIONSYSTEMDEV_API void UMASFunctionLibrary::CreateMirrorSequenceFromAnimSequence_CS( + UAnimSequence* MirrorSequence, + const TEnumAsByte MirrorAxis, + const FString Substring_A, + const FString Substring_B, + const bool Symmetrical) +{ + const int32 NumFrames = MirrorSequence->GetRawNumberOfFrames(); + const float DT = MirrorSequence->SequenceLength / NumFrames; + + USkeleton* Skeleton = MirrorSequence->GetSkeleton(); + const auto& RefSkeleton = Skeleton->GetReferenceSkeleton(); + + + + TArray Already; Already.SetNumZeroed(Skeleton->GetBoneTree().Num()); + + TArray TwinPairs; + TArray NonTwinIDs; + TArray NonTwinFlipAxis; + FMASUtils::CSMirrorSettings(RefSkeleton, MirrorAxis, Substring_A, Substring_B, TwinPairs, NonTwinIDs, NonTwinFlipAxis); + + const bool DeltaStep = !Symmetrical; + + FVector TwinMirrorScale = FVector(1.f); + FVector TargetAxis = FVector::ZeroVector; + + check(MirrorAxis != EAxis::None); + { + TwinMirrorScale[MirrorAxis - 1] = -1.f; + TargetAxis[MirrorAxis - 1] = 1.f; + } + FTransform TwinMirrorModTM(FQuat::Identity, FVector::ZeroVector, TwinMirrorScale); + + + TMap BoneTracks; + + for (int32 i = 0; i < RefSkeleton.GetNum(); i++) + { + BoneTracks.Add(i, FRawAnimSequenceTrack()); + } + + for (int32 j = 0; j < NumFrames; j++) + { + TArray NewCSTMs; NewCSTMs.SetNum(RefSkeleton.GetNum()); + + for (int32 i = 0; i < NonTwinIDs.Num(); i++) + { + int32 BoneTreeIndex = NonTwinIDs[i]; + int32 BoneTrackIndex = Skeleton->GetRawAnimationTrackIndex(BoneTreeIndex, MirrorSequence); + + if (BoneTrackIndex == INDEX_NONE) + { + const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneTreeIndex); + if (ParentIndex != INDEX_NONE) + { + NewCSTMs[BoneTreeIndex] = RefSkeleton.GetRefBonePose()[BoneTreeIndex] * NewCSTMs[ParentIndex]; + } + else + { + NewCSTMs[BoneTreeIndex] = RefSkeleton.GetRefBonePose()[BoneTreeIndex]; + } + + continue; + } + + FTransform CSTM = GetAnimBoneCSTM(MirrorSequence, BoneTreeIndex, DT * j); + CSTM.Mirror(MirrorAxis, NonTwinFlipAxis[i]); + + NewCSTMs[BoneTreeIndex] = CSTM; + } + + + for (int32 i = 0; i < TwinPairs.Num(); i++) + { + const int32 BoneIndex = TwinPairs[i].X; + const FCompactPoseBoneIndex CmptBoneIndex(BoneIndex); + + const int32 TwinBoneIndex = TwinPairs[i].Y; + + const FCompactPoseBoneIndex TwinCmptBoneIndex(TwinBoneIndex); + + const FTransform RefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, BoneIndex); + const FTransform TwinRefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, TwinBoneIndex); + + const FTransform TM = GetAnimBoneCSTM(MirrorSequence, BoneIndex, DT * j); + //Output.Pose.GetComponentSpaceTransform(CmptBoneIndex); + const FTransform TwinTM = GetAnimBoneCSTM(MirrorSequence, TwinBoneIndex, DT * j); + //Output.Pose.GetComponentSpaceTransform(TwinCmptBoneIndex); + + const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex); + const int32 TwinParentIndex = RefSkeleton.GetParentIndex(TwinBoneIndex); + + const bool SameParent = ParentIndex == TwinParentIndex; + + // twin 1º + { + const FTransform MirrRef = RefTM * TwinMirrorModTM; + const FTransform Delta = TwinRefTM.GetRelativeTransform(MirrRef); + const FQuat DeltaQuat = Delta.GetRotation(); + + FTransform MirrTM = TM * TwinMirrorModTM; + + MirrTM.SetRotation(MirrTM.GetRotation() * DeltaQuat); + MirrTM.SetScale3D(TwinTM.GetScale3D()); + + if (DeltaStep) + { + if (SameParent) + { + FTransform RefBS = RefTM; + RefBS = RefBS * TwinMirrorModTM; + const FVector PosDelta = MirrTM.GetLocation() - RefBS.GetLocation(); + MirrTM.SetLocation(TwinRefTM.GetLocation() + PosDelta); + } + else + { + const FTransform& ParentTwinTM = NewCSTMs[RefSkeleton.GetParentIndex(TwinBoneIndex)]; + const FTransform& IParentTM =// Output.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(ParentIndex)); + GetAnimBoneCSTM(MirrorSequence, ParentIndex, DT * j); + FTransform RefBS = RefSkeleton.GetRefBonePose()[BoneIndex] * IParentTM; + RefBS = RefBS * TwinMirrorModTM; + RefBS.SetRotation(RefBS.GetRotation() * DeltaQuat); + RefBS.SetScale3D(TwinTM.GetScale3D()); + + MirrTM = (MirrTM.GetRelativeTransform(RefBS) * RefSkeleton.GetRefBonePose()[TwinBoneIndex]) * ParentTwinTM; + } + } + + NewCSTMs[TwinBoneIndex] = MirrTM; + } + + // twin 2º + { + FTransform TwinMirrRef = TwinRefTM * TwinMirrorModTM; + const FQuat TwinDeltaQuat = TwinMirrRef.GetRotation().Inverse() * RefTM.GetRotation(); + + FTransform TwinMirrTM = TwinTM * TwinMirrorModTM; + + TwinMirrTM.SetRotation(TwinMirrTM.GetRotation() * TwinDeltaQuat); + TwinMirrTM.SetScale3D(TM.GetScale3D()); + + if (DeltaStep) + { + if (SameParent) + { + FTransform TwinRefBS = TwinRefTM; + TwinRefBS = TwinRefBS * TwinMirrorModTM; + const FVector PosDelta = TwinMirrTM.GetLocation() - TwinRefBS.GetLocation(); + TwinMirrTM.SetLocation(RefTM.GetLocation() + PosDelta); + } + else + { + const FTransform& ParentTM = NewCSTMs[RefSkeleton.GetParentIndex(BoneIndex)]; + const FTransform& IParentTwinTM = //Output.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(TwinParentIndex)); + GetAnimBoneCSTM(MirrorSequence, TwinParentIndex, DT * j); + FTransform TwinRefBS = RefSkeleton.GetRefBonePose()[TwinBoneIndex] * IParentTwinTM; + TwinRefBS = TwinRefBS * TwinMirrorModTM; + TwinRefBS.SetRotation(TwinRefBS.GetRotation() * TwinDeltaQuat); + TwinRefBS.SetScale3D(TM.GetScale3D()); + + TwinMirrTM = (TwinMirrTM.GetRelativeTransform(TwinRefBS) * RefSkeleton.GetRefBonePose()[BoneIndex]) * ParentTM; + } + } + + NewCSTMs[BoneIndex] = TwinMirrTM; + } + } + + + for (int32 i = 0; i < NewCSTMs.Num(); i++) + { + const int32 ParentIndex = RefSkeleton.GetParentIndex(i); + FTransform BSTM; + if (ParentIndex != INDEX_NONE) BSTM = NewCSTMs[i].GetRelativeTransform(NewCSTMs[ParentIndex]); + else BSTM = NewCSTMs[i]; + + auto& BoneTrack = BoneTracks[i]; + BoneTrack.PosKeys.Add(BSTM.GetLocation()); + BoneTrack.RotKeys.Add(BSTM.GetRotation()); + BoneTrack.ScaleKeys.Add(BSTM.GetScale3D()); + } + } + + for (auto Pair : BoneTracks) + { + const FName TrackName = Skeleton->GetReferenceSkeleton().GetBoneName(Pair.Key); + MirrorSequence->AddNewRawTrack(TrackName, &Pair.Value); + } + // Have to also apply to pelvis and spine_01 + MirrorSequence->MarkRawDataAsModified(); + MirrorSequence->OnRawDataChanged(); + MirrorSequence->MarkPackageDirty(); +} + +#endif + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MirrorAnimationSystemDev.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MirrorAnimationSystemDev.cpp new file mode 100644 index 0000000..92f6299 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MirrorAnimationSystemDev.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MirrorAnimationSystemDev.h" + +#define LOCTEXT_NAMESPACE "FMirrorAnimationSystemDevModule" + +void FMirrorAnimationSystemDevModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FMirrorAnimationSystemDevModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FMirrorAnimationSystemDevModule, MirrorAnimationSystemDev) \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MASFunctionLibrary.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MASFunctionLibrary.h new file mode 100644 index 0000000..8306487 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MASFunctionLibrary.h @@ -0,0 +1,30 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" + +#include "MASFunctionLibrary.generated.h" + + +class UAnimSequence; +class UMirrorTable; + + +UCLASS(MinimalAPI, meta = (ScriptName = "MirrorLibrary")) +class UMASFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() +public: + UFUNCTION(BlueprintCallable, Category = "Mirror Animation", meta = (DevelopmentOnly)) + static MIRRORANIMATIONSYSTEMDEV_API void BulkMirrorEditorOnly(const TArray Anims, const UMirrorTable* MirrorTable, TArray & OutNewAnims); + UFUNCTION(BlueprintCallable, Category = "Mirror Animation", meta = (DevelopmentOnly)) + static MIRRORANIMATIONSYSTEMDEV_API void BulkMirror_CS_EditorOnly(const TArray Anims, + const TEnumAsByte MirrorAxis, const FString Substring_A, const FString Substring_B, const bool Symmetrical, TArray & OutNewAnims); +#if WITH_EDITOR + static MIRRORANIMATIONSYSTEMDEV_API void CreateMirrorSequenceFromAnimSequence(UAnimSequence* MirrorSequence, const UMirrorTable* MirrorTable); + static MIRRORANIMATIONSYSTEMDEV_API void CreateMirrorSequenceFromAnimSequence_CS(UAnimSequence* MirrorSequence, + const TEnumAsByte MirrorAxis, const FString Substring_A, const FString Substring_B, const bool Symmetrical); +#endif +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MirrorAnimationSystemDev.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MirrorAnimationSystemDev.h new file mode 100644 index 0000000..2a5fb52 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MirrorAnimationSystemDev.h @@ -0,0 +1,15 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FMirrorAnimationSystemDevModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/MirrorAnimationSystemEditor.Build.cs b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/MirrorAnimationSystemEditor.Build.cs new file mode 100644 index 0000000..137841b --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/MirrorAnimationSystemEditor.Build.cs @@ -0,0 +1,72 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class MirrorAnimationSystemEditor : ModuleRules +{ + public MirrorAnimationSystemEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + // Start + "Engine", + "InputCore", + "UnrealEd", // for FAssetEditorManager + "KismetWidgets", + "Kismet", // for FWorkflowCentricApplication + "PropertyEditor", + "RenderCore", + "ContentBrowser", + "WorkspaceMenuStructure", + "EditorStyle", + "MeshPaint", + "EditorWidgets", + "Projects", + "MirrorAnimationSystem", + "MirrorAnimationSystemDev", + "AnimGraph", + "BlueprintGraph", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + "AssetTools", + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_Mirror.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_Mirror.cpp new file mode 100644 index 0000000..a2690af --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_Mirror.cpp @@ -0,0 +1,34 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "AnimGraphNode_Mirror.h" +#include "MirrorAnimationSystemEditor.h" + + +#define LOCTEXT_NAMESPACE "A3Nodes" + +UAnimGraphNode_Mirror::UAnimGraphNode_Mirror(const FObjectInitializer& ObjectInitializer) + :Super(ObjectInitializer) +{ + +} + +FLinearColor UAnimGraphNode_Mirror::GetNodeTitleColor() const +{ + return FLinearColor::Red; +} + +FText UAnimGraphNode_Mirror::GetTooltipText() const +{ + return LOCTEXT("Mirrors_the_designated_bones", "Mirrors the pose based on the designated Mirror Table"); +} + +FText UAnimGraphNode_Mirror::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + return LOCTEXT("Mirror_Pose", "Mirror Pose"); +} + +FString UAnimGraphNode_Mirror::GetNodeCategory() const +{ + return TEXT("Tools"); +} + +#undef LOCTEXT_NAMESPACE diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_MirrorCS.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_MirrorCS.cpp new file mode 100644 index 0000000..1457082 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_MirrorCS.cpp @@ -0,0 +1,53 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "AnimGraphNode_MirrorCS.h" +#include "MirrorAnimationSystemEditor.h" + + +#define LOCTEXT_NAMESPACE "A3Nodes" + + +UAnimGraphNode_MirrorCS::UAnimGraphNode_MirrorCS(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +FLinearColor UAnimGraphNode_MirrorCS::GetNodeTitleColor() const +{ + return FLinearColor::Blue; +} + +FText UAnimGraphNode_MirrorCS::GetControllerDescription() const +{ + return LOCTEXT("AnimGraphNode_MirrorCS", "Mirror Pose CS"); +} + +FText UAnimGraphNode_MirrorCS::GetTooltipText() const +{ + return LOCTEXT("AnimGraphNode_MirrorCS_Tooltip", "Mirror the pose in Component Space."); +} + +FText UAnimGraphNode_MirrorCS::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + FText NodeTitle; + if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle) + { + NodeTitle = GetControllerDescription(); + } + else + { + NodeTitle = FText(LOCTEXT("AnimGraphNode_MirrorCS_Title", "Mirror Pose CS")); + } + return NodeTitle; +} + +void UAnimGraphNode_MirrorCS::ValidateAnimNodePostCompile(class FCompilerResultsLog& MessageLog, class UAnimBlueprintGeneratedClass* CompiledClass, int32 CompiledNodeIndex) +{ + Super::ValidateAnimNodePostCompile(MessageLog, CompiledClass, CompiledNodeIndex); +} + +bool UAnimGraphNode_MirrorCS::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const +{ + return Super::IsCompatibleWithGraph(TargetGraph); +} + +#undef LOCTEXT_NAMESPACE diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AssetTypeActions_MirrorTable.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AssetTypeActions_MirrorTable.cpp new file mode 100644 index 0000000..c0c1ebe --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AssetTypeActions_MirrorTable.cpp @@ -0,0 +1,56 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "AssetTypeActions_MirrorTable.h" +#include "MirrorAnimationSystemEditor.h" + +#include "MirrorTable.h" + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + +////////////////////////////////////////////////////////////////////////// +// FFlipbookAssetTypeActions + +FAssetTypeActions_MirrorTable::FAssetTypeActions_MirrorTable(EAssetTypeCategories::Type InAssetCategory) + : MyAssetCategory(InAssetCategory) +{ +} + +FText FAssetTypeActions_MirrorTable::GetName() const +{ + return LOCTEXT("FMirrorTableAssetTypeActionsName", "Mirror Table"); +} + +FColor FAssetTypeActions_MirrorTable::GetTypeColor() const +{ + return FColor::Blue; +} + +UClass* FAssetTypeActions_MirrorTable::GetSupportedClass() const +{ + return UMirrorTable::StaticClass(); +} + +void FAssetTypeActions_MirrorTable::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor) +{ + const EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone; + + for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt) + { + if (UMirrorTable* MirrorTable = Cast(*ObjIt)) + { + FSimpleAssetEditor::CreateEditor(Mode, Mode == EToolkitMode::WorldCentric ? EditWithinLevelEditor : TSharedPtr(), MirrorTable); + /* + TSharedRef NewFlipbookEditor(new FFlipbookEditor()); + NewFlipbookEditor->InitFlipbookEditor(Mode, EditWithinLevelEditor, Flipbook); + */ + } + } +} + +uint32 FAssetTypeActions_MirrorTable::GetCategories() +{ + return EAssetTypeCategories::Animation | MyAssetCategory; +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/ContentBrowserTools.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/ContentBrowserTools.cpp new file mode 100644 index 0000000..dcd4cb7 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/ContentBrowserTools.cpp @@ -0,0 +1,254 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "ContentBrowserTools.h" +#include "MirrorAnimationSystemEditor.h" + + +#include "Animation/AnimSequence.h" +#include "Components/SkeletalMeshComponent.h" +#include "ContentBrowserModule.h" +#include "Framework/MultiBox/MultiBoxExtender.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "EditorStyleSet.h" +#include "IContentBrowserSingleton.h" +#include "MirrorAnimationSystemStyle.h" + + +#include "MirrorAnimAssetDialog.h" +#include "MirrorTableFromSkeletonDialog.h" + + +#include "IAssetTools.h" +#include "AssetToolsModule.h" + +////////////////////////////////////////////////////////////////////////// +// FContentBrowserSelectedAssetExtensionBase + +#define LOCTEXT_NAMESPACE "MirrorAnimationSystem" + +//DECLARE_LOG_CATEGORY_EXTERN(LogMirrorCBExtensions, Log, All); +//DEFINE_LOG_CATEGORY(LogMirrorCBExtensions); + +FContentBrowserMenuExtender_SelectedAssets ContentBrowserExtenderDelegate; +FDelegateHandle ContentBrowserExtenderDelegateHandle; + +//Base struct to be extended and merely hold primordial data +struct FContentBrowserSelectedAssetExtensionBase +{ +public: + TArray SelectedAssets; + +public: + virtual void Execute() {} + virtual ~FContentBrowserSelectedAssetExtensionBase() {} +}; + +//Struct that contains the functionality for opening the Dialog of the "Mirror AnimAsset" tool +struct FMirrorAnimAssetExtension : public FContentBrowserSelectedAssetExtensionBase +{ + FMirrorAnimAssetExtension() + { + } + + void MirrorAnimAssets(TArray& Animations) + { + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + TArray ObjectsToSync; + + for (auto AnimationIt = Animations.CreateConstIterator(); AnimationIt; ++AnimationIt) + { + UAnimSequence* Animation = *AnimationIt; + + SMirrorAnimAssetDialog::ShowWindow(Animation); + + } + + if (ObjectsToSync.Num() > 0) + { + ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync); + } + } + + virtual void Execute() override + { + // Mirror Animations from selected animassets + TArray Animations; + for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) + { + const FAssetData& AssetData = *AssetIt; + if (UAnimSequence* Animation = Cast(AssetData.GetAsset())) + { + Animations.Add(Animation); + } + } + + MirrorAnimAssets(Animations); + } +}; + +////////////////////////////////////////////////////////////////////////// + +//Struct that contains the functionality for opening the Dialog of the "Mirror Table from Skeleton" tool +struct FCreateMirrorTableExtension : public FContentBrowserSelectedAssetExtensionBase +{ + FCreateMirrorTableExtension() + { + } + + void CreateMirrorTable(TArray& Skeletons) + { + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + TArray ObjectsToSync; + + for (auto SkeletonIt = Skeletons.CreateConstIterator(); SkeletonIt; ++SkeletonIt) + { + USkeleton* Skeleton = *SkeletonIt; + + SMirrorTableFromSkeletonDialog::ShowWindow(Skeleton); + + } + + if (ObjectsToSync.Num() > 0) + { + ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync); + } + } + + virtual void Execute() override + { + // Create Mirror Table + TArray Skeletons; + for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) + { + const FAssetData& AssetData = *AssetIt; + if (USkeleton* Skeleton = Cast(AssetData.GetAsset())) + { + Skeletons.Add(Skeleton); + } + } + CreateMirrorTable(Skeletons); + } +} + + +////////////////////////////////////////////////////////////////////////// +// FContentBrowserToolExtensions_Impl +; +/*class that identifies and constructs the tool's buttons adding the functionality for opening +the corresponding dialog when clicked*/ +class FContentBrowserToolExtensions_Impl +{ +public: + static void ExecuteSelectedContentFunctor(TSharedPtr SelectedAssetFunctor) + { + SelectedAssetFunctor->Execute(); + } + + static void CreateMirrorButton(FMenuBuilder& MenuBuilder, TArray SelectedAssets) + { + TSharedPtr AnimationMirrorFunctor = MakeShareable(new FMirrorAnimAssetExtension()); + AnimationMirrorFunctor->SelectedAssets = SelectedAssets; + + FUIAction Action_MirrorAnimAsset( + FExecuteAction::CreateStatic(&FContentBrowserToolExtensions_Impl::ExecuteSelectedContentFunctor, StaticCastSharedPtr(AnimationMirrorFunctor))); + + const FName MyToolEditorStyleSetName = FMirrorAnimationSystemStyle::GetStyleSetName(); + + MenuBuilder.AddMenuEntry( + LOCTEXT("Mirror_Animation_Asset", "Mirror Animation Asset"), + LOCTEXT("Mirror_This_Animation_Based_On_Mirror_Table", "Mirror this Animation asset based on a Mirror Table"), + FSlateIcon(MyToolEditorStyleSetName, "Icon.MirrorAnimAsset"), + Action_MirrorAnimAsset, + NAME_None, + EUserInterfaceActionType::Button); + } + + static void CreateMirrorTableButton(FMenuBuilder& MenuBuilder, TArray SelectedAssets) + { + TSharedPtr CreateMirrorTableFunctor = MakeShareable(new FCreateMirrorTableExtension()); + CreateMirrorTableFunctor->SelectedAssets = SelectedAssets; + + FUIAction Action_CreateMirrorTable( + FExecuteAction::CreateStatic(&FContentBrowserToolExtensions_Impl::ExecuteSelectedContentFunctor, StaticCastSharedPtr(CreateMirrorTableFunctor))); + + const FName MyToolEditorStyleSetName = FMirrorAnimationSystemStyle::GetStyleSetName(); + + MenuBuilder.AddMenuEntry( + LOCTEXT("Create_Mirror_Table", "Mirror Table From Skeleton"), + LOCTEXT("Create_Mirror_Table_From_Skeleton", "Creates a Mirror Table template based on this Skeleton's Hierarchy"), + FSlateIcon(MyToolEditorStyleSetName, "Icon.CreateMirrorTable"), + Action_CreateMirrorTable, + NAME_None, + EUserInterfaceActionType::Button); + } + + static TSharedRef OnExtendContentBrowserAssetSelectionMenu(const TArray& SelectedAssets) + { + TSharedRef Extender(new FExtender()); + + // Run thru the assets to determine if any meet our criteria + bool bShowButton = false; + bool bIsSkeletonAsset = false; + for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) + { + const FAssetData& Asset = *AssetIt; + bShowButton = bShowButton || (Asset.AssetClass == UAnimSequence::StaticClass()->GetFName()) || (Asset.AssetClass == USkeleton::StaticClass()->GetFName()); + bIsSkeletonAsset = (Asset.AssetClass == USkeleton::StaticClass()->GetFName()); + } + + if (bShowButton) + { + if (bIsSkeletonAsset) + { + // Add the Mirror AnimAsset sub-menu extender + Extender->AddMenuExtension( + "GetAssetActions", + EExtensionHook::After, + nullptr, + FMenuExtensionDelegate::CreateStatic(&FContentBrowserToolExtensions_Impl::CreateMirrorTableButton, SelectedAssets)); + } + else + { + // Add the Mirror AnimAsset sub-menu extender + Extender->AddMenuExtension( + "GetAssetActions", + EExtensionHook::After, + nullptr, + FMenuExtensionDelegate::CreateStatic(&FContentBrowserToolExtensions_Impl::CreateMirrorButton, SelectedAssets)); + } + } + + return Extender; + } + + static TArray& GetExtenderDelegates() + { + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked(TEXT("ContentBrowser")); + return ContentBrowserModule.GetAllAssetViewContextMenuExtenders(); + } +}; + +////////////////////////////////////////////////////////////////////////// + + +void FContentBrowserTools::InstallHooks() +{ + ContentBrowserExtenderDelegate = FContentBrowserMenuExtender_SelectedAssets::CreateStatic(&FContentBrowserToolExtensions_Impl::OnExtendContentBrowserAssetSelectionMenu); + + TArray& CBMenuExtenderDelegates = FContentBrowserToolExtensions_Impl::GetExtenderDelegates(); + CBMenuExtenderDelegates.Add(ContentBrowserExtenderDelegate); + ContentBrowserExtenderDelegateHandle = CBMenuExtenderDelegates.Last().GetHandle(); +} + +void FContentBrowserTools::RemoveHooks() +{ + TArray& CBMenuExtenderDelegates = FContentBrowserToolExtensions_Impl::GetExtenderDelegates(); + CBMenuExtenderDelegates.RemoveAll([](const FContentBrowserMenuExtender_SelectedAssets& Delegate) { return Delegate.GetHandle() == ContentBrowserExtenderDelegateHandle; }); +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetDialog.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetDialog.cpp new file mode 100644 index 0000000..ba127b3 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetDialog.cpp @@ -0,0 +1,230 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "MirrorAnimAssetDialog.h" +#include "MirrorAnimationSystemEditor.h" + + +#include "Widgets/SBoxPanel.h" +#include "Widgets/SWindow.h" +#include "Widgets/SViewport.h" +#include "Misc/FeedbackContext.h" +#include "Misc/ScopedSlowTask.h" +#include "Misc/MessageDialog.h" +#include "Modules/ModuleManager.h" +#include "Misc/PackageName.h" +#include "Layout/WidgetPath.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/Input/SButton.h" +#include "Framework/Docking/TabManager.h" +#include "EditorStyleSet.h" +#include "CanvasItem.h" + +#include "PropertyEditorModule.h" +#include "IContentBrowserSingleton.h" +#include "ContentBrowserModule.h" +#include "IAssetTools.h" +#include "AssetToolsModule.h" +#include "CanvasTypes.h" + +#include "Factories/AnimSequenceFactory.h" + +#include "MASFunctionLibrary.h" + +#define LOCTEXT_NAMESPACE "MirrorAnimationSystemEditor" + +void SMirrorAnimAssetDialog::Construct(const FArguments & InArgs, UAnimSequence * AnimSequence) +{ + SourceAnimSequence = AnimSequence; + + MirrorAnimAssetSettings = NewObject(); + MirrorAnimAssetSettings->AddToRoot(); + + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + FDetailsViewArgs DetailsViewArgs(/*bUpdateFromSelection=*/ false, /*bLockable=*/ false, /*bAllowSearch=*/ false, /*InNameAreaSettings=*/ FDetailsViewArgs::HideNameArea, /*bHideSelectionTip=*/ true); + MainPropertyView = EditModule.CreateDetailView(DetailsViewArgs); + MainPropertyView->SetObject(MirrorAnimAssetSettings); + + + + + ChildSlot + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("DetailsView.CategoryTop")) + .Padding(FMargin(1.0f, 1.0f, 1.0f, 0.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(1.0f) + .AutoHeight() + [ + MainPropertyView.ToSharedRef() + ] + + SVerticalBox::Slot() + .Padding(1.0f) + .HAlign(HAlign_Right) + .VAlign(VAlign_Bottom) + [ + SNew(SUniformGridPanel) + .SlotPadding(1) + + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "FlatButton.Success") + .ForegroundColor(FLinearColor::White) + .Text(LOCTEXT("PaperExtractSpritesExtractButton", "Mirror")) + .OnClicked(this, &SMirrorAnimAssetDialog::MirrorClicked) + ] + + SUniformGridPanel::Slot(1, 0) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "FlatButton") + .ForegroundColor(FLinearColor::White) + .Text(LOCTEXT("PaperExtractSpritesCancelButton", "Cancel")) + .OnClicked(this, &SMirrorAnimAssetDialog::CancelClicked) + ] + ] + ] + ] + ]; +} + +SMirrorAnimAssetDialog::~SMirrorAnimAssetDialog() +{ +} + +bool SMirrorAnimAssetDialog::ShowWindow(UAnimSequence * SourceAnimSequence) +{ + + const FText TitleText = NSLOCTEXT("MirrorAnimationSystem", "MirrorAnimationSystem_MirrorAnimSequence", "Mirror AnimSequence"); + // Create the window to pick the class + TSharedRef MirrorAnimSequenceWindow = SNew(SWindow) + .Title(TitleText) + .SizingRule(ESizingRule::UserSized) + .ClientSize(FVector2D(720.f, 720.f)) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .SupportsMinimize(false); + + TSharedRef MirrorAnimSequenceDialog = SNew(SMirrorAnimAssetDialog, SourceAnimSequence); + + MirrorAnimSequenceWindow->SetContent(MirrorAnimSequenceDialog); + TSharedPtr RootWindow = FGlobalTabmanager::Get()->GetRootWindow(); + if (RootWindow.IsValid()) + { + FSlateApplication::Get().AddWindowAsNativeChild(MirrorAnimSequenceWindow, RootWindow.ToSharedRef()); + } + else + { + FSlateApplication::Get().AddWindow(MirrorAnimSequenceWindow); + } + + return false; +} + +bool SMirrorAnimAssetDialog::ValidateCSMirrorData() +{ + if (MirrorAnimAssetSettings->MirrorTable) + { + return true; + } + + if (MirrorAnimAssetSettings->MirrorAxis == EAxis::None) + { + FText DialogText = FText::AsCultureInvariant(FString(TEXT("Mirror Axis Cannot be None"))); + FMessageDialog::Open(EAppMsgType::Ok, DialogText); + return false; + } + + if ((MirrorAnimAssetSettings->Substring_A == "None") + || + (MirrorAnimAssetSettings->Substring_B == "None")) + { + FText DialogText = FText::AsCultureInvariant(FString(TEXT("Invalid Substring value"))); + FMessageDialog::Open(EAppMsgType::Ok, DialogText); + return false; + } + + return true; +} + +FReply SMirrorAnimAssetDialog::MirrorClicked() +{ + if (ValidateCSMirrorData()) + { + CreateMirroredAnimSequences(); + + CloseContainingWindow(); + } + + return FReply::Handled(); +} + +FReply SMirrorAnimAssetDialog::CancelClicked() +{ + CloseContainingWindow(); + return FReply::Handled(); +} + +void SMirrorAnimAssetDialog::CloseContainingWindow() +{ + TSharedPtr ContainingWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + if (ContainingWindow.IsValid()) + { + ContainingWindow->RequestDestroyWindow(); + } +} + +void SMirrorAnimAssetDialog::CreateMirroredAnimSequences() +{ + + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + TArray ObjectsToSync; + + // Create the asset + FString Name; + FString PackageName; + + + FString Suffix = TEXT("_Mirrored"); + + AssetToolsModule.Get().CreateUniqueAssetName(SourceAnimSequence->GetOutermost()->GetName(), Suffix, /*out*/ PackageName, /*out*/ Name); + const FString PackagePath = FPackageName::GetLongPackagePath(PackageName); + + + UObject* NewAsset = AssetToolsModule.Get().DuplicateAsset(Name, PackagePath, SourceAnimSequence); + + if (NewAsset != NULL) + { + + UAnimSequence* MirrorAnimSequence = Cast(NewAsset); + + if (MirrorAnimAssetSettings->MirrorTable != NULL) + { + UMASFunctionLibrary::CreateMirrorSequenceFromAnimSequence(MirrorAnimSequence, MirrorAnimAssetSettings->MirrorTable); + } + else + { + UMASFunctionLibrary::CreateMirrorSequenceFromAnimSequence_CS(MirrorAnimSequence, + MirrorAnimAssetSettings->MirrorAxis, + MirrorAnimAssetSettings->Substring_A, + MirrorAnimAssetSettings->Substring_B, + MirrorAnimAssetSettings->CompletlySymmetrical); + } + + ObjectsToSync.Add(NewAsset); + } + if (ObjectsToSync.Num() > 0) + { + ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetSettings.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetSettings.cpp new file mode 100644 index 0000000..b15432e --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetSettings.cpp @@ -0,0 +1,9 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "MirrorAnimAssetSettings.h" +#include "MirrorAnimationSystemEditor.h" + + +UMirrorAnimAssetSettings::UMirrorAnimAssetSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemEditor.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemEditor.cpp new file mode 100644 index 0000000..3ab8286 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemEditor.cpp @@ -0,0 +1,106 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MirrorAnimationSystemEditor.h" + +#include "SlateBasics.h" +#include "SlateExtras.h" + +#include "LevelEditor.h" +#include "ContentBrowserTools.h" +#include "MirrorAnimationSystemStyle.h" + +#include "AssetToolsModule.h" +#include "MirrorTableDetails.h" +#include "AssetTypeActions_MirrorTable.h" + + +static const FName MirrorAnimationSystemEditorTabName("MirrorAnimationSystemEditor"); + + +#define LOCTEXT_NAMESPACE "FMirrorAnimationSystemEditorModule" + +void FMirrorAnimationSystemEditorModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + FMirrorAnimationSystemStyle::Initialize(); + + + if (!IsRunningCommandlet()) + { + FContentBrowserTools::InstallHooks(); + } + + + + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); + RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_MirrorTable(MirrorTableAssetCategoryBit))); + + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + RegisterCustomClassLayout("MirrorTable", FOnGetDetailCustomizationInstance::CreateStatic(&FMirrorTableDetails::MakeInstance)); + PropertyModule.NotifyCustomizationModuleChanged(); + +} + +void FMirrorAnimationSystemEditorModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + + if (!IsRunningCommandlet()) + { + FContentBrowserTools::RemoveHooks(); + } + FMirrorAnimationSystemStyle::Shutdown(); + + + + + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + IAssetTools& AssetTools = FModuleManager::GetModuleChecked("AssetTools").Get(); + for (int32 Index = 0; Index < CreatedAssetTypeActions.Num(); ++Index) + { + AssetTools.UnregisterAssetTypeActions(CreatedAssetTypeActions[Index].ToSharedRef()); + } + } + CreatedAssetTypeActions.Empty(); + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + + // Unregister all classes customized by name + for (auto It = RegisteredClassNames.CreateConstIterator(); It; ++It) + { + if (It->IsValid()) + { + PropertyModule.UnregisterCustomClassLayout(*It); + } + } + PropertyModule.NotifyCustomizationModuleChanged(); + } + +} + + +void FMirrorAnimationSystemEditorModule::RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef Action) +{ + AssetTools.RegisterAssetTypeActions(Action); + CreatedAssetTypeActions.Add(Action); +} + +void FMirrorAnimationSystemEditorModule::RegisterCustomClassLayout(FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate) +{ + check(ClassName != NAME_None); + + RegisteredClassNames.Add(ClassName); + + static FName PropertyEditor("PropertyEditor"); + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked(PropertyEditor); + PropertyModule.RegisterCustomClassLayout(ClassName, DetailLayoutDelegate); +} + + + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FMirrorAnimationSystemEditorModule, MirrorAnimationSystemEditor) \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemStyle.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemStyle.cpp new file mode 100644 index 0000000..df7550e --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemStyle.cpp @@ -0,0 +1,64 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. +#include "MirrorAnimationSystemStyle.h" +#include "MirrorAnimationSystemEditor.h" + +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyleRegistry.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" + +TSharedPtr< FSlateStyleSet > FMirrorAnimationSystemStyle::StyleInstance = NULL; + +void FMirrorAnimationSystemStyle::Initialize() +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} + +void FMirrorAnimationSystemStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +FName FMirrorAnimationSystemStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("MirrorAnimationSystemStyle")); + return StyleSetName; +} + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + + + +TSharedRef< FSlateStyleSet > FMirrorAnimationSystemStyle::Create() +{ + const FVector2D Icon16x16(16.0f, 16.0f); + const FVector2D Icon20x20(20.0f, 20.0f); + const FVector2D Icon40x40(40.0f, 40.0f); + const FVector2D Icon64x64(64.0f, 64.0f); + + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("MirrorAnimationSystemStyle")); + Style->SetContentRoot(IPluginManager::Get().FindPlugin("MirrorAnimationSystem")->GetBaseDir() / TEXT("Resources")); + + Style->Set("Icon.MirrorAnimAsset", new IMAGE_BRUSH(TEXT("MirrorAnimAsset_Icon"), Icon16x16)); + Style->Set("Icon.CreateMirrorTable", new IMAGE_BRUSH(TEXT("CreateMirrorTable_Icon"), Icon16x16)); + Style->Set("ClassIcon.MirrorTable", new IMAGE_BRUSH(TEXT("MirrorTable_ClassIcon"), Icon20x20)); + Style->Set("ClassThumbnail.MirrorTable", new IMAGE_BRUSH(TEXT("MirrorTable_ClassIcon"), Icon64x64)); + + return Style; +} + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef OTF_FONT diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableDetails.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableDetails.cpp new file mode 100644 index 0000000..aa8d20b --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableDetails.cpp @@ -0,0 +1,239 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "MirrorTableDetails.h" +#include "MirrorAnimationSystemEditor.h" + + +#include "Widgets/SWidget.h" +#include "Layout/Margin.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SBoxPanel.h" +#include "EditorStyleSet.h" +#include "IDetailChildrenBuilder.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SButton.h" + +#include "Misc/MessageDialog.h" + +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "IDetailsView.h" +#include "PropertyCustomizationHelpers.h" + +#include "MirrorTable.h" +//#include "DialogueWaveWidgets.h" + +#define LOCTEXT_NAMESPACE "MirrorTableDetails" + +FMirrorTableNodeBuilder::FMirrorTableNodeBuilder(IDetailLayoutBuilder* InDetailLayoutBuilder, const TSharedPtr& InPropertyHandle) + : DetailLayoutBuilder(InDetailLayoutBuilder) + , ContextMappingPropertyHandle(InPropertyHandle) + , BoneNameHandle(ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, BoneName))) + , TwinBoneNameHandle(ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, TwinBoneName))) + , IsTwinBoneHandle(ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, IsTwinBone))) +{ + //check(BoneNameHandle.IsValid()); + //check(TwinBoneNameHandle.IsValid()); +// check(IsTwinBoneHandle.IsValid()); +} + +void FMirrorTableNodeBuilder::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) +{ + if (ContextMappingPropertyHandle->IsValidHandle()) + { + const TSharedPtr ContextPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, MirrorAxis)); + if (ContextPropertyHandle->IsValidHandle())//if (BoneNameHandle->IsValidHandle()) + { + const TSharedPtr FlipAxisPropertyHandle = ContextPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, FlipAxis)); + const TSharedPtr RotationOffsetPropertyHandle = ContextPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, RotationOffset)); + const TSharedPtr BoneNamePropertyHandle = ContextPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, BoneName)); + const TSharedPtr ParentHandle = ContextMappingPropertyHandle->GetParentHandle(); + const TSharedPtr ParentArrayHandle = ParentHandle->AsArray(); + + + uint32 ContextCount; + ParentArrayHandle->GetNumElements(ContextCount); + + TSharedRef ClearButton = PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateSP(this, &FMirrorTableNodeBuilder::RemoveContextButton_OnClick), + ContextCount > 1 ? LOCTEXT("RemoveBoneToolTip", "Remove Bone.") : LOCTEXT("RemoveBoneDisabledToolTip", "Cannot remove Bone - a mirror table must have at least one bone."), + ContextCount > 1); + + NodeRow + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("DialogueWaveDetails.HeaderBorder")) + [ + //SNew(SDialogueContextHeaderWidget, ContextPropertyHandle.ToSharedRef(), DetailLayoutBuilder->GetThumbnailPool().ToSharedRef()) + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(2.0f) + .HAlign(HAlign_Center) + .AutoHeight() + [ + // Voice Description + SNew(STextBlock) + .Text(this, &FMirrorTableNodeBuilder::GetText) + ] + ] + ] + + SHorizontalBox::Slot() + .Padding(2.0f) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .AutoWidth() + [ + ClearButton + ] + ]; + } + } +} + +void FMirrorTableNodeBuilder::Tick(float DeltaTime) +{ + +} + +void FMirrorTableNodeBuilder::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) +{ + if (ContextMappingPropertyHandle->IsValidHandle()) + { + ChildrenBuilder.AddProperty(BoneNameHandle.ToSharedRef()); + + const TSharedPtr MirrorAxisPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, MirrorAxis)); + ChildrenBuilder.AddProperty(MirrorAxisPropertyHandle.ToSharedRef()); + + const TSharedPtr FlipAxisPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, FlipAxis)); + ChildrenBuilder.AddProperty(FlipAxisPropertyHandle.ToSharedRef()); + + const TSharedPtr RotationOffsetPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, RotationOffset)); + ChildrenBuilder.AddProperty(RotationOffsetPropertyHandle.ToSharedRef()); + + ChildrenBuilder.AddProperty(IsTwinBoneHandle.ToSharedRef()); + + ChildrenBuilder.AddProperty(TwinBoneNameHandle.ToSharedRef()); + + const TSharedPtr MirrorTranslationPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, MirrorTranslation)); + ChildrenBuilder.AddProperty(MirrorTranslationPropertyHandle.ToSharedRef()); + + + } +} + +FText FMirrorTableNodeBuilder::GetText() const +{ + FString ValueStr; + FText RowText = FText::FromString(TEXT("Basic")); + if (BoneNameHandle->GetValue(ValueStr) == FPropertyAccess::Success) + { + bool ValueBl; + RowText = FText::FromString(TEXT("BoneNameHandle")); + if (IsTwinBoneHandle->GetValue(ValueBl) == FPropertyAccess::Success) + { + RowText = FText::FromString(TEXT("IsTwinBoneHandle")); + if (ValueBl) + { + RowText = FText::FromString(TEXT("ValueBl")); + FString AddedStr; + if (TwinBoneNameHandle->GetValue(AddedStr) == FPropertyAccess::Success) + { + ValueStr += (" / " + AddedStr); + } + } + } + RowText = FText::FromString(MoveTemp(ValueStr)); + } + return RowText; +} + +void FMirrorTableNodeBuilder::RemoveContextButton_OnClick() +{ + if (ContextMappingPropertyHandle->IsValidHandle()) + { + const TSharedPtr ParentHandle = ContextMappingPropertyHandle->GetParentHandle(); + const TSharedPtr ParentArrayHandle = ParentHandle->AsArray(); + + uint32 ContextCount; + ParentArrayHandle->GetNumElements(ContextCount); + if (ContextCount != 1) // Mustn't remove the only context. + { + ParentArrayHandle->DeleteItem(ContextMappingPropertyHandle->GetIndexInArray()); + } + } +} + +TSharedRef FMirrorTableDetails::MakeInstance() +{ + return MakeShareable(new FMirrorTableDetails); +} + +void FMirrorTableDetails::CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder) +{ + DetailLayoutBuilder = &DetailBuilder; + + IDetailCategoryBuilder& MirrorBonesDetailCategoryBuilder = DetailBuilder.EditCategory("MirrorBones", FText::GetEmpty(), ECategoryPriority::Important); + + // Add Context Button + MirrorBonesDetailCategoryBuilder.AddCustomRow(LOCTEXT("AddBoneToMirrorTable", "Add Bone to Mirror Table")) + [ + SNew(SBox) + .Padding(2.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SButton) + .Text(LOCTEXT("AddBoneToMirrorTable", "Add Bone to Mirror Table")) + .ToolTipText(LOCTEXT("AddMirrorBoneToolTip", "Adds a new Bone to the Mirror table.")) + .OnClicked(FOnClicked::CreateSP(this, &FMirrorTableDetails::AddMirrorTableMapping_OnClicked)) + ] + ]; + + // Individual Context Mappings + const TSharedPtr MirrorBonesPropertyHandle = DetailLayoutBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(UMirrorTable, MirrorBones), UMirrorTable::StaticClass()); + MirrorBonesPropertyHandle->MarkHiddenByCustomization(); + + const TSharedPtr MirrorBonesPropertyArrayHandle = MirrorBonesPropertyHandle->AsArray(); + + uint32 DialogueContextMappingCount; + MirrorBonesPropertyArrayHandle->GetNumElements(DialogueContextMappingCount); + + FSimpleDelegate RebuildChildrenDelegate = FSimpleDelegate::CreateRaw(this, &FMirrorTableDetails::RebuildChildren); + //MirrorBonesPropertyArrayHandle->SetSetOnPropertyValueChanged(RebuildChildrenDelegate); + MirrorBonesPropertyArrayHandle->SetOnNumElementsChanged(RebuildChildrenDelegate); + + for (uint32 j = 0; j < DialogueContextMappingCount; ++j) + { + const TSharedPtr ChildContextMappingPropertyHandle = MirrorBonesPropertyArrayHandle->GetElement(j); + + const TSharedRef DialogueContextMapping = MakeShareable(new FMirrorTableNodeBuilder(DetailLayoutBuilder, ChildContextMappingPropertyHandle)); + MirrorBonesDetailCategoryBuilder.AddCustomBuilder(DialogueContextMapping); + } +} + +void FMirrorTableDetails::RebuildChildren() +{ + const TSharedPtr MirrorBonesPropertyHandle = DetailLayoutBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(UMirrorTable, MirrorBones), UMirrorTable::StaticClass()); + const TSharedPtr MirrorBonesPropertyArrayHandle = MirrorBonesPropertyHandle->AsArray(); + + DetailLayoutBuilder->ForceRefreshDetails(); +} + + + +FReply FMirrorTableDetails::AddMirrorTableMapping_OnClicked() +{ + const TSharedPtr MirrorBonesPropertyHandle = DetailLayoutBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(UMirrorTable, MirrorBones), UMirrorTable::StaticClass()); + const TSharedPtr MirrorBonesPropertyArrayHandle = MirrorBonesPropertyHandle->AsArray(); + MirrorBonesPropertyArrayHandle->AddItem(); + + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonDialog.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonDialog.cpp new file mode 100644 index 0000000..9ebcec4 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonDialog.cpp @@ -0,0 +1,288 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "MirrorTableFromSkeletonDialog.h" +#include "MirrorAnimationSystemEditor.h" + + + +#include "Widgets/SBoxPanel.h" +#include "Widgets/SWindow.h" +#include "Widgets/SViewport.h" +#include "Misc/FeedbackContext.h" +#include "Misc/ScopedSlowTask.h" +#include "Misc/MessageDialog.h" +#include "Modules/ModuleManager.h" +#include "Misc/PackageName.h" +#include "Layout/WidgetPath.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/Input/SButton.h" +#include "Framework/Docking/TabManager.h" +#include "EditorStyleSet.h" +#include "CanvasItem.h" +#include "AssetRegistryModule.h" +#include "PropertyEditorModule.h" +#include "IContentBrowserSingleton.h" +#include "ContentBrowserModule.h" +#include "IAssetTools.h" +#include "AssetToolsModule.h" +#include "CanvasTypes.h" + +#include "Factories/DataAssetFactory.h" + +#define LOCTEXT_NAMESPACE "MirrorAnimationSystemlEditor" + +void SMirrorTableFromSkeletonDialog::Construct(const FArguments & InArgs, USkeleton * Skeleton) +{ + SourceSkeleton = Skeleton; + + MirrorTableSettings = NewObject(); + + MirrorTableSettings->AddToRoot(); + + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + FDetailsViewArgs DetailsViewArgs(/*bUpdateFromSelection=*/ false, /*bLockable=*/ false, /*bAllowSearch=*/ false, /*InNameAreaSettings=*/ FDetailsViewArgs::HideNameArea, /*bHideSelectionTip=*/ true); + MainPropertyView = EditModule.CreateDetailView(DetailsViewArgs); + MainPropertyView->SetObject(MirrorTableSettings); + + + + + ChildSlot + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("DetailsView.CategoryTop")) + .Padding(FMargin(1.0f, 1.0f, 1.0f, 0.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(1.0f) + .AutoHeight() + [ + MainPropertyView.ToSharedRef() + ] + + SVerticalBox::Slot() + .Padding(1.0f) + .HAlign(HAlign_Right) + .VAlign(VAlign_Bottom) + [ + SNew(SUniformGridPanel) + .SlotPadding(1) + + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "FlatButton.Success") + .ForegroundColor(FLinearColor::White) + .Text(LOCTEXT("PaperExtractSpritesExtractButton", "Create")) + .OnClicked(this, &SMirrorTableFromSkeletonDialog::CreateClicked) + ] + + SUniformGridPanel::Slot(1, 0) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "FlatButton") + .ForegroundColor(FLinearColor::White) + .Text(LOCTEXT("PaperExtractSpritesCancelButton", "Cancel")) + .OnClicked(this, &SMirrorTableFromSkeletonDialog::CancelClicked) + ] + ] + ] + ] + ]; +} + +SMirrorTableFromSkeletonDialog::~SMirrorTableFromSkeletonDialog() +{ +} + +bool SMirrorTableFromSkeletonDialog::ShowWindow(USkeleton * SourceSkeleton) +{ + + const FText TitleText = NSLOCTEXT("MirrorAnimationSystem", "Create_Mirror_Table_From_Skeleton", "Create Mirror Table From Skeleton"); + // Create the window to pick the class + TSharedRef MirrorAnimSequenceWindow = SNew(SWindow) + .Title(TitleText) + .SizingRule(ESizingRule::UserSized) + .ClientSize(FVector2D(550.f, 375.f)) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .SupportsMinimize(false); + + TSharedRef MirrorAnimSequenceDialog = SNew(SMirrorTableFromSkeletonDialog, SourceSkeleton); + + MirrorAnimSequenceWindow->SetContent(MirrorAnimSequenceDialog); + TSharedPtr RootWindow = FGlobalTabmanager::Get()->GetRootWindow(); + if (RootWindow.IsValid()) + { + FSlateApplication::Get().AddWindowAsNativeChild(MirrorAnimSequenceWindow, RootWindow.ToSharedRef()); + } + else + { + FSlateApplication::Get().AddWindow(MirrorAnimSequenceWindow); + } + + return false; +} + +FReply SMirrorTableFromSkeletonDialog::CreateClicked() +{ + if ( + (MirrorTableSettings->Substring_A != "none") + && (!MirrorTableSettings->Substring_A.IsEmpty()) + && (MirrorTableSettings->Substring_B != "none") + && (!MirrorTableSettings->Substring_B.IsEmpty()) + ) + { + CreateMirrorTableFromSkeletonFunction(); + + CloseContainingWindow(); + } + else + { + FText DialogText = FText::AsCultureInvariant(FString(TEXT("Enter valid Substrings for correctly identifying twin bones"))); + FMessageDialog::Open(EAppMsgType::Ok, DialogText); + } + + return FReply::Handled(); +} + +FReply SMirrorTableFromSkeletonDialog::CancelClicked() +{ + CloseContainingWindow(); + return FReply::Handled(); +} + +void SMirrorTableFromSkeletonDialog::CloseContainingWindow() +{ + TSharedPtr ContainingWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + if (ContainingWindow.IsValid()) + { + ContainingWindow->RequestDestroyWindow(); + } +} + +void SMirrorTableFromSkeletonDialog::CreateMirrorTableFromSkeletonFunction() +{ + + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + TArray ObjectsToSync; + + UDataAssetFactory* DataFactory = NewObject(); + + FString Name; + FString PackageName; + + + FString Suffix = TEXT("_MirrorTable"); + + ////////// + + + AssetToolsModule.Get().CreateUniqueAssetName(SourceSkeleton->GetOutermost()->GetName(), Suffix, /*out*/ PackageName, /*out*/ Name); + const FString PackagePath = FPackageName::GetLongPackagePath(PackageName); + + if (UObject* NewAsset = AssetToolsModule.Get().CreateAsset(Name, PackagePath, UMirrorTable::StaticClass(), DataFactory)) + { + UMirrorTable* MirrorTable = Cast(NewAsset); + SetMirrorTableFromSettings(MirrorTable); + + ObjectsToSync.Add(NewAsset); + + } + if (ObjectsToSync.Num() > 0) + { + ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync); + } +} + +void SMirrorTableFromSkeletonDialog::SetMirrorTableFromSettings(UMirrorTable * MirrorTable) +{ + if (MirrorTable != NULL) + { + int NumBones = SourceSkeleton->GetReferenceSkeleton().GetNum(); + TArray CreatedBoneStrings; + for (int i = 0; i < NumBones; i++) + { + FString CurrentBoneString = SourceSkeleton->GetReferenceSkeleton().GetBoneName(i).ToString(); + + int ResultA = CurrentBoneString.Find(MirrorTableSettings->Substring_A, ESearchCase::IgnoreCase, ESearchDir::FromEnd, -1); + int ResultB = CurrentBoneString.Find(MirrorTableSettings->Substring_B, ESearchCase::IgnoreCase, ESearchDir::FromEnd, -1); + + if ((ResultA != -1) || (ResultB != -1)) + { + FString TwinBoneString; + bool ValidSymmetry = true; + if (ResultB != -1) + { + + TwinBoneString = CurrentBoneString.Mid(0, ResultB); + TwinBoneString += MirrorTableSettings->Substring_A; + TwinBoneString += CurrentBoneString.Mid(ResultB + MirrorTableSettings->Substring_B.Len()); + + if ((!(abs(TwinBoneString.Len() - MirrorTableSettings->Substring_A.Len()) == abs(CurrentBoneString.Len() - MirrorTableSettings->Substring_B.Len()))) && ValidSymmetry) + { + ValidSymmetry = false; + } + } + else + { + + TwinBoneString = CurrentBoneString.Mid(0, ResultA); + TwinBoneString += MirrorTableSettings->Substring_B; + TwinBoneString += CurrentBoneString.Mid(ResultA + MirrorTableSettings->Substring_A.Len()); + + if ((!(abs(TwinBoneString.Len() - MirrorTableSettings->Substring_B.Len()) == abs(CurrentBoneString.Len() - MirrorTableSettings->Substring_A.Len()))) && ValidSymmetry) + { + ValidSymmetry = false; + } + } + if (ValidSymmetry) + { + + FName TwinBoneName = FName(*TwinBoneString); + int TwinBoneIndex = SourceSkeleton ? SourceSkeleton->GetReferenceSkeleton().FindBoneIndex(FName(*TwinBoneString)) : INDEX_NONE; + if (TwinBoneIndex != INDEX_NONE) + { + if ((!CreatedBoneStrings.Contains(TwinBoneString)) && (!CreatedBoneStrings.Contains(CurrentBoneString))) + { + FMirrorBone NewMirrorBone = FMirrorBone(); + NewMirrorBone.BoneName = FName(*CurrentBoneString); + NewMirrorBone.MirrorAxis = MirrorTableSettings->DefaultTwinMirrorAxis; + NewMirrorBone.FlipAxis = MirrorTableSettings->DefaultTwinFlipAxis; + NewMirrorBone.RotationOffset = MirrorTableSettings->DefaultTwinRotationOffset; + NewMirrorBone.IsTwinBone = true; + NewMirrorBone.TwinBoneName = FName(*TwinBoneString); + NewMirrorBone.MirrorTranslation = MirrorTableSettings->DefaultTwinMirrorTranslation; + + MirrorTable->MirrorBones.Add(NewMirrorBone); + CreatedBoneStrings.Add(TwinBoneString); + CreatedBoneStrings.Add(CurrentBoneString); + continue; + } + + } + } + } + if (!CreatedBoneStrings.Contains(CurrentBoneString)) + { + ///// + FMirrorBone NewMirrorBone = FMirrorBone(); + NewMirrorBone.BoneName = FName(*CurrentBoneString); + NewMirrorBone.MirrorAxis = MirrorTableSettings->DefaultMirrorAxis; + NewMirrorBone.FlipAxis = MirrorTableSettings->DefaultFlipAxis; + NewMirrorBone.RotationOffset = MirrorTableSettings->DefaultRotationOffset; + NewMirrorBone.IsTwinBone = false; + + MirrorTable->MirrorBones.Add(NewMirrorBone); + CreatedBoneStrings.Add(CurrentBoneString); + } + } + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonSettings.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonSettings.cpp new file mode 100644 index 0000000..8507231 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonSettings.cpp @@ -0,0 +1,9 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "MirrorTableFromSkeletonSettings.h" +#include "MirrorAnimationSystemEditor.h" + + +UMirrorTableFromSkeletonSettings::UMirrorTableFromSkeletonSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_Mirror.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_Mirror.h new file mode 100644 index 0000000..ab7061a --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_Mirror.h @@ -0,0 +1,29 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once +#include "AnimGraphNode_Base.h" +#include "AnimNode_Mirror.h" +#include "AnimGraphNode_Mirror.generated.h" +/** + * + */ +/*class that holds Editor version of the AnimGraph Node Mirror Pose, along its tittle, tooltip, Node Color, and the category of the node*/ +UCLASS() +class MIRRORANIMATIONSYSTEMEDITOR_API UAnimGraphNode_Mirror : public UAnimGraphNode_Base +{ + GENERATED_BODY() + UPROPERTY(EditAnywhere, Category = Settings) + FAnimNode_Mirror Node; + + //~ Begin UEdGraphNode Interface. + virtual FLinearColor GetNodeTitleColor() const override; + virtual FText GetTooltipText() const override; + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + //~ End UEdGraphNode Interface. + + //~ Begin UAnimGraphNode_Base Interface + virtual FString GetNodeCategory() const override; + //~ End UAnimGraphNode_Base Interface + + UAnimGraphNode_Mirror(const FObjectInitializer& ObjectInitializer); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_MirrorCS.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_MirrorCS.h new file mode 100644 index 0000000..c532137 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_MirrorCS.h @@ -0,0 +1,51 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once +//#include "AnimGraphNode_Base.h" +#include "AnimGraphNode_SkeletalControlBase.h" +#include "AnimNode_MirrorCS.h" +#include "AnimGraphNode_MirrorCS.generated.h" +/** + * + */ +/*class that holds Editor version of the AnimGraph Node Mirror Pose, along its tittle, tooltip, Node Color, and the category of the node*/ +UCLASS() +class MIRRORANIMATIONSYSTEMEDITOR_API UAnimGraphNode_MirrorCS : public UAnimGraphNode_SkeletalControlBase +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = Settings) + FAnimNode_MirrorCS Node; + /* + //~ Begin UEdGraphNode Interface. + virtual FLinearColor GetNodeTitleColor() const override; + virtual FText GetTooltipText() const override; + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + //~ End UEdGraphNode Interface. + + //~ Begin UAnimGraphNode_Base Interface + virtual FString GetNodeCategory() const override; + //~ End UAnimGraphNode_Base Interface + + UAnimGraphNode_MirrorCS(const FObjectInitializer& ObjectInitializer); + */ + + +public: + UAnimGraphNode_MirrorCS(const FObjectInitializer& ObjectInitializer); + + // UEdGraphNode interface + virtual FLinearColor GetNodeTitleColor() const override; + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual FText GetTooltipText() const override; + // validate if this is within VehicleAnimInstance + virtual void ValidateAnimNodePostCompile(class FCompilerResultsLog& MessageLog, class UAnimBlueprintGeneratedClass* CompiledClass, int32 CompiledNodeIndex) override; + virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override; + // End of UEdGraphNode interface + +protected: + // UAnimGraphNode_SkeletalControlBase interface + virtual FText GetControllerDescription() const override; + virtual const FAnimNode_SkeletalControlBase* GetNode() const override { return &Node; } + // End of UAnimGraphNode_SkeletalControlBase interface +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AssetTypeActions_MirrorTable.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AssetTypeActions_MirrorTable.h new file mode 100644 index 0000000..f690c61 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AssetTypeActions_MirrorTable.h @@ -0,0 +1,28 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Toolkits/IToolkitHost.h" +#include "AssetTypeActions_Base.h" + +/** +* +*/ +/*AssetTypeActions class for the Mirror table so it can have a distinctive color and opens the Mirror Table class's custom asset editor*/ +class FAssetTypeActions_MirrorTable : public FAssetTypeActions_Base +{ +public: + FAssetTypeActions_MirrorTable(EAssetTypeCategories::Type InAssetCategory); + + // IAssetTypeActions interface + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; + virtual uint32 GetCategories() override; + // End of IAssetTypeActions interface + +private: + EAssetTypeCategories::Type MyAssetCategory; +}; \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/ContentBrowserTools.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/ContentBrowserTools.h new file mode 100644 index 0000000..e6ad1fe --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/ContentBrowserTools.h @@ -0,0 +1,15 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +/** + * + */ +/*Class that adds the "Mirror AnimAsset" and the "Mirror Table From Skeleton" tool's buttons +when an Animation Asset or a Skeleton asset is right clicked*/ +class FContentBrowserTools +{ +public: + static void InstallHooks(); + static void RemoveHooks(); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetDialog.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetDialog.h new file mode 100644 index 0000000..a789793 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetDialog.h @@ -0,0 +1,46 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Engine/Texture2D.h" +#include "IDetailsView.h" +#include "Animation/AnimSequence.h" +#include "MirrorAnimAssetSettings.h" + +/*Dialog used to choose the Mirror Table, +it then Mirrors the animation according to the data contained in the Mirror Table*/ +class MIRRORANIMATIONSYSTEMEDITOR_API SMirrorAnimAssetDialog : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SMirrorAnimAssetDialog) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, UAnimSequence* AnimSequence); + + ~SMirrorAnimAssetDialog(); + + static bool ShowWindow(UAnimSequence* SourceAnimSequence); + + static void CreateMirrorSequenceFromAnimSequence(UAnimSequence* MirrorSequence, UMirrorTable* MirrorTable); +private: + + UAnimSequence* SourceAnimSequence; + + class UMirrorAnimAssetSettings* MirrorAnimAssetSettings; + + bool ValidateCSMirrorData(); + + FReply MirrorClicked(); + + FReply CancelClicked(); + + void CloseContainingWindow(); + + void CreateMirroredAnimSequences(); + + TSharedPtr MainPropertyView; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetSettings.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetSettings.h new file mode 100644 index 0000000..6a85619 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetSettings.h @@ -0,0 +1,34 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/UObjectGlobals.h" +#include "UObject/Object.h" +#include "MirrorTable.h" +#include "MirrorAnimAssetSettings.generated.h" +/** + * + */ +/*class used in Mirror AnimAsset Dialog for choosing the Mirror Table asset in a details view*/ +UCLASS() +class MIRRORANIMATIONSYSTEMEDITOR_API UMirrorAnimAssetSettings : public UObject +{ + GENERATED_BODY() +public: + + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ComponentSpaceMirrorSettings) + TEnumAsByte MirrorAxis = EAxis::None; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ComponentSpaceMirrorSettings) + bool CompletlySymmetrical = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ComponentSpaceMirrorSettings) + FString Substring_A = "None"; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ComponentSpaceMirrorSettings) + FString Substring_B = "None"; + + UPROPERTY(Category = BoneSpaceMirrorSettings, EditAnywhere, meta = (HideAlphaChannel)) + UMirrorTable* MirrorTable; + + UMirrorAnimAssetSettings(const FObjectInitializer& ObjectInitializer); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemEditor.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemEditor.h new file mode 100644 index 0000000..7ad0ca1 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemEditor.h @@ -0,0 +1,55 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + + +#include "Toolkits/AssetEditorToolkit.h" +#include "Modules/ModuleInterface.h" + +#include "AssetTypeCategories.h" +#include "Engine/Texture2D.h" +#include "Editor.h" +#include "EditorModeRegistry.h" +#include "Modules/ModuleManager.h" +#include "UObject/UObjectHash.h" +#include "UObject/UObjectIterator.h" +#include "ThumbnailRendering/ThumbnailManager.h" + +#include "AssetToolsModule.h" +#include "PropertyEditorModule.h" + +#include "IAssetTypeActions.h" + +#include "ISettingsModule.h" + +#include "PropertyEditorDelegates.h" + +class FToolBarBuilder; +class FMenuBuilder; + + +class FMirrorAnimationSystemEditorModule : public IModuleInterface +{ +public: + FMirrorAnimationSystemEditorModule() + :MirrorTableAssetCategoryBit(EAssetTypeCategories::Misc) + { + } + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +private: + + + void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef Action); + void RegisterCustomClassLayout(FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate); + + + TSet< FName > RegisteredClassNames; + EAssetTypeCategories::Type MirrorTableAssetCategoryBit; + TArray< TSharedPtr > CreatedAssetTypeActions; + +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemStyle.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemStyle.h new file mode 100644 index 0000000..1fc4df6 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemStyle.h @@ -0,0 +1,29 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +/** + * + */ +/*class containing the SlateStyle for the Mirror Animation System, +Adds the ability to use the Mirror Animation System's icons stored in the "resources" folder inside the plugin's directory*/ + +class FMirrorAnimationSystemStyle +{ +public: + static void Initialize(); + + static void Shutdown(); + + static FName GetStyleSetName(); + +private: + static TSharedRef< class FSlateStyleSet > Create(); + +private: + + static TSharedPtr< class FSlateStyleSet > StyleInstance; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableDetails.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableDetails.h new file mode 100644 index 0000000..23d0aa6 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableDetails.h @@ -0,0 +1,73 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "IDetailCustomization.h" +#include "IDetailCustomNodeBuilder.h" + +class FDetailWidgetRow; +class IDetailChildrenBuilder; +class IDetailLayoutBuilder; +class IPropertyHandle; +class SEditableTextBox; +/*class containing the custom layout for the Mirror Table class. +It merely displays the data to setup inside the Mirror Table in a more user friendly way, +where it is easy to identify the bone's name and whether it is a twin bone or not*/ +class FMirrorTableNodeBuilder : public IDetailCustomNodeBuilder, public TSharedFromThis +{ +public: + FMirrorTableNodeBuilder(IDetailLayoutBuilder* InDetailLayoutBuilder, const TSharedPtr& InPropertyHandle); + + /** IDetailCustomNodeBuilder interface */ + virtual void SetOnRebuildChildren(FSimpleDelegate InOnRebuildChildren) override { OnRebuildChildren = InOnRebuildChildren; } + virtual bool RequiresTick() const override { return true; } + virtual void Tick(float DeltaTime) override; + virtual void GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) override; + virtual void GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) override; + virtual bool InitiallyCollapsed() const override { return true; } + virtual FName GetName() const override { return NAME_None; } + virtual FText GetText() const; +private: + void RemoveContextButton_OnClick(); + +private: + /** Called to rebuild the children of the detail tree */ + FSimpleDelegate OnRebuildChildren; + + /** Associated detail layout builder */ + IDetailLayoutBuilder* DetailLayoutBuilder; + + /** Property handle to associated context mapping */ + TSharedPtr ContextMappingPropertyHandle; + + /** Property handle to the localization key format property within this context mapping */ + TSharedPtr BoneNameHandle; + + + /** Property handle to the localization key format property within this context mapping */ + TSharedPtr TwinBoneNameHandle; + + /** Property handle to the localization key format property within this context mapping */ + TSharedPtr IsTwinBoneHandle; +}; + +class FMirrorTableDetails : public IDetailCustomization +{ +public: + /** Makes a new instance of this detail layout class for a specific detail view requesting it */ + static TSharedRef MakeInstance(); + + /** ILayoutDetails interface */ + virtual void CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder) override; + + void RebuildChildren(); +private: + FReply AddMirrorTableMapping_OnClicked(); + + +private: + /** Associated detail layout builder */ + IDetailLayoutBuilder* DetailLayoutBuilder; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonDialog.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonDialog.h new file mode 100644 index 0000000..1e35976 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonDialog.h @@ -0,0 +1,52 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Engine/Texture2D.h" +#include "IDetailsView.h" +#include "Animation/AnimSequence.h" +#include "Animation/Skeleton.h" +#include "MirrorTable.h" + +#include "MirrorTableFromSkeletonSettings.h" +/** + * + */ +class USkeleton; +/*SCompound widget class containing the functionality for creating a new Mirror Table asset based on a Skeleton Asset, +the dialog serves to input the Prefixes or suffixes used in the skeleton to identify whether a bone is a twin bone and if so find it's corresponding twin. +At the same time it allows to setup quick initial values for all the bones that are being added to the new Mirror Table*/ +class MIRRORANIMATIONSYSTEMEDITOR_API SMirrorTableFromSkeletonDialog : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SMirrorTableFromSkeletonDialog) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, USkeleton* Skeleton); + + ~SMirrorTableFromSkeletonDialog(); + + static bool ShowWindow(USkeleton* SourceSkeleton); + + void SetMirrorTableFromSettings(UMirrorTable * MirrorTable); + +private: + + USkeleton* SourceSkeleton; + + class UMirrorTableFromSkeletonSettings* MirrorTableSettings; + + FReply CreateClicked(); + + FReply CancelClicked(); + + void CloseContainingWindow(); + + void CreateMirrorTableFromSkeletonFunction(); + + TSharedPtr MainPropertyView; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonSettings.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonSettings.h new file mode 100644 index 0000000..46e9e12 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonSettings.h @@ -0,0 +1,42 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once +#include "UObject/NoExportTypes.h" +#include "MirrorTableFromSkeletonSettings.generated.h" +/** + * + */ +/*class used in Mirror Table from Skeleton Dialog for creating the details view panel +and settting up the Suffix or preffix for twin identification as well as +defining the default values for the bones that will be added to the new Mirror Table*/ +UCLASS() +class MIRRORANIMATIONSYSTEMEDITOR_API UMirrorTableFromSkeletonSettings : public UObject +{ + GENERATED_BODY() + +public: + + UPROPERTY(Category = SearchSettings, EditAnywhere, meta = (HideAlphaChannel)) + FString Substring_A = "none"; + UPROPERTY(Category = SearchSettings, EditAnywhere, meta = (HideAlphaChannel)) + FString Substring_B = "none"; + + UPROPERTY(Category = NonTwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + TEnumAsByte DefaultMirrorAxis = EAxis::None; + UPROPERTY(Category = NonTwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + TEnumAsByte DefaultFlipAxis = EAxis::None; + UPROPERTY(Category = NonTwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + FRotator DefaultRotationOffset = FRotator(0, 0, 0); + + UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + TEnumAsByte DefaultTwinMirrorAxis = EAxis::None; + UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + TEnumAsByte DefaultTwinFlipAxis = EAxis::None; + UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + FRotator DefaultTwinRotationOffset = FRotator(0, 0, 0); + UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + bool DefaultTwinMirrorTranslation = false; + + + UMirrorTableFromSkeletonSettings(const FObjectInitializer& ObjectInitializer); +};