diff --git a/Engine/Build/Commit.gitdeps.xml b/Engine/Build/Commit.gitdeps.xml
index f6c49e3f5233..a128d7c90411 100644
--- a/Engine/Build/Commit.gitdeps.xml
+++ b/Engine/Build/Commit.gitdeps.xml
@@ -5228,6 +5228,7 @@
+
@@ -6004,6 +6005,8 @@
+
+
@@ -17511,76 +17514,76 @@
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -17619,148 +17622,148 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
@@ -27638,6 +27641,10 @@
+
+
+
+
@@ -39620,6 +39627,7 @@
+
@@ -39725,7 +39733,7 @@
-
+
@@ -39836,6 +39844,7 @@
+
@@ -39959,7 +39968,6 @@
-
@@ -40110,6 +40118,7 @@
+
@@ -40208,7 +40217,7 @@
-
+
@@ -40308,6 +40317,7 @@
+
@@ -40418,7 +40428,6 @@
-
@@ -40549,7 +40558,6 @@
-
@@ -40769,7 +40777,6 @@
-
@@ -40828,7 +40835,7 @@
-
+
@@ -41017,7 +41024,6 @@
-
@@ -41100,6 +41106,7 @@
+
@@ -41158,7 +41165,6 @@
-
@@ -41194,7 +41200,6 @@
-
@@ -41241,7 +41246,6 @@
-
@@ -41326,7 +41330,7 @@
-
+
@@ -41372,6 +41376,7 @@
+
@@ -41390,6 +41395,7 @@
+
@@ -41414,9 +41420,9 @@
-
+
@@ -41600,7 +41606,7 @@
-
+
@@ -41979,6 +41985,7 @@
+
@@ -41989,7 +41996,6 @@
-
@@ -42004,7 +42010,7 @@
-
+
@@ -42152,6 +42158,7 @@
+
@@ -42162,6 +42169,7 @@
+
@@ -42355,6 +42363,7 @@
+
@@ -42384,6 +42393,7 @@
+
@@ -42391,6 +42401,7 @@
+
@@ -42444,6 +42455,7 @@
+
@@ -42521,7 +42533,7 @@
-
+
@@ -42548,7 +42560,7 @@
-
+
@@ -42585,7 +42597,7 @@
-
+
@@ -42641,7 +42653,7 @@
-
+
@@ -42782,7 +42794,6 @@
-
@@ -42854,7 +42865,7 @@
-
+
@@ -43006,7 +43017,6 @@
-
@@ -43018,6 +43028,7 @@
+
@@ -43066,7 +43077,6 @@
-
@@ -43201,6 +43211,7 @@
+
@@ -43231,7 +43242,7 @@
-
+
@@ -43277,6 +43288,7 @@
+
@@ -43451,7 +43463,6 @@
-
@@ -43585,7 +43596,7 @@
-
+
@@ -43631,7 +43642,7 @@
-
+
@@ -43687,7 +43698,6 @@
-
@@ -43700,7 +43710,7 @@
-
+
@@ -43723,7 +43733,6 @@
-
@@ -43986,7 +43995,6 @@
-
@@ -44051,6 +44059,7 @@
+
@@ -44137,7 +44146,7 @@
-
+
@@ -44278,6 +44287,7 @@
+
@@ -44443,6 +44453,7 @@
+
@@ -44451,7 +44462,6 @@
-
@@ -44470,6 +44480,7 @@
+
@@ -44510,7 +44521,7 @@
-
+
@@ -44670,7 +44681,7 @@
-
+
@@ -44808,6 +44819,7 @@
+
@@ -44839,6 +44851,7 @@
+
@@ -44850,7 +44863,6 @@
-
@@ -45128,6 +45140,7 @@
+
@@ -45151,7 +45164,6 @@
-
@@ -45195,6 +45207,7 @@
+
@@ -45238,7 +45251,7 @@
-
+
@@ -45250,6 +45263,7 @@
+
@@ -45291,7 +45305,6 @@
-
@@ -45542,7 +45555,6 @@
-
@@ -45553,6 +45565,7 @@
+
@@ -45623,6 +45636,7 @@
+
@@ -45686,7 +45700,6 @@
-
@@ -45745,7 +45758,6 @@
-
@@ -45758,8 +45770,8 @@
-
+
@@ -45792,6 +45804,7 @@
+
@@ -45937,6 +45950,7 @@
+
@@ -46077,7 +46091,7 @@
-
+
@@ -46099,7 +46113,7 @@
-
+
@@ -46116,6 +46130,7 @@
+
@@ -46182,7 +46197,6 @@
-
@@ -46192,6 +46206,7 @@
+
@@ -46226,7 +46241,7 @@
-
+
@@ -46355,6 +46370,7 @@
+
@@ -46369,7 +46385,7 @@
-
+
@@ -46392,7 +46408,7 @@
-
+
@@ -46451,7 +46467,7 @@
-
+
@@ -46567,6 +46583,7 @@
+
@@ -46650,6 +46667,7 @@
+
@@ -46677,7 +46695,7 @@
-
+
@@ -46795,6 +46813,7 @@
+
@@ -46991,7 +47010,7 @@
-
+
@@ -47017,7 +47036,7 @@
-
+
@@ -47067,6 +47086,7 @@
+
@@ -47124,6 +47144,7 @@
+
@@ -47278,6 +47299,7 @@
+
@@ -47430,6 +47452,7 @@
+
@@ -47464,16 +47487,19 @@
+
-
+
+
+
@@ -47517,7 +47543,7 @@
-
+
@@ -47593,7 +47619,7 @@
-
+
@@ -47651,6 +47677,7 @@
+
@@ -47881,7 +47908,7 @@
-
+
@@ -47919,7 +47946,7 @@
-
+
@@ -48076,6 +48103,7 @@
+
@@ -48150,13 +48178,14 @@
-
+
+
@@ -48166,7 +48195,6 @@
-
@@ -48217,6 +48245,7 @@
+
@@ -48226,7 +48255,7 @@
-
+
@@ -48265,7 +48294,6 @@
-
@@ -48323,6 +48351,7 @@
+
@@ -48359,7 +48388,6 @@
-
@@ -48457,6 +48485,7 @@
+
@@ -48490,7 +48519,6 @@
-
@@ -48537,6 +48565,7 @@
+
@@ -48559,7 +48588,6 @@
-
@@ -48589,7 +48617,7 @@
-
+
@@ -48878,6 +48906,7 @@
+
@@ -48909,7 +48938,7 @@
-
+
@@ -48990,7 +49019,6 @@
-
@@ -49013,7 +49041,6 @@
-
@@ -49250,6 +49277,7 @@
+
@@ -49282,6 +49310,7 @@
+
@@ -49325,6 +49354,7 @@
+
@@ -49411,6 +49441,7 @@
+
@@ -49518,7 +49549,6 @@
-
@@ -49619,7 +49649,7 @@
-
+
@@ -49629,6 +49659,7 @@
+
@@ -49754,7 +49785,6 @@
-
@@ -49874,7 +49904,7 @@
-
+
@@ -49899,7 +49929,6 @@
-
@@ -50039,6 +50068,7 @@
+
@@ -50078,7 +50108,6 @@
-
@@ -50164,7 +50193,7 @@
-
+
@@ -50208,7 +50237,6 @@
-
@@ -50388,7 +50416,6 @@
-
@@ -50424,7 +50451,6 @@
-
@@ -50594,7 +50620,6 @@
-
@@ -50659,6 +50684,7 @@
+
@@ -50743,7 +50769,6 @@
-
@@ -50994,6 +51019,7 @@
+
@@ -51089,6 +51115,7 @@
+
@@ -51110,7 +51137,7 @@
-
+
@@ -51290,6 +51317,7 @@
+
@@ -51423,11 +51451,9 @@
-
-
@@ -51503,7 +51529,6 @@
-
@@ -51583,7 +51608,6 @@
-
@@ -51730,7 +51754,7 @@
-
+
@@ -51872,7 +51896,7 @@
-
+
@@ -51999,7 +52023,6 @@
-
@@ -52088,7 +52111,6 @@
-
@@ -52374,7 +52396,6 @@
-
@@ -52410,6 +52431,7 @@
+
@@ -52485,7 +52507,7 @@
-
+
@@ -52501,7 +52523,7 @@
-
+
@@ -52589,7 +52611,6 @@
-
@@ -52648,7 +52669,7 @@
-
+
@@ -52710,7 +52731,6 @@
-
@@ -52733,7 +52753,6 @@
-
@@ -52778,7 +52797,7 @@
-
+
@@ -52857,7 +52876,7 @@
-
+
@@ -52873,7 +52892,7 @@
-
+
@@ -53158,6 +53177,7 @@
+
@@ -53405,7 +53425,6 @@
-
@@ -53495,7 +53514,6 @@
-
@@ -53562,6 +53580,7 @@
+
@@ -53598,6 +53617,7 @@
+
@@ -53679,7 +53699,6 @@
-
@@ -53773,7 +53792,7 @@
-
+
@@ -53918,7 +53937,7 @@
-
+
@@ -53995,6 +54014,7 @@
+
@@ -54121,7 +54141,6 @@
-
@@ -54156,6 +54175,7 @@
+
@@ -54246,8 +54266,8 @@
+
-
@@ -54359,7 +54379,6 @@
-
@@ -54406,7 +54425,7 @@
-
+
@@ -54737,8 +54756,10 @@
+
+
@@ -54993,7 +55014,7 @@
-
+
@@ -55309,7 +55330,7 @@
-
+
@@ -55330,7 +55351,6 @@
-
@@ -55429,7 +55449,6 @@
-
@@ -55440,6 +55459,7 @@
+
@@ -55449,7 +55469,6 @@
-
@@ -55597,7 +55616,7 @@
-
+
@@ -55615,6 +55634,7 @@
+
@@ -55654,12 +55674,14 @@
+
+
@@ -55758,7 +55780,7 @@
-
+
@@ -55781,7 +55803,6 @@
-
@@ -55817,6 +55838,7 @@
+
@@ -55875,7 +55897,6 @@
-
@@ -55983,7 +56004,6 @@
-
@@ -56012,7 +56032,6 @@
-
@@ -56049,6 +56068,7 @@
+
@@ -56093,7 +56113,6 @@
-
@@ -56148,7 +56167,7 @@
-
+
@@ -56159,7 +56178,7 @@
-
+
@@ -56196,6 +56215,7 @@
+
@@ -56220,13 +56240,14 @@
-
+
+
@@ -56255,7 +56276,6 @@
-
@@ -56427,7 +56447,7 @@
-
+
@@ -56451,7 +56471,6 @@
-
@@ -56769,7 +56788,6 @@
-
@@ -56795,6 +56813,7 @@
+
@@ -57021,6 +57040,7 @@
+
@@ -57032,7 +57052,6 @@
-
@@ -57040,7 +57059,7 @@
-
+
@@ -57289,7 +57308,6 @@
-
@@ -57313,7 +57331,7 @@
-
+
@@ -57439,7 +57457,7 @@
-
+
@@ -57717,6 +57735,7 @@
+
@@ -57727,6 +57746,7 @@
+
@@ -57747,7 +57767,6 @@
-
@@ -57901,6 +57920,7 @@
+
@@ -57963,7 +57983,6 @@
-
@@ -58091,7 +58110,7 @@
-
+
@@ -58271,7 +58290,7 @@
-
+
@@ -58350,7 +58369,6 @@
-
@@ -58402,6 +58420,7 @@
+
@@ -58555,7 +58574,6 @@
-
@@ -58691,7 +58709,6 @@
-
@@ -58744,6 +58761,7 @@
+
@@ -58774,6 +58792,7 @@
+
@@ -58958,7 +58977,7 @@
-
+
@@ -59018,7 +59037,6 @@
-
@@ -59130,7 +59148,7 @@
-
+
@@ -59177,7 +59195,6 @@
-
@@ -59190,6 +59207,7 @@
+
@@ -59297,7 +59315,7 @@
-
+
@@ -59328,7 +59346,7 @@
-
+
@@ -59512,7 +59530,6 @@
-
@@ -59615,7 +59632,6 @@
-
@@ -59684,6 +59700,7 @@
+
@@ -59695,7 +59712,6 @@
-
@@ -59731,6 +59747,7 @@
+
@@ -59757,7 +59774,7 @@
-
+
@@ -59787,7 +59804,7 @@
-
+
@@ -59945,7 +59962,6 @@
-
@@ -59989,7 +60005,6 @@
-
@@ -60116,11 +60131,12 @@
-
+
+
@@ -60136,12 +60152,12 @@
+
-
@@ -60276,6 +60292,7 @@
+
@@ -60402,7 +60419,7 @@
-
+
@@ -60495,7 +60512,6 @@
-
@@ -60713,7 +60729,6 @@
-
@@ -60795,7 +60810,7 @@
-
+
@@ -60806,7 +60821,6 @@
-
@@ -60835,7 +60849,6 @@
-
@@ -60982,7 +60995,7 @@
-
+
@@ -61033,6 +61046,7 @@
+
@@ -61146,7 +61160,7 @@
-
+
@@ -61198,7 +61212,6 @@
-
@@ -61217,7 +61230,6 @@
-
@@ -61300,6 +61312,7 @@
+
@@ -61357,7 +61370,7 @@
-
+
@@ -61437,6 +61450,7 @@
+
@@ -61551,7 +61565,6 @@
-
@@ -61617,7 +61630,7 @@
-
+
@@ -61708,7 +61721,6 @@
-
@@ -61797,7 +61809,6 @@
-
@@ -61875,12 +61886,10 @@
-
-
@@ -62001,7 +62010,7 @@
-
+
@@ -62016,6 +62025,7 @@
+
@@ -62024,12 +62034,12 @@
-
+
-
+
@@ -62077,7 +62087,6 @@
-
@@ -62139,7 +62148,7 @@
-
+
@@ -62207,7 +62216,7 @@
-
+
@@ -62264,6 +62273,7 @@
+
@@ -62301,9 +62311,8 @@
-
-
+
@@ -62325,6 +62334,7 @@
+
@@ -62346,7 +62356,7 @@
-
+
@@ -62417,7 +62427,6 @@
-
@@ -62498,7 +62507,6 @@
-
@@ -62524,7 +62532,6 @@
-
@@ -62727,7 +62734,7 @@
-
+
@@ -62743,7 +62750,6 @@
-
@@ -62761,7 +62767,6 @@
-
@@ -62945,7 +62950,7 @@
-
+
@@ -62979,7 +62984,7 @@
-
+
@@ -63130,7 +63135,6 @@
-
@@ -63175,7 +63179,7 @@
-
+
@@ -63190,6 +63194,7 @@
+
@@ -63607,7 +63612,7 @@
-
+
@@ -63823,7 +63828,7 @@
-
+
@@ -63873,6 +63878,7 @@
+
@@ -63897,6 +63903,7 @@
+
@@ -64058,7 +64065,6 @@
-
@@ -64089,7 +64095,7 @@
-
+
@@ -64140,7 +64146,6 @@
-
@@ -64243,7 +64248,7 @@
-
+
@@ -64269,6 +64274,7 @@
+
@@ -64341,7 +64347,7 @@
-
+
@@ -64356,7 +64362,6 @@
-
@@ -64372,7 +64377,7 @@
-
+
@@ -64438,9 +64443,10 @@
+
-
+
@@ -64478,6 +64484,7 @@
+
@@ -64498,6 +64505,7 @@
+
@@ -64665,7 +64673,6 @@
-
@@ -64911,7 +64918,6 @@
-
@@ -65117,7 +65123,7 @@
-
+
@@ -65186,7 +65192,7 @@
-
+
@@ -65278,7 +65284,6 @@
-
@@ -65307,7 +65312,7 @@
-
+
@@ -65339,6 +65344,7 @@
+
@@ -65404,7 +65410,6 @@
-
@@ -65487,7 +65492,7 @@
-
+
@@ -65599,6 +65604,7 @@
+
@@ -65719,6 +65725,7 @@
+
@@ -66028,7 +66035,6 @@
-
@@ -66108,7 +66114,7 @@
-
+
@@ -66366,7 +66372,6 @@
-
@@ -66425,7 +66430,6 @@
-
@@ -66486,7 +66490,7 @@
-
+
@@ -66511,7 +66515,6 @@
-
@@ -66523,7 +66526,7 @@
-
+
@@ -66552,6 +66555,7 @@
+
@@ -66614,6 +66618,7 @@
+
@@ -66664,6 +66669,7 @@
+
@@ -66677,6 +66683,7 @@
+
@@ -66731,7 +66738,6 @@
-
@@ -66760,6 +66766,7 @@
+
@@ -66908,7 +66915,7 @@
-
+
@@ -66960,6 +66967,7 @@
+
@@ -66982,6 +66990,7 @@
+
@@ -67050,11 +67059,13 @@
+
+
@@ -67177,6 +67188,7 @@
+
@@ -67228,7 +67240,6 @@
-
@@ -67249,7 +67260,6 @@
-
@@ -67290,7 +67300,6 @@
-
@@ -67368,6 +67377,7 @@
+
@@ -67460,6 +67470,7 @@
+
@@ -67527,7 +67538,6 @@
-
@@ -67547,7 +67557,7 @@
-
+
@@ -67556,10 +67566,10 @@
+
-
@@ -67670,6 +67680,7 @@
+
@@ -67683,6 +67694,7 @@
+
@@ -67690,13 +67702,13 @@
-
+
@@ -67704,7 +67716,6 @@
-
@@ -67717,6 +67728,7 @@
+
@@ -67727,6 +67739,7 @@
+
@@ -67737,7 +67750,6 @@
-
@@ -67752,7 +67764,6 @@
-
@@ -67796,10 +67807,8 @@
-
-
@@ -67816,6 +67825,7 @@
+
@@ -67837,7 +67847,7 @@
-
+
@@ -67855,7 +67865,6 @@
-
@@ -67863,6 +67872,7 @@
+
@@ -67898,6 +67908,7 @@
+
@@ -67922,10 +67933,8 @@
-
-
@@ -67952,6 +67961,7 @@
+
@@ -67967,7 +67977,7 @@
-
+
@@ -68027,6 +68037,7 @@
+
@@ -68053,6 +68064,7 @@
+
@@ -68062,7 +68074,6 @@
-
@@ -68072,7 +68083,6 @@
-
@@ -68083,10 +68093,7 @@
-
-
-
@@ -68107,7 +68114,7 @@
-
+
@@ -68145,6 +68152,7 @@
+
@@ -68153,7 +68161,7 @@
-
+
@@ -68170,6 +68178,7 @@
+
@@ -68178,6 +68187,7 @@
+
@@ -68195,7 +68205,6 @@
-
@@ -68209,7 +68218,6 @@
-
@@ -68219,7 +68227,6 @@
-
@@ -68228,6 +68235,7 @@
+
@@ -68240,7 +68248,6 @@
-
@@ -68259,11 +68266,11 @@
-
+
@@ -68360,7 +68367,7 @@
-
+
@@ -68387,7 +68394,6 @@
-
@@ -68414,6 +68420,7 @@
+
@@ -68466,11 +68473,11 @@
-
+
@@ -68482,6 +68489,7 @@
+
@@ -68508,7 +68516,6 @@
-
@@ -68549,7 +68556,6 @@
-
@@ -68563,6 +68569,7 @@
+
@@ -68577,12 +68584,14 @@
+
+
@@ -68591,12 +68600,14 @@
+
+
@@ -68617,6 +68628,7 @@
+
@@ -68626,12 +68638,11 @@
+
-
-
@@ -68654,26 +68665,25 @@
+
+
-
-
+
-
-
@@ -68691,6 +68701,7 @@
+
@@ -68700,7 +68711,6 @@
-
@@ -68720,6 +68730,7 @@
+
@@ -68741,13 +68752,13 @@
-
+
@@ -68772,8 +68783,6 @@
-
-
@@ -68800,6 +68809,8 @@
+
+
@@ -68834,6 +68845,7 @@
+
@@ -68851,13 +68863,16 @@
+
+
+
@@ -68905,6 +68920,7 @@
+
@@ -68929,7 +68945,6 @@
-
@@ -68942,7 +68957,6 @@
-
@@ -68969,7 +68983,9 @@
+
+
@@ -68988,12 +69004,14 @@
+
+
@@ -69007,6 +69025,7 @@
+
@@ -69114,6 +69133,7 @@
+
@@ -69125,6 +69145,7 @@
+
@@ -69134,7 +69155,6 @@
-
@@ -69151,12 +69171,12 @@
-
+
@@ -69169,6 +69189,7 @@
+
@@ -69200,19 +69221,19 @@
+
-
-
+
@@ -69288,12 +69309,10 @@
-
-
@@ -69313,7 +69332,7 @@
-
+
@@ -69323,6 +69342,7 @@
+
@@ -69340,7 +69360,6 @@
-
@@ -69348,12 +69367,12 @@
-
+
@@ -69392,7 +69411,7 @@
-
+
@@ -69407,19 +69426,19 @@
-
+
-
+
@@ -69457,7 +69476,6 @@
-
@@ -69539,6 +69557,7 @@
+
@@ -69584,7 +69603,6 @@
-
@@ -69598,11 +69616,11 @@
-
+
@@ -69610,6 +69628,7 @@
+
@@ -69640,7 +69659,6 @@
-
@@ -69675,11 +69693,11 @@
-
+
@@ -69688,16 +69706,17 @@
+
+
-
@@ -69712,10 +69731,12 @@
+
+
@@ -69730,6 +69751,7 @@
+
@@ -69766,10 +69788,9 @@
+
-
-
@@ -69813,6 +69834,7 @@
+
@@ -69829,7 +69851,6 @@
-
@@ -69866,7 +69887,6 @@
-
@@ -69887,7 +69907,6 @@
-
@@ -69914,10 +69933,10 @@
+
-
@@ -69942,7 +69961,6 @@
-
@@ -69962,8 +69980,10 @@
+
+
@@ -70034,6 +70054,7 @@
+
@@ -70073,10 +70094,9 @@
-
+
-
@@ -70111,6 +70131,7 @@
+
@@ -70118,8 +70139,7 @@
-
-
+
@@ -70131,6 +70151,7 @@
+
@@ -70148,7 +70169,6 @@
-
@@ -70177,6 +70197,7 @@
+
@@ -70201,8 +70222,6 @@
-
-
@@ -70211,22 +70230,22 @@
+
-
-
+
@@ -70242,9 +70261,9 @@
-
+
@@ -70252,12 +70271,10 @@
-
-
@@ -70288,6 +70305,7 @@
+
@@ -70304,7 +70322,6 @@
-
@@ -70317,8 +70334,6 @@
-
-
@@ -70328,6 +70343,7 @@
+
@@ -70335,9 +70351,9 @@
-
+
@@ -70374,7 +70390,6 @@
-
@@ -70409,6 +70424,7 @@
+
@@ -70445,11 +70461,13 @@
+
+
@@ -70473,7 +70491,6 @@
-
@@ -70502,7 +70519,6 @@
-
@@ -70518,19 +70534,17 @@
-
-
+
-
@@ -70592,7 +70606,7 @@
-
+
diff --git a/Engine/Config/BaseEngine.ini b/Engine/Config/BaseEngine.ini
index 7ca17d27720e..e1f99a0158f4 100644
--- a/Engine/Config/BaseEngine.ini
+++ b/Engine/Config/BaseEngine.ini
@@ -2156,6 +2156,10 @@ HMDWornMovementThreshold = 50.0
[/Script/Engine.AnimationSettings]
bStripAnimationDataOnDedicatedServer=False
+
+[Animation.DefaultObjectSettings]
+CurveCompressionSettings="/Engine/Animation/DefaultAnimCurveCompressionSettings"
+
[/Script/Engine.MeshSimplificationSettings]
r.MeshReductionModule="QuadricMeshReduction"
diff --git a/Engine/Plugins/Developer/AnimationSharing/AnimationSharing.uplugin b/Engine/Plugins/Developer/AnimationSharing/AnimationSharing.uplugin
new file mode 100644
index 000000000000..6071a35ea124
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/AnimationSharing.uplugin
@@ -0,0 +1,35 @@
+{
+ "FileVersion": 1,
+ "Version": 1,
+ "VersionName": "1.0",
+ "FriendlyName": "Animation Sharing",
+ "Description": "Plugin to create Shared Animation systems using the Master-Child pose functionality",
+ "Category": "Programming",
+ "CreatedBy": "Epic Games, Inc.",
+ "CreatedByURL": "http://epicgames.com",
+ "DocsURL": "",
+ "MarketplaceURL": "",
+ "SupportURL": "",
+ "EnabledByDefault": true,
+ "CanContainContent": true,
+ "IsBetaVersion": false,
+ "Installed": false,
+ "Modules": [
+ {
+ "Name": "AnimationSharing",
+ "Type": "Runtime",
+ "LoadingPhase": "PreDefault"
+ },
+ {
+ "Name": "AnimationSharingEd",
+ "Type": "Editor",
+ "LoadingPhase": "Default"
+ }
+ ],
+ "Plugins": [
+ {
+ "Name": "SignificanceManager",
+ "Enabled": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/AnimationSharing.Build.cs b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/AnimationSharing.Build.cs
new file mode 100644
index 000000000000..2ea79518ea8c
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/AnimationSharing.Build.cs
@@ -0,0 +1,31 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+using UnrealBuildTool;
+
+public class AnimationSharing : ModuleRules
+{
+ public AnimationSharing(ReadOnlyTargetRules Target) : base(Target)
+ {
+ PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[] {
+ "Core",
+ "CoreUObject",
+ "Engine",
+ "EngineSettings",
+ "SignificanceManager"
+ }
+ );
+
+ if (Target.Type == TargetType.Editor)
+ {
+ PrivateDependencyModuleNames.Add("TargetPlatform");
+ }
+
+ PrivateIncludePaths.AddRange(
+ new string[] {
+ "AnimationSharing/Private"
+ });
+ }
+}
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AdditiveAnimationInstance.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AdditiveAnimationInstance.cpp
new file mode 100644
index 000000000000..6c24680622a4
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AdditiveAnimationInstance.cpp
@@ -0,0 +1,76 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "AdditiveAnimationInstance.h"
+#include "AnimationSharingInstances.h"
+#include "AnimationSharingManager.h"
+
+FAdditiveAnimationInstance::FAdditiveAnimationInstance() : SkeletalMeshComponent(nullptr), AdditiveInstance(nullptr), AdditiveAnimationSequence(nullptr), BaseComponent(nullptr), bLoopingState(false)
+{
+}
+
+void FAdditiveAnimationInstance::Initialise(USkeletalMeshComponent* InSkeletalMeshComponent, UClass* InAnimationBPClass)
+{
+ if (InSkeletalMeshComponent && InAnimationBPClass)
+ {
+ SkeletalMeshComponent = InSkeletalMeshComponent;
+ SkeletalMeshComponent->SetAnimInstanceClass(InAnimationBPClass);
+ SkeletalMeshComponent->SetComponentTickEnabled(false);
+ SkeletalMeshComponent->SetForcedLOD(1);
+ SkeletalMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
+ AdditiveInstance = Cast(SkeletalMeshComponent->GetAnimInstance());
+ }
+}
+
+void FAdditiveAnimationInstance::Setup(USkeletalMeshComponent* InBaseComponent, UAnimSequence* InAnimSequence)
+{
+ UAnimationSharingManager::SetDebugMaterial(SkeletalMeshComponent, 1);
+ SkeletalMeshComponent->SetComponentTickEnabled(true);
+ if (AdditiveInstance)
+ {
+ AdditiveInstance->BaseComponent = BaseComponent = InBaseComponent;
+ AdditiveInstance->AdditiveAnimation = AdditiveAnimationSequence = InAnimSequence;
+ AdditiveInstance->Alpha = 1.0f;
+ AdditiveInstance->bStateBool = bLoopingState = true;
+
+ SkeletalMeshComponent->AddTickPrerequisiteComponent(BaseComponent);
+ }
+}
+
+void FAdditiveAnimationInstance::UpdateBaseComponent(USkeletalMeshComponent* InBaseComponent)
+{
+ if (AdditiveInstance)
+ {
+ SkeletalMeshComponent->RemoveTickPrerequisiteComponent(BaseComponent);
+ AdditiveInstance->BaseComponent = BaseComponent = InBaseComponent;
+ SkeletalMeshComponent->AddTickPrerequisiteComponent(BaseComponent);
+ }
+}
+
+void FAdditiveAnimationInstance::Stop()
+{
+ if (AdditiveInstance)
+ {
+ UAnimationSharingManager::SetDebugMaterial(SkeletalMeshComponent, 0);
+ SkeletalMeshComponent->SetComponentTickEnabled(false);
+ SkeletalMeshComponent->RemoveTickPrerequisiteComponent(BaseComponent);
+ }
+}
+
+void FAdditiveAnimationInstance::Start()
+{
+ if (AdditiveInstance)
+ {
+ AdditiveInstance->bStateBool = bLoopingState = false;
+ }
+}
+
+USkeletalMeshComponent* FAdditiveAnimationInstance::GetBaseComponent() const
+{
+ return BaseComponent;
+}
+
+USkeletalMeshComponent* FAdditiveAnimationInstance::GetComponent() const
+{
+ return SkeletalMeshComponent;
+}
+
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingInstances.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingInstances.cpp
new file mode 100644
index 000000000000..40e8821ca297
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingInstances.cpp
@@ -0,0 +1,47 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "AnimationSharingInstances.h"
+#include "AnimationSharingManager.h"
+#include "Algo/Transform.h"
+#include "Stats/Stats.h"
+
+UAnimSharingStateInstance::UAnimSharingStateInstance(const FObjectInitializer& ObjectInitializer)
+ : Super(ObjectInitializer), AnimationToPlay(nullptr), PermutationTimeOffset(0.f), PlayRate(1.f), StateIndex(INDEX_NONE), ComponentIndex(INDEX_NONE), Instance(nullptr)
+{
+
+}
+
+void UAnimSharingStateInstance::GetInstancedActors(TArray& Actors)
+{
+ QUICK_SCOPE_CYCLE_COUNTER(STAT_GetInstancedActors);
+ if (Instance && Instance->PerStateData.IsValidIndex(StateIndex))
+ {
+ FPerStateData& StateData = Instance->PerStateData[StateIndex];
+ if (StateData.Components.IsValidIndex(ComponentIndex))
+ {
+ USkeletalMeshComponent* Component = StateData.Components[ComponentIndex];
+ const TArray>& SlaveComponents = Component->GetSlavePoseComponents();
+
+ Algo::TransformIf(SlaveComponents, Actors,
+ [&Actors](const TWeakObjectPtr& WeakPtr)
+ {
+ // Needs to be valid and unique
+ return WeakPtr.IsValid() && !Actors.Contains(WeakPtr->GetOwner());
+ },
+ [](const TWeakObjectPtr& WeakPtr)
+ {
+ return WeakPtr->GetOwner();
+ });
+ }
+ }
+}
+
+UAnimSharingTransitionInstance::UAnimSharingTransitionInstance(const FObjectInitializer& ObjectInitializer)
+ : Super(ObjectInitializer), FromComponent(nullptr), ToComponent(nullptr), BlendTime(.5f), bBlendBool(false)
+{
+}
+
+UAnimSharingAdditiveInstance::UAnimSharingAdditiveInstance(const FObjectInitializer& ObjectInitializer)
+ : Super(ObjectInitializer)
+{
+}
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingManager.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingManager.cpp
new file mode 100644
index 000000000000..e0e4dab9fd06
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingManager.cpp
@@ -0,0 +1,2108 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "AnimationSharingManager.h"
+#include "AnimationSharingModule.h"
+#include "Stats/Stats.h"
+#include "Components/SkinnedMeshComponent.h"
+#include "TransitionBlendInstance.h"
+#include "Animation/AnimationAsset.h"
+#include "Animation/AnimSequence.h"
+#include "SignificanceManager.h"
+#include "AnimationSharingSetup.h"
+#include "AdditiveAnimationInstance.h"
+#include "AnimationSharingInstances.h"
+
+#include "Misc/CoreMisc.h"
+#include "DrawDebugHelpers.h"
+#include "Math/NumericLimits.h"
+#include "Logging/LogMacros.h"
+#include "Engine/Engine.h"
+#include "ProfilingDebugging/CsvProfiler.h"
+#include "Misc/DefaultValueHelper.h"
+
+#if WITH_EDITOR
+#include "PlatformInfo.h"
+#include "Interfaces/ITargetPlatformManagerModule.h"
+#include "Interfaces/ITargetPlatform.h"
+#endif // WITH_EDITOR
+
+DEFINE_LOG_CATEGORY(LogAnimationSharing);
+
+DECLARE_CYCLE_STAT(TEXT("Tick"), STAT_AnimationSharing_Tick, STATGROUP_AnimationSharing);
+DECLARE_CYCLE_STAT(TEXT("UpdateBlends"), STAT_AnimationSharing_UpdateBlends, STATGROUP_AnimationSharing);
+DECLARE_CYCLE_STAT(TEXT("UpdateOnDemands"), STAT_AnimationSharing_UpdateOnDemands, STATGROUP_AnimationSharing);
+DECLARE_CYCLE_STAT(TEXT("UpdateAdditives"), STAT_AnimationSharing_UpdateAdditives, STATGROUP_AnimationSharing);
+DECLARE_CYCLE_STAT(TEXT("TickActorStates"), STAT_AnimationSharing_TickActorStates, STATGROUP_AnimationSharing);
+DECLARE_CYCLE_STAT(TEXT("KickoffInstances"), STAT_AnimationSharing_KickoffInstances, STATGROUP_AnimationSharing);
+
+DECLARE_DWORD_COUNTER_STAT(TEXT("NumBlends"), STAT_AnimationSharing_NumBlends, STATGROUP_AnimationSharing);
+DECLARE_DWORD_COUNTER_STAT(TEXT("NumOnDemands"), STAT_AnimationSharing_NumOnDemands, STATGROUP_AnimationSharing);
+DECLARE_DWORD_COUNTER_STAT(TEXT("NumActors"), STAT_AnimationSharing_NumActors, STATGROUP_AnimationSharing);
+DECLARE_DWORD_COUNTER_STAT(TEXT("NumComponent"), STAT_AnimationSharing_NumComponent, STATGROUP_AnimationSharing);
+
+static int32 GAnimationSharingDebugging = 0;
+static FAutoConsoleVariableRef CVarAnimSharing_DebugStates(
+ TEXT("a.Sharing.DebugStates"),
+ GAnimationSharingDebugging,
+ TEXT("Values: 0/1/2/3\n")
+ TEXT("Controls whether and which animation sharing debug features are enabled.\n")
+ TEXT("0: Turned off.\n")
+ TEXT("1: Turns on active master-components and blend with material coloring, and printing state information for each actor above their capsule.\n")
+ TEXT("2: Turns printing state information about currently active animation states, blend etc. Also enables line drawing from slave-components to currently assigned master components."),
+ ECVF_Cheat);
+
+static int32 GAnimationSharingEnabled = 1;
+static FAutoConsoleCommandWithWorldAndArgs CVarAnimSharing_Enabled(
+ TEXT("a.Sharing.Enabled"),
+ TEXT("Arguments: 0/1\n")
+ TEXT("Controls whether the animation sharing is enabled."),
+ FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& Args, UWorld* World)
+ {
+ if (Args.Num() != 0)
+ {
+ const bool bShouldBeEnabled = Args[0].ToBool();
+ if (!bShouldBeEnabled && GAnimationSharingEnabled && World)
+ {
+ /** Need to unregister actors here*/
+ UAnimationSharingManager* Manager = FAnimSharingModule::Get(World);
+ if (Manager)
+ {
+ Manager->ClearActorData();
+ }
+ }
+
+ GAnimationSharingEnabled = bShouldBeEnabled;
+ UE_LOG(LogAnimationSharing, Log, TEXT("Animation Sharing System - %s"), GAnimationSharingEnabled ? TEXT("Enabled") : TEXT("Disabled"));
+ }
+ }),
+ ECVF_Cheat);
+
+#if !UE_BUILD_SHIPPING
+static int32 GMasterComponentsVisible = 0;
+static FAutoConsoleCommandWithWorldAndArgs CVarAnimSharing_ToggleVisibility(
+ TEXT("a.Sharing.ToggleVisibility"),
+ TEXT("Toggles the visibility of the Master Pose Components."),
+ FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& Args, UWorld* World)
+ {
+ const bool bShouldBeVisible = !GMasterComponentsVisible;
+
+ /** Need to unregister actors here*/
+ UAnimationSharingManager* Manager = FAnimSharingModule::Get(World);
+ if (Manager)
+ {
+ Manager->SetMasterComponentsVisibility(bShouldBeVisible);
+ }
+
+ GMasterComponentsVisible = bShouldBeVisible;
+ }),
+ ECVF_Cheat);
+
+#else
+static const int32 GMasterComponentsVisible = 0;
+#endif
+
+#if WITH_EDITOR
+static TAutoConsoleVariable CVarAnimSharing_PreviewScalabilityPlatform(
+ TEXT("a.Sharing.ScalabilityPlatform"),
+ "",
+ TEXT("Controls which platform should be used when retrieving per platform scalability settings.\n")
+ TEXT("Empty: Current platform.\n")
+ TEXT("Name of Platform\n")
+ TEXT("Name of Platform Group\n"),
+ ECVF_Cheat);
+#endif
+
+#define LOG_STATES 0
+#define CSV_STATS 0
+#define DETAIL_STATS 0
+
+#if DEBUG_MATERIALS
+TArray UAnimationSharingManager::DebugMaterials;
+#endif
+
+void UAnimationSharingManager::BeginDestroy()
+{
+ Super::BeginDestroy();
+ PerSkeletonData.Empty();
+
+ // Unregister tick function
+ TickFunction.UnRegisterTickFunction();
+ TickFunction.Manager = nullptr;
+}
+
+UWorld* UAnimationSharingManager::GetWorld() const
+{
+ return Cast(GetOuter());
+}
+
+UAnimationSharingManager* UAnimationSharingManager::GetAnimationSharingManager(UObject* WorldContextObject)
+{
+ if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
+ {
+ return GetManagerForWorld(World);
+ }
+
+ return nullptr;
+}
+
+UAnimationSharingManager* UAnimationSharingManager::GetManagerForWorld(UWorld* InWorld)
+{
+ return FAnimSharingModule::Get(InWorld);
+}
+
+void FTickAnimationSharingFunction::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
+{
+ if (ensure(Manager))
+ {
+ Manager->Tick(DeltaTime);
+ }
+}
+
+FString FTickAnimationSharingFunction::DiagnosticMessage()
+{
+ return TEXT("FTickAnimationSharingFunction");
+}
+
+FName FTickAnimationSharingFunction::DiagnosticContext(bool bDetailed)
+{
+ return FName(TEXT("TickAnimationSharing"));
+}
+
+
+FTickAnimationSharingFunction& UAnimationSharingManager::GetTickFunction()
+{
+ return TickFunction;
+}
+
+void UAnimationSharingManager::Initialise(const UAnimationSharingSetup* InSetup)
+{
+ if (InSetup)
+ {
+ TickFunction.Manager = this;
+ TickFunction.RegisterTickFunction(GetWorld()->PersistentLevel);
+
+ ScalabilitySettings = InSetup->ScalabilitySettings;
+
+#if WITH_EDITOR
+ // Update local copy defaults with current platform value
+ const FName PlatformName = UAnimationSharingManager::GetPlatformName();
+ ScalabilitySettings.UseBlendTransitions = ScalabilitySettings.UseBlendTransitions.GetValueForPlatformIdentifiers(PlatformName, PlatformName);
+ ScalabilitySettings.BlendSignificanceValue = ScalabilitySettings.BlendSignificanceValue.GetValueForPlatformIdentifiers(PlatformName, PlatformName);
+ ScalabilitySettings.MaximumNumberConcurrentBlends = ScalabilitySettings.MaximumNumberConcurrentBlends.GetValueForPlatformIdentifiers(PlatformName, PlatformName);
+ ScalabilitySettings.TickSignificanceValue = ScalabilitySettings.TickSignificanceValue.GetValueForPlatformIdentifiers(PlatformName, PlatformName);
+#endif
+
+ // Debug materials
+#if DEBUG_MATERIALS
+ DebugMaterials.Empty();
+ {
+ UMaterialInterface* RedMaterial = LoadObject(nullptr, TEXT("/AnimationSharing/AnimSharingRed.AnimSharingRed"));
+ DebugMaterials.Add(RedMaterial);
+ UMaterialInterface* GreenMaterial = LoadObject(nullptr, TEXT("/AnimationSharing/AnimSharingGreen.AnimSharingGreen"));
+ DebugMaterials.Add(GreenMaterial);
+ UMaterialInterface* BlueMaterial = LoadObject(nullptr, TEXT("/AnimationSharing/AnimSharingBlue.AnimSharingBlue"));
+ DebugMaterials.Add(BlueMaterial);
+ }
+#endif
+ UWorld* World = GetWorld();
+
+ for (const FPerSkeletonAnimationSharingSetup& SkeletonSetup : InSetup->SkeletonSetups)
+ {
+ SetupPerSkeletonData(SkeletonSetup);
+ }
+ }
+}
+
+const FAnimationSharingScalability& UAnimationSharingManager::GetScalabilitySettings() const
+{
+ return ScalabilitySettings;
+}
+
+void UAnimationSharingManager::SetupPerSkeletonData(const FPerSkeletonAnimationSharingSetup& SkeletonSetup)
+{
+ const USkeleton* Skeleton = SkeletonSetup.Skeleton.LoadSynchronous();
+ UEnum* StateEnum = SkeletonSetup.StateProcessorClass->GetDefaultObject()->GetAnimationStateEnum();
+ if (Skeleton && StateEnum)
+ {
+ UAnimSharingInstance* Data = NewObject(this);
+ PerSkeletonData.Add(Data);
+ Skeletons.Add(Skeleton);
+ Data->Setup(this, SkeletonSetup, &ScalabilitySettings, Skeletons.Num() - 1);
+ }
+}
+
+uint32 UAnimationSharingManager::CreateActorHandle(uint8 SkeletonIndex, uint32 ActorIndex) const
+{
+ ensureMsgf(ActorIndex <= 0xFFFFFF, TEXT("Invalid Actor Handle due to overflow"));
+ return (SkeletonIndex << 24) | ActorIndex;
+}
+
+uint8 UAnimationSharingManager::GetSkeletonIndexFromHandle(uint32 InHandle) const
+{
+ return (InHandle & 0xFF000000) >> 24;
+}
+
+uint32 UAnimationSharingManager::GetActorIndexFromHandle(uint32 InHandle) const
+{
+ return (InHandle & 0x00FFFFFF);
+}
+
+void UAnimationSharingManager::Tick(float DeltaTime)
+{
+ SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_Tick);
+
+ const float WorldTime = GetWorld()->GetTimeSeconds();
+
+ /** Keeping track of currently running instances / animations for debugging purposes */
+ int32 TotalNumBlends = 0;
+ int32 TotalNumOnDemands = 0;
+ int32 TotalNumComponents = 0;
+ int32 TotalNumActors = 0;
+
+ int32 TotalNumRunningStates = 0;
+ int32 TotalNumRunningComponents = 0;
+
+ /** Iterator over all Skeleton setups */
+ for (int32 Index = 0; Index < PerSkeletonData.Num(); ++Index)
+ {
+ UAnimSharingInstance* Instance = PerSkeletonData[Index];
+ Instance->WorldTime = WorldTime;
+
+ /** Tick both Blend and On-Demand instances first, as they could be finishing */
+ Instance->TickBlendInstances();
+ Instance->TickOnDemandInstances();
+ Instance->TickAdditiveInstances();
+
+ /** Tick actor states */
+ Instance->TickActorStates();
+
+ /** Setup and start any blending transitions created while ticking the actor states */
+ Instance->KickoffInstances();
+
+#if !UE_BUILD_SHIPPING
+ if (GAnimationSharingDebugging >= 1)
+ {
+ Instance->TickDebugInformation();
+ }
+#endif
+ /** Tick the animation states to determine which components should be turned on/off */
+ Instance->TickAnimationStates();
+
+#if DETAIL_STATS
+ /** Stat counters */
+ TotalNumOnDemands += Instance->OnDemandInstances.Num();
+ TotalNumBlends += Instance->BlendInstances.Num();
+ TotalNumActors += Instance->PerActorData.Num();
+ TotalNumComponents += Instance->PerComponentData.Num();
+
+ for (FPerStateData& StateData : Instance->PerStateData)
+ {
+ if (StateData.InUseComponentFrameBits.Contains(true))
+ {
+ ++TotalNumRunningStates;
+ }
+
+ for (int32 ComponentIndex = 0; ComponentIndex < StateData.PreviousInUseComponentFrameBits.Num(); ++ComponentIndex)
+ {
+ if (StateData.PreviousInUseComponentFrameBits[ComponentIndex] == true)
+ {
+ ++TotalNumRunningComponents;
+ }
+ }
+ }
+#endif // DETAIL_STATS
+ }
+
+#if DETAIL_STATS
+ SET_DWORD_STAT(STAT_AnimationSharing_NumOnDemands, TotalNumOnDemands);
+ SET_DWORD_STAT(STAT_AnimationSharing_NumBlends, TotalNumBlends);
+
+ SET_DWORD_STAT(STAT_AnimationSharing_NumActors, TotalNumActors);
+ SET_DWORD_STAT(STAT_AnimationSharing_NumComponent, TotalNumComponents);
+
+ SET_DWORD_STAT(STAT_AnimationSharing_NumBlends, TotalNumBlends);
+#endif // DETAIL_STATS
+
+#if CSV_STATS
+ CSV_CUSTOM_STAT_GLOBAL(NumOnDemands, TotalNumOnDemands, ECsvCustomStatOp::Set);
+ CSV_CUSTOM_STAT_GLOBAL(NumBlends, TotalNumBlends, ECsvCustomStatOp::Set);
+ CSV_CUSTOM_STAT_GLOBAL(NumRunningStates, TotalNumRunningStates, ECsvCustomStatOp::Set);
+ CSV_CUSTOM_STAT_GLOBAL(NumRunningComponents, TotalNumRunningComponents, ECsvCustomStatOp::Set);
+#endif
+
+}
+
+void UAnimationSharingManager::RegisterActor(AActor* InActor, FUpdateActorHandle CallbackDelegate)
+{
+ if (!UAnimationSharingManager::AnimationSharingEnabled())
+ {
+ return;
+ }
+
+ if (InActor)
+ {
+ TArray> OwnedComponents;
+ InActor->GetComponents(OwnedComponents);
+ checkf(OwnedComponents.Num(), TEXT("No SkeletalMeshComponents found in actor!"));
+
+ const USkeleton* UsedSkeleton = [&]()
+ {
+ USkeleton* CurrentSkeleton = nullptr;
+ for (USkeletalMeshComponent* SkeletalMeshComponent : OwnedComponents)
+ {
+ const USkeletalMesh* Mesh = SkeletalMeshComponent->SkeletalMesh;
+ USkeleton* Skeleton = Mesh->Skeleton;
+
+ if (CurrentSkeleton == nullptr)
+ {
+ CurrentSkeleton = Skeleton;
+ }
+ else if (CurrentSkeleton != Skeleton)
+ {
+ if (!CurrentSkeleton->IsCompatibleMesh(Mesh))
+ {
+ checkf(false, TEXT("Multiple different skeletons within same actor"));
+ }
+ }
+ }
+
+ return CurrentSkeleton;
+ }();
+
+ RegisterActorWithSkeleton(InActor, UsedSkeleton, CallbackDelegate);
+ }
+}
+
+void UAnimationSharingManager::RegisterActorWithSkeleton(AActor* InActor, const USkeleton* SharingSkeleton, FUpdateActorHandle CallbackDelegate)
+{
+ if (!UAnimationSharingManager::AnimationSharingEnabled())
+ {
+ return;
+ }
+
+ const AnimationSharingDataHandle Handle = [&]() -> uint32
+ {
+ uint32 ArrayIndex = Skeletons.IndexOfByPredicate([&](const USkeleton* Skeleton)
+ {
+ return (Skeleton == SharingSkeleton) || (Skeleton->IsCompatible(SharingSkeleton));
+ });
+ ensureMsgf(ArrayIndex != INDEX_NONE, TEXT("Invalid skeleton for which there is no sharing setup available!"));
+ return ArrayIndex;
+ }();
+
+ if (Handle != INDEX_NONE)
+ {
+ TArray> OwnedComponents;
+ InActor->GetComponents(OwnedComponents);
+ checkf(OwnedComponents.Num(), TEXT("No SkeletalMeshComponents found in actor!"));
+
+ UAnimSharingInstance* Data = PerSkeletonData[Handle];
+
+ // Register the actor
+ const int32 ActorIndex = Data->RegisteredActors.Add(InActor);
+
+ FPerActorData& ActorData = Data->PerActorData.AddZeroed_GetRef();
+ ActorData.BlendInstanceIndex = ActorData.OnDemandInstanceIndex = ActorData.AdditiveInstanceIndex = INDEX_NONE;
+ ActorData.SignificanceValue = Data->SignificanceManager->GetSignificance(InActor);
+ ActorData.UpdateActorHandleDelegate = CallbackDelegate;
+
+ bool bShouldProcess = true;
+ ActorData.CurrentState = ActorData.PreviousState = Data->DetermineStateForActor(ActorIndex, bShouldProcess);
+
+ for (USkeletalMeshComponent* Component : OwnedComponents)
+ {
+ FPerComponentData& ComponentData = Data->PerComponentData.AddZeroed_GetRef();
+ ComponentData.ActorIndex = ActorIndex;
+ ComponentData.Component = Component;
+
+ Component->PrimaryComponentTick.bCanEverTick = false;
+ Component->SetComponentTickEnabled(false);
+ Component->bIgnoreMasterPoseComponentLOD = true;
+
+ ActorData.ComponentIndices.Add(Data->PerComponentData.Num() - 1);
+
+ const int32 ComponentIndex = Data->PerComponentData.Num() - 1;
+ Data->SetupSlaveComponent(ActorData.CurrentState, ActorIndex);
+ }
+
+ if (Data->PerStateData[ActorData.CurrentState].bIsOnDemand && ActorData.OnDemandInstanceIndex != INDEX_NONE)
+ {
+ // We will have setup an on-demand instance so we need to kick it off here before we next tick
+ Data->OnDemandInstances[ActorData.OnDemandInstanceIndex].bActive = true;
+ Data->OnDemandInstances[ActorData.OnDemandInstanceIndex].StartTime = Data->WorldTime;
+ }
+
+ const int32 ActorHandle = CreateActorHandle(Handle, ActorIndex);
+ ActorData.UpdateActorHandleDelegate.ExecuteIfBound(ActorHandle);
+ }
+}
+
+void UAnimationSharingManager::RegisterActorWithSkeletonBP(AActor* InActor, const USkeleton* SharingSkeleton)
+{
+ RegisterActorWithSkeleton(InActor, SharingSkeleton, FUpdateActorHandle::CreateLambda([](int32 A) {}));
+}
+
+void UAnimationSharingManager::UnregisterActor(AActor* InActor)
+{
+ if (!UAnimationSharingManager::AnimationSharingEnabled())
+ {
+ return;
+ }
+
+ for (int32 SkeletonIndex = 0; SkeletonIndex < PerSkeletonData.Num(); ++SkeletonIndex)
+ {
+ UAnimSharingInstance* SkeletonData = PerSkeletonData[SkeletonIndex];
+ const int32 ActorIndex = SkeletonData->RegisteredActors.IndexOfByKey(InActor);
+
+ if (ActorIndex != INDEX_NONE )
+ {
+ const FPerActorData& ActorData = SkeletonData->PerActorData[ActorIndex];
+
+ const bool bNeedsSwap = SkeletonData->PerActorData.Num() > 1 && ActorIndex != SkeletonData->PerActorData.Num() - 1;
+
+ for (int32 ComponentIndex : ActorData.ComponentIndices)
+ {
+ SkeletonData->PerComponentData[ComponentIndex].Component->SetMasterPoseComponent(nullptr, true);
+ SkeletonData->PerComponentData[ComponentIndex].Component->SetComponentTickEnabled(true);
+ SkeletonData->RemoveComponent(ComponentIndex);
+ }
+
+ const int32 SwapIndex = SkeletonData->PerActorData.Num() - 1;
+
+ // Remove actor index from any blend instances
+ for (FBlendInstance& Instance : SkeletonData->BlendInstances)
+ {
+ Instance.ActorIndices.Remove(ActorIndex);
+
+ // If we are swapping and the actor we are swapping with is part of the instance make sure we update the actor index
+ const uint32 SwapActorIndex = bNeedsSwap ? Instance.ActorIndices.IndexOfByKey(SwapIndex) : INDEX_NONE;
+ if (SwapActorIndex != INDEX_NONE)
+ {
+ Instance.ActorIndices[SwapActorIndex] = ActorIndex;
+ }
+ }
+
+ // Remove actor index from any running on demand instances
+ for (FOnDemandInstance& Instance : SkeletonData->OnDemandInstances)
+ {
+ Instance.ActorIndices.Remove(ActorIndex);
+
+ // If we are swapping and the actor we are swapping with is part of the instance make sure we update the actor index
+ const uint32 SwapActorIndex = bNeedsSwap ? Instance.ActorIndices.IndexOfByKey(SwapIndex) : INDEX_NONE;
+ if (SwapActorIndex != INDEX_NONE)
+ {
+ Instance.ActorIndices[SwapActorIndex] = ActorIndex;
+ }
+ }
+
+ // Remove actor index from any additive instances
+ for (FAdditiveInstance& Instance : SkeletonData->AdditiveInstances)
+ {
+ if (Instance.ActorIndex == ActorIndex)
+ {
+ Instance.ActorIndex = INDEX_NONE;
+ }
+ else if (bNeedsSwap && Instance.ActorIndex == SwapIndex)
+ {
+ Instance.ActorIndex = ActorIndex;
+ }
+ }
+
+ if (bNeedsSwap)
+ {
+ // Swap actor index for all components which are part of the actor we are swapping with
+ for (uint32 ComponentIndex : SkeletonData->PerActorData[SwapIndex].ComponentIndices)
+ {
+ SkeletonData->PerComponentData[ComponentIndex].ActorIndex = ActorIndex;
+ }
+
+ // Make sure we update the handle on the swapped actor
+ SkeletonData->PerActorData[SwapIndex].UpdateActorHandleDelegate.ExecuteIfBound(CreateActorHandle(SkeletonIndex, ActorIndex));
+ }
+
+ SkeletonData->PerActorData.RemoveAtSwap(ActorIndex, 1, false);
+ SkeletonData->RegisteredActors.RemoveAtSwap(ActorIndex, 1, false);
+ }
+ }
+}
+
+void UAnimationSharingManager::UpdateSignificanceForActorHandle(uint32 InHandle, float InValue)
+{
+ // Retrieve actor
+ if (FPerActorData* ActorData = GetActorDataByHandle(InHandle))
+ {
+ ActorData->SignificanceValue = InValue;
+ }
+}
+
+FPerActorData* UAnimationSharingManager::GetActorDataByHandle(uint32 InHandle)
+{
+ FPerActorData* ActorDataPtr = nullptr;
+ uint8 SkeletonIndex = GetSkeletonIndexFromHandle(InHandle);
+ uint32 ActorIndex = GetActorIndexFromHandle(InHandle);
+ if (PerSkeletonData.IsValidIndex(SkeletonIndex))
+ {
+ if (PerSkeletonData[SkeletonIndex]->PerActorData.IsValidIndex(ActorIndex))
+ {
+ ActorDataPtr = &PerSkeletonData[SkeletonIndex]->PerActorData[ActorIndex];
+ }
+ }
+
+ return ActorDataPtr;
+}
+
+void UAnimationSharingManager::ClearActorData()
+{
+ UnregisterAllActors();
+
+ for (UAnimSharingInstance* Data : PerSkeletonData)
+ {
+ Data->BlendInstances.Empty();
+ Data->OnDemandInstances.Empty();
+ }
+}
+
+void UAnimationSharingManager::UnregisterAllActors()
+{
+ for (UAnimSharingInstance* Data : PerSkeletonData)
+ {
+ for (int32 ActorIndex = 0; ActorIndex < Data->RegisteredActors.Num(); ++ActorIndex)
+ {
+ AActor* RegisteredActor = Data->RegisteredActors[ActorIndex];
+ if (RegisteredActor)
+ {
+ FPerActorData& ActorData = Data->PerActorData[ActorIndex];
+ for (int32 ComponentIndex : ActorData.ComponentIndices)
+ {
+ Data->PerComponentData[ComponentIndex].Component->SetMasterPoseComponent(nullptr, true);
+ Data->PerComponentData[ComponentIndex].Component->PrimaryComponentTick.bCanEverTick = true;
+ Data->PerComponentData[ComponentIndex].Component->SetComponentTickEnabled(true);
+ Data->PerComponentData[ComponentIndex].Component->bRecentlyRendered = false;
+ }
+ ActorData.ComponentIndices.Empty();
+ }
+ }
+
+ Data->PerActorData.Empty();
+ Data->PerComponentData.Empty();
+ Data->RegisteredActors.Empty();
+ }
+}
+
+void UAnimationSharingManager::SetMasterComponentsVisibility(bool bVisible)
+{
+ for (UAnimSharingInstance* Data : PerSkeletonData)
+ {
+ for (FPerStateData& StateData : Data->PerStateData)
+ {
+ for (USkeletalMeshComponent* Component : StateData.Components)
+ {
+ Component->SetVisibility(bVisible);
+ }
+ }
+
+ for (FTransitionBlendInstance* Instance : Data->BlendInstanceStack.AvailableInstances)
+ {
+ Instance->GetComponent()->SetVisibility(bVisible);
+ }
+
+ for (FTransitionBlendInstance* Instance : Data->BlendInstanceStack.InUseInstances)
+ {
+ Instance->GetComponent()->SetVisibility(bVisible);
+ }
+
+ for (FAdditiveAnimationInstance* Instance : Data->AdditiveInstanceStack.AvailableInstances)
+ {
+ Instance->GetComponent()->SetVisibility(bVisible);
+ }
+
+ for (FAdditiveAnimationInstance* Instance : Data->AdditiveInstanceStack.InUseInstances)
+ {
+ Instance->GetComponent()->SetVisibility(bVisible);
+ }
+ }
+}
+
+bool UAnimationSharingManager::AnimationSharingEnabled()
+{
+ return GAnimationSharingEnabled == 1;
+}
+
+bool UAnimationSharingManager::CreateAnimationSharingManager(UObject* WorldContextObject, const UAnimationSharingSetup* Setup)
+{
+ if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
+ {
+ return FAnimSharingModule::CreateAnimationSharingManager(World, Setup);
+ }
+
+ return false;
+}
+
+void UAnimationSharingManager::SetDebugMaterial(USkeletalMeshComponent* Component, uint8 State)
+{
+#if DEBUG_MATERIALS
+ if (GAnimationSharingDebugging >= 1 && DebugMaterials.IsValidIndex(State))
+ {
+ const int32 NumMaterials = Component->GetNumMaterials();
+ for (int32 Index = 0; Index < NumMaterials; ++Index)
+ {
+ Component->SetMaterial(Index, DebugMaterials[State]);
+ }
+ }
+#endif
+}
+
+void UAnimationSharingManager::SetDebugMaterialForActor(UAnimSharingInstance* Data, uint32 ActorIndex, uint8 State)
+{
+#if DEBUG_MATERIALS
+ for (uint32 ComponentIndex : Data->PerActorData[ActorIndex].ComponentIndices)
+ {
+ SetDebugMaterial(Data->PerComponentData[ComponentIndex].Component, State);
+ }
+#endif
+}
+
+#if WITH_EDITOR
+FName UAnimationSharingManager::GetPlatformName()
+{
+ const FString PlatformString = CVarAnimSharing_PreviewScalabilityPlatform.GetValueOnAnyThread();
+ if (PlatformString.IsEmpty())
+ {
+ ITargetPlatform* CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform();
+ return CurrentPlatform->GetPlatformInfo().PlatformGroupName;
+ }
+
+ FName PlatformNameFromString(*PlatformString);
+ return PlatformNameFromString;
+}
+#endif
+
+void UAnimSharingInstance::BeginDestroy()
+{
+ Super::BeginDestroy();
+ for (FPerActorData& ActorData : PerActorData)
+ {
+ for (uint32 ComponentIndex : ActorData.ComponentIndices)
+ {
+ PerComponentData[ComponentIndex].Component->SetMasterPoseComponent(nullptr, true);
+ }
+ }
+
+ RegisteredActors.Empty();
+ PerActorData.Empty();
+ PerComponentData.Empty();
+ PerStateData.Empty();
+ StateProcessor = nullptr;
+ StateEnum = nullptr;
+ BlendInstances.Empty();
+ OnDemandInstances.Empty();
+}
+
+uint8 UAnimSharingInstance::DetermineStateForActor(uint32 ActorIndex, bool& bShouldProcess)
+{
+ const FPerActorData& ActorData = PerActorData[ActorIndex];
+ int32 State = 0;
+ if (bNativeStateProcessor)
+ {
+ StateProcessor->ProcessActorState_Implementation(State, RegisteredActors[ActorIndex], ActorData.CurrentState, ActorData.OnDemandInstanceIndex != INDEX_NONE ? OnDemandInstances[ActorData.OnDemandInstanceIndex].State : INDEX_NONE, bShouldProcess);
+ }
+ else
+ {
+ StateProcessor->ProcessActorState(State, RegisteredActors[ActorIndex], ActorData.CurrentState, ActorData.OnDemandInstanceIndex != INDEX_NONE ? OnDemandInstances[ActorData.OnDemandInstanceIndex].State : INDEX_NONE, bShouldProcess);
+ }
+
+ return FMath::Max(0, State);
+}
+
+void UAnimSharingInstance::Setup(UAnimationSharingManager* AnimationSharingManager, const FPerSkeletonAnimationSharingSetup& SkeletonSetup, const FAnimationSharingScalability* InScalabilitySettings, uint32 Index)
+{
+ USkeletalMesh* SkeletalMesh = SkeletonSetup.SkeletalMesh.LoadSynchronous();
+ /** Retrieve the state processor to use */
+ if (UAnimationSharingStateProcessor* Processor = SkeletonSetup.StateProcessorClass.GetDefaultObject())
+ {
+ StateProcessor = Processor;
+ bNativeStateProcessor = SkeletonSetup.StateProcessorClass->HasAnyClassFlags(CLASS_Native);
+ }
+
+ if (SkeletalMesh && StateProcessor)
+ {
+ SkeletalMeshBounds = SkeletalMesh->GetBounds().BoxExtent * 2;
+ ScalabilitySettings = InScalabilitySettings;
+ StateEnum = StateProcessor->GetAnimationStateEnum();
+ const uint32 NumStates = StateEnum->NumEnums();
+ PerStateData.AddDefaulted(NumStates);
+
+ UWorld* World = GetWorld();
+ SharingActor = World->SpawnActor();
+ // Make sure the actor stays around when scrubbing through replays, states will be updated correctly in next tick
+ SharingActor->bReplayRewindable = true;
+ SignificanceManager = USignificanceManager::Get(World);
+ AnimSharingManager = AnimationSharingManager;
+
+ /** Create runtime data structures for unique animation states */
+ NumSetups = 0;
+ for (const FAnimationStateEntry& StateEntry : SkeletonSetup.AnimationStates)
+ {
+ const uint8 StateValue = StateEntry.State;
+ const uint32 StateIndex = StateEnum->GetIndexByValue(StateValue);
+
+ checkf(!PerStateData.FindByPredicate([&](const FPerStateData& State) { return State.StateEnumValue == StateValue; }), TEXT("State already defined"));
+
+ FPerStateData& StateData = PerStateData[StateIndex];
+ StateData.StateEnumValue = StateValue;
+ SetupState(StateData, StateEntry, SkeletalMesh, SkeletonSetup, Index);
+ }
+
+ /** Setup blend actors, if enabled*/
+ if (ScalabilitySettings->UseBlendTransitions.Default)
+ {
+ const uint32 TotalNumberOfBlendActorsRequired = ScalabilitySettings->MaximumNumberConcurrentBlends.Default;
+ const float ZOffset = Index * SkeletalMeshBounds.Z * 2.f;
+ for (uint32 BlendIndex = 0; BlendIndex < TotalNumberOfBlendActorsRequired; ++BlendIndex)
+ {
+ const FVector SpawnLocation(BlendIndex * SkeletalMeshBounds.X, 0.f, ZOffset + SkeletalMeshBounds.Z);
+ const FName BlendComponentName(*(SkeletalMesh->GetName() + TEXT("_BlendComponent") + FString::FromInt(BlendIndex)));
+ USkeletalMeshComponent* BlendComponent = NewObject(SharingActor, BlendComponentName);
+ BlendComponent->RegisterComponent();
+ BlendComponent->SetRelativeLocation(SpawnLocation);
+ BlendComponent->SetSkeletalMesh(SkeletalMesh);
+ BlendComponent->SetVisibility(GMasterComponentsVisible == 1);
+
+ BlendComponent->PrimaryComponentTick.AddPrerequisite(AnimSharingManager, AnimSharingManager->GetTickFunction());
+
+ FTransitionBlendInstance* BlendActor = new FTransitionBlendInstance();
+ BlendActor->Initialise(BlendComponent, SkeletonSetup.BlendAnimBlueprint.Get());
+ BlendInstanceStack.AddInstance(BlendActor);
+ }
+ }
+ }
+ else
+ {
+ UE_LOG(LogAnimationSharing, Error, TEXT("Invalid Skeletal Mesh or State Processing Class"));
+ }
+}
+
+void UAnimSharingInstance::SetupState(FPerStateData& StateData, const FAnimationStateEntry& StateEntry, USkeletalMesh* SkeletalMesh, const FPerSkeletonAnimationSharingSetup& SkeletonSetup, uint32 Index)
+{
+ /** Used for placing components into rows / columns at origin for debugging purposes */
+ const float ZOffset = Index * SkeletalMeshBounds.Z * 2.f;
+
+ /** Setup overall data and flags */
+ StateData.bIsOnDemand = StateEntry.bOnDemand;
+ StateData.bIsAdditive = StateEntry.bAdditive;
+ StateData.AdditiveAnimationSequence = (StateEntry.bAdditive && StateEntry.AnimationSetups.IsValidIndex(0)) ? StateEntry.AnimationSetups[0].AnimSequence.LoadSynchronous() : nullptr;
+
+ /** Keep hard reference to animation sequence */
+ if (StateData.AdditiveAnimationSequence)
+ {
+ UsedAnimationSequences.Add(StateData.AdditiveAnimationSequence);
+ }
+
+ StateData.BlendTime = StateEntry.BlendTime;
+ StateData.bReturnToPreviousState = StateEntry.bReturnToPreviousState;
+ StateData.bShouldForwardToState = StateEntry.bSetNextState;
+ StateData.ForwardStateValue = StateEntry.NextState;
+
+ int32 MaximumNumberOfConcurrentInstances = StateEntry.MaximumNumberOfConcurrentInstances.Default;
+#if WITH_EDITOR
+ const FName PlatformName = UAnimationSharingManager::GetPlatformName();
+ MaximumNumberOfConcurrentInstances = StateEntry.MaximumNumberOfConcurrentInstances.GetValueForPlatformIdentifiers(PlatformName, PlatformName);
+#endif
+
+ /** Ensure that we spread our number over the number of enabled setups */
+ const int32 NumInstancesPerSetup = [MaximumNumberOfConcurrentInstances, &StateEntry]()
+ {
+ int32 TotalEnabled = 0;
+ for (const FAnimationSetup& AnimationSetup : StateEntry.AnimationSetups)
+ {
+ bool bEnabled = AnimationSetup.Enabled.Default;
+#if WITH_EDITOR
+ const FName PlatformName = UAnimationSharingManager::GetPlatformName();
+ bEnabled = AnimationSetup.Enabled.GetValueForPlatformIdentifiers(PlatformName, PlatformName);
+#endif
+ TotalEnabled += bEnabled ? 1 : 0;
+ }
+
+ return (TotalEnabled > 0) ? FMath::CeilToInt((float)MaximumNumberOfConcurrentInstances / (float)TotalEnabled) : 0;
+ }();
+
+ UWorld* World = GetWorld();
+ /** Setup animations used for this state and the number of permutations */
+ TArray& Components = StateData.Components;
+ for (int32 SetupIndex = 0; SetupIndex < StateEntry.AnimationSetups.Num(); ++SetupIndex)
+ {
+ const FAnimationSetup& AnimationSetup = StateEntry.AnimationSetups[SetupIndex];
+ /** User can setup either an AnimBP or AnimationSequence */
+ UClass* AnimBPClass = AnimationSetup.AnimBlueprint.Get();
+ UAnimSequence* AnimSequence = AnimationSetup.AnimSequence.LoadSynchronous();
+ ensureMsgf(AnimBPClass != nullptr || AnimSequence != nullptr, TEXT("Animation setup without either an Animation Blueprint Class of Animation Sequence"));
+
+ bool bEnabled = AnimationSetup.Enabled.Default;
+#if WITH_EDITOR
+ bEnabled = AnimationSetup.Enabled.GetValueForPlatformIdentifiers(PlatformName, PlatformName);
+#endif
+
+ /** Only create component if the setup is enabled for this platform and we have a valid animation asset */
+ if (bEnabled && (AnimBPClass || AnimSequence))
+ {
+ int32 NumRandomizedInstances = AnimationSetup.NumRandomizedInstances.Default;
+#if WITH_EDITOR
+ NumRandomizedInstances = AnimationSetup.NumRandomizedInstances.GetValueForPlatformIdentifiers(PlatformName, PlatformName);
+#endif
+ const uint32 NumInstances = StateEntry.bOnDemand ? NumInstancesPerSetup : NumRandomizedInstances;
+ for (uint32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex)
+ {
+ if (!StateData.bIsAdditive)
+ {
+ const FName StateComponentName(*(SkeletalMesh->GetName() + TEXT("_") + StateEnum->GetNameStringByIndex(StateEntry.State) + FString::FromInt(SetupIndex) + FString::FromInt(InstanceIndex)));
+ USkeletalMeshComponent* Component = NewObject(SharingActor, StateComponentName);
+ Component->RegisterComponent();
+ /** Arrange component in correct row / column */
+ Component->SetRelativeLocation(FVector(NumSetups * SkeletalMeshBounds.X, 0.f, ZOffset));
+ /** Set shared skeletal mesh */
+ Component->SetSkeletalMesh(SkeletalMesh);
+ Component->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
+ Component->SetForcedLOD(1);
+ Component->SetVisibility(GMasterComponentsVisible == 1);
+ Component->bPropagateCurvesToSlaves = StateEntry.bRequiresCurves;
+
+ if (AnimBPClass != nullptr && AnimSequence != nullptr)
+ {
+ Component->SetAnimInstanceClass(AnimBPClass);
+ if (UAnimSharingStateInstance* AnimInstance = Cast(Component->GetAnimInstance()))
+ {
+ AnimInstance->AnimationToPlay = AnimSequence;
+ if (InstanceIndex > 0)
+ {
+ const float Steps = (AnimSequence->SequenceLength * 0.9f) / (NumInstances);
+ const float StartTimeOffset = Steps * InstanceIndex;
+ AnimInstance->PermutationTimeOffset = StartTimeOffset;
+ }
+
+ AnimInstance->PlayRate = StateData.bIsOnDemand ? 0.f : 1.0f;
+
+ AnimInstance->Instance = this;
+ AnimInstance->StateIndex = StateEntry.State;
+ AnimInstance->ComponentIndex = Components.Num();
+
+ /** Set the current animation length length */
+ StateData.AnimationLengths.Add(AnimSequence->SequenceLength);
+ }
+ }
+ else if (AnimSequence != nullptr)
+ {
+ Component->PlayAnimation(AnimSequence, true);
+
+ /** If this is an on-demand state we pause the animation as we'll want to start it from the beginning anytime we start an on-demand instance */
+ if (StateData.bIsOnDemand)
+ {
+ Component->Stop();
+ }
+ else
+ {
+ if (InstanceIndex > 0)
+ {
+ const float Steps = (AnimSequence->SequenceLength * 0.9f) / (NumInstances);
+ const float StartTimeOffset = Steps * InstanceIndex;
+ Component->SetPosition(StartTimeOffset, false);
+ }
+ }
+
+ /** Set the current animation length length */
+ StateData.AnimationLengths.Add(AnimSequence->SequenceLength);
+ }
+
+ /** Set material to red to indicate that it's not in use*/
+ UAnimationSharingManager::SetDebugMaterial(Component, 0);
+
+ Component->PrimaryComponentTick.AddPrerequisite(AnimSharingManager, AnimSharingManager->GetTickFunction());
+ Components.Add(Component);
+ }
+ else
+ {
+ const FVector SpawnLocation(FVector(NumSetups * SkeletalMeshBounds.X, 0.f, ZOffset));
+ const FName AdditiveComponentName(*(SkeletalMesh->GetName() + TEXT("_") + StateEnum->GetNameStringByIndex(StateEntry.State) + FString::FromInt(InstanceIndex)));
+ USkeletalMeshComponent* AdditiveComponent = NewObject(SharingActor, AdditiveComponentName);
+ AdditiveComponent->RegisterComponent();
+ AdditiveComponent->SetRelativeLocation(SpawnLocation);
+ AdditiveComponent->SetSkeletalMesh(SkeletalMesh);
+ AdditiveComponent->SetVisibility(GMasterComponentsVisible == 1);
+
+ AdditiveComponent->PrimaryComponentTick.AddPrerequisite(AnimSharingManager, AnimSharingManager->GetTickFunction());
+
+ FAdditiveAnimationInstance* AdditiveInstance = new FAdditiveAnimationInstance();
+ AdditiveInstance->Initialise(AdditiveComponent, SkeletonSetup.AdditiveAnimBlueprint.Get());
+ AdditiveInstanceStack.AddInstance(AdditiveInstance);
+ }
+
+ ++NumSetups;
+ }
+ }
+ }
+
+ float TotalLength = 0.f;
+ for (float Length : StateData.AnimationLengths)
+ {
+ TotalLength += Length;
+ }
+ const float AverageLength = (StateData.AnimationLengths.Num() > 0) ? TotalLength / FMath::Min((float)StateData.AnimationLengths.Num(), 1.f) : 0.f;
+ StateData.WiggleTime = AverageLength * StateEntry.WiggleTimePercentage;
+
+ /** Randomizes the order of Components so we actually hit different animations when running on demand */
+ if (StateData.bIsOnDemand && !StateData.bIsAdditive && StateEntry.AnimationSetups.Num() > 1)
+ {
+ TArray RandomizedComponents;
+ while (Components.Num() > 0)
+ {
+ const int32 RandomIndex = FMath::RandRange(0, Components.Num() - 1);
+ RandomizedComponents.Add(Components[RandomIndex]);
+ Components.RemoveAt(RandomIndex, 1);
+ }
+
+ Components = RandomizedComponents;
+ }
+
+ /** Initialize component (previous frame) usage flags */
+ StateData.InUseComponentFrameBits.Init(false, Components.Num());
+ /** This should enforce turning off the components tick during the first frame */
+ StateData.PreviousInUseComponentFrameBits.Init(true, Components.Num());
+
+ StateData.SlaveTickRequiredFrameBits.Init(false, Components.Num());
+}
+
+void UAnimSharingInstance::TickDebugInformation()
+{
+#if !UE_BUILD_SHIPPING
+#if UE_BUILD_DEVELOPMENT
+ if (GMasterComponentsVisible && GAnimationSharingDebugging >= 2)
+ {
+ for (const FPerStateData& StateData : PerStateData)
+ {
+ for (int32 Index = 0; Index < StateData.InUseComponentFrameBits.Num(); ++Index)
+ {
+ const FString ComponentString = FString::Printf(TEXT("In Use %s - Required %s"), StateData.InUseComponentFrameBits[Index] ? TEXT("True") : TEXT("False"), StateData.SlaveTickRequiredFrameBits[Index] ? TEXT("True") : TEXT("False"));
+ DrawDebugString(GetWorld(), StateData.Components[Index]->GetComponentLocation() + FVector(0,0,StateData.Components[Index]->Bounds.BoxExtent.Z), ComponentString, nullptr, FColor::White, 0.016f, false);
+ }
+ }
+ }
+#endif // UE_BUILD_DEVELOPMENT
+
+
+ for (int32 ActorIndex = 0; ActorIndex < RegisteredActors.Num(); ++ActorIndex)
+ {
+ // Non-const for DrawDebugString
+ AActor* Actor = RegisteredActors[ActorIndex];
+ if (Actor)
+ {
+ const FPerActorData& ActorData = PerActorData[ActorIndex];
+ const uint8 State = ActorData.CurrentState;
+
+ const FString StateString = [&]() -> FString
+ {
+ /** Check whether or not we are currently blending */
+ const uint32 BlendInstanceIndex = ActorData.BlendInstanceIndex;
+ if (BlendInstanceIndex != INDEX_NONE && BlendInstances.IsValidIndex(BlendInstanceIndex))
+ {
+ const float TimeLeft = BlendInstances[BlendInstanceIndex].BlendTime - (GetWorld()->GetTimeSeconds() - BlendInstances[BlendInstanceIndex].EndTime);
+ return FString::Printf(TEXT("Blending states - %s to %s [%1.3f] (%i)"), *StateEnum->GetDisplayNameTextByValue(BlendInstances[BlendInstanceIndex].StateFrom).ToString(), *StateEnum->GetDisplayNameTextByValue(BlendInstances[BlendInstanceIndex].StateTo).ToString(), TimeLeft, ActorData.BlendInstanceIndex);
+ }
+
+ /** Check if we are part of an on-demand instance */
+ const uint32 DemandInstanceIndex = ActorData.OnDemandInstanceIndex;
+ if (DemandInstanceIndex != INDEX_NONE && OnDemandInstances.IsValidIndex(DemandInstanceIndex))
+ {
+ return FString::Printf(TEXT("On demand state - %s [%i]"), *StateEnum->GetDisplayNameTextByValue(State).ToString(), ActorData.OnDemandInstanceIndex);
+ }
+
+ /** Otherwise we should just be part of a state */
+ return FString::Printf(TEXT("State - %s %1.2f"), *StateEnum->GetDisplayNameTextByValue(State).ToString(), ActorData.SignificanceValue);
+ }();
+
+ const FColor DebugColor = [&]()
+ {
+ const uint32 BlendInstanceIndex = ActorData.BlendInstanceIndex;
+ const uint32 DemandInstanceIndex = ActorData.OnDemandInstanceIndex;
+
+ /** Colors match debug material colors */
+ if (ActorData.bBlending && BlendInstanceIndex != INDEX_NONE)
+ {
+ return FColor::Blue;
+ }
+ else if (ActorData.bRunningOnDemand && DemandInstanceIndex != INDEX_NONE)
+ {
+ return FColor::Red;
+ }
+
+ return FColor::Green;
+ }();
+
+#if UE_BUILD_DEVELOPMENT
+ /** Draw text above AI pawn's head */
+ DrawDebugString(GetWorld(), FVector(0.f, 0.f, 100.f), StateString, Actor, DebugColor, 0.016f, false);
+#endif
+ if (GAnimationSharingDebugging >= 2)
+ {
+ const FString OnScreenString = FString::Printf(TEXT("%s\n\tState %s [%i]\n\t%s\n\tBlending %i On-Demand %i"), *Actor->GetName(), *StateEnum->GetDisplayNameTextByValue(ActorData.CurrentState).ToString(), ActorData.PermutationIndex, *StateEnum->GetDisplayNameTextByValue(ActorData.PreviousState).ToString(), ActorData.bBlending, ActorData.bRunningOnDemand);
+
+ GEngine->AddOnScreenDebugMessage(1337, 1, FColor::White, OnScreenString);
+
+ const USkinnedMeshComponent* Component = PerComponentData[ActorData.ComponentIndices[0]].Component->MasterPoseComponent.Get();
+#if UE_BUILD_DEVELOPMENT
+ if (Component != nullptr)
+ {
+ DrawDebugLine(GetWorld(), Actor->GetActorLocation(), Component->GetComponentLocation(), FColor::Magenta);
+ }
+#endif
+ }
+
+
+ }
+ }
+#endif
+}
+
+void UAnimSharingInstance::TickOnDemandInstances()
+{
+ SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_UpdateOnDemands);
+ for (int32 InstanceIndex = 0; InstanceIndex < OnDemandInstances.Num(); ++ InstanceIndex)
+ {
+ FOnDemandInstance& Instance = OnDemandInstances[InstanceIndex];
+ checkf(Instance.bActive, TEXT("Container should be active at this point"));
+
+ // Mark on-demand component as in-use
+ SetComponentUsage(true, Instance.State, Instance.UsedPerStateComponentIndex);
+
+ const bool bShouldTick = DoAnyActorsRequireTicking(Instance);
+ if (bShouldTick)
+ {
+ // Mark component to tick
+ SetComponentTick(Instance.State, Instance.UsedPerStateComponentIndex);
+ }
+
+ // Check and see whether or not the animation has finished
+ if (Instance.EndTime <= WorldTime)
+ {
+ // Set in-use flag to false this should set the component to not tick during the next TickAnimationStates
+ SetComponentUsage(false, Instance.State, Instance.UsedPerStateComponentIndex);
+
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Finished on demand %s"), *StateEnum->GetDisplayNameTextByValue(Instance.State).ToString());
+#endif
+ auto SetActorState = [&](uint32 ActorIndex, uint8 NewState)
+ {
+ if (Instance.BlendToPermutationIndex != INDEX_NONE)
+ {
+ SetPermutationSlaveComponent(NewState, ActorIndex, Instance.BlendToPermutationIndex);
+ }
+ else
+ {
+ SetupSlaveComponent(NewState, ActorIndex);
+ }
+
+ // Set actor states
+ PerActorData[ActorIndex].PreviousState = PerActorData[ActorIndex].CurrentState;
+ PerActorData[ActorIndex].CurrentState = NewState;
+ };
+
+
+ // Set the components to their current state animation
+ for (uint32 ActorIndex : Instance.ActorIndices)
+ {
+ const uint32 CurrentState = PerActorData[ActorIndex].CurrentState;
+ // Return to the previous active animation state
+ if (Instance.bReturnToPreviousState)
+ {
+ //for (uint32 ActorIndex : Instance.ActorIndices)
+ {
+ // Retrieve previous state for the actor
+ const uint8 PreviousActorState = PerActorData[ActorIndex].PreviousState;
+ SetActorState(ActorIndex, PreviousActorState);
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Returning [%i] to %s"), ActorIndex, *StateEnum->GetDisplayNameTextByValue(PreviousActorState).ToString());
+#endif
+ }
+ }
+ else if (Instance.ForwardState != (uint8)INDEX_NONE)
+ {
+ // We could forward it to a different state at this point
+ SetActorState(ActorIndex, Instance.ForwardState);
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Forwarding [%i] to %s"), ActorIndex, *StateEnum->GetDisplayNameTextByValue(Instance.ForwardState).ToString());
+#endif
+ }
+ // Only do this if the state is different than the current on-demand one
+ else if (CurrentState != Instance.State)
+ {
+ // If the new state is not an on-demand one and we are not currently blending, if we are blending the blend will set the final master component
+ if (!PerStateData[CurrentState].bIsOnDemand || !Instance.bBlendActive)
+ {
+ SetActorState(ActorIndex, CurrentState);
+
+ UAnimationSharingManager::SetDebugMaterialForActor(this, ActorIndex, 1);
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Setting [%i] to %s"), ActorIndex, *StateEnum->GetDisplayNameTextByValue(CurrentState).ToString());
+#endif
+ }
+ }
+ else
+ {
+ // Otherwise what do we do TODO
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("TODO-ing [%i]"), ActorIndex);
+#endif
+ }
+ }
+
+ // Clear out data for each actor part of this instance
+ for (uint32 ActorIndex : Instance.ActorIndices)
+ {
+ const bool bPartOfOtherOnDemand = PerActorData[ActorIndex].OnDemandInstanceIndex != InstanceIndex;
+ //ensureMsgf(!bPartOfOtherOnDemand, TEXT("Actor on demand index differs from current instance"));
+
+ PerActorData[ActorIndex].OnDemandInstanceIndex = INDEX_NONE;
+ PerActorData[ActorIndex].bRunningOnDemand = false;
+ }
+
+ // Remove this instance as it has finished work
+ RemoveOnDemandInstance(InstanceIndex);
+
+ // Decrement index so we don't skip the swapped instance
+ --InstanceIndex;
+ }
+ else if (!Instance.bBlendActive && Instance.StartBlendTime <= WorldTime)
+ {
+ for (uint32 ActorIndex : Instance.ActorIndices)
+ {
+ // Whether or not we can/should actually blend
+ const bool bShouldBlend = ScalabilitySettings->UseBlendTransitions.Default && PerActorData[ActorIndex].SignificanceValue >= ScalabilitySettings->BlendSignificanceValue.Default;
+
+ // Determine state to blend to
+ const uint8 BlendToState = [&]() -> uint8
+ {
+ if (bShouldBlend)
+ {
+ bool bShouldProcess;
+ const uint32 DeterminedState = DetermineStateForActor(ActorIndex, bShouldProcess);
+ const uint32 CurrentState = PerActorData[ActorIndex].CurrentState != DeterminedState ? DeterminedState : PerActorData[ActorIndex].CurrentState;
+
+ if (Instance.bReturnToPreviousState)
+ {
+ // Setup blend from on-demand animation into next state animation
+ return PerActorData[ActorIndex].PreviousState;
+ }
+ else if (Instance.ForwardState != (uint8)INDEX_NONE)
+ {
+ // Blend into the forward state
+ return Instance.ForwardState;
+ }
+ else if (PerActorData[ActorIndex].CurrentState != Instance.State)
+ {
+ // Blend to the actor's current state
+ return PerActorData[ActorIndex].CurrentState;
+ }
+ }
+ return INDEX_NONE;
+ }();
+
+ // Try to setup blending
+ if (BlendToState != (uint8)INDEX_NONE)
+ {
+ const uint32 BlendIndex = SetupBlendFromOnDemand(BlendToState, InstanceIndex, ActorIndex);
+
+ if (BlendIndex != INDEX_NONE)
+ {
+ // TODO what if two actors have a different state they are blending to? --> Store permutation index
+ Instance.BlendToPermutationIndex = BlendInstances[BlendIndex].ToPermutationIndex;
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Blending [%i] out from %s to %s"), ActorIndex, *StateEnum->GetDisplayNameTextByValue(Instance.State).ToString(), *StateEnum->GetDisplayNameTextByValue(BlendToState).ToString());
+#endif
+ }
+ }
+
+ // OR results, some actors could not be blending
+ Instance.bBlendActive |= bShouldBlend;
+ }
+ }
+ }
+}
+
+void UAnimSharingInstance::TickAdditiveInstances()
+{
+ SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_UpdateAdditives);
+
+ for (int32 InstanceIndex = 0; InstanceIndex < AdditiveInstances.Num(); ++InstanceIndex)
+ {
+ FAdditiveInstance& Instance = AdditiveInstances[InstanceIndex];
+ if (Instance.bActive)
+ {
+ const float WorldTimeSeconds = GetWorld()->GetTimeSeconds();
+ if (WorldTimeSeconds >= Instance.EndTime)
+ {
+ // Finish
+ if (PerActorData.IsValidIndex(Instance.ActorIndex))
+ {
+ PerActorData[Instance.ActorIndex].bRunningAdditive = false;
+ PerActorData[Instance.ActorIndex].AdditiveInstanceIndex = INDEX_NONE;
+
+ // Set it to base component on top of the additive animation is playing
+ SetMasterComponentForActor(Instance.ActorIndex, Instance.AdditiveAnimationInstance->GetBaseComponent());
+ }
+ FreeAdditiveInstance(Instance.AdditiveAnimationInstance);
+ RemoveAdditiveInstance(InstanceIndex);
+ --InstanceIndex;
+ }
+ }
+ else
+ {
+ Instance.bActive = true;
+ Instance.AdditiveAnimationInstance->Start();
+ if (Instance.ActorIndex != INDEX_NONE)
+ {
+ SetMasterComponentForActor(Instance.ActorIndex, Instance.AdditiveAnimationInstance->GetComponent());
+ }
+ }
+ }
+}
+
+void UAnimSharingInstance::TickActorStates()
+{
+ SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_TickActorStates);
+ /** Tick each registered actor's state */
+ for (int32 ActorIndex = 0; ActorIndex < RegisteredActors.Num(); ++ActorIndex)
+ {
+ /** Ensure Actor is still available */
+ const AActor* Actor = RegisteredActors[ActorIndex];
+ if (Actor)
+ {
+ FPerActorData& ActorData = PerActorData[ActorIndex];
+ checkf(ActorData.ComponentIndices.Num(), TEXT("Registered Actor without SkeletalMeshComponents"));
+
+ // Update actor and component visibility
+ ActorData.bRequiresTick = ActorData.SignificanceValue >= ScalabilitySettings->TickSignificanceValue.Default;
+ for (int32 ComponentIndex : ActorData.ComponentIndices)
+ {
+ if (PerComponentData[ComponentIndex].Component->LastRenderTime > (WorldTime - 1.f))
+ {
+ PerComponentData[ComponentIndex].Component->bRecentlyRendered = true;
+ ActorData.bRequiresTick = true;
+ }
+ }
+
+ // Determine current state for Actor
+ uint8& PreviousState = ActorData.CurrentState;
+ bool bShouldProcess = false;
+ const uint8 CurrentState = DetermineStateForActor(ActorIndex, bShouldProcess);
+
+ // Determine whether we should blend according to the scalability settings
+ const bool bShouldBlend = ScalabilitySettings->UseBlendTransitions.Default && ActorData.SignificanceValue >= ScalabilitySettings->BlendSignificanceValue.Default;
+
+ /** If the state is different we need to change animations and setup a transition */
+ if (CurrentState != PreviousState)
+ {
+ /** When we are currently running an on-demand state we do not want as state change to impact the current animation */
+ const bool bShouldNotProcess = ActorData.bRunningOnDemand && !PerStateData[CurrentState].bIsOnDemand;
+
+ auto UpdateState = [&]()
+ {
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Setting %i state to %i previous %i | %i"), ActorIndex, CurrentState, PreviousState, ActorData.PermutationIndex);
+#endif
+ ActorData.PreviousState = PreviousState;
+ ActorData.CurrentState = CurrentState;
+ };
+
+ /** If the processor explicitly outputs that the change in state should not impact behavior, just change state and do nothing */
+ if (!bShouldProcess || bShouldNotProcess)
+ {
+ UpdateState();
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Changing state to %s from %s while running on demand %i"), *StateEnum->GetDisplayNameTextByValue(CurrentState).ToString(), *StateEnum->GetDisplayNameTextByValue(ActorData.PreviousState).ToString(), ActorIndex);
+#endif
+ }
+ /** Play additive animation only if actor isn't already playing one */
+ else if (PerStateData[CurrentState].bIsAdditive && !ActorData.bRunningAdditive)
+ {
+ const uint32 AdditiveInstanceIndex = SetupAdditiveInstance(CurrentState, PreviousState, ActorData.PermutationIndex);
+ if (AdditiveInstanceIndex != INDEX_NONE)
+ {
+ ActorData.bRunningAdditive = true;
+ ActorData.AdditiveInstanceIndex = AdditiveInstanceIndex;
+ AdditiveInstances[AdditiveInstanceIndex].ActorIndex = ActorIndex;
+ }
+ }
+ /** If we are _already_ running an on-demand instance and the new state is also an on-demand we'll have to blend the new state in*/
+ else if (PerStateData[CurrentState].bIsOnDemand)
+ {
+ /** If the new state is different than the currently running on-demand state, this could happen if we previously only updated the state and not processed it */
+ const bool bSetupInstance = (!ActorData.bRunningOnDemand || (ActorData.bRunningOnDemand && OnDemandInstances[ActorData.OnDemandInstanceIndex].State != CurrentState));
+ const uint32 OnDemandIndex = bSetupInstance ? SetupOnDemandInstance(CurrentState) : INDEX_NONE;
+
+ if (OnDemandIndex != INDEX_NONE)
+ {
+ // Make sure we end any current blends
+ RemoveFromCurrentBlend(ActorIndex);
+ RemoveFromCurrentOnDemand(ActorIndex);
+
+ bool bShouldSwitch = true;
+ if (bShouldBlend && !FMath::IsNearlyZero(PerStateData[CurrentState].BlendTime))
+ {
+ if (ActorData.bRunningOnDemand)
+ {
+ /** Setup a blend between the current and a new instance*/
+ const uint32 BlendInstanceIndex = SetupBlendBetweenOnDemands(ActorData.OnDemandInstanceIndex, OnDemandIndex, ActorIndex);
+ ActorData.BlendInstanceIndex = BlendInstanceIndex;
+ }
+ else
+ {
+ /** Setup a blend to an on-demand state/instance */
+ const uint32 BlendInstanceIndex = SetupBlendToOnDemand(PreviousState, OnDemandIndex, ActorIndex);
+ ActorData.BlendInstanceIndex = BlendInstanceIndex;
+ }
+
+ /** Blend was not succesfully set up so switch anyway */
+ bShouldSwitch = (ActorData.BlendInstanceIndex == INDEX_NONE);
+ }
+
+ if (bShouldSwitch)
+ {
+ /** Not blending so just switch to other on-demand instance */
+ SwitchBetweenOnDemands(ActorData.OnDemandInstanceIndex, OnDemandIndex, ActorIndex);
+ }
+
+ /** Add the current actor to the on-demand instance*/
+ OnDemandInstances[OnDemandIndex].ActorIndices.Add(ActorIndex);
+ /** Also change actor data accordingly*/
+ ActorData.OnDemandInstanceIndex = OnDemandIndex;
+ ActorData.bRunningOnDemand = true;
+
+ UpdateState();
+ }
+ }
+ /** Otherwise blend towards the new shared state */
+ else
+ {
+ /** If actor is within blending distance setup/reuse a blend instance*/
+ bool bShouldSwitch = true;
+ if (bShouldBlend)
+ {
+ const uint32 BlendInstanceIndex = SetupBlend(PreviousState, CurrentState, ActorIndex);
+ ActorData.BlendInstanceIndex = BlendInstanceIndex;
+ /** Blend was not succesfully set up so switch anyway */
+ bShouldSwitch = (ActorData.BlendInstanceIndex == INDEX_NONE);
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Changing state to %s from %s with blend %i"), *StateEnum->GetDisplayNameTextByValue(CurrentState).ToString(), *StateEnum->GetDisplayNameTextByValue(PreviousState).ToString(), ActorIndex);
+#endif
+ }
+ /** Otherwise just switch it to the new state */
+ if (bShouldSwitch)
+ {
+ SetupSlaveComponent(CurrentState, ActorIndex);
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Changing state to %s from %s %i"), *StateEnum->GetDisplayNameTextByValue(CurrentState).ToString(), *StateEnum->GetDisplayNameTextByValue(PreviousState).ToString(), ActorIndex);
+#endif
+ }
+
+ UpdateState();
+ }
+ }
+ /** Flag the currently master component as in-use */
+ else if (!ActorData.bRunningOnDemand && !ActorData.bBlending)
+ {
+#if LOG_STATES
+ if (!PerStateData[ActorData.CurrentState].Components.IsValidIndex(ActorData.PermutationIndex))
+ {
+ UE_LOG(LogAnimationSharing, Log, TEXT("Invalid permutation for actor %i is out of range of %i for state %s by actor %i"), ActorData.PermutationIndex, PerStateData[ActorData.CurrentState].Components.Num(), *StateEnum->GetDisplayNameTextByValue(ActorData.CurrentState).ToString(), ActorIndex);
+ }
+ else if (!PerStateData[ActorData.CurrentState].Components[ActorData.PermutationIndex]->IsComponentTickEnabled())
+ {
+ UE_LOG(LogAnimationSharing, Log, TEXT("Component not active %i for state %s by actor %i"), ActorData.PermutationIndex, *StateEnum->GetDisplayNameTextByValue(ActorData.CurrentState).ToString(), ActorIndex);
+ }
+#endif
+
+ SetComponentUsage(true, ActorData.CurrentState, ActorData.PermutationIndex);
+ }
+
+ // Propagate visibility to master component
+ if (ActorData.bRequiresTick)
+ {
+ SetComponentTick(ActorData.CurrentState, ActorData.PermutationIndex);
+ }
+ }
+ }
+}
+
+void UAnimSharingInstance::RemoveFromCurrentBlend(int32 ActorIndex)
+{
+ if (PerActorData[ActorIndex].bBlending && PerActorData[ActorIndex].BlendInstanceIndex != INDEX_NONE && BlendInstances.IsValidIndex(PerActorData[ActorIndex].BlendInstanceIndex))
+ {
+ FBlendInstance& OldBlendInstance = BlendInstances[PerActorData[ActorIndex].BlendInstanceIndex];
+ SetMasterComponentForActor(ActorIndex, OldBlendInstance.TransitionBlendInstance->GetToComponent());
+ OldBlendInstance.ActorIndices.Remove(ActorIndex);
+ PerActorData[ActorIndex].BlendInstanceIndex = INDEX_NONE;
+ }
+}
+
+void UAnimSharingInstance::RemoveFromCurrentOnDemand(int32 ActorIndex)
+{
+ if (PerActorData[ActorIndex].bRunningOnDemand && PerActorData[ActorIndex].OnDemandInstanceIndex != INDEX_NONE && OnDemandInstances.IsValidIndex(PerActorData[ActorIndex].OnDemandInstanceIndex))
+ {
+ OnDemandInstances[PerActorData[ActorIndex].OnDemandInstanceIndex].ActorIndices.Remove(ActorIndex);
+ }
+}
+
+void UAnimSharingInstance::TickBlendInstances()
+{
+ SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_UpdateBlends);
+ for (int32 InstanceIndex = 0; InstanceIndex < BlendInstances.Num(); ++InstanceIndex)
+ {
+ FBlendInstance& Instance = BlendInstances[InstanceIndex];
+ checkf(Instance.bActive, TEXT("Blends should be active at this point"));
+
+ /** Check whether or not the blend has ended */
+ if (Instance.EndTime <= WorldTime)
+ {
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Finished blend %s from %s"), *StateEnum->GetDisplayNameTextByValue(Instance.StateTo).ToString(), *StateEnum->GetDisplayNameTextByValue(Instance.StateFrom).ToString());
+#endif
+
+ // Finish blend into unique animation, need to just set it to use the correct component
+ const bool bToStateIsOnDemand = PerStateData[Instance.StateTo].bIsOnDemand;
+ const bool bFromStateIsOnDemand = PerStateData[Instance.StateFrom].bIsOnDemand;
+
+ // If we were blending to an on-demand state we need to set the on-demand component as the new master component
+ if (bToStateIsOnDemand)
+ {
+ for (uint32 ActorIndex : Instance.ActorIndices)
+ {
+ SetMasterComponentForActor(ActorIndex, Instance.TransitionBlendInstance->GetToComponent());
+ PerActorData[ActorIndex].PermutationIndex = 0;
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Setting %i to on-demand component %i"), ActorIndex, Instance.ToOnDemandInstanceIndex);
+#endif
+
+ for (uint32 ComponentIndex : PerActorData[ActorIndex].ComponentIndices)
+ {
+ UAnimationSharingManager::SetDebugMaterial(PerComponentData[ComponentIndex].Component, 0);
+ }
+ }
+ }
+ /** Otherwise if the state we were blending from was not on-demand we set the new state component as the new master component,
+ if we are blending from an on-demand state FOnDemandInstance with set the correct master component when it finishes */
+ else if (!bFromStateIsOnDemand)
+ {
+ for (uint32 ActorIndex : Instance.ActorIndices)
+ {
+ if (PerActorData[ActorIndex].CurrentState == Instance.StateTo)
+ {
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Setting %i to state %i | %i"), ActorIndex, Instance.StateTo, Instance.ToPermutationIndex);
+#endif
+ SetPermutationSlaveComponent(Instance.StateTo, ActorIndex, Instance.ToPermutationIndex);
+#if !UE_BUILD_SHIPPING
+ for (uint32 ComponentIndex : PerActorData[ActorIndex].ComponentIndices)
+ {
+ UAnimationSharingManager::SetDebugMaterial(PerComponentData[ComponentIndex].Component, 1);
+ }
+#endif
+ }
+
+ }
+ }
+
+ // Free up the used blend actor
+ FreeBlendInstance(Instance.TransitionBlendInstance);
+
+ // Clear flags and index on the actor data as the blend has finished
+ for (uint32 ActorIndex : Instance.ActorIndices)
+ {
+ PerActorData[ActorIndex].BlendInstanceIndex = INDEX_NONE;
+ PerActorData[ActorIndex].bBlending = 0;
+ }
+
+ // Remove this blend instance as it has finished
+ RemoveBlendInstance(InstanceIndex);
+ --InstanceIndex;
+ }
+ else
+ {
+ // Check whether or not the blend has started, if not set up the actors as slaves at this point
+ if (!Instance.bBlendStarted)
+ {
+ for (uint32 ActorIndex : Instance.ActorIndices)
+ {
+ SetMasterComponentForActor(ActorIndex, Instance.TransitionBlendInstance->GetComponent());
+
+ for (uint32 ComponentIndex : PerActorData[ActorIndex].ComponentIndices)
+ {
+ UAnimationSharingManager::SetDebugMaterial(PerComponentData[ComponentIndex].Component, 2);
+ }
+ }
+
+ Instance.bBlendStarted = true;
+ }
+
+ const bool bShouldTick = DoAnyActorsRequireTicking(Instance);
+
+ if (!PerStateData[Instance.StateFrom].bIsOnDemand)
+ {
+ SetComponentUsage(true, Instance.StateFrom, Instance.FromPermutationIndex);
+ if (bShouldTick)
+ {
+ SetComponentTick(Instance.StateFrom, Instance.FromPermutationIndex);
+ }
+ }
+
+ if (!PerStateData[Instance.StateTo].bIsOnDemand)
+ {
+ SetComponentUsage(true, Instance.StateTo, Instance.ToPermutationIndex);
+ if (bShouldTick)
+ {
+ SetComponentTick(Instance.StateTo, Instance.ToPermutationIndex);
+ }
+ }
+ }
+ }
+}
+
+void UAnimSharingInstance::TickAnimationStates()
+{
+ for (FPerStateData& StateData : PerStateData)
+ {
+ for (int32 Index = 0; Index < StateData.Components.Num(); ++Index)
+ {
+ const bool bPreviousState = StateData.PreviousInUseComponentFrameBits[Index];
+ const bool bCurrentState = StateData.InUseComponentFrameBits[Index];
+
+ const bool bShouldTick = StateData.SlaveTickRequiredFrameBits[Index];
+
+ if (bCurrentState != bPreviousState)
+ {
+ if (bCurrentState)
+ {
+ // Turn on
+ UAnimationSharingManager::SetDebugMaterial(StateData.Components[Index], 1);
+ StateData.Components[Index]->SetComponentTickEnabled(true);
+ }
+ else
+ {
+ // Turn off
+ UAnimationSharingManager::SetDebugMaterial(StateData.Components[Index], 0);
+ StateData.Components[Index]->SetComponentTickEnabled(false);
+ }
+ }
+ else if (!bCurrentState && StateData.Components[Index]->IsComponentTickEnabled())
+ {
+ // Turn off
+ UAnimationSharingManager::SetDebugMaterial(StateData.Components[Index], 0);
+ StateData.Components[Index]->SetComponentTickEnabled(false);
+ }
+
+ StateData.Components[Index]->bRecentlyRendered = bShouldTick;
+ StateData.Components[Index]->VisibilityBasedAnimTickOption = bShouldTick ? EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones : EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
+ }
+
+ // Set previous to current and reset current bits
+ StateData.PreviousInUseComponentFrameBits = StateData.InUseComponentFrameBits;
+ StateData.InUseComponentFrameBits.Init(false, StateData.PreviousInUseComponentFrameBits.Num());
+ StateData.SlaveTickRequiredFrameBits.Init(false, StateData.SlaveTickRequiredFrameBits.Num());
+
+ /** Reset on demand index for next frame */
+ StateData.CurrentFrameOnDemandIndex = INDEX_NONE;
+ }
+}
+
+void UAnimSharingInstance::SetComponentUsage(bool bUsage, uint8 StateIndex, uint32 ComponentIndex)
+{
+ // TODO component index should always be valid
+#if LOG_STATES
+ if (!PerStateData[StateIndex].InUseComponentFrameBits.IsValidIndex(ComponentIndex))
+ {
+ UE_LOG(LogAnimationSharing, Log, TEXT("Invalid set component usage %i is out of range of %i for state %s by component %i"), ComponentIndex, PerStateData[StateIndex].Components.Num(), *StateEnum->GetDisplayNameTextByValue(StateIndex).ToString(), ComponentIndex);
+ }
+#endif
+
+ if (PerStateData.IsValidIndex(StateIndex) && PerStateData[StateIndex].InUseComponentFrameBits.IsValidIndex(ComponentIndex))
+ {
+ PerStateData[StateIndex].InUseComponentFrameBits[ComponentIndex] = bUsage;
+ }
+}
+
+void UAnimSharingInstance::SetComponentTick(uint8 StateIndex, uint32 ComponentIndex)
+{
+ if (PerStateData[StateIndex].SlaveTickRequiredFrameBits.IsValidIndex(ComponentIndex))
+ {
+ PerStateData[StateIndex].SlaveTickRequiredFrameBits[ComponentIndex] = true;
+ }
+}
+
+void UAnimSharingInstance::FreeBlendInstance(FTransitionBlendInstance* Instance)
+{
+ Instance->Stop();
+ BlendInstanceStack.FreeInstance(Instance);
+}
+
+void UAnimSharingInstance::FreeAdditiveInstance(FAdditiveAnimationInstance* Instance)
+{
+ Instance->Stop();
+ AdditiveInstanceStack.FreeInstance(Instance);
+}
+
+void UAnimSharingInstance::SetMasterComponentForActor(uint32 ActorIndex, USkeletalMeshComponent* Component)
+{
+ // Always ensure the component is ticking
+ if (Component)
+ {
+ Component->SetComponentTickEnabled(true);
+ }
+
+ const FPerActorData& ActorData = PerActorData[ActorIndex];
+ // Do not update the component of the additive actor itself, otherwise update the base component
+ if (ActorData.bRunningAdditive && AdditiveInstances.IsValidIndex(ActorData.AdditiveInstanceIndex) && AdditiveInstances[ActorData.AdditiveInstanceIndex].AdditiveAnimationInstance->GetComponent() != Component)
+ {
+ AdditiveInstances[ActorData.AdditiveInstanceIndex].BaseComponent = Component;
+ AdditiveInstances[ActorData.AdditiveInstanceIndex].AdditiveAnimationInstance->UpdateBaseComponent(Component);
+
+ return;
+ }
+
+ for (uint32 ComponentIndex : ActorData.ComponentIndices)
+ {
+ PerComponentData[ComponentIndex].Component->SetMasterPoseComponent(Component, true);
+ }
+}
+
+void UAnimSharingInstance::SetupSlaveComponent(uint8 CurrentState, uint32 ActorIndex)
+{
+ const FPerStateData& StateData = PerStateData[CurrentState];
+ if (!StateData.bIsOnDemand)
+ {
+ const uint32 PermutationIndex = DeterminePermutationIndex(ActorIndex, CurrentState);
+ SetPermutationSlaveComponent(CurrentState, ActorIndex, PermutationIndex);
+ }
+ else
+ {
+ const uint32 OnDemandInstanceIndex = SetupOnDemandInstance(CurrentState);
+
+ if (OnDemandInstanceIndex != INDEX_NONE)
+ {
+ USkeletalMeshComponent* MasterComponent = StateData.Components[OnDemandInstances[OnDemandInstanceIndex].UsedPerStateComponentIndex];
+ SetMasterComponentForActor(ActorIndex, MasterComponent);
+ OnDemandInstances[OnDemandInstanceIndex].ActorIndices.Add(ActorIndex);
+
+ PerActorData[ActorIndex].OnDemandInstanceIndex = OnDemandInstanceIndex;
+ PerActorData[ActorIndex].bRunningOnDemand = true;
+
+ // TODO do we need to reset
+ PerActorData[ActorIndex].PermutationIndex = 0;
+ }
+ }
+}
+
+void UAnimSharingInstance::SetPermutationSlaveComponent(uint8 StateIndex, uint32 ActorIndex, uint32 PermutationIndex)
+{
+ const FPerStateData& StateData = PerStateData[StateIndex];
+
+ // TODO Min should not be needed if PermutationIndex is always valid
+ PermutationIndex = FMath::Min((uint32)StateData.Components.Num() - 1, PermutationIndex);
+#if LOG_STATES
+ if (!StateData.Components.IsValidIndex(PermutationIndex))
+ {
+ UE_LOG(LogAnimationSharing, Log, TEXT("Invalid set component usage %i is out of range of %i for state %s by actor %i"), PermutationIndex, StateData.Components.Num(), *StateEnum->GetDisplayNameTextByValue(StateIndex).ToString(), ActorIndex);
+ }
+#endif
+
+ SetMasterComponentForActor(ActorIndex, StateData.Components[PermutationIndex]);
+ PerActorData[ActorIndex].PermutationIndex = PermutationIndex;
+ UAnimationSharingManager::SetDebugMaterial(StateData.Components[PermutationIndex], 1);
+}
+
+uint32 UAnimSharingInstance::DeterminePermutationIndex(uint32 ActorIndex, uint8 State) const
+{
+ const FPerStateData& StateData = PerStateData[State];
+ const TArray& Components = StateData.Components;
+
+ // This can grow to be more intricate to take into account surrounding actors?
+ const uint32 PermutationIndex = FMath::RandHelper(Components.Num());
+ checkf(Components.IsValidIndex(PermutationIndex), TEXT("Not enough MasterComponents initialised!"));
+
+ return PermutationIndex;
+}
+
+uint32 UAnimSharingInstance::SetupBlend(uint8 FromState, uint8 ToState, uint32 ActorIndex)
+{
+ const bool bConcurrentBlendsReached = !BlendInstanceStack.InstanceAvailable();
+ const bool bOnDemand = PerStateData[ToState].bIsOnDemand;
+
+ uint32 BlendInstanceIndex = INDEX_NONE;
+ if (!bConcurrentBlendsReached)
+ {
+ BlendInstanceIndex = BlendInstances.IndexOfByPredicate([&](const FBlendInstance& Instance)
+ {
+ return (!Instance.bActive && // The instance should not have started yet
+ Instance.StateFrom == FromState && // It should be blending from the same state
+ Instance.StateTo == ToState && // It should be blending to the same state
+ Instance.bOnDemand == bOnDemand && // It should match whether or not it is an on-demand state QQQ is this needed?
+ Instance.FromPermutationIndex == PerActorData[ActorIndex].PermutationIndex); // It should be blending from the same permutation inside of the state
+ });
+
+ FBlendInstance* BlendInstance = BlendInstanceIndex != INDEX_NONE ? &BlendInstances[BlendInstanceIndex] : nullptr;
+
+ if (!BlendInstance)
+ {
+ BlendInstance = &BlendInstances.AddDefaulted_GetRef();
+ BlendInstanceIndex = BlendInstances.Num() - 1;
+ BlendInstance->bActive = false;
+ BlendInstance->FromOnDemandInstanceIndex = BlendInstance->ToOnDemandInstanceIndex = INDEX_NONE;
+ BlendInstance->StateFrom = FromState;
+ BlendInstance->StateTo = ToState;
+ BlendInstance->BlendTime = CalculateBlendTime( ToState);
+ BlendInstance->bOnDemand = bOnDemand;
+ BlendInstance->EndTime = GetWorld()->GetTimeSeconds() + BlendInstance->BlendTime;
+ BlendInstance->TransitionBlendInstance = BlendInstanceStack.GetInstance();
+
+ BlendInstance->TransitionBlendInstance->GetComponent()->SetComponentTickEnabled(true);
+
+ // Setup permutation indices to and from we are blending
+ BlendInstance->FromPermutationIndex = PerActorData[ActorIndex].PermutationIndex;
+ BlendInstance->ToPermutationIndex = DeterminePermutationIndex( ActorIndex, ToState);
+ }
+
+ checkf(BlendInstance, TEXT("Unable to create blendcontainer"));
+
+ BlendInstance->ActorIndices.Add(ActorIndex);
+ PerActorData[ActorIndex].bBlending = true;
+ }
+
+ return BlendInstanceIndex;
+}
+
+uint32 UAnimSharingInstance::SetupBlendFromOnDemand(uint8 ToState, uint32 OnDemandInstanceIndex, uint32 ActorIndex)
+{
+ const uint8 FromState = OnDemandInstances[OnDemandInstanceIndex].State;
+ const uint32 BlendInstanceIndex = SetupBlend(FromState, ToState, ActorIndex);
+
+ if (BlendInstanceIndex != INDEX_NONE)
+ {
+ BlendInstances[BlendInstanceIndex].FromOnDemandInstanceIndex = OnDemandInstanceIndex;
+ }
+
+ return BlendInstanceIndex;
+}
+
+uint32 UAnimSharingInstance::SetupBlendBetweenOnDemands(uint8 FromOnDemandInstanceIndex, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex)
+{
+ const uint8 FromState = OnDemandInstances[FromOnDemandInstanceIndex].State;
+ const uint8 ToState = OnDemandInstances[ToOnDemandInstanceIndex].State;
+ const uint32 BlendInstanceIndex = SetupBlend(FromState, ToState, ActorIndex);
+
+ if (BlendInstanceIndex != INDEX_NONE)
+ {
+ BlendInstances[BlendInstanceIndex].FromOnDemandInstanceIndex = FromOnDemandInstanceIndex;
+ BlendInstances[BlendInstanceIndex].ToOnDemandInstanceIndex = ToOnDemandInstanceIndex;
+ }
+
+ return BlendInstanceIndex;
+}
+
+uint32 UAnimSharingInstance::SetupBlendToOnDemand(uint8 FromState, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex)
+{
+ const uint8 ToState = OnDemandInstances[ToOnDemandInstanceIndex].State;
+ const uint32 BlendInstanceIndex = SetupBlend(FromState, ToState, ActorIndex);
+
+ if (BlendInstanceIndex != INDEX_NONE)
+ {
+ BlendInstances[BlendInstanceIndex].ToOnDemandInstanceIndex = ToOnDemandInstanceIndex;
+ }
+
+ return BlendInstanceIndex;
+}
+
+void UAnimSharingInstance::SwitchBetweenOnDemands(uint32 FromOnDemandInstanceIndex, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex)
+{
+ /** Remove this actor from the currently running on-demand instance */
+ if (FromOnDemandInstanceIndex != INDEX_NONE)
+ {
+ OnDemandInstances[FromOnDemandInstanceIndex].ActorIndices.Remove(ActorIndex);
+ }
+
+ const FOnDemandInstance& Instance = OnDemandInstances[ToOnDemandInstanceIndex];
+ const uint32 ComponentIndex = Instance.UsedPerStateComponentIndex;
+ const uint32 StateIndex = Instance.State;
+ PerActorData[ActorIndex].PermutationIndex = 0;
+ SetMasterComponentForActor(ActorIndex, PerStateData[StateIndex].Components[ComponentIndex]);
+}
+
+uint32 UAnimSharingInstance::SetupOnDemandInstance(uint8 StateIndex)
+{
+ uint32 InstanceIndex = INDEX_NONE;
+
+ FPerStateData& StateData = PerStateData[StateIndex];
+ if (StateData.CurrentFrameOnDemandIndex != INDEX_NONE && OnDemandInstances.IsValidIndex(StateData.CurrentFrameOnDemandIndex))
+ {
+ InstanceIndex = StateData.CurrentFrameOnDemandIndex;
+ }
+ else
+ {
+ // Otherwise we'll need to kick one of right now so try and set one up
+ if (StateData.Components.Num())
+ {
+ const uint32 AvailableIndex = StateData.InUseComponentFrameBits.FindAndSetFirstZeroBit();
+
+ if (AvailableIndex != INDEX_NONE)
+ {
+ FOnDemandInstance& Instance = OnDemandInstances.AddDefaulted_GetRef();
+ InstanceIndex = OnDemandInstances.Num() - 1;
+ StateData.CurrentFrameOnDemandIndex = InstanceIndex;
+
+ Instance.bActive = 0;
+ Instance.bBlendActive = 0;
+ Instance.State = StateIndex;
+ Instance.ForwardState = StateData.bShouldForwardToState ? StateData.ForwardStateValue : INDEX_NONE;
+ Instance.UsedPerStateComponentIndex = AvailableIndex;
+ Instance.bReturnToPreviousState = StateData.bReturnToPreviousState;
+ Instance.StartTime = 0.f;
+ Instance.BlendToPermutationIndex = INDEX_NONE;
+
+ const float WorldTimeSeconds = GetWorld()->GetTimeSeconds();
+ Instance.EndTime = WorldTimeSeconds + StateData.AnimationLengths[AvailableIndex];
+ Instance.StartBlendTime = Instance.EndTime - CalculateBlendTime(StateIndex);
+
+ USkeletalMeshComponent* FreeComponent = StateData.Components[AvailableIndex];
+
+ UAnimationSharingManager::SetDebugMaterial(FreeComponent, 1);
+
+ FreeComponent->SetComponentTickEnabled(true);
+ FreeComponent->SetPosition(0.f, false);
+ FreeComponent->Play(false);
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Setup on demand state %s"), *StateEnum->GetDisplayNameTextByValue(StateIndex).ToString());
+#endif
+ }
+ else
+ {
+ // Next resort
+ const float MaxStartTime = WorldTime - PerStateData[StateIndex].WiggleTime;
+ float WiggleStartTime = TNumericLimits::Max();
+ float NonWiggleStartTime = TNumericLimits::Max();
+ int32 WiggleIndex = INDEX_NONE;
+ int32 NonWiggleIndex = INDEX_NONE;
+ for (int32 RunningInstanceIndex = 0; RunningInstanceIndex < OnDemandInstances.Num(); ++RunningInstanceIndex)
+ {
+ FOnDemandInstance& Instance = OnDemandInstances[RunningInstanceIndex];
+
+ if (Instance.State == StateIndex)
+ {
+ if (Instance.StartTime <= MaxStartTime && Instance.StartTime < WiggleStartTime)
+ {
+ WiggleStartTime = Instance.StartTime;
+ WiggleIndex = RunningInstanceIndex;
+ }
+ else if (Instance.StartTime < NonWiggleStartTime)
+ {
+ NonWiggleStartTime = Instance.StartTime;
+ NonWiggleIndex = RunningInstanceIndex;
+ }
+ }
+ }
+
+ // Snap to on demand instance that has started last within the number of wiggle frames
+ if (WiggleIndex != INDEX_NONE)
+ {
+ InstanceIndex = WiggleIndex;
+ }
+ // Snap to closest on demand instance outside of the number of wiggle frames
+ else if (NonWiggleIndex != INDEX_NONE)
+ {
+ InstanceIndex = NonWiggleIndex;
+ }
+ else
+ {
+ // No instances available and none actually currently running this state, should probably up the number of available concurrent on demand instances at this point
+ UE_LOG(LogAnimationSharing, Warning, TEXT("No more on demand components available"));
+ }
+ }
+ }
+ }
+
+ return InstanceIndex;
+}
+
+uint32 UAnimSharingInstance::SetupAdditiveInstance(uint8 StateIndex, uint8 FromState, uint8 StateComponentIndex)
+{
+ uint32 InstanceIndex = INDEX_NONE;
+
+ const FPerStateData& StateData = PerStateData[StateIndex];
+ if (AdditiveInstanceStack.InstanceAvailable())
+ {
+ FAdditiveAnimationInstance* AnimationInstance = AdditiveInstanceStack.GetInstance();
+ FAdditiveInstance& Instance = AdditiveInstances.AddDefaulted_GetRef();
+ Instance.bActive = false;
+ Instance.AdditiveAnimationInstance = AnimationInstance;
+ Instance.BaseComponent = PerStateData[FromState].Components[StateComponentIndex];
+ const float WorldTimeSeconds = GetWorld()->GetTimeSeconds();
+ Instance.EndTime = WorldTimeSeconds + StateData.AdditiveAnimationSequence->SequenceLength;
+ Instance.State = StateIndex;
+
+ InstanceIndex = AdditiveInstances.Num() - 1;
+ AnimationInstance->Setup(Instance.BaseComponent, StateData.AdditiveAnimationSequence);
+ }
+
+ return InstanceIndex;
+}
+
+void UAnimSharingInstance::KickoffInstances()
+{
+ SCOPE_CYCLE_COUNTER(STAT_AnimationSharing_KickoffInstances);
+ for (FBlendInstance& BlendInstance : BlendInstances)
+ {
+ if (!BlendInstance.bActive)
+ {
+ BlendInstance.bBlendStarted = false;
+
+ FString ActorIndicesString;
+ for (uint32 ActorIndex : BlendInstance.ActorIndices)
+ {
+ if (ActorIndex != BlendInstance.ActorIndices.Last())
+ {
+ ActorIndicesString += FString::Printf(TEXT("%i, "), ActorIndex);
+ }
+ else
+ {
+ ActorIndicesString += FString::Printf(TEXT("%i"), ActorIndex);
+ }
+ }
+#if LOG_STATES
+ UE_LOG(LogAnimationSharing, Log, TEXT("Starting blend from %s to %s [%s]"), *StateEnum->GetDisplayNameTextByValue(BlendInstance.StateFrom).ToString(), *StateEnum->GetDisplayNameTextByValue(BlendInstance.StateTo).ToString(), *ActorIndicesString);
+#endif
+
+ // TODO should be able to assume permutation indices are valid here
+ BlendInstance.FromPermutationIndex = FMath::Min((uint32)PerStateData[BlendInstance.StateFrom].Components.Num() - 1, BlendInstance.FromPermutationIndex);
+ BlendInstance.ToPermutationIndex = FMath::Min((uint32)PerStateData[BlendInstance.StateTo].Components.Num() - 1, BlendInstance.ToPermutationIndex);
+
+ USkeletalMeshComponent* From = PerStateData[BlendInstance.StateFrom].Components[BlendInstance.FromPermutationIndex];
+ USkeletalMeshComponent* To = PerStateData[BlendInstance.StateTo].Components[BlendInstance.ToPermutationIndex];
+
+ if (PerStateData[BlendInstance.StateTo].bIsOnDemand && (BlendInstance.ToOnDemandInstanceIndex != INDEX_NONE))
+ {
+ To = PerStateData[BlendInstance.StateTo].Components[OnDemandInstances[BlendInstance.ToOnDemandInstanceIndex].UsedPerStateComponentIndex];
+ }
+
+ if (PerStateData[BlendInstance.StateFrom].bIsOnDemand && (BlendInstance.FromOnDemandInstanceIndex != INDEX_NONE))
+ {
+ const uint32 UsedComponentIndex = OnDemandInstances[BlendInstance.FromOnDemandInstanceIndex].UsedPerStateComponentIndex;
+ From = PerStateData[BlendInstance.StateFrom].Components[UsedComponentIndex];
+ }
+
+ for (uint32 ActorIndex : BlendInstance.ActorIndices)
+ {
+ PerActorData[ActorIndex].PermutationIndex = BlendInstance.ToPermutationIndex;
+ PerActorData[ActorIndex].bBlending = true;
+ }
+
+ BlendInstance.TransitionBlendInstance->Setup(From, To, BlendInstance.BlendTime);
+ BlendInstance.bActive = true;
+ }
+ }
+
+ for (FOnDemandInstance& OnDemandInstance : OnDemandInstances)
+ {
+ if (!OnDemandInstance.bActive)
+ {
+ OnDemandInstance.bActive = true;
+ OnDemandInstance.StartTime = WorldTime;
+ }
+ }
+}
+
+float UAnimSharingInstance::CalculateBlendTime(uint8 StateIndex) const
+{
+ checkf(PerStateData.IsValidIndex(StateIndex), TEXT("Invalid State index"));
+ return PerStateData[StateIndex].BlendTime;
+}
+
+void UAnimSharingInstance::RemoveComponent(int32 ComponentIndex)
+{
+ if (PerComponentData.Num() > 1 && ComponentIndex != PerComponentData.Num() - 1)
+ {
+ // Index of the component we will swap with
+ const uint32 SwapIndex = PerComponentData.Num() - 1;
+
+ // Find actor for component we will swap with
+ const uint32 SwapActorIndex = PerComponentData[SwapIndex].ActorIndex;
+
+ // Update component index in the actor to match with ComponentIndex (which it will be swapped with)
+ const uint32 ActorDataComponentIndex = PerActorData[SwapActorIndex].ComponentIndices.IndexOfByKey(SwapIndex);
+ if (ActorDataComponentIndex != INDEX_NONE)
+ {
+ PerActorData[SwapActorIndex].ComponentIndices[ActorDataComponentIndex] = ComponentIndex;
+ }
+ }
+
+ PerComponentData.RemoveAtSwap(ComponentIndex, 1, false);
+}
+
+void UAnimSharingInstance::RemoveBlendInstance(int32 InstanceIndex)
+{
+ FBlendInstance& Instance = BlendInstances[InstanceIndex];
+
+ // Index we could swap with
+ const uint32 SwapIndex = BlendInstances.Num() - 1;
+ if (BlendInstances.Num() > 1 && InstanceIndex != SwapIndex)
+ {
+ FBlendInstance& SwapInstance = BlendInstances[SwapIndex];
+ // Remap all of the actors to point to our new index
+ for (uint32 ActorIndex : SwapInstance.ActorIndices)
+ {
+ PerActorData[ActorIndex].BlendInstanceIndex = InstanceIndex;
+ }
+ }
+
+ BlendInstances.RemoveAtSwap(InstanceIndex, 1, false);
+}
+
+void UAnimSharingInstance::RemoveOnDemandInstance(int32 InstanceIndex)
+{
+ const FOnDemandInstance& Instance = OnDemandInstances[InstanceIndex];
+
+ // Index we could swap with
+ const uint32 SwapIndex = OnDemandInstances.Num() - 1;
+ if (OnDemandInstances.Num() > 1 && InstanceIndex != SwapIndex)
+ {
+ const FOnDemandInstance& SwapInstance = OnDemandInstances[SwapIndex];
+ // Remap all of the actors to point to our new index
+ for (uint32 ActorIndex : SwapInstance.ActorIndices)
+ {
+ // Only remap if it's still part of this instance
+ const bool bPartOfOtherOnDemand = PerActorData[ActorIndex].OnDemandInstanceIndex != InstanceIndex;
+ // Could be swapping with other instance in which case we should update the index
+ const bool bShouldUpdateIndex = !bPartOfOtherOnDemand || (PerActorData[ActorIndex].OnDemandInstanceIndex == SwapIndex);
+
+ if (bShouldUpdateIndex)
+ {
+ PerActorData[ActorIndex].OnDemandInstanceIndex = InstanceIndex;
+ }
+ }
+ }
+
+ // Remove and swap
+ OnDemandInstances.RemoveAtSwap(InstanceIndex, 1, false);
+}
+
+void UAnimSharingInstance::RemoveAdditiveInstance(int32 InstanceIndex)
+{
+ FAdditiveInstance& Instance = AdditiveInstances[InstanceIndex];
+
+ // Index we could swap with
+ const uint32 SwapIndex = AdditiveInstances.Num() - 1;
+ if (AdditiveInstances.Num() > 1 && InstanceIndex != SwapIndex)
+ {
+ FAdditiveInstance& SwapInstance = AdditiveInstances[SwapIndex];
+ // Remap all of the actors to point to our new index
+ if (SwapInstance.ActorIndex != INDEX_NONE)
+ {
+ PerActorData[SwapInstance.ActorIndex].AdditiveInstanceIndex = InstanceIndex;
+ }
+ }
+
+ AdditiveInstances.RemoveAtSwap(InstanceIndex, 1, false);
+}
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp
new file mode 100644
index 000000000000..a4cbfe067f3d
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingModule.cpp
@@ -0,0 +1,50 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "AnimationSharingModule.h"
+#include "Engine/Engine.h"
+#include "Components/SkeletalMeshComponent.h"
+#include "AnimationSharingManager.h"
+
+IMPLEMENT_MODULE( FAnimSharingModule, AnimationSharing);
+
+TMap FAnimSharingModule::WorldAnimSharingManagers;
+
+void FAnimSharingModule::StartupModule()
+{
+ FWorldDelegates::OnPostWorldCleanup.AddStatic(&FAnimSharingModule::OnWorldCleanup);
+}
+
+void FAnimSharingModule::AddReferencedObjects(FReferenceCollector& Collector)
+{
+ for (TPair& WorldAnimSharingManagerPair : WorldAnimSharingManagers)
+ {
+ Collector.AddReferencedObject(WorldAnimSharingManagerPair.Value, WorldAnimSharingManagerPair.Key);
+ }
+
+#if DEBUG_MATERIALS
+ for (UMaterialInterface* Material : UAnimationSharingManager::DebugMaterials)
+ {
+ Collector.AddReferencedObject(Material);
+ }
+#endif
+}
+
+bool FAnimSharingModule::CreateAnimationSharingManager(UWorld* InWorld, const UAnimationSharingSetup* Setup)
+{
+ if (InWorld && InWorld->IsGameWorld() && Setup && UAnimationSharingManager::AnimationSharingEnabled() && !WorldAnimSharingManagers.Contains(InWorld))
+ {
+ UAnimationSharingManager* Manager = NewObject(InWorld);
+ Manager->Initialise(Setup);
+ WorldAnimSharingManagers.Add(InWorld, Manager);
+
+ return true;
+ }
+
+ return false;
+}
+
+void FAnimSharingModule::OnWorldCleanup(UWorld* World, bool bSessionEnded, bool bCleanupResources)
+{
+ WorldAnimSharingManagers.Remove(World);
+}
+
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingSetup.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingSetup.cpp
new file mode 100644
index 000000000000..1f977d82b69d
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/AnimationSharingSetup.cpp
@@ -0,0 +1,30 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "AnimationSharingSetup.h"
+
+UAnimationSharingSetup::UAnimationSharingSetup(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
+{
+
+}
+
+#if WITH_EDITOR
+void UAnimationSharingSetup::PostLoad()
+{
+ Super::PostLoad();
+
+ /** Ensure all data required for the UI is loaded */
+ for (const FPerSkeletonAnimationSharingSetup& SharingSetup : SkeletonSetups)
+ {
+ SharingSetup.Skeleton.LoadSynchronous();
+ SharingSetup.SkeletalMesh.LoadSynchronous();
+
+ for (const FAnimationStateEntry& Entry : SharingSetup.AnimationStates)
+ {
+ for (const FAnimationSetup& AnimSetup : Entry.AnimationSetups)
+ {
+ AnimSetup.AnimSequence.LoadSynchronous();
+ }
+ }
+ }
+}
+#endif // WITH_EDITOR
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/TransitionBlendInstance.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/TransitionBlendInstance.cpp
new file mode 100644
index 000000000000..34c234cc8c6b
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Private/TransitionBlendInstance.cpp
@@ -0,0 +1,72 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "TransitionBlendInstance.h"
+#include "AnimationSharingInstances.h"
+#include "AnimationSharingManager.h"
+
+FTransitionBlendInstance::FTransitionBlendInstance() : SkeletalMeshComponent(nullptr), TransitionInstance(nullptr), FromComponent(nullptr), ToComponent(nullptr), BlendTime(0.f), bBlendState(false) {}
+
+void FTransitionBlendInstance::Initialise(USkeletalMeshComponent* InSkeletalMeshComponent, UClass* InAnimationBPClass)
+{
+ if (InSkeletalMeshComponent && InAnimationBPClass)
+ {
+ SkeletalMeshComponent = InSkeletalMeshComponent;
+ SkeletalMeshComponent->SetAnimInstanceClass(InAnimationBPClass);
+ SkeletalMeshComponent->SetComponentTickEnabled(false);
+ SkeletalMeshComponent->SetForcedLOD(0);
+ SkeletalMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
+ TransitionInstance = Cast(SkeletalMeshComponent->GetAnimInstance());
+ }
+}
+
+void FTransitionBlendInstance::Setup(USkeletalMeshComponent* InFromComponent, USkeletalMeshComponent* InToComponent, float InBlendTime)
+{
+ UAnimationSharingManager::SetDebugMaterial(SkeletalMeshComponent, 1);
+ SkeletalMeshComponent->SetComponentTickEnabled(true);
+ BlendTime = InBlendTime;
+ if (TransitionInstance)
+ {
+ if (TransitionInstance->bBlendBool)
+ {
+ TransitionInstance->FromComponent = FromComponent = InFromComponent;
+ TransitionInstance->ToComponent = ToComponent = InToComponent;
+ }
+ else
+ {
+ TransitionInstance->FromComponent = FromComponent = InToComponent;
+ TransitionInstance->ToComponent = ToComponent = InFromComponent;
+ }
+
+ TransitionInstance->BlendTime = InBlendTime;
+ TransitionInstance->bBlendBool = bBlendState = !TransitionInstance->bBlendBool;
+
+ SkeletalMeshComponent->AddTickPrerequisiteComponent(FromComponent);
+ SkeletalMeshComponent->AddTickPrerequisiteComponent(ToComponent);
+ }
+}
+
+void FTransitionBlendInstance::Stop()
+{
+ if (TransitionInstance)
+ {
+ UAnimationSharingManager::SetDebugMaterial(SkeletalMeshComponent, 0);
+ SkeletalMeshComponent->SetComponentTickEnabled(false);
+ SkeletalMeshComponent->RemoveTickPrerequisiteComponent(FromComponent);
+ SkeletalMeshComponent->RemoveTickPrerequisiteComponent(ToComponent);
+ }
+}
+
+USkeletalMeshComponent* FTransitionBlendInstance::GetComponent() const
+{
+ return SkeletalMeshComponent;
+}
+
+USkeletalMeshComponent* FTransitionBlendInstance::GetToComponent() const
+{
+ return bBlendState == false ? ToComponent : FromComponent;
+}
+
+USkeletalMeshComponent* FTransitionBlendInstance::GetFromComponent() const
+{
+ return bBlendState == false ? FromComponent : ToComponent;
+}
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AdditiveAnimationInstance.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AdditiveAnimationInstance.h
new file mode 100644
index 000000000000..dd224303facc
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AdditiveAnimationInstance.h
@@ -0,0 +1,30 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+#pragma once
+
+#include "Animation/SkeletalMeshActor.h"
+#include "Components/SkeletalMeshComponent.h"
+
+class UAnimSharingAdditiveInstance;
+class UAnimSequence;
+
+struct ANIMATIONSHARING_API FAdditiveAnimationInstance
+{
+public:
+ FAdditiveAnimationInstance();
+
+ void Initialise(USkeletalMeshComponent* InSkeletalMeshComponent, UClass* InAnimationBP);
+ void Setup(USkeletalMeshComponent* InBaseComponent, UAnimSequence* InAnimSequence);
+ void UpdateBaseComponent(USkeletalMeshComponent* InBaseComponent);
+ void Stop();
+ void Start();
+
+ USkeletalMeshComponent* GetComponent() const;
+ USkeletalMeshComponent* GetBaseComponent() const;
+
+protected:
+ USkeletalMeshComponent * SkeletalMeshComponent;
+ UAnimSharingAdditiveInstance* AdditiveInstance;
+ UAnimSequence* AdditiveAnimationSequence;
+ USkeletalMeshComponent* BaseComponent;
+ bool bLoopingState;
+};
\ No newline at end of file
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingInstances.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingInstances.h
new file mode 100644
index 000000000000..384125229e26
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingInstances.h
@@ -0,0 +1,80 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "UObject/ObjectMacros.h"
+#include "Animation/AnimInstance.h"
+#include "AnimationSharingInstances.generated.h"
+
+UCLASS(transient, Blueprintable)
+class ANIMATIONSHARING_API UAnimSharingStateInstance : public UAnimInstance
+{
+ friend class UAnimSharingInstance;
+
+ GENERATED_UCLASS_BODY()
+
+protected:
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = AnimationSharing)
+ UAnimSequence* AnimationToPlay;
+
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = AnimationSharing)
+ float PermutationTimeOffset;
+
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = AnimationSharing)
+ float PlayRate;
+
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = AnimationSharing)
+ bool bStateBool;
+
+ UFUNCTION(BlueprintCallable, Category = AnimationSharing)
+ void GetInstancedActors(TArray& Actors);
+
+private:
+ uint8 StateIndex;
+ uint8 ComponentIndex;
+ class UAnimSharingInstance* Instance;
+};
+
+UCLASS(transient, Blueprintable)
+class UAnimSharingTransitionInstance : public UAnimInstance
+{
+ friend struct FTransitionBlendInstance;
+
+ GENERATED_UCLASS_BODY()
+protected:
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Transition)
+ TWeakObjectPtr FromComponent;
+
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Transition)
+ TWeakObjectPtr ToComponent;
+
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Transition)
+ float BlendTime;
+
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Transition)
+ bool bBlendBool;
+};
+
+
+UCLASS(transient, Blueprintable)
+class UAnimSharingAdditiveInstance : public UAnimInstance
+{
+ friend struct FAdditiveAnimationInstance;
+
+ GENERATED_UCLASS_BODY()
+protected:
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Additive)
+ TWeakObjectPtr BaseComponent;
+
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Additive)
+ TWeakObjectPtr AdditiveAnimation;
+
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Additive)
+ float Alpha;
+
+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Transient, Category = Additive)
+ bool bStateBool;
+};
+
+
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingManager.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingManager.h
new file mode 100644
index 000000000000..a799b9ff8220
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingManager.h
@@ -0,0 +1,555 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "AnimationSharingModule.h"
+#include "AnimationSharingTypes.h"
+
+#include "Tickable.h"
+#include "UObject/SoftObjectPtr.h"
+#include "Engine/SkeletalMesh.h"
+#include "Engine/DeveloperSettings.h"
+#include "Animation/AnimBlueprint.h"
+#include "AdditiveAnimationInstance.h"
+#include "TransitionBlendInstance.h"
+
+#include "AnimationSharingManager.generated.h"
+
+DECLARE_LOG_CATEGORY_EXTERN(LogAnimationSharing, Log, All);
+DECLARE_STATS_GROUP(TEXT("Animation Sharing Manager"), STATGROUP_AnimationSharing, STATCAT_Advanced);
+
+#define DEBUG_MATERIALS (UE_EDITOR && !UE_BUILD_SHIPPING)
+
+DECLARE_DELEGATE_OneParam(FUpdateActorHandle, int32);
+
+class USignificanceManager;
+typedef uint32 AnimationSharingDataHandle;
+
+/** Structure which holds data about a currently in progress blend between two states */
+struct FBlendInstance
+{
+ /** Flag whether or not this instance is currently active */
+ bool bActive;
+ /** Flag whether or not the actor's part of this have been setup as a slave component to the blend actor, this is done so the blend actor atleast ticks once (otherwise it can pop from the previous blend end pose) */
+ bool bBlendStarted;
+ /** Flag whether or not this instance is blending towards an on-demand state */
+ bool bOnDemand;
+ /** World time in seconds at which the blend has finished (calculated at start of blend world time + blend duration) */
+ float EndTime;
+ /** Duration of the blend */
+ float BlendTime;
+ /** State value to blend from */
+ uint8 StateFrom;
+ /** State value to blend to */
+ uint8 StateTo;
+
+ /** Permutation indices from and to which we are blending, used to ensure we 'forward' the actor to the correct MasterPoseComponent when finished blending */
+ uint32 FromPermutationIndex;
+ uint32 ToPermutationIndex;
+
+ /** Actor used for blending between the two states */
+ FTransitionBlendInstance* TransitionBlendInstance;
+ /** Indices of actors who are set up as slaves to BlendActor's main skeletal mesh component */
+ TArray ActorIndices;
+
+ /** Optional index into OnDemandInstances from which we are blending */
+ uint32 FromOnDemandInstanceIndex;
+ /** Optional index into OnDemandInstance to which we are blending */
+ uint32 ToOnDemandInstanceIndex;
+};
+
+/** Structure which holds data about a currently running on-demand state animation instance */
+struct FOnDemandInstance
+{
+ /** Flag whether or not instance is active*/
+ bool bActive;
+ bool bBlendActive;
+
+ /** Flag whether or not the component should be 'returned' to the state they were in before the on-demand animation */
+ bool bReturnToPreviousState;
+
+ /** State value which is active */
+ uint8 State;
+
+ /** State value which the components should be set to when the on-demand animation has finished playing (used when !bReturnToPreviousState) */
+ uint8 ForwardState;
+
+ /** Time at which this instance was started */
+ float StartTime;
+
+ /** Time at which this on demand instance should blend out into the 'next' state the actor is in */
+ float StartBlendTime;
+
+ /** World time in seconds at which the animation has finished playing (calculated at start of blend world time + animation sequence length) */
+ float EndTime;
+
+ /** Index into Components array for the current state data which is used for playing the animation*/
+ uint32 UsedPerStateComponentIndex;
+
+ /** Permutation index that we are blending to before the end of the animation */
+ uint32 BlendToPermutationIndex;
+
+ /** Indices of actors who are set up as slaves to the skeletal mesh component running the animation */
+ TArray ActorIndices;
+};
+
+struct FAdditiveInstance
+{
+ /** Flag whether or not instance is active */
+ bool bActive;
+
+ /** State index this instance is running */
+ uint8 State;
+
+ /** Time at which this instance finishes */
+ float EndTime;
+
+ /** Current actor indices as part of this instance */
+ uint32 ActorIndex;
+
+ /** Skeletal mesh component on which the additive animation is applied */
+ USkeletalMeshComponent* BaseComponent;
+
+ /** Actor used for playing the additive animation */
+ FAdditiveAnimationInstance* AdditiveAnimationInstance;
+};
+
+/** Structure which holds data about a unique state which is linked to an enumeration value defined by the user. The data is populated from the user exposed FAnimationStateEntry */
+struct FPerStateData
+{
+ FPerStateData() : bIsOnDemand(false), bIsAdditive(false), BlendTime(0.f), CurrentFrameOnDemandIndex(INDEX_NONE), StateEnumValue(INDEX_NONE), AdditiveAnimationSequence(nullptr) {}
+
+ /** Flag whether or not this state is an on-demand state, this means that we kick off a unique animation when needed */
+ bool bIsOnDemand;
+
+ /** Flag whether or not this state is an additive state */
+ bool bIsAdditive;
+
+ /** Flag whether or not we should return to the previous state, only used when this state is an on-demand one*/
+ bool bReturnToPreviousState;
+
+ /** Flag whether or not ForwardStateValue should be used hwen the animation has finished*/
+ bool bShouldForwardToState;
+
+ /** Duration of blending when blending to this state */
+ float BlendTime;
+
+ /** This is (re-)set every frame, and allows for quickly finding an on-demand instance which was setup this frame */
+ uint32 CurrentFrameOnDemandIndex;
+ /** Number of 'wiggle' frames, this is used when we run out of available entries in Components, if one of the FOnDemandInstance has started NumWiggleFrames ago or earlier,
+ it is used instead of a brand new one */
+ float WiggleTime;
+
+ /** State value to which the actors part of an FOnDemandInstance should be set to when its animation has finished */
+ uint8 ForwardStateValue;
+ /** Enum value linked to this state */
+ uint8 StateEnumValue;
+
+ /** Animation Sequence that is used for Additive States */
+ UAnimSequence* AdditiveAnimationSequence;
+
+ /** Components setup to play animations for this state */
+ TArray Components;
+ /** Bits keeping track which of the components are in-use, in case of On Demand state this is managed by FOnDemandInstance, otherwise we clear and populate the flags each frame */
+ TBitArray<> InUseComponentFrameBits;
+ TBitArray<> PreviousInUseComponentFrameBits;
+
+ /** Bits keeping track whether or not any of the slave components requires the master component to tick */
+ TBitArray<> SlaveTickRequiredFrameBits;
+
+ /** Length of the animations used for an on-demand state, array as it could contain different animation permutations */
+ TArray AnimationLengths;
+};
+
+struct FPerComponentData
+{
+ /** Skeletal mesh component registered for this component */
+ USkeletalMeshComponent* Component;
+ /** Index to the owning actor (used to index PerActorData) */
+ int32 ActorIndex;
+};
+
+struct FPerActorData
+{
+ /** Current state value (used to index PerStateData) */
+ uint8 CurrentState;
+ /** Previous state value (used to index PerStateData) */
+ uint8 PreviousState;
+ /** Permutation index (used to index Components array inside of PerStateData) */
+ uint8 PermutationIndex;
+
+ /** Flag whether or not we are currently blending */
+ bool bBlending;
+ /** Flag whether or not we are currently part of an on-demand animation state */
+ bool bRunningOnDemand;
+
+ /** Flag whether or not we are currently part of an additive animation state */
+ bool bRunningAdditive;
+
+ /** Cached significance value */
+ float SignificanceValue;
+
+ /** Flag whether or not this actor requires the master component to tick */
+ bool bRequiresTick;
+
+ /** Index to blend instance which is currently driving this actor's animation */
+ uint32 BlendInstanceIndex;
+ /** Index to on demand instance which is running accord to our current state (or previous state) */
+ uint32 OnDemandInstanceIndex;
+ /** Index to additive instance which is running on top of our state */
+ uint32 AdditiveInstanceIndex;
+
+ /** Indices of the Components owned by this actor (used to index into PerComponentData) */
+ TArray ComponentIndices;
+
+ /** Register delegate called when actor is swapped and the handle should be updated */
+ FUpdateActorHandle UpdateActorHandleDelegate;
+};
+
+template
+struct FInstanceStack
+{
+ ~FInstanceStack()
+ {
+ for (InstanceType* Instance : AvailableInstances)
+ {
+ delete Instance;
+ }
+ AvailableInstances.Empty();
+
+ for (InstanceType* Instance : InUseInstances)
+ {
+ delete Instance;
+ }
+ InUseInstances.Empty();
+ }
+
+
+ /** Return whether instance are available */
+ bool InstanceAvailable() const
+ {
+ return AvailableInstances.Num() != 0;
+ }
+
+ /** Get an available instance */
+ InstanceType* GetInstance()
+ {
+ InstanceType* Instance = nullptr;
+ if (AvailableInstances.Num())
+ {
+ Instance = AvailableInstances.Pop(false);
+ InUseInstances.Add(Instance);
+ }
+
+ return Instance;
+ }
+
+ /** Return instance back */
+ void FreeInstance(InstanceType* Instance)
+ {
+ AvailableInstances.Add(Instance);
+ InUseInstances.RemoveSwap(Instance);
+ }
+
+ /** Add a new instance to the 'stack' */
+ void AddInstance(InstanceType* Instance)
+ {
+ AvailableInstances.Add(Instance);
+ }
+
+ TArray AvailableInstances;
+ TArray InUseInstances;
+};
+
+UCLASS()
+class UAnimSharingInstance : public UObject
+{
+ GENERATED_BODY()
+
+public:
+ // Begin UObject overrides
+ virtual void BeginDestroy() override;
+ // End UObject overrides
+
+ /** This uses the StateProcessor to determine the state index the actor is currently in */
+ uint8 DetermineStateForActor(uint32 ActorIndex, bool& bShouldProcess);
+
+ /** Initial set up of all animation sharing data and states */
+ void Setup(UAnimationSharingManager* AnimationSharingManager, const FPerSkeletonAnimationSharingSetup& SkeletonSetup, const FAnimationSharingScalability* ScalabilitySettings, uint32 Index);
+ /** Populates data for a state setup */
+ void SetupState(FPerStateData& StateData, const FAnimationStateEntry& StateEntry, USkeletalMesh* SkeletalMesh, const FPerSkeletonAnimationSharingSetup& SkeletonSetup, uint32 Index);
+
+ /** Retrieves a blend instance, this could either mean reusing an already in progress one or a brand new one (if available according to scalability settings)
+ returns whether or not the setup was successful */
+ uint32 SetupBlend(uint8 FromState, uint8 ToState, uint32 ActorIndex);
+
+ /** Retrieves a blend instance, and sets up a blend from a currently running On-Demand instance to ToState */
+ uint32 SetupBlendFromOnDemand(uint8 ToState, uint32 OnDemandInstanceIndex, uint32 ActorIndex);
+
+ /** Retrieves a blend instance, and sets up a blend between a currently running On-Demand instance and another one which was started this frame */
+ uint32 SetupBlendBetweenOnDemands(uint8 FromOnDemandInstanceIndex, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex);
+
+ /** Retrieves a blend instance, and setups up a blend to an On-Demand instance from a regular animation state */
+ uint32 SetupBlendToOnDemand(uint8 FromState, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex);
+
+ /** Switches between on-demand instances directly, without blending */
+ void SwitchBetweenOnDemands(uint32 FromOnDemandInstanceIndex, uint32 ToOnDemandInstanceIndex, uint32 ActorIndex);
+
+ /** Retrieves a blend instance, this could either mean reusing an already in progress one or a brand new one (if available according to scalability settings)
+ returns an index into OnDemandInstances array or INDEX_NONE if unable to setup an instance */
+ uint32 SetupOnDemandInstance(uint8 StateIndex);
+
+ /** Retrieves an additive instance, these are unique and cannot be reused */
+ uint32 SetupAdditiveInstance(uint8 StateIndex, uint8 FromState, uint8 StateComponentIndex);
+
+ /** Retrieves the blend-time for this specific state */
+ float CalculateBlendTime(uint8 StateIndex) const;
+
+ /** Kicks off the blend and on-demand instances at the end of the current frame tick, this sets up the blend instance with the correct components to blend between */
+ void KickoffInstances();
+
+ /** Ticks all currently running blend instances, checks whether or not the blend is finished and forwards the actor/components to the correct animation state */
+ void TickBlendInstances();
+
+ /** Ticks all currently running on-demand instances, this checks whether or not the animation has finished or if we have to start blending out of the state already */
+ void TickOnDemandInstances();
+
+ template
+ bool DoAnyActorsRequireTicking(const InstanceType& Instance)
+ {
+ bool bShouldTick = false;
+ for (uint32 ActorIndex : Instance.ActorIndices)
+ {
+ if (PerActorData[ActorIndex].bRequiresTick)
+ {
+ bShouldTick = true;
+ break;
+ }
+ }
+
+ return bShouldTick;
+ }
+
+ /** Ticks all currently running additive animation instances, this checks whether or not it has finished yet and sets the base-component as the master component when it has */
+ void TickAdditiveInstances();
+
+ /** Ticks all Actor Data entries and determines their current state, if changed since last tick it will alter their animation accordingly */
+ void TickActorStates();
+
+ /** Ticks various types of debugging data / drawing (not active in shipping build) */
+ void TickDebugInformation();
+
+ /** Ticks all unique animation states, this checks which components are currently used and turns of those which currently don't have any slaves */
+ void TickAnimationStates();
+
+ /** Removal functions which also make sure that indices of other data-structres are correctly remapped */
+ void RemoveBlendInstance(int32 InstanceIndex);
+ void RemoveOnDemandInstance(int32 InstanceIndex);
+ void RemoveAdditiveInstance(int32 InstanceIndex);
+ void RemoveComponent(int32 ComponentIndex);
+
+ /** Removal functions which also make sure the actor is set to the correct master pose component */
+ void RemoveFromCurrentBlend(int32 ActorIndex);
+ void RemoveFromCurrentOnDemand(int32 ActorIndex);
+
+ /** Frees up a Blend instance and resets its state */
+ void FreeBlendInstance(FTransitionBlendInstance* Instance);
+
+ /** Frees up an Additive Animation instance and resets it state*/
+ void FreeAdditiveInstance(FAdditiveAnimationInstance* Instance);
+
+ /** Sets up all components of an actor to be slaves of Component */
+ void SetMasterComponentForActor(uint32 ActorIndex, USkeletalMeshComponent* Component);
+
+ /** Sets up the correct MasterPoseComponent for the passed in Component and State indices */
+ void SetupSlaveComponent(uint8 CurrentState, uint32 ActorIndex);
+
+ /** Sets up the correct MasterPoseComponent according to the state and permutation indices */
+ void SetPermutationSlaveComponent(uint8 StateIndex, uint32 ActorIndex, uint32 PermutationIndex);
+
+ /** Determines a permutation index for the given actor and state */
+ uint32 DeterminePermutationIndex(uint32 ActorIndex, uint8 State) const;
+
+ /** Marks the component as either used/not-used, this is used to disable ticking of components which are not in use*/
+ void SetComponentUsage(bool bUsage, uint8 StateIndex, uint32 ComponentIndex);
+
+ /** Sets the whether or not any of the slave components are visible */
+ void SetComponentTick(uint8 StateIndex, uint32 ComponentIndex);
+
+ /** Actors currently registered to be animation driven by the AnimManager using this setup */
+ UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing)
+ TArray RegisteredActors;
+
+ /** Per actor data, matches RegisteredActors*/
+ TArray PerActorData;
+ /** Per component state data indexed from FPerActorData.ComponentIndices */
+ TArray PerComponentData;
+
+ /** Array of unique state data */
+ TArray PerStateData;
+
+ /** Blend and Additive actors data structure */
+ FInstanceStack BlendInstanceStack;
+ FInstanceStack AdditiveInstanceStack;
+
+ /** (Blueprint)class instance used for determining the state enum value for each registered actor */
+ UPROPERTY(EditAnywhere, Transient, Category = AnimationSharing)
+ UAnimationSharingStateProcessor* StateProcessor;
+ bool bNativeStateProcessor;
+
+ /** Currently running blend instances */
+ TArray BlendInstances;
+ /** Currently running on-demand instance */
+ TArray OnDemandInstances;
+ /** Currently running additive instances */
+ TArray AdditiveInstances;
+
+ UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing)
+ TArray UsedAnimationSequences;
+
+ /** Significance manager used for retrieve AI actor significance values */
+ USignificanceManager* SignificanceManager;
+
+ /** Animation sharing manager for the current world */
+ UAnimationSharingManager* AnimSharingManager;
+
+ /** Enum class set up by the user to 'describe' the animation states */
+ UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing)
+ UEnum* StateEnum;
+
+ /** Actor to which all the running SkeletalMeshComponents used for the sharing are attached to */
+ UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing)
+ AActor* SharingActor;
+
+ /** Platform specific scalability settings */
+ const FAnimationSharingScalability* ScalabilitySettings;
+
+ /** Bounds for the currently used skeletal mesh */
+ FVector SkeletalMeshBounds;
+
+ /** Number of animation setups */
+ uint32 NumSetups;
+
+ /** Holds the current frame world time */
+ float WorldTime;
+};
+
+USTRUCT()
+struct FTickAnimationSharingFunction : public FTickFunction
+{
+ GENERATED_USTRUCT_BODY()
+
+ FTickAnimationSharingFunction() : Manager(nullptr) {}
+
+ // Begin FTickFunction overrides
+ virtual void ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override;
+ virtual FString DiagnosticMessage() override;
+ virtual FName DiagnosticContext(bool bDetailed) override;
+ // End FTickFunction overrides
+
+ class UAnimationSharingManager* Manager;
+};
+
+
+template<>
+struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2
+{
+ enum
+ {
+ WithCopy = false
+ };
+};
+
+UCLASS(config = Engine, defaultconfig)
+class ANIMATIONSHARING_API UAnimationSharingManager : public UObject
+{
+ GENERATED_BODY()
+
+public:
+ // Begin UObject overrides
+ virtual void BeginDestroy() override;
+ virtual UWorld* GetWorld()const override;
+ // End UObject overrides
+
+ /** Returns the AnimationSharing Manager, nullptr if none was set up */
+ UFUNCTION(BlueprintCallable, Category = AnimationSharing, meta = (WorldContext = "WorldContextObject"))
+ static UAnimationSharingManager* GetAnimationSharingManager(UObject* WorldContextObject);
+
+ /** Create an Animation Sharing Manager using the provided Setup */
+ UFUNCTION(BlueprintCallable, Category = AnimationSharing, meta = (WorldContext = "WorldContextObject"))
+ static bool CreateAnimationSharingManager(UObject* WorldContextObject, const UAnimationSharingSetup* Setup);
+
+ /** Register an Actor with this Animation Sharing manager, according to the SharingSkeleton */
+ UFUNCTION(BlueprintCallable, Category = AnimationSharing, meta = (DisplayName = "Register Actor"))
+ void RegisterActorWithSkeletonBP(AActor* InActor, const USkeleton* SharingSkeleton);
+
+ /** Returns whether or not the animation sharing is enabled */
+ UFUNCTION(BlueprintPure, Category = AnimationSharing)
+ static bool AnimationSharingEnabled();
+
+ /** Returns the AnimationSharing Manager for a specific UWorld, nullptr if none was set up */
+ static UAnimationSharingManager* GetManagerForWorld(UWorld* InWorld);
+
+ /** Registers actor with the animation sharing system */
+ void RegisterActor(AActor* InActor, FUpdateActorHandle Delegate);
+
+ /** Registers actor with the animation sharing system according to the SharingSkeleton's sharing setup (if available) */
+ void RegisterActorWithSkeleton(AActor* InActor, const USkeleton* SharingSkeleton, FUpdateActorHandle Delegate);
+
+ /** Unregisters actor with the animation sharing system */
+ void UnregisterActor(AActor* InActor);
+
+ /** Update cached significance for registered actor */
+ void UpdateSignificanceForActorHandle(uint32 InHandle, float InValue);
+
+ /** Ensures all actor data is cleared */
+ void ClearActorData();
+
+ /** Ensures all currently registered actors are removed */
+ void UnregisterAllActors();
+
+ /** Sets the visibility of currently used Master Pose Components */
+ void SetMasterComponentsVisibility(bool bVisible);
+
+ /** Initialize sharing data structures */
+ void Initialise(const UAnimationSharingSetup* InSetup);
+
+ /** Returns current scalability settings */
+ const FAnimationSharingScalability& GetScalabilitySettings() const;
+
+ void Tick(float DeltaTime);
+ FTickAnimationSharingFunction& GetTickFunction();
+protected:
+
+ /** Populates all data required for a Skeleton setup */
+ void SetupPerSkeletonData(const FPerSkeletonAnimationSharingSetup& SkeletonSetup);
+
+ /** Dealing with Actor data and handles */
+ uint32 CreateActorHandle(uint8 SkeletonIndex, uint32 ActorIndex) const;
+ uint8 GetSkeletonIndexFromHandle(uint32 InHandle) const;
+ uint32 GetActorIndexFromHandle(uint32 InHandle) const;
+ FPerActorData* GetActorDataByHandle(uint32 InHandle);
+protected:
+ /** Array of unique skeletons, matches PerSkeletonData array entries*/
+ TArray Skeletons;
+
+ /** Sharing data required for the unique Skeleton setups */
+ UPROPERTY(VisibleAnywhere, Transient, Category = AnimationSharing)
+ TArray PerSkeletonData;
+
+ /** Platform specific scalability settings */
+ FAnimationSharingScalability ScalabilitySettings;
+
+ /** Tick function for this manager */
+ FTickAnimationSharingFunction TickFunction;
+public:
+#if DEBUG_MATERIALS
+ static TArray DebugMaterials;
+#endif
+ static void SetDebugMaterial(USkeletalMeshComponent* Component, uint8 State);
+ static void SetDebugMaterialForActor(UAnimSharingInstance* Data, uint32 ActorIndex, uint8 State);
+
+#if WITH_EDITOR
+ static FName GetPlatformName();
+#endif
+};
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h
new file mode 100644
index 000000000000..ba268f27b01f
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingModule.h
@@ -0,0 +1,36 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "UObject/ObjectMacros.h"
+#include "UObject/Object.h"
+#include "Modules/ModuleManager.h"
+#include "UObject/GCObject.h"
+#include "Engine/World.h"
+
+class UAnimationSharingManager;
+class UAnimationSharingSetup;
+
+class ANIMATIONSHARING_API FAnimSharingModule : public FDefaultGameModuleImpl, public FGCObject
+{
+public:
+ // Begin IModuleInterface overrides
+ virtual void StartupModule() override;
+ // End IModuleInterface overrides
+
+ // Begin FGCObject overrides
+ virtual void AddReferencedObjects( FReferenceCollector& Collector ) override;
+ // End FGCObject overrides
+
+ FORCEINLINE static UAnimationSharingManager* Get(const UWorld* World)
+ {
+ return WorldAnimSharingManagers.FindRef(World);
+ }
+
+ /** Creates an animation sharing manager for the given UWorld (must be a Game World) */
+ static bool CreateAnimationSharingManager(UWorld* InWorld, const UAnimationSharingSetup* Setup);
+private:
+ static void OnWorldCleanup(UWorld* World, bool bSessionEnded, bool bCleanupResources);
+ static TMap WorldAnimSharingManagers;
+};
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingSetup.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingSetup.h
new file mode 100644
index 000000000000..821007a4ebd5
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingSetup.h
@@ -0,0 +1,27 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "UObject/ObjectMacros.h"
+#include "UObject/Object.h"
+#include "AnimationSharingTypes.h"
+
+#include "AnimationSharingSetup.generated.h"
+
+UCLASS(hidecategories = Object, Blueprintable, config = Engine)
+class ANIMATIONSHARING_API UAnimationSharingSetup : public UObject
+{
+ GENERATED_UCLASS_BODY()
+public:
+
+#if WITH_EDITOR
+ virtual void PostLoad() override;
+#endif // WITH_EDITOR
+
+ UPROPERTY(EditAnywhere, config, Category = AnimationSharing)
+ TArray SkeletonSetups;
+
+ UPROPERTY(EditAnywhere, config, Category = AnimationSharing)
+ FAnimationSharingScalability ScalabilitySettings;
+};
\ No newline at end of file
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingTypes.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingTypes.h
new file mode 100644
index 000000000000..d525f4b188b5
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/AnimationSharingTypes.h
@@ -0,0 +1,181 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "Engine/DeveloperSettings.h"
+#include "PerPlatformProperties.h"
+#include "Animation/AnimBlueprint.h"
+#include "Animation/AnimSequence.h"
+#include "Animation/Skeleton.h"
+#include "Engine/SkeletalMesh.h"
+#include "UObject/Class.h"
+#include "Animation/AnimInstance.h"
+#include "AnimationSharingInstances.h"
+#include "AnimationSharingTypes.generated.h"
+
+USTRUCT()
+struct FAnimationSetup
+{
+ GENERATED_BODY()
+public:
+ FAnimationSetup() : AnimSequence(nullptr), AnimBlueprint(nullptr), NumRandomizedInstances(1), Enabled(true) {}
+
+ /** Animation Sequence to play for this particular setup */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ TSoftObjectPtr AnimSequence;
+
+ /** Animation blueprint to use for playing back the Animation Sequence */
+ UPROPERTY(EditAnywhere, AdvancedDisplay, Category = AnimationSharing)
+ TSubclassOf AnimBlueprint;
+
+ /** The number of randomized instances created from this setup, it will create instance with different start time offsets (Length / Number of Instance) * InstanceIndex */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ FPerPlatformInt NumRandomizedInstances;
+
+ /** Whether or not this setup is enabled for specific platforms */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ FPerPlatformBool Enabled;
+};
+
+USTRUCT()
+struct FAnimationStateEntry
+{
+ GENERATED_BODY()
+public:
+ FAnimationStateEntry() : State(0), bOnDemand(false), bAdditive(false), BlendTime(0.f), bReturnToPreviousState(false), bSetNextState(false), NextState(0), MaximumNumberOfConcurrentInstances(1), WiggleTimePercentage(0.1f), bRequiresCurves(false) {}
+
+ /** Enum value linked to this state */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ uint8 State;
+
+ /** Per state animation setup */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ TArray AnimationSetups;
+
+ /** Flag whether or not this state is an on-demand state, this means that we kick off a unique animation when needed */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ bool bOnDemand;
+
+ /** Whether or not this state is an additive state */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bOnDemand"))
+ bool bAdditive;
+
+ /** Duration of blending when blending to this state */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "!bAdditive"))
+ float BlendTime;
+
+ /** Flag whether or not we should return to the previous state, only used when this state is an on-demand one*/
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bOnDemand"))
+ bool bReturnToPreviousState;
+
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bOnDemand"))
+ bool bSetNextState;
+
+ /** State value to which the actors part of an on demand state should be set to when its animation has finished */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bSetNextState"))
+ uint8 NextState;
+
+ /** Number of instances that will be created for this state (platform-specific) */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta=(EditCondition="bOnDemand"))
+ FPerPlatformInt MaximumNumberOfConcurrentInstances;
+
+ /** Percentage of 'wiggle' frames, this is used when we run out of available entries in Components, if one of the on-demand animations has started SequenceLength * WiggleFramePercentage ago or earlier,
+ it is used instead of a brand new one */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bOnDemand", UIMin="0.0", UIMax="1.0"))
+ float WiggleTimePercentage;
+
+ /** Whether or not this animation requires curves or morphtargets to function correctly for slave components */
+ UPROPERTY(EditAnywhere, AdvancedDisplay, Category = AnimationSharing)
+ bool bRequiresCurves;
+};
+
+UCLASS(Blueprintable)
+class ANIMATIONSHARING_API UAnimationSharingStateProcessor : public UObject
+{
+ GENERATED_BODY()
+public:
+ UFUNCTION(BlueprintNativeEvent, Category = AnimationSharing)
+ void ProcessActorState(int32& OutState, AActor* InActor, uint8 CurrentState, uint8 OnDemandState, bool& bShouldProcess);
+ void ProcessActorState_Implementation(int32& OutState, AActor* InActor, uint8 CurrentState, uint8 OnDemandState, bool& bShouldProcess)
+ {
+ ProcessActorState_Internal(OutState, InActor, CurrentState, OnDemandState, bShouldProcess);
+ }
+
+ UFUNCTION(BlueprintNativeEvent, Category = AnimationSharing)
+ UEnum* GetAnimationStateEnum();
+ UEnum* GetAnimationStateEnum_Implementation()
+ {
+ return GetAnimationStateEnum_Internal();
+ }
+
+ virtual void ProcessActorStates(TArray& OutStates, const TArray& InActors, const TArray& CurrentStates, const TArray& OnDemandStates, TArray& ShouldProcessFlags)
+ {
+ for (int32 Index = 0; Index < InActors.Num(); ++Index)
+ {
+ ProcessActorState(OutStates[Index], InActors[Index], CurrentStates[Index], OnDemandStates[Index], ShouldProcessFlags[Index]);
+ }
+ }
+
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ TSoftObjectPtr AnimationStateEnum;
+protected:
+ virtual UEnum* GetAnimationStateEnum_Internal() { return AnimationStateEnum.LoadSynchronous(); }
+ virtual void ProcessActorState_Internal(int32& OutState, AActor* InActor, uint8 CurrentState, uint8 OnDemandState, bool& bShouldProcess) {}
+};
+
+USTRUCT()
+struct FPerSkeletonAnimationSharingSetup
+{
+ GENERATED_BODY()
+public:
+ FPerSkeletonAnimationSharingSetup() : Skeleton(nullptr), SkeletalMesh(nullptr), BlendAnimBlueprint(nullptr), AdditiveAnimBlueprint(nullptr), StateProcessorClass(nullptr) {}
+
+ /** Skeleton compatible with the animation sharing setup */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ TSoftObjectPtr Skeleton;
+
+ /** Skeletal mesh used to setup skeletal mesh components */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ TSoftObjectPtr SkeletalMesh;
+
+ /** Animation blueprint used to perform the blending between states */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (DisplayName="Animation Blueprint for Blending"))
+ TSubclassOf BlendAnimBlueprint;
+
+ /** Animation blueprint used to apply additive animation on top of other states */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (DisplayName = "Animation Blueprint for Additive Animation"))
+ TSubclassOf AdditiveAnimBlueprint;
+
+ /** Interface class used when determining which state an actor is in */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ TSubclassOf StateProcessorClass;
+
+ /** Definition of different animation states */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ TArray AnimationStates;
+};
+
+USTRUCT()
+struct FAnimationSharingScalability
+{
+ GENERATED_BODY()
+public:
+ FAnimationSharingScalability() : UseBlendTransitions(true), BlendSignificanceValue(0.5f), MaximumNumberConcurrentBlends(1) {}
+
+ /** Flag whether or not to use blend transitions between states */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing)
+ FPerPlatformBool UseBlendTransitions;
+
+ /** Significance value tied to whether or not a transition should be blended */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bBlendTransitions"))
+ FPerPlatformFloat BlendSignificanceValue;
+
+ /** Maximum number of blends which can be running concurrently */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bBlendTransitions"))
+ FPerPlatformInt MaximumNumberConcurrentBlends;
+
+ /** Significance value tied to whether or not the master pose components should be ticking */
+ UPROPERTY(EditAnywhere, Category = AnimationSharing, meta = (EditCondition = "bBlendTransitions"))
+ FPerPlatformFloat TickSignificanceValue;
+};
+
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/TransitionBlendInstance.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/TransitionBlendInstance.h
new file mode 100644
index 000000000000..0ece3a9b1ff0
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharing/Public/TransitionBlendInstance.h
@@ -0,0 +1,28 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+#pragma once
+
+#include "Animation/SkeletalMeshActor.h"
+#include "Components/SkeletalMeshComponent.h"
+
+class UAnimSharingTransitionInstance;
+
+struct ANIMATIONSHARING_API FTransitionBlendInstance
+{
+public:
+ FTransitionBlendInstance();
+ void Initialise(USkeletalMeshComponent* InSkeletalMeshComponent, UClass* InAnimationBP);
+ void Setup(USkeletalMeshComponent* InFromComponent, USkeletalMeshComponent* InToComponent, float InBlendTime);
+ void Stop();
+
+ USkeletalMeshComponent* GetComponent() const;
+ USkeletalMeshComponent* GetToComponent() const;
+ USkeletalMeshComponent* GetFromComponent() const;
+
+protected:
+ USkeletalMeshComponent * SkeletalMeshComponent;
+ UAnimSharingTransitionInstance* TransitionInstance;
+ USkeletalMeshComponent* FromComponent;
+ USkeletalMeshComponent* ToComponent;
+ float BlendTime;
+ bool bBlendState;
+};
\ No newline at end of file
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/AnimationSharingEd.Build.cs b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/AnimationSharingEd.Build.cs
new file mode 100644
index 000000000000..20d11299c409
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/AnimationSharingEd.Build.cs
@@ -0,0 +1,32 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+using UnrealBuildTool;
+
+public class AnimationSharingEd : ModuleRules
+{
+ public AnimationSharingEd(ReadOnlyTargetRules Target) : base(Target)
+ {
+ PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[] {
+ "Core",
+ "CoreUObject",
+ "Engine",
+ "UnrealEd",
+ "AssetTools",
+ "AnimationSharing",
+ "Slate",
+ "SlateCore",
+ "PropertyEditor",
+ "EditorStyle"
+ }
+ );
+
+ PrivateIncludePaths.AddRange(
+ new string[] {
+ "AnimationSharingEd/Private"
+ });
+ }
+
+}
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingEdModule.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingEdModule.cpp
new file mode 100644
index 000000000000..04a4ebed6f4a
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingEdModule.cpp
@@ -0,0 +1,43 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "AnimationSharingEdModule.h"
+#include "AssetTypeActions_AnimationSharingSetup.h"
+#include "AssetToolsModule.h"
+#include "SetupDetailsViewCustomizations.h"
+
+IMPLEMENT_MODULE(FAnimSharingEdModule, AnimationSharingEd );
+
+void FAnimSharingEdModule::StartupModule()
+{
+ FAssetToolsModule& AssetToolsModule = FAssetToolsModule::GetModule();
+
+ IAssetTools& AssetTools = AssetToolsModule.Get();
+ AssetAction = new FAssetTypeActions_AnimationSharingSetup();
+ AssetTools.RegisterAssetTypeActions(MakeShareable(AssetAction));
+
+ FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor");
+ {
+ PropertyModule.RegisterCustomPropertyTypeLayout("PerSkeletonAnimationSharingSetup", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FPerSkeletonAnimationSharingSetupCustomization::MakeInstance));
+ PropertyModule.RegisterCustomPropertyTypeLayout("AnimationStateEntry", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAnimationStateEntryCustomization::MakeInstance));
+
+ PropertyModule.RegisterCustomPropertyTypeLayout("AnimationSetup", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAnimationSetupCustomization::MakeInstance));
+ }
+}
+
+void FAnimSharingEdModule::ShutdownModule()
+{
+ if (UObjectInitialized())
+ {
+ FAssetToolsModule& AssetToolsModule = FAssetToolsModule::GetModule();
+ IAssetTools& AssetTools = AssetToolsModule.Get();
+ AssetTools.UnregisterAssetTypeActions(AssetAction->AsShared());
+
+ FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor");
+ {
+ PropertyModule.UnregisterCustomPropertyTypeLayout("PerSkeletonAnimationSharingSetup");
+ PropertyModule.UnregisterCustomPropertyTypeLayout("AnimationStateEntry");
+ PropertyModule.UnregisterCustomPropertyTypeLayout("AnimationSetup");
+ }
+ }
+}
+
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingSetupFactory.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingSetupFactory.cpp
new file mode 100644
index 000000000000..7186c5fd6f24
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/AnimationSharingSetupFactory.cpp
@@ -0,0 +1,22 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "AnimationSharingSetupFactory.h"
+#include "AnimationSharingSetup.h"
+#include "AssetTypeCategories.h"
+
+UAnimationSharingSetupFactory::UAnimationSharingSetupFactory(const FObjectInitializer& ObjectInitializer)
+ : Super(ObjectInitializer)
+{
+ SupportedClass = UAnimationSharingSetup::StaticClass();
+ bCreateNew = true;
+}
+
+UObject* UAnimationSharingSetupFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
+{
+ return NewObject(InParent, Class, Name, Flags);
+}
+
+uint32 UAnimationSharingSetupFactory::GetMenuCategories() const
+{
+ return EAssetTypeCategories::Animation;
+}
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/SetupDetailsViewCustomizations.cpp b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/SetupDetailsViewCustomizations.cpp
new file mode 100644
index 000000000000..9a57bbd8db28
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Private/SetupDetailsViewCustomizations.cpp
@@ -0,0 +1,379 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "SetupDetailsViewCustomizations.h"
+#include "IDetailChildrenBuilder.h"
+#include "EditorStyleSet.h"
+#include "UObject/UObjectGlobals.h"
+#include "Widgets/Input/STextComboBox.h"
+#include "AnimationSharingTypes.h"
+
+#define LOCTEXT_NAMESPACE "AnimationSharingSetupCustomization"
+
+TSharedRef FPerSkeletonAnimationSharingSetupCustomization::MakeInstance()
+{
+ return MakeShareable(new FPerSkeletonAnimationSharingSetupCustomization());
+}
+
+void FPerSkeletonAnimationSharingSetupCustomization::CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils)
+{
+ SkeletonPropertyHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, Skeleton));
+ if (SkeletonPropertyHandle.IsValid())
+ {
+ HeaderRow
+ .NameContent()
+ [
+ SNew(SHorizontalBox)
+ +SHorizontalBox::Slot()
+ .FillWidth(1)
+ .VAlign(VAlign_Center)
+ [
+ // Show the name of the asset or actor
+ SNew(STextBlock)
+ .Font(FEditorStyle::GetFontStyle("PropertyWindow.NormalFont"))
+ .Text(this, &FPerSkeletonAnimationSharingSetupCustomization::GetSkeletonName)
+ ]
+ ];
+ }
+}
+
+void FPerSkeletonAnimationSharingSetupCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
+{
+ uint32 NumChildren;
+ StructPropertyHandle->GetNumChildren(NumChildren);
+
+ const TArray SkeletonDisabledProperties =
+ {
+ GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, SkeletalMesh),
+ GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, StateProcessorClass),
+ GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, BlendAnimBlueprint),
+ GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, AdditiveAnimBlueprint)
+ };
+
+ const TArray EnumDisabledProperties =
+ {
+ GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, AnimationStates)
+ };
+
+ TSharedPtr ProcessorProperty = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup,
+ StateProcessorClass));
+
+ void* StructPtr = nullptr;
+ StructPropertyHandle->GetValueData(StructPtr);
+
+ for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex)
+ {
+ TSharedRef ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef();
+ IDetailPropertyRow& Property = StructBuilder.AddProperty(ChildHandle);
+
+ const FName& PropertyName = ChildHandle->GetProperty()->GetFName();
+
+ /** Properties disabled by an invalid USkeleton */
+ if (SkeletonDisabledProperties.Contains(PropertyName))
+ {
+ Property.IsEnabled(TAttribute::Create([this]() -> bool
+ {
+ UObject* Object = nullptr;
+ return (SkeletonPropertyHandle->GetValue(Object) == FPropertyAccess::Success) && (Object != nullptr);
+ }));
+ }
+
+ /** Properties disabled by invalid UEnum class */
+ if (EnumDisabledProperties.Contains(PropertyName))
+ {
+ Property.IsEnabled(TAttribute::Create([ProcessorProperty]() -> bool
+ {
+ const UEnum* EnumClass = GetStateEnumClass(ProcessorProperty);
+ return (EnumClass != nullptr);
+ }));
+ }
+
+ /** Disable additive Anim BP property if there aren't any additive states in the setup */
+ if (StructPtr)
+ {
+ if (PropertyName == GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, AdditiveAnimBlueprint))
+ {
+ Property.IsEnabled(TAttribute::Create([StructPtr]() -> bool
+ {
+ FPerSkeletonAnimationSharingSetup* SetupStruct = (FPerSkeletonAnimationSharingSetup*)StructPtr;
+ if (SetupStruct)
+ {
+ const bool bContainsAdditive = SetupStruct->AnimationStates.ContainsByPredicate([](FAnimationStateEntry& Entry) -> bool
+ {
+ return Entry.bAdditive;
+ });
+
+ return bContainsAdditive;
+ }
+
+ return false;
+ }));
+ }
+ }
+ }
+}
+
+FText FPerSkeletonAnimationSharingSetupCustomization::GetSkeletonName() const
+{
+ UObject* SkeletonObject;
+ FPropertyAccess::Result Result = SkeletonPropertyHandle->GetValue(SkeletonObject);
+
+ FText Name = LOCTEXT("None", "None");
+ if (Result == FPropertyAccess::Success)
+ {
+ if (SkeletonObject)
+ {
+ Name = FText::AsCultureInvariant(SkeletonObject->GetName());
+ }
+ }
+
+ return Name;
+}
+
+TSharedRef FAnimationStateEntryCustomization::MakeInstance()
+{
+ return MakeShareable(new FAnimationStateEntryCustomization());
+}
+
+void FAnimationStateEntryCustomization::CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils)
+{
+ TSharedPtr ParentHandle = PropertyHandle->GetParentHandle()->GetParentHandle();
+
+ // We make the assumption here that the parent handle is the array part of the FPerSkeletonAnimationSharingSetup
+ ProcessorPropertyHandle = ParentHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FPerSkeletonAnimationSharingSetup, StateProcessorClass));
+
+ StatePropertyHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, State));
+ if (StatePropertyHandle.IsValid())
+ {
+ HeaderRow
+ .NameContent()
+ [
+ SNew(SHorizontalBox)
+ +SHorizontalBox::Slot()
+ .FillWidth(1)
+ .VAlign(VAlign_Center)
+ [
+ // Show the name of the asset or actor
+ SNew(STextBlock)
+ .Font(FEditorStyle::GetFontStyle("PropertyWindow.NormalFont"))
+ .Text(this, &FAnimationStateEntryCustomization::GetStateName, StatePropertyHandle)
+ ]
+ ];
+ }
+}
+
+void FAnimationStateEntryCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
+{
+ uint32 NumChildren;
+ StructPropertyHandle->GetNumChildren(NumChildren);
+
+ const TArray OnDemandProperties = { GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, bReturnToPreviousState), GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, bSetNextState), GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, NextState), GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, WiggleTimePercentage) };
+
+ const TArray EnumProperties = { GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, State), GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, NextState) };
+
+ TSharedPtr OnDemandHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, bOnDemand));
+ TSharedPtr AdditiveHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimationStateEntry, bAdditive));
+
+ for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex)
+ {
+ TSharedRef ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef();
+
+ // Hide any on-demand settings when either the state is not an on-demand or it is but an additive as well
+ TAttribute VisibilityAttribute = TAttribute::Create([OnDemandHandle, AdditiveHandle]() -> EVisibility
+ {
+ if (OnDemandHandle.IsValid() && AdditiveHandle.IsValid())
+ {
+ bool bOnDemandValue = false;
+ OnDemandHandle->GetValue(bOnDemandValue);
+
+ bool bAdditiveValue = false;
+ AdditiveHandle->GetValue(bAdditiveValue);
+
+ return bOnDemandValue && !bAdditiveValue ? EVisibility::Visible : EVisibility::Collapsed;
+ }
+
+ return EVisibility::Visible;
+ });
+
+ if (EnumProperties.Contains(ChildHandle->GetProperty()->GetFName()))
+ {
+ FDetailWidgetRow& WidgetRow = CreateEnumSelectionWidget(ChildHandle, StructBuilder);
+ if (OnDemandProperties.Contains(ChildHandle->GetProperty()->GetFName()))
+ {
+ WidgetRow.Visibility(VisibilityAttribute);
+ }
+ }
+ else
+ {
+ IDetailPropertyRow& PropertyRow = StructBuilder.AddProperty(ChildHandle);
+ if (OnDemandProperties.Contains(ChildHandle->GetProperty()->GetFName()))
+ {
+ PropertyRow.Visibility(VisibilityAttribute);
+ }
+ }
+ }
+}
+
+FText FAnimationStateEntryCustomization::GetStateName(TSharedPtr PropertyHandle) const
+{
+ uint8 EnumValue;
+ FPropertyAccess::Result Result = PropertyHandle->GetValue(EnumValue);
+
+ FText Name = LOCTEXT("None", "None");
+ if (Result == FPropertyAccess::Success)
+ {
+ if (UEnum* EnumClass = GetStateEnumClass(ProcessorPropertyHandle))
+ {
+ Name = EnumClass->GetDisplayNameTextByIndex(EnumValue);
+ }
+ else
+ {
+ FFormatNamedArguments Args;
+ Args.Add(TEXT("EnumIndex"), EnumValue);
+ Name = FText::Format(LOCTEXT("EnumIndexValue", "Enum Index {EnumIndex}"), Args);
+ }
+ }
+
+ return Name;
+}
+
+FDetailWidgetRow& FAnimationStateEntryCustomization::CreateEnumSelectionWidget(TSharedRef ChildHandle, IDetailChildrenBuilder& StructBuilder)
+{
+ GenerateEnumComboBoxItems();
+
+ TSharedPtr CurrentlySelected = GetSelectedEnum(ChildHandle);
+
+ FDetailWidgetRow& DetailRow = StructBuilder.AddCustomRow(LOCTEXT("EnumStateSearchLabel", "State"))
+ .NameContent()
+ [
+ ChildHandle->CreatePropertyNameWidget()
+ ]
+ .ValueContent()
+ [
+ SNew(SHorizontalBox)
+ +SHorizontalBox::Slot()
+ [
+ SNew(STextComboBox)
+ .OptionsSource(&ComboBoxItems)
+ .InitiallySelectedItem(CurrentlySelected)
+ .OnSelectionChanged(this, &FAnimationStateEntryCustomization::SelectedEnumChanged, ChildHandle)
+ .Font(FEditorStyle::GetFontStyle("PropertyWindow.NormalFont"))
+ ]
+ ];
+
+ return DetailRow;
+}
+
+const TArray> FAnimationStateEntryCustomization::GetComboBoxSourceItems() const
+{
+ TArray> Items;
+
+ if (UEnum* EnumClass = GetStateEnumClass(ProcessorPropertyHandle))
+ {
+ const int32 NumEnums = EnumClass->NumEnums();
+ for (int32 Index = 0; Index < NumEnums; ++Index)
+ {
+ Items.Add(MakeShareable(new FString(EnumClass->GetDisplayNameTextByIndex(Index).ToString())));
+ }
+ }
+
+ return Items;
+}
+
+const TSharedPtr FAnimationStateEntryCustomization::GetSelectedEnum(TSharedPtr PropertyHandle) const
+{
+ const TSharedPtr* StringPtr = ComboBoxItems.FindByPredicate([this, PropertyHandle](TSharedPtr SharedString) {
+ return *SharedString == GetStateName(PropertyHandle).ToString();
+ });
+
+ return StringPtr != nullptr ? *StringPtr : MakeShareable(new FString(GetStateName(PropertyHandle).ToString()));
+}
+
+void FAnimationStateEntryCustomization::SelectedEnumChanged(TSharedPtr Selection, ESelectInfo::Type SelectInfo, TSharedRef PropertyHandle)
+{
+ if (UEnum* EnumClass = GetStateEnumClass(ProcessorPropertyHandle))
+ {
+ if (Selection && SelectInfo != ESelectInfo::Type::Direct)
+ {
+ uint8 NewEnumValue = EnumClass->GetValueByIndex(ComboBoxItems.IndexOfByKey(Selection));
+ PropertyHandle->SetValue(NewEnumValue);
+ }
+
+ }
+}
+
+void FAnimationStateEntryCustomization::GenerateEnumComboBoxItems()
+{
+ if (UEnum* EnumClass = GetStateEnumClass(ProcessorPropertyHandle))
+ {
+ if (EnumClass != CachedComboBoxEnumClass)
+ {
+ ComboBoxItems.Empty();
+ const int32 NumEnums = EnumClass->NumEnums();
+ for (int32 Index = 0; Index < NumEnums; ++Index)
+ {
+ ComboBoxItems.Add(MakeShareable(new FString(EnumClass->GetDisplayNameTextByIndex(Index).ToString())));
+ }
+
+ CachedComboBoxEnumClass = EnumClass;
+ }
+ }
+}
+
+UEnum* GetStateEnumClass(const TSharedPtr& InProperty)
+{
+ UObject* EnumObject = nullptr;
+ if (InProperty.IsValid())
+ {
+ UObject* ProcessorObject = nullptr;
+ InProperty->GetValue(ProcessorObject);
+ UClass* ProcessorClass = Cast(ProcessorObject);
+ if (ProcessorClass)
+ {
+ UAnimationSharingStateProcessor* Processor = ProcessorClass->GetDefaultObject();
+ return Processor->GetAnimationStateEnum();
+ }
+ }
+
+ return nullptr;
+}
+
+
+TSharedRef FAnimationSetupCustomization::MakeInstance()
+{
+ return MakeShareable(new FAnimationSetupCustomization());
+}
+
+void FAnimationSetupCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
+{
+ uint32 NumChildren;
+ StructPropertyHandle->GetNumChildren(NumChildren);
+ const FName AnimSequencePropertyName = GET_MEMBER_NAME_CHECKED(FAnimationSetup, AnimSequence);
+ AnimSequencePropertyHandle = StructPropertyHandle->GetChildHandle(AnimSequencePropertyName);
+
+ for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex)
+ {
+ TSharedRef ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef();
+ IDetailPropertyRow& Property = StructBuilder.AddProperty(ChildHandle);
+
+ /** Disable all properties if there is not a valid Animation Sequence provided */
+ if (AnimSequencePropertyHandle.IsValid() && (ChildHandle->GetProperty()->GetFName() != AnimSequencePropertyName))
+ {
+ Property.IsEnabled(TAttribute::Create([this]() -> bool
+ {
+ if ( AnimSequencePropertyHandle.Get())
+ {
+ UObject* ObjectPtr = nullptr;
+ if (AnimSequencePropertyHandle->GetValue(ObjectPtr) == FPropertyAccess::Success)
+ {
+ return ObjectPtr != nullptr;
+ }
+ }
+
+ return false;
+ }));
+ }
+ }
+}
+
+#undef LOCTEXT_NAMESPACE // "AnimationSharingSetupCustomization"
+
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingEdModule.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingEdModule.h
new file mode 100644
index 000000000000..a4b63d179e7b
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingEdModule.h
@@ -0,0 +1,22 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Modules/ModuleInterface.h"
+#include "Modules/ModuleManager.h"
+
+/**
+* The public interface to this module
+*/
+class FAnimSharingEdModule : public IModuleInterface
+{
+public:
+ FAnimSharingEdModule() {}
+
+ virtual void StartupModule();
+ virtual void ShutdownModule();
+private:
+ class FAssetTypeActions_AnimationSharingSetup* AssetAction;
+};
+
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingSetupFactory.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingSetupFactory.h
new file mode 100644
index 000000000000..4577dafea068
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AnimationSharingSetupFactory.h
@@ -0,0 +1,25 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "UObject/ObjectMacros.h"
+#include "Factories/Factory.h"
+#include "AnimationSharingSetupFactory.generated.h"
+
+struct FAssetData;
+class SWindow;
+
+UCLASS(hidecategories = Object, MinimalAPI)
+class UAnimationSharingSetupFactory : public UFactory
+{
+ GENERATED_UCLASS_BODY()
+
+ //~ Begin UFactory Interface
+ virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
+ virtual uint32 GetMenuCategories() const;
+ //~ Begin UFactory Interface
+};
+
+
+
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AssetTypeActions_AnimationSharingSetup.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AssetTypeActions_AnimationSharingSetup.h
new file mode 100644
index 000000000000..bec0bfc20627
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/AssetTypeActions_AnimationSharingSetup.h
@@ -0,0 +1,18 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "AnimationSharingManager.h"
+#include "AssetTypeCategories.h"
+#include "AssetTypeActions_Base.h"
+
+class FAssetTypeActions_AnimationSharingSetup : public FAssetTypeActions_Base
+{
+public:
+ // IAssetTypeActions Implementation
+ virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AnimationSharingSetup", "AnimationSharingSetup"); }
+ virtual FColor GetTypeColor() const override { return FColor(255, 10, 255); }
+ virtual UClass* GetSupportedClass() const override { return UAnimationSharingManager::StaticClass(); }
+ virtual uint32 GetCategories() override { return EAssetTypeCategories::Animation; }
+};
diff --git a/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/SetupDetailsViewCustomizations.h b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/SetupDetailsViewCustomizations.h
new file mode 100644
index 000000000000..60fdb9f3a9a0
--- /dev/null
+++ b/Engine/Plugins/Developer/AnimationSharing/Source/AnimationSharingEd/Public/SetupDetailsViewCustomizations.h
@@ -0,0 +1,74 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "IDetailCustomization.h"
+#include "PropertyCustomizationHelpers.h"
+
+static UEnum* GetStateEnumClass(const TSharedPtr& InProperty);
+
+class FPerSkeletonAnimationSharingSetupCustomization : public IPropertyTypeCustomization
+{
+public:
+ static TSharedRef MakeInstance();
+
+ FPerSkeletonAnimationSharingSetupCustomization() {}
+ virtual ~FPerSkeletonAnimationSharingSetupCustomization() {}
+
+ /** Begin IPropertyTypeCustomization interface */
+ virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override;
+ virtual void CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override;
+ /** End IPropertyTypeCustomization interface */
+
+protected:
+ FText GetSkeletonName() const;
+
+ TSharedPtr SkeletonPropertyHandle;
+};
+
+class FAnimationStateEntryCustomization : public IPropertyTypeCustomization
+{
+public:
+ static TSharedRef MakeInstance();
+
+ FAnimationStateEntryCustomization() : StatePropertyHandle(nullptr), ProcessorPropertyHandle(nullptr), CachedComboBoxEnumClass(nullptr) {}
+ virtual ~FAnimationStateEntryCustomization() {}
+
+ /** Begin IPropertyTypeCustomization interface */
+ virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override;
+ virtual void CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override;
+ /** End IPropertyTypeCustomization interface */
+
+protected:
+ FText GetStateName(TSharedPtr PropertyHandle) const;
+ FDetailWidgetRow& CreateEnumSelectionWidget(TSharedRef ChildHandle, IDetailChildrenBuilder& StructBuilder);
+ const TArray> GetComboBoxSourceItems() const;
+ const TSharedPtr GetSelectedEnum(TSharedPtr PropertyHandle) const;
+ void SelectedEnumChanged(TSharedPtr Selection, ESelectInfo::Type SelectInfo, TSharedRef PropertyHandle);
+ void GenerateEnumComboBoxItems();
+
+protected:
+ TSharedPtr StatePropertyHandle;
+ TSharedPtr ProcessorPropertyHandle;
+
+ UEnum* CachedComboBoxEnumClass;
+ TArray> ComboBoxItems;
+};
+
+class FAnimationSetupCustomization : public IPropertyTypeCustomization
+{
+public:
+ static TSharedRef MakeInstance();
+
+ FAnimationSetupCustomization() {}
+ virtual ~FAnimationSetupCustomization() {}
+
+ /** Begin IPropertyTypeCustomization interface */
+ virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override {}
+ virtual void CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override;
+ /** End IPropertyTypeCustomization interface */
+
+protected:
+ TSharedPtr AnimSequencePropertyHandle;
+};
diff --git a/Engine/Plugins/Enterprise/VariantManagerContent/Source/VariantManagerContent/Private/PropertyValueTransform.cpp b/Engine/Plugins/Enterprise/VariantManagerContent/Source/VariantManagerContent/Private/PropertyValueTransform.cpp
index 89fadffb70de..a0f8ac9144b9 100644
--- a/Engine/Plugins/Enterprise/VariantManagerContent/Source/VariantManagerContent/Private/PropertyValueTransform.cpp
+++ b/Engine/Plugins/Enterprise/VariantManagerContent/Source/VariantManagerContent/Private/PropertyValueTransform.cpp
@@ -54,7 +54,7 @@ FVector UPropertyValueTransform::GetScale3D()
}
}
-void UPropertyValueTransform::SetLocation(FVector NewValue)
+void UPropertyValueTransform::SetLocation(const FVector& NewValue)
{
if (PropCategory == EPropertyValueCategory::RelativeLocation)
{
@@ -62,7 +62,7 @@ void UPropertyValueTransform::SetLocation(FVector NewValue)
}
}
-void UPropertyValueTransform::SetRotation(FQuat NewValue)
+void UPropertyValueTransform::SetRotation(const FQuat& NewValue)
{
if (PropCategory == EPropertyValueCategory::RelativeRotation)
{
@@ -71,7 +71,7 @@ void UPropertyValueTransform::SetRotation(FQuat NewValue)
}
}
-void UPropertyValueTransform::SetScale3D(FVector NewValue)
+void UPropertyValueTransform::SetScale3D(const FVector& NewValue)
{
if (PropCategory == EPropertyValueCategory::RelativeScale3D)
{
diff --git a/Engine/Plugins/Enterprise/VariantManagerContent/Source/VariantManagerContent/Public/PropertyValueTransform.h b/Engine/Plugins/Enterprise/VariantManagerContent/Source/VariantManagerContent/Public/PropertyValueTransform.h
index 5430e4e7498c..98dfb6e22a17 100644
--- a/Engine/Plugins/Enterprise/VariantManagerContent/Source/VariantManagerContent/Public/PropertyValueTransform.h
+++ b/Engine/Plugins/Enterprise/VariantManagerContent/Source/VariantManagerContent/Public/PropertyValueTransform.h
@@ -19,9 +19,9 @@ public:
FQuat GetRotation();
FVector GetScale3D();
- void SetLocation(FVector NewValue);
- void SetRotation(FQuat NewValue);
- void SetScale3D(FVector NewValue);
+ void SetLocation(const FVector& NewValue);
+ void SetRotation(const FQuat& NewValue);
+ void SetScale3D(const FVector& NewValue);
virtual void ApplyDataToResolvedObject() override;
};
\ No newline at end of file
diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.cpp b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.cpp
index 7a4464bd0035..6949cbeb8d56 100644
--- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.cpp
+++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.cpp
@@ -88,8 +88,13 @@ void SAlembicImportOptions::Construct(const FArguments& InArgs)
SNew(SHeaderRow)
+ SHeaderRow::Column("ShouldImport")
- .DefaultLabel(FText::FromString(TEXT("Include")))
.FillWidth(0.1f)
+ .DefaultLabel(FText::FromString(TEXT("Include")))
+ [
+ SNew(SCheckBox)
+ .HAlign(HAlign_Center)
+ .OnCheckStateChanged(this, &SAlembicImportOptions::OnToggleAllItems)
+ ]
+ SHeaderRow::Column("TrackName")
.DefaultLabel(LOCTEXT("TrackNameHeader", "Track Name"))
@@ -157,11 +162,24 @@ bool SAlembicImportOptions::CanImport() const
return true;
}
-void SAlembicImportOptions::OnItemDoubleClicked(FPolyMeshDataPtr ClickedItem)
+void SAlembicImportOptions::OnToggleAllItems(ECheckBoxState CheckType)
{
+ /** Set all items to top level checkbox state */
for (FPolyMeshDataPtr& Item : PolyMeshData)
{
- Item->PolyMesh->bShouldImport = (Item == ClickedItem);
+ Item->PolyMesh->bShouldImport = CheckType == ECheckBoxState::Checked;
+ }
+}
+
+void SAlembicImportOptions::OnItemDoubleClicked(FPolyMeshDataPtr ClickedItem)
+{
+ /** Toggle state on / off for the selected list entry */
+ for (FPolyMeshDataPtr& Item : PolyMeshData)
+ {
+ if (Item == ClickedItem)
+ {
+ Item->PolyMesh->bShouldImport = !Item->PolyMesh->bShouldImport;
+ }
}
}
diff --git a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.h b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.h
index 01080bba276f..240b70ba745d 100644
--- a/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.h
+++ b/Engine/Plugins/Experimental/AlembicImporter/Source/AlembicImporter/Private/AlembicImportOptions.h
@@ -86,6 +86,7 @@ private:
TSharedRef OnGenerateWidgetForList(FPolyMeshDataPtr InItem, const TSharedRef& OwnerTable);
bool CanImport() const;
+ void OnToggleAllItems(ECheckBoxState CheckType);
void OnItemDoubleClicked(FPolyMeshDataPtr ClickedItem);
private:
UAbcImportSettings* ImportSettings;
diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp
index 5faaf061ef69..fc280beef002 100644
--- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp
+++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Editor/ControlRigEditor.cpp
@@ -833,6 +833,9 @@ void FControlRigEditor::SetJointTransform(const FName& InJoint, const FTransform
EditorSkelComp->RebuildDebugDrawSkeleton();
}
+ // I don't think I have to mark dirty here.
+ // FBlueprintEditorUtils::MarkBlueprintAsModified(GetControlRigBlueprint());
+
// I don't think I have to mark dirty here.
// FBlueprintEditorUtils::MarkBlueprintAsModified(GetControlRigBlueprint());
{
diff --git a/Engine/Plugins/Media/AvfMedia/Source/AvfMedia/Private/Player/AvfMediaPlayer.cpp b/Engine/Plugins/Media/AvfMedia/Source/AvfMedia/Private/Player/AvfMediaPlayer.cpp
index 9c45803db2c5..902b0392b012 100644
--- a/Engine/Plugins/Media/AvfMedia/Source/AvfMedia/Private/Player/AvfMediaPlayer.cpp
+++ b/Engine/Plugins/Media/AvfMedia/Source/AvfMedia/Private/Player/AvfMediaPlayer.cpp
@@ -14,12 +14,15 @@
#include "Mac/CocoaThread.h"
#else
#include "IOS/IOSAsyncTask.h"
- #include "HAL/FileManager.h"
#endif
+#include "HAL/FileManager.h"
+
#include "AvfMediaTracks.h"
#include "AvfMediaUtils.h"
#include "IMediaAudioSample.h"
+#include "HAL/PlatformFilemanager.h"
+#include "Async/Async.h"
/* FAVPlayerDelegate
@@ -94,7 +97,163 @@
@end
-/* Sync Control Class for consumed samples */
+/* Media Resource Data loader, e.g for Pak files
+ *****************************************************************************/
+@interface FAVMediaAssetResourceLoaderDelegate : NSObject
+{
+ @public TSharedPtr FileAReader;
+ @public FCriticalSection CriticalSection;
+}
+@end
+
+@implementation FAVMediaAssetResourceLoaderDelegate
+
+-(FAVMediaAssetResourceLoaderDelegate*) initWithPath:(FString)InPath
+{
+ self = [super init];
+ if (self != nil)
+ {
+ Async(EAsyncExecution::ThreadPool, [self, InPath]()
+ {
+ FileAReader = MakeShareable( IFileManager::Get().CreateFileReader(*InPath) );
+ });
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ {
+ FScopeLock ScopeLock(&CriticalSection);
+
+ if(FileAReader.IsValid())
+ {
+ FileAReader->Close();
+ FileAReader = nullptr;
+ }
+ }
+
+ [super dealloc];
+}
+
+- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
+{
+ // There should be no need to queue these up - if it turns out we need to do that - then add an ordered queue of loadingRequest objects
+ BOOL bShouldHandleLoad = NO;
+
+ FScopeLock ScopeLock(&CriticalSection);
+
+ if(FileAReader.IsValid() && !FileAReader->IsError())
+ {
+ // Fill out content information request - if required
+ if(loadingRequest.contentInformationRequest)
+ {
+ // See https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html
+ // And loadingRequest.contentInformationRequest.allowedContentTypes;
+ loadingRequest.contentInformationRequest.contentType = @"public.mpeg-4";
+ loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
+ loadingRequest.contentInformationRequest.contentLength = FileAReader->TotalSize();
+
+ // Handle a rare case where it only asks for content information and no data - finish it now
+ if(loadingRequest.dataRequest == nil)
+ {
+ [loadingRequest finishLoading];
+ }
+ }
+
+ // Fetch data from file - if required
+ if(loadingRequest.dataRequest)
+ {
+ // Holdon to this otherwise it'll disappear
+ [loadingRequest retain];
+
+ // Allow this function to return so the resource loader knows the data is coming and doesn't send more requests while we block
+ Async(EAsyncExecution::ThreadPool, [self, loadingRequest]()
+ {
+ bool bDataLoadSuccess = false;
+
+ FScopeLock ScopeLock(&CriticalSection);
+ if(FileAReader.IsValid() && !FileAReader->IsError())
+ {
+ int64 Offset = loadingRequest.dataRequest.requestedOffset;
+ int64 ByteCount = loadingRequest.dataRequest.requestedLength;
+
+ check(Offset >= 0);
+ check(ByteCount > 0);
+
+ if(Offset + ByteCount <= FileAReader->TotalSize())
+ {
+ FileAReader->Seek(Offset);
+
+ // Don't read the whole requested data range at once - the resource loader often asks for very large data sizes
+ // If we feed it (using respondWithData:) in chunks, it decides it has had enough data usually after a few MB,
+ // then it marks the request as cancelled, this is not an error, before issuing a different request at some point later.
+ // This keeps our peak memory usage down and limits the amount of data we are serializing.
+
+ const int64 MaxChunkBytes = 1024 * 1024 * 1; // in single MB chunks
+ while(ByteCount > 0 && !loadingRequest.isCancelled && !FileAReader->IsError())
+ {
+ int64 ChunkByteCount = MIN(MaxChunkBytes, ByteCount);
+ ByteCount -= ChunkByteCount;
+ check(ByteCount >= 0);
+
+ NSMutableData* nsLoadedData = [[NSMutableData alloc] initWithLength:ChunkByteCount];
+ uint8* pMemory = (uint8*)nsLoadedData.mutableBytes;
+ check(pMemory);
+
+ FileAReader->Serialize(pMemory, ChunkByteCount);
+
+ [loadingRequest.dataRequest respondWithData:nsLoadedData];
+ [nsLoadedData release];
+ }
+
+ bDataLoadSuccess = !FileAReader->IsError();
+ }
+ }
+
+ if(bDataLoadSuccess)
+ {
+ [loadingRequest finishLoading];
+ }
+ else
+ {
+ [loadingRequest finishLoadingWithError:nil];
+ }
+
+ [loadingRequest release];
+ });
+ }
+
+ bShouldHandleLoad = YES;
+ }
+
+ return bShouldHandleLoad;
+}
+
+- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForRenewalOfRequestedResource:(AVAssetResourceRenewalRequest *)renewalRequest
+{
+ // Don't set contentInformationRequest.renewalDate and we should not have to handle this case
+ return NO;
+}
+
+- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest
+{
+
+}
+
+- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForResponseToAuthenticationChallenge:(NSURLAuthenticationChallenge *)authenticationChallenge
+{
+ return NO;
+}
+
+- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)authenticationChallenge
+{
+
+}
+@end
+
+/* Sync Control Class for consumed samples
+ *****************************************************************************/
class FAvfMediaSamples : public FMediaSamples
{
public:
@@ -158,6 +317,7 @@ FAvfMediaPlayer::FAvfMediaPlayer(IMediaEventSink& InEventSink)
MediaHelper = nil;
MediaPlayer = nil;
PlayerItem = nil;
+ MediaResourceLoader = nil;
bPrerolled = false;
bTimeSynced = false;
@@ -379,6 +539,12 @@ void FAvfMediaPlayer::Close()
MediaPlayer = nil;
}
+ if(MediaResourceLoader != nil)
+ {
+ [MediaResourceLoader release];
+ MediaResourceLoader = nil;
+ }
+
Tracks->Reset();
EventSink.ReceiveMediaEvent(EMediaEvent::TracksChanged);
@@ -455,7 +621,6 @@ IMediaView& FAvfMediaPlayer::GetView()
return *this;
}
-
bool FAvfMediaPlayer::Open(const FString& Url, const IMediaOptions* /*Options*/)
{
Close();
@@ -463,12 +628,27 @@ bool FAvfMediaPlayer::Open(const FString& Url, const IMediaOptions* /*Options*/)
NSURL* nsMediaUrl = nil;
FString Path;
+ bool bPakResourceLoading = false;
+
if (Url.StartsWith(TEXT("file://")))
{
// Media Framework doesn't percent encode the URL, so the path portion is just a native file path.
// Extract it and then use it create a proper URL.
Path = Url.Mid(7);
nsMediaUrl = [NSURL fileURLWithPath:Path.GetNSString() isDirectory:NO];
+
+ // Is this from a Pak file - can't find a way to directly check - attempt to check the reverse logic
+ // as we don't want to change behaviour of normal files from a standard file URL
+ IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
+ if(PlatformFile.GetLowerLevel() && !PlatformFile.GetLowerLevel()->FileExists(*Path) && FPaths::FileExists(*Path))
+ {
+ // Force the AV player to not be able to decode the scheme - this makes it use our ResourceLoader
+ NSString* formatString = [NSString stringWithFormat:@"UE4-Media://%@", [Path.GetNSString() stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]];
+ nsMediaUrl = [NSURL URLWithString:formatString];
+ [formatString release];
+
+ bPakResourceLoading = true;
+ }
}
else
{
@@ -502,7 +682,6 @@ bool FAvfMediaPlayer::Open(const FString& Url, const IMediaOptions* /*Options*/)
if (!MediaPlayer)
{
UE_LOG(LogAvfMedia, Error, TEXT("Failed to create instance of an AVPlayer"));
-
return false;
}
@@ -512,12 +691,21 @@ bool FAvfMediaPlayer::Open(const FString& Url, const IMediaOptions* /*Options*/)
MediaHelper = [[FAVPlayerDelegate alloc] initWithMediaPlayer:this];
check(MediaHelper != nil);
- PlayerItem = [[AVPlayerItem playerItemWithURL:nsMediaUrl] retain];
-
+ // Use URL asset which gives us resource loading ability if system can't handle the scheme
+ AVURLAsset* urlAsset = [[AVURLAsset alloc] initWithURL:nsMediaUrl options:nil];
+
+ if(bPakResourceLoading)
+ {
+ MediaResourceLoader = [[FAVMediaAssetResourceLoaderDelegate alloc] initWithPath:Path];
+ [urlAsset.resourceLoader setDelegate:MediaResourceLoader queue:dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)];
+ }
+
+ PlayerItem = [[AVPlayerItem playerItemWithAsset:urlAsset] retain];
+ [urlAsset release];
+
if (PlayerItem == nil)
{
UE_LOG(LogAvfMedia, Error, TEXT("Failed to open player item with Url:"), *Url);
-
return false;
}
diff --git a/Engine/Plugins/Media/AvfMedia/Source/AvfMedia/Private/Player/AvfMediaPlayer.h b/Engine/Plugins/Media/AvfMedia/Source/AvfMedia/Private/Player/AvfMediaPlayer.h
index 262f7b485240..6fb1c4f2ff6f 100644
--- a/Engine/Plugins/Media/AvfMedia/Source/AvfMedia/Private/Player/AvfMediaPlayer.h
+++ b/Engine/Plugins/Media/AvfMedia/Source/AvfMedia/Private/Player/AvfMediaPlayer.h
@@ -17,6 +17,7 @@ class IMediaEventSink;
@class AVPlayer;
@class AVPlayerItem;
@class FAVPlayerDelegate;
+@class FAVMediaAssetResourceLoaderDelegate;
/**
@@ -128,6 +129,9 @@ private:
/** Cocoa helper object we can use to keep track of ns property changes in our media items */
FAVPlayerDelegate* MediaHelper;
+ /** Cocoa Media helper object for Pak file loading */
+ FAVMediaAssetResourceLoaderDelegate* MediaResourceLoader;
+
/** The AVFoundation media player */
AVPlayer* MediaPlayer;
diff --git a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.cpp b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.cpp
new file mode 100644
index 000000000000..603d5926bc9b
--- /dev/null
+++ b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.cpp
@@ -0,0 +1,20 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "AnimationBudgetBlueprintLibrary.h"
+#include "AnimationBudgetAllocatorModule.h"
+#include "Modules/ModuleManager.h"
+#include "IAnimationBudgetAllocator.h"
+#include "Engine/Engine.h"
+
+void UAnimationBudgetBlueprintLibrary::EnableAnimationBudget(UObject* WorldContextObject, bool bEnabled)
+{
+ if(UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
+ {
+ FAnimationBudgetAllocatorModule& AnimationBudgetAllocatorModule = FModuleManager::LoadModuleChecked("AnimationBudgetAllocator");
+ IAnimationBudgetAllocator* AnimationBudgetAllocator = AnimationBudgetAllocatorModule.GetBudgetAllocatorForWorld(World);
+ if(AnimationBudgetAllocator)
+ {
+ AnimationBudgetAllocator->SetEnabled(bEnabled);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.h b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.h
new file mode 100644
index 000000000000..06487547bb38
--- /dev/null
+++ b/Engine/Plugins/Runtime/AnimationBudgetAllocator/Source/AnimationBudgetAllocator/Private/AnimationBudgetBlueprintLibrary.h
@@ -0,0 +1,23 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Kismet/BlueprintFunctionLibrary.h"
+#include "AnimationBudgetBlueprintLibrary.generated.h"
+
+/**
+ * Function library to expose the budget allocator to Blueprints
+ */
+UCLASS(meta = (ScriptName = "Animation Budget"))
+class UAnimationBudgetBlueprintLibrary : public UBlueprintFunctionLibrary
+{
+ GENERATED_BODY()
+
+ /**
+ * Enable/disable the animation budgeting system.
+ * Note that the system can also be disabled 'globally' via CVar, which overrides this setting.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Animation Budget", meta=(WorldContext="WorldContextObject"))
+ static void EnableAnimationBudget(UObject* WorldContextObject, bool bEnabled);
+};
\ No newline at end of file
diff --git a/Engine/Plugins/Runtime/Database/ADOSupport/ADOSupport.uplugin b/Engine/Plugins/Runtime/Database/ADOSupport/ADOSupport.uplugin
index 54da3d86744c..39fba8413d6e 100644
--- a/Engine/Plugins/Runtime/Database/ADOSupport/ADOSupport.uplugin
+++ b/Engine/Plugins/Runtime/Database/ADOSupport/ADOSupport.uplugin
@@ -12,7 +12,6 @@
"SupportURL": "",
"EnabledByDefault": false,
"CanContainContent": false,
- "Hidden": true,
"IsBetaVersion": false,
"Installed": false,
"Modules": [
diff --git a/Engine/Plugins/Runtime/Database/RemoteDatabaseSupport/RemoteDatabaseSupport.uplugin b/Engine/Plugins/Runtime/Database/RemoteDatabaseSupport/RemoteDatabaseSupport.uplugin
index dc9a9182286e..817bca472007 100644
--- a/Engine/Plugins/Runtime/Database/RemoteDatabaseSupport/RemoteDatabaseSupport.uplugin
+++ b/Engine/Plugins/Runtime/Database/RemoteDatabaseSupport/RemoteDatabaseSupport.uplugin
@@ -12,7 +12,6 @@
"SupportURL": "",
"EnabledByDefault": false,
"CanContainContent": false,
- "Hidden": true,
"IsBetaVersion": false,
"Installed": false,
"Modules": [
diff --git a/Engine/Plugins/Runtime/Database/SQLiteCore/SQLiteCore.uplugin b/Engine/Plugins/Runtime/Database/SQLiteCore/SQLiteCore.uplugin
index 69c6a2706005..f66226b7afb8 100644
--- a/Engine/Plugins/Runtime/Database/SQLiteCore/SQLiteCore.uplugin
+++ b/Engine/Plugins/Runtime/Database/SQLiteCore/SQLiteCore.uplugin
@@ -12,7 +12,6 @@
"SupportURL": "",
"EnabledByDefault": false,
"CanContainContent": false,
- "Hidden": true,
"IsBetaVersion": false,
"Installed": false,
"Modules": [
diff --git a/Engine/Plugins/Runtime/Database/SQLiteCore/Source/SQLiteCore/SQLiteCore.Build.cs b/Engine/Plugins/Runtime/Database/SQLiteCore/Source/SQLiteCore/SQLiteCore.Build.cs
index 8723732566a5..b92e926a4dd0 100644
--- a/Engine/Plugins/Runtime/Database/SQLiteCore/Source/SQLiteCore/SQLiteCore.Build.cs
+++ b/Engine/Plugins/Runtime/Database/SQLiteCore/Source/SQLiteCore/SQLiteCore.Build.cs
@@ -20,6 +20,9 @@ namespace UnrealBuildTool.Rules
PrivateDefinitions.Add("SQLITE_THREADSAFE=0"); // No threading on HTML5
}
+ // Use the math.h version of isnan rather than the SQLite version to avoid a -ffast-math error
+ PrivateDefinitions.Add("SQLITE_HAVE_ISNAN=1");
+
// Enable SQLite debug checks?
//PrivateDefinitions.Add("SQLITE_DEBUG");
diff --git a/Engine/Plugins/Runtime/Database/SQLiteSupport/SQLiteSupport.uplugin b/Engine/Plugins/Runtime/Database/SQLiteSupport/SQLiteSupport.uplugin
index c41614afd814..85e8301663d2 100644
--- a/Engine/Plugins/Runtime/Database/SQLiteSupport/SQLiteSupport.uplugin
+++ b/Engine/Plugins/Runtime/Database/SQLiteSupport/SQLiteSupport.uplugin
@@ -12,7 +12,6 @@
"SupportURL": "",
"EnabledByDefault": false,
"CanContainContent": false,
- "Hidden": true,
"IsBetaVersion": false,
"Installed": false,
"Modules": [
diff --git a/Engine/Source/Developer/AssetTools/AssetTools.Build.cs b/Engine/Source/Developer/AssetTools/AssetTools.Build.cs
index 57749b00489f..581e4aab21da 100644
--- a/Engine/Source/Developer/AssetTools/AssetTools.Build.cs
+++ b/Engine/Source/Developer/AssetTools/AssetTools.Build.cs
@@ -59,6 +59,7 @@ public class AssetTools : ModuleRules
"SkeletalMeshEditor",
"AnimationEditor",
"AnimationBlueprintEditor",
+ "AnimationModifiers"
}
);
@@ -83,6 +84,7 @@ public class AssetTools : ModuleRules
"SkeletalMeshEditor",
"AnimationEditor",
"AnimationBlueprintEditor",
+ "AnimationModifiers"
}
);
}
diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp
index bde27ea3951c..9a675c57fd47 100644
--- a/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp
+++ b/Engine/Source/Developer/AssetTools/Private/AssetTools.cpp
@@ -38,6 +38,7 @@
#include "AssetTypeActions/AssetTypeActions_AnimationAsset.h"
#include "AssetTypeActions/AssetTypeActions_AnimBlueprint.h"
#include "AssetTypeActions/AssetTypeActions_AnimComposite.h"
+#include "AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h"
#include "AssetTypeActions/AssetTypeActions_AnimMontage.h"
#include "AssetTypeActions/AssetTypeActions_AnimSequence.h"
#include "AssetTypeActions/AssetTypeActions_BlendSpace.h"
@@ -162,6 +163,7 @@ UAssetToolsImpl::UAssetToolsImpl(const FObjectInitializer& ObjectInitializer)
RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimationAsset));
RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimBlueprint));
RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimComposite));
+ RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimCurveCompressionSettings));
RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimMontage));
RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AnimSequence));
RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_AimOffset));
diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.cpp
new file mode 100644
index 000000000000..4b9adac4ae89
--- /dev/null
+++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.cpp
@@ -0,0 +1,110 @@
+// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
+
+#include "AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h"
+#include "Animation/AnimSequence.h"
+#include "Dialogs/Dialogs.h"
+#include "EditorStyleSet.h"
+#include "Framework/MultiBox/MultiBoxBuilder.h"
+#include "Misc/ScopedSlowTask.h"
+#include "UObject/UObjectIterator.h"
+
+#define LOCTEXT_NAMESPACE "AssetTypeActions"
+
+void FAssetTypeActions_AnimCurveCompressionSettings::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor)
+{
+ TSharedRef AssetEditor = FSimpleAssetEditor::CreateEditor(EToolkitMode::Standalone, EditWithinLevelEditor, InObjects);
+
+ auto SettingAssets = GetTypedWeakObjectPtrs(InObjects);
+ if (SettingAssets.Num() == 1)
+ {
+ TSharedPtr PluginCommands = MakeShareable(new FUICommandList);
+ TSharedPtr ToolbarExtender = MakeShareable(new FExtender);
+ ToolbarExtender->AddToolBarExtension("Asset", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FAssetTypeActions_AnimCurveCompressionSettings::AddToolbarExtension, SettingAssets[0]));
+ AssetEditor->AddToolbarExtender(ToolbarExtender);
+
+ AssetEditor->RegenerateMenusAndToolbars();
+ }
+}
+
+void FAssetTypeActions_AnimCurveCompressionSettings::AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr CurveSettings)
+{
+ Builder.BeginSection("Compress");
+ Builder.AddToolBarButton(
+ FUIAction(
+ FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimCurveCompressionSettings::ExecuteCompression, CurveSettings)
+ ),
+ NAME_None,
+ LOCTEXT("AnimCurveCompressionSettings_Compress", "Compress"),
+ LOCTEXT("AnimCurveCompressionSettings_CompressTooltip", "All animation sequences that use these settings will be compressed."),
+ FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression")
+ );
+ Builder.EndSection();
+}
+
+void FAssetTypeActions_AnimCurveCompressionSettings::GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder)
+{
+ auto SettingAssets = GetTypedWeakObjectPtrs(InObjects);
+
+ if (SettingAssets.Num() != 1)
+ {
+ return;
+ }
+
+ MenuBuilder.AddMenuEntry(
+ LOCTEXT("AnimCurveCompressionSettings_Compress", "Compress"),
+ LOCTEXT("AnimCurveCompressionSettings_CompressTooltip", "All animation sequences that use these settings will be compressed."),
+ FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.ApplyCompression.Small"),
+ FUIAction(
+ FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimCurveCompressionSettings::ExecuteCompression, SettingAssets[0])
+ )
+ );
+}
+
+void FAssetTypeActions_AnimCurveCompressionSettings::ExecuteCompression(TWeakObjectPtr CurveSettings)
+{
+ if (!CurveSettings.IsValid())
+ {
+ return;
+ }
+
+ UAnimCurveCompressionSettings* Settings = CurveSettings.Get();
+
+ TArray AnimSeqsToRecompress;
+ for (TObjectIterator It; It; ++It)
+ {
+ UAnimSequence* AnimSeq = *It;
+ if (AnimSeq->GetOutermost() == GetTransientPackage())
+ {
+ continue;
+ }
+
+ if (AnimSeq->CurveCompressionSettings == Settings)
+ {
+ AnimSeqsToRecompress.Add(AnimSeq);
+ }
+ }
+
+ if (AnimSeqsToRecompress.Num() == 0)
+ {
+ return;
+ }
+
+ FFormatNamedArguments Arguments;
+ Arguments.Add(TEXT("NumAnimSequences"), FText::AsNumber(AnimSeqsToRecompress.Num()));
+ FText DialogText = FText::Format(LOCTEXT("AnimCurveCompressionSettings_CompressWarningText", "{NumAnimSequences} animation sequences are about to compress."), Arguments);
+ const EAppReturnType::Type DlgResult = OpenMsgDlgInt(EAppMsgType::OkCancel, DialogText, LOCTEXT("AnimCurveCompressionSettings_CompressWarning", "Warning"));
+ if (DlgResult != EAppReturnType::Ok)
+ {
+ return;
+ }
+
+ const FText StatusText = FText::Format(LOCTEXT("AnimCurveCompressionSettings_Compressing", "Compressing '{0}' animations"), FText::AsNumber(AnimSeqsToRecompress.Num()));
+ FScopedSlowTask LoadingAnimSlowTask(AnimSeqsToRecompress.Num(), StatusText);
+ LoadingAnimSlowTask.MakeDialog();
+
+ for (UAnimSequence* AnimSeq : AnimSeqsToRecompress)
+ {
+ LoadingAnimSlowTask.EnterProgressFrame();
+ AnimSeq->RequestSyncAnimRecompression(false);
+ }
+}
diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h
new file mode 100644
index 000000000000..fef0cd3247fb
--- /dev/null
+++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimCurveCompressionSettings.h
@@ -0,0 +1,27 @@
+// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "AssetTypeActions_Base.h"
+#include "Animation/AnimCurveCompressionSettings.h"
+
+class FAssetTypeActions_AnimCurveCompressionSettings : public FAssetTypeActions_Base
+{
+public:
+ // IAssetTypeActions Implementation
+ virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_AnimCurveCompressionSettings", "Curve Compression Settings"); }
+ virtual FColor GetTypeColor() const override { return FColor(255, 255, 0); }
+ virtual UClass* GetSupportedClass() const override { return UAnimCurveCompressionSettings::StaticClass(); }
+ virtual bool CanFilter() override { return true; }
+ virtual uint32 GetCategories() override { return EAssetTypeCategories::Animation; }
+
+ virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override;
+
+ virtual bool HasActions(const TArray& InObjects) const override { return true; }
+ virtual void GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) override;
+
+private:
+ void AddToolbarExtension(FToolBarBuilder& Builder, TWeakObjectPtr CurveSettings);
+ void ExecuteCompression(TWeakObjectPtr CurveSettings);
+};
diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.cpp b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.cpp
index e444698e0d5a..7854b01a1f6c 100644
--- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.cpp
+++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.cpp
@@ -15,7 +15,8 @@
#include "AssetTools.h"
#include "IContentBrowserSingleton.h"
#include "ContentBrowserModule.h"
-
+#include "IAnimationModifiersModule.h"
+#include "Algo/Transform.h"
#define LOCTEXT_NAMESPACE "AssetTypeActions"
@@ -44,6 +45,13 @@ void FAssetTypeActions_AnimSequence::GetActions( const TArray& InObjec
FUIAction(FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteReimportWithNewSource, Sequences))
);
+ MenuBuilder.AddMenuEntry(
+ LOCTEXT("AnimSequence_AddAnimationModifier", "Add Animation Modifier(s)"),
+ LOCTEXT("AnimSequence_AddAnimationModifierTooltip", "Apply new animation modifier(s)."),
+ FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.AnimationModifier"),
+ FUIAction(FExecuteAction::CreateSP(this, &FAssetTypeActions_AnimSequence::ExecuteAddNewAnimationModifier, Sequences))
+ );
+
FAssetTypeActions_AnimationAsset::GetActions(InObjects, MenuBuilder);
}
@@ -137,6 +145,26 @@ void FAssetTypeActions_AnimSequence::ExecuteNewPoseAsset(TArray> Objects)
+{
+ TArray AnimSequences;
+
+ Algo::TransformIf(Objects, AnimSequences,
+ [](const TWeakObjectPtr& WeakAnimSequence)
+ {
+ return WeakAnimSequence.Get() && WeakAnimSequence->IsA();
+ },
+ [](const TWeakObjectPtr& WeakAnimSequence)
+ {
+ return WeakAnimSequence.Get();
+ });
+
+ if (IAnimationModifiersModule* Module = FModuleManager::Get().LoadModulePtr("AnimationModifiers"))
+ {
+ Module->ShowAddAnimationModifierWindow(AnimSequences);
+ }
+}
+
bool FAssetTypeActions_AnimSequence::ConfigureFactoryForAnimComposite(UFactory* AssetFactory, UAnimSequence* SourceAnimation) const
{
UAnimCompositeFactory* CompositeFactory = CastChecked(AssetFactory);
diff --git a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.h b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.h
index e2f214d40a2f..b6f760d762b5 100644
--- a/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.h
+++ b/Engine/Source/Developer/AssetTools/Private/AssetTypeActions/AssetTypeActions_AnimSequence.h
@@ -38,6 +38,9 @@ private:
/** Handler for when Create Pose Asset is selected */
void ExecuteNewPoseAsset(TArray> Objects) const;
+ /** Handler for when Add Animation Modifier is selected */
+ void ExecuteAddNewAnimationModifier(TArray> Objects);
+
/** Delegate handler passed to CreateAnimationAssets when creating AnimComposites */
bool ConfigureFactoryForAnimComposite(UFactory* AssetFactory, UAnimSequence* SourceAnimation) const;
diff --git a/Engine/Source/Developer/Localization/Private/LocTextHelper.cpp b/Engine/Source/Developer/Localization/Private/LocTextHelper.cpp
index d0fcad2cd4a6..2e7bd85bcece 100644
--- a/Engine/Source/Developer/Localization/Private/LocTextHelper.cpp
+++ b/Engine/Source/Developer/Localization/Private/LocTextHelper.cpp
@@ -973,7 +973,7 @@ void FLocTextHelper::GetRuntimeText(const FString& InCulture, const FLocKey& InN
OutTranslation = InSource;
TSharedPtr ArchiveEntry = FindTranslationImpl(InCulture, InNamespace, InKey, InKeyMetadataObj);
- if (ArchiveEntry.IsValid())
+ if (ArchiveEntry.IsValid() && !ArchiveEntry->Translation.Text.IsEmpty())
{
if (bSkipSourceCheck)
{
diff --git a/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs b/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs
index 03968f2bd787..5dce24d4e223 100644
--- a/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs
+++ b/Engine/Source/Developer/MeshBuilder/MeshBuilder.Build.cs
@@ -27,20 +27,6 @@ namespace UnrealBuildTool.Rules
AddEngineThirdPartyPrivateStaticDependencies(Target, "ForsythTriOptimizer");
AddEngineThirdPartyPrivateStaticDependencies(Target, "nvTessLib");
AddEngineThirdPartyPrivateStaticDependencies(Target, "QuadricMeshReduction");
-
- if (Target.bCompileSimplygon == true)
- {
- AddEngineThirdPartyPrivateDynamicDependencies(Target, "SimplygonMeshReduction");
-
- if (Target.bCompileSimplygonSSF == true)
- {
- DynamicallyLoadedModuleNames.AddRange(
- new string[] {
- "SimplygonSwarm"
- }
- );
- }
- }
}
}
}
diff --git a/Engine/Source/Developer/MeshUtilities/MeshUtilities.Build.cs b/Engine/Source/Developer/MeshUtilities/MeshUtilities.Build.cs
index 4ce2408b30ab..09ec58a3dfb5 100644
--- a/Engine/Source/Developer/MeshUtilities/MeshUtilities.Build.cs
+++ b/Engine/Source/Developer/MeshUtilities/MeshUtilities.Build.cs
@@ -75,20 +75,6 @@ public class MeshUtilities : ModuleRules
AddEngineThirdPartyPrivateStaticDependencies(Target, "DX9");
}
- if (Target.bCompileSimplygon == true)
- {
- AddEngineThirdPartyPrivateDynamicDependencies(Target, "SimplygonMeshReduction");
-
- if (Target.bCompileSimplygonSSF == true)
- {
- DynamicallyLoadedModuleNames.AddRange(
- new string[] {
- "SimplygonSwarm"
- }
- );
- }
- }
-
// EMBREE
if (Target.Platform == UnrealTargetPlatform.Win64)
{
diff --git a/Engine/Source/Developer/SimplygonMeshReduction/Private/SimplygonMeshReduction.cpp b/Engine/Source/Developer/SimplygonMeshReduction/Private/SimplygonMeshReduction.cpp
deleted file mode 100644
index c5c2d7002590..000000000000
--- a/Engine/Source/Developer/SimplygonMeshReduction/Private/SimplygonMeshReduction.cpp
+++ /dev/null
@@ -1,3179 +0,0 @@
-// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
-
-#include "CoreMinimal.h"
-#include "IMeshReductionInterfaces.h"
-#include "MeshUtilities.h"
-#include "MaterialUtilities.h"
-#include "SimplygonTypes.h"
-#include "StaticMeshResources.h"
-#include "Components/SplineMeshComponent.h"
-#include "SimplygonSDK.h"
-#include "ProfilingDebugging/ScopedTimers.h"
-#include "Misc/ScopedSlowTask.h"
-#include "Modules/ModuleManager.h"
-#include "HAL/Runnable.h"
-#include "HAL/RunnableThread.h"
-#include "Engine/SkeletalMesh.h"
-#include "Misc/CommandLine.h"
-#include "Misc/Paths.h"
-#include "Misc/FileHelper.h"
-#include "Components/SkinnedMeshComponent.h"
-#include "Templates/UniquePtr.h"
-#include "Features/IModularFeatures.h"
-#include "Rendering/SkeletalMeshModel.h"
-#include "AnimationBlueprintLibrary.h"
-#include "AnimationRuntime.h"
-
-#include "MeshMergeData.h"
-#include "Rendering/MultiSizeIndexContainer.h"
-
-#include "MeshDescription.h"
-#include "MeshAttributes.h"
-#include "MeshAttributeArray.h"
-#include "MeshDescriptionOperations.h"
-
-// Standard Simplygon channels have some issues with extracting color data back from simplification,
-// so we use this workaround with user channels
-static const char* USER_MATERIAL_CHANNEL_METALLIC = "UserMetallic";
-static const char* USER_MATERIAL_CHANNEL_ROUGHNESS = "UserRoughness";
-static const char* USER_MATERIAL_CHANNEL_SPECULAR = "UserSpecular";
-
-static const TCHAR* SG_UE_INTEGRATION_REV = TEXT("@305");
-
-#ifdef __clang__
- // SimplygonSDK.h uses 'deprecated' pragma which Clang does not recognize
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown pragma ignored [-Wunknown-pragmas]
-#endif
-
-#include "SimplygonSDK.h"
-
-#ifdef __clang__
- #pragma clang diagnostic pop
-#endif
-
-#include "MeshBoneReduction.h"
-#include "ComponentReregisterContext.h"
-//@third party code BEGIN SIMPLYGON
-#include "ImageUtils.h"
-
-// Notes about IChartAggregator:
-// - Available since Simplygon 7.0 (defined(SIMPLYGONSDK_VERSION) && SIMPLYGONSDK_VERSION >= 0x700).
-// - Use of pure IChartAggregator will re-introduce bugs with UVs outside of 0..1 range, so UVs should
-// be scaled.
-// - IChartAggregator probably needs more settings to be provided, because it will grow number of mesh
-// vertices a lot (up to 3x of mesh face count).
-#define USE_SIMPLYGON_CHART_AGGREGATOR 0
-
-// Use Engine's FLayoutUV class to generate non-overlapping texture coordinates. If disabled, Simplygon
-// will be used.
-#define USE_FLAYOUT_UV 0
-
-//#define DEBUG_PROXY_MESH
-
-#define LOCTEXT_NAMESPACE "SimplygonMeshReduction"
-
-class FSimplygonMeshReductionModule : public IMeshReductionModule
-{
-public:
- // IModuleInterface interface.
- virtual void StartupModule() override;
- virtual void ShutdownModule() override;
-
- // IMeshReductionModule interface.
- virtual class IMeshReduction* GetStaticMeshReductionInterface() override;
- virtual class IMeshReduction* GetSkeletalMeshReductionInterface() override;
- virtual class IMeshMerging* GetMeshMergingInterface() override;
- virtual class IMeshMerging* GetDistributedMeshMergingInterface() override;
- virtual FString GetName() override;
-};
-
-DEFINE_LOG_CATEGORY_STATIC(LogSimplygon, Log, All);
-IMPLEMENT_MODULE(FSimplygonMeshReductionModule, SimplygonMeshReduction);
-
-#define SIMPLYGON_COLOR_CHANNEL "VertexColors"
-
-/** Receives error messages generated by Simplygon. These errors are presented via a message box. */
-class FDefaultErrorHandler : public SimplygonSDK::rerrorhandler
-{
-public :
- virtual void HandleError(
- SimplygonSDK::IObject* Object,
- const char* InterfaceName,
- const char* MethodName,
- SimplygonSDK::rid ErrorType,
- const char* ErrorText )
- {
- FString ErrorString = FString::Printf(TEXT("Simplygon Error:\n\nInterface: %s\nMethod: %s\nError: (%d) %s"),
- ANSI_TO_TCHAR(InterfaceName),
- ANSI_TO_TCHAR(MethodName),
- ErrorType,
- ANSI_TO_TCHAR(ErrorText)
- );
- UE_LOG(LogSimplygon, Log, TEXT("%s"), *ErrorString);
- //FMessageDialog::Open(EAppMsgType::Ok, *ErrorString);
- }
-};
-
-/** Receives progress events from Simplygon and updates the status window. */
-class FDefaultEventHandler : public SimplygonSDK::robserver
-{
-public:
-
- FDefaultEventHandler() : Task(nullptr), PreviousProgress( 0 ) {}
- FScopedSlowTask* Task;
-
- int32 PreviousProgress;
-
- virtual void Execute(
- SimplygonSDK::IObject* Object,
- SimplygonSDK::rid EventId,
- void* EventParameterBlock,
- unsigned int EventParameterBlockSize )
- {
- if ( EventId == SimplygonSDK::SG_EVENT_PROGRESS )
- {
- check( sizeof(int32) == EventParameterBlockSize );
- int32 ProgressPercent = *((int32*)EventParameterBlock);
-
- if (IsInGameThread())
- {
- if ( Task != nullptr)
- {
- // @todo: jurre this is temporary fix for UE-48222
- //Task->EnterProgressFrame(ProgressPercent - PreviousProgress);
- PreviousProgress = ProgressPercent;
- }
- }
-
- // We are required to pass '1' back through the EventParametersBlock for the process to continue.
- *((int32*)EventParameterBlock) = 1;
- }
- }
-};
-
-/** Winding modes. */
-enum EWindingMode
-{
- /** Maintain the winding of the mesh. */
- WINDING_Keep,
- /** Reverse the winding of the mesh. */
- WINDING_Reverse,
- WINDING_MAX
-};
-
-static void* GSimplygonSDKDLLHandle = nullptr;
-
-class FSimplygonMeshReduction
- : public IMeshReduction
- , public IMeshMerging
-{
-public:
- virtual ~FSimplygonMeshReduction()
- {
- }
-
- virtual const FString& GetVersionString() const override
- {
- return VersionString;
- }
-
- virtual FString GetName() override
- {
- return FString("SimplygonMeshReduction");
- }
-
- bool ReduceLODModel(
- const FSkeletalMeshLODModel * SrcModel,
- FSkeletalMeshLODModel *& OutModel,
- const FBoxSphereBounds & Bounds,
- float &MaxDeviation,
- const FReferenceSkeleton& RefSkeleton,
- const FSkeletalMeshOptimizationSettings& Settings,
- const TArray& BoneMatrices,
- const int32 LODIndex
- )
- {
- const bool bUsingMaxDeviation = (Settings.ReductionMethod != SMOT_NumOfTriangles && Settings.MaxDeviationPercentage > 0.0f);
- const bool bUsingReductionRatio = (Settings.ReductionMethod != SMOT_MaxDeviation && Settings.NumOfTrianglesPercentage < 1.0f);
- const bool bProcessGeometry = ( bUsingMaxDeviation || bUsingReductionRatio );
- const bool bProcessBones = (Settings.MaxBonesPerVertex < MAX_TOTAL_INFLUENCES);
- const bool bOptimizeMesh = (bProcessGeometry || bProcessBones);
-
- // We'll need to store the max deviation after optimization if we wish to recalculate the LOD's display distance
- MaxDeviation = 0.0f;
-
- if ( bOptimizeMesh )
- {
- //Create a simplygon scene. The scene by default contains a bone table that is required for bone reduction.
- SimplygonSDK::spScene Scene = SDK->CreateScene();
- check( Scene );
-
- // Create bone hierarchy for simplygon.
- TArray BoneTableIDs;
- CreateSkeletalHierarchy(Scene, RefSkeleton, BoneTableIDs);
-
- // Create a new scene mesh object
- SimplygonSDK::spGeometryData GeometryData = CreateGeometryFromSkeletalLODModel( Scene, *SrcModel, BoneTableIDs, BoneMatrices, LODIndex);
-
- FDefaultEventHandler SimplygonEventHandler;
- SimplygonSDK::spReductionProcessor ReductionProcessor = SDK->CreateReductionProcessor();
- ReductionProcessor->AddObserver( &EventHandler, SimplygonSDK::SG_EVENT_PROGRESS );
- ReductionProcessor->SetScene(Scene);
-
- SimplygonSDK::spRepairSettings RepairSettings = ReductionProcessor->GetRepairSettings();
- RepairSettings->SetWeldDist( Settings.WeldingThreshold );
- RepairSettings->SetTjuncDist( Settings.WeldingThreshold );
-
- SimplygonSDK::spReductionSettings ReductionSettings = ReductionProcessor->GetReductionSettings();
- SetReductionSettings( Settings, Bounds.SphereRadius, GeometryData->GetTriangleCount(), ReductionSettings );
-
- SimplygonSDK::spNormalCalculationSettings NormalSettings = ReductionProcessor->GetNormalCalculationSettings();
- SetNormalSettings( Settings, NormalSettings );
-
- if(bProcessBones)
- {
- SimplygonSDK::spBoneSettings BoneSettings = ReductionProcessor->GetBoneSettings();
- SetBoneSettings( Settings, BoneSettings );
- }
-
- ReductionProcessor->RunProcessing();
-
- // We require the max deviation if we're calculating the LOD's display distance.
- MaxDeviation = ReductionProcessor->GetResultDeviation();
- CreateSkeletalLODModelFromGeometry(GeometryData, RefSkeleton, OutModel);
- // return true if it created any vertice
- return (OutModel->NumVertices > 0);
- }
-
- return false;
- }
-
- bool RemoveMeshSection(FSkeletalMeshLODModel* Model, int32 SectionIndex)
- {
- // Need a valid section
- if (!Model->Sections.IsValidIndex(SectionIndex))
- {
- return false;
- }
-
- FSkelMeshSection& SectionToRemove = Model->Sections[SectionIndex];
-
- if (SectionToRemove.CorrespondClothAssetIndex != INDEX_NONE)
- {
- // Can't remove this, clothing currently relies on it
- return false;
- }
-
- const uint32 NumVertsToRemove = SectionToRemove.GetNumVertices();
- const uint32 BaseVertToRemove = SectionToRemove.BaseVertexIndex;
- const uint32 NumIndicesToRemove = SectionToRemove.NumTriangles * 3;
- const uint32 BaseIndexToRemove = SectionToRemove.BaseIndex;
-
-
- // Strip indices
- Model->IndexBuffer.RemoveAt(BaseIndexToRemove, NumIndicesToRemove);
-
- Model->Sections.RemoveAt(SectionIndex);
-
- // Fixup indices above base vert
- for (uint32& Index : Model->IndexBuffer)
- {
- if (Index >= BaseVertToRemove)
- {
- Index -= NumVertsToRemove;
- }
- }
-
- Model->NumVertices -= NumVertsToRemove;
-
- // Fixup anything needing section indices
- for (FSkelMeshSection& Section : Model->Sections)
- {
- // Push back clothing indices
- if (Section.CorrespondClothAssetIndex > SectionIndex)
- {
- Section.CorrespondClothAssetIndex--;
- }
-
- // Removed indices, rebase further sections
- if (Section.BaseIndex > BaseIndexToRemove)
- {
- Section.BaseIndex -= NumIndicesToRemove;
- }
-
- // Remove verts, rebase further sections
- if (Section.BaseVertexIndex > BaseVertToRemove)
- {
- Section.BaseVertexIndex -= NumVertsToRemove;
- }
- }
- return true;
- }
-
- /** internal only access function, so that you can use with register or with no-register */
- void Reduce(
- USkeletalMesh* SkeletalMesh,
- FSkeletalMeshModel* SkeletalMeshResource,
- int32 LODIndex
- )
- {
- //If the Current LOD is an import from file
- bool OldLodWasFromFile = SkeletalMesh->IsValidLODIndex(LODIndex) && SkeletalMesh->GetLODInfo(LODIndex)->bHasBeenSimplified == false;
-
- // Insert a new LOD model entry if needed.
- if (LODIndex == SkeletalMeshResource->LODModels.Num())
- {
- SkeletalMeshResource->LODModels.Add(0);
- }
-
- // We'll need to store the max deviation after optimization if we wish to recalculate the LOD's display distance
- float MaxDeviation = 0.0f;
-
- // Swap in a new model, delete the old.
- check(LODIndex < SkeletalMeshResource->LODModels.Num());
- FSkeletalMeshLODModel** LODModels = SkeletalMeshResource->LODModels.GetData();
- //delete LODModels[LODIndex]; -- keep model valid until we'll be ready to replace it; required to be able to refresh UI with mesh stats
-
- // Copy over LOD info from LOD0 if there is no previous info.
- if (LODIndex == SkeletalMesh->GetLODNum())
- {
- // if there is no LOD, add one more
- SkeletalMesh->AddLODInfo();
- }
-
- // get settings
- FSkeletalMeshOptimizationSettings& Settings = SkeletalMesh->GetLODInfo(LODIndex)->ReductionSettings;
-
- // select which mesh we're reducing from
- // use BaseLOD
- int32 BaseLOD = 0;
- FSkeletalMeshModel* SkelResource = SkeletalMesh->GetImportedModel();
- FSkeletalMeshLODModel* SrcModel = &SkelResource->LODModels[0];
-
- // only allow to set BaseLOD if the LOD is less than this
- if (Settings.BaseLOD > 0)
- {
- if (Settings.BaseLOD < LODIndex && SkeletalMeshResource->LODModels.IsValidIndex(Settings.BaseLOD))
- {
- BaseLOD = Settings.BaseLOD;
- SrcModel = &SkeletalMeshResource->LODModels[BaseLOD];
- }
- else
- {
- // warn users
- UE_LOG(LogSimplygon, Warning, TEXT("Building LOD %d - Invalid Base LOD entered. Using Base LOD 0"), LODIndex);
- }
- }
-
- //Reducing Base LOD, we need to use the temporary data so it can be iterative
- if (BaseLOD == LODIndex && SkelResource->OriginalReductionSourceMeshData.IsValidIndex(BaseLOD) && !SkelResource->OriginalReductionSourceMeshData[BaseLOD]->IsEmpty())
- {
- TMap> TempLODMorphTargetData;
- SkelResource->OriginalReductionSourceMeshData[BaseLOD]->LoadReductionData(*SrcModel, TempLODMorphTargetData);
- }
- else
- {
- check(BaseLOD < LODIndex);
- }
-
- // now try bone reduction process if it's setup
- TMap BonesToRemove;
-
- IMeshBoneReductionModule& MeshBoneReductionModule = FModuleManager::Get().LoadModuleChecked("MeshBoneReduction");
- IMeshBoneReduction * MeshBoneReductionInterface = MeshBoneReductionModule.GetMeshBoneReductionInterface();
-
- TArray BoneNames;
- const int32 NumBones = SkeletalMesh->RefSkeleton.GetNum();
- for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
- {
- BoneNames.Add(SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex));
- }
-
- // get the relative to ref pose matrices
- TArray RelativeToRefPoseMatrices;
- RelativeToRefPoseMatrices.AddUninitialized(NumBones);
- // if it has bake pose, gets ref to local matrices using bake pose
- if (const UAnimSequence* BakePoseAnim = SkeletalMesh->GetBakePose(LODIndex))
- {
- TArray BonePoses;
- UAnimationBlueprintLibrary::GetBonePosesForFrame(BakePoseAnim, BoneNames, 0, true, BonePoses, SkeletalMesh);
-
- const FReferenceSkeleton& RefSkeleton = SkeletalMesh->RefSkeleton;
- const TArray& RefPoseInLocal = RefSkeleton.GetRefBonePose();
-
- // get component ref pose
- TArray RefPoseInCS;
- FAnimationRuntime::FillUpComponentSpaceTransforms(RefSkeleton, RefPoseInLocal, RefPoseInCS);
-
- // calculate component space bake pose
- TArray ComponentSpacePose, ComponentSpaceRefPose, AnimPoseMatrices;
- ComponentSpacePose.AddUninitialized(NumBones);
- ComponentSpaceRefPose.AddUninitialized(NumBones);
- AnimPoseMatrices.AddUninitialized(NumBones);
-
- // to avoid scale issue, we use matrices here
- for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
- {
- ComponentSpaceRefPose[BoneIndex] = RefPoseInCS[BoneIndex].ToMatrixWithScale();
- AnimPoseMatrices[BoneIndex] = BonePoses[BoneIndex].ToMatrixWithScale();
- }
-
- for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
- {
- const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
- if (ParentIndex != INDEX_NONE)
- {
- ComponentSpacePose[BoneIndex] = AnimPoseMatrices[BoneIndex] * ComponentSpacePose[ParentIndex];
- }
- else
- {
- ComponentSpacePose[BoneIndex] = AnimPoseMatrices[BoneIndex];
- }
- }
-
- // calculate relative to ref pose transform and convert to matrices
- for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
- {
- RelativeToRefPoseMatrices[BoneIndex] = ComponentSpaceRefPose[BoneIndex].Inverse() * ComponentSpacePose[BoneIndex];
- }
- }
- else
- {
- for (int32 Index = 0; Index < NumBones; ++Index)
- {
- RelativeToRefPoseMatrices[Index] = FMatrix::Identity;
- }
- }
-
- FSkeletalMeshLODModel * NewModel = new FSkeletalMeshLODModel();
- LODModels[LODIndex] = NewModel;
-
- // Reduce LOD model with SrcMesh
- if (ReduceLODModel(SrcModel, NewModel, SkeletalMesh->GetImportedBounds(), MaxDeviation, SkeletalMesh->RefSkeleton, Settings, RelativeToRefPoseMatrices, LODIndex))
- {
- // See if we'd like to remove extra bones first
- if (MeshBoneReductionInterface->GetBoneReductionData(SkeletalMesh, LODIndex, BonesToRemove))
- {
- // fix up chunks to remove the bones that set to be removed
- for (int32 SectionIndex = 0; SectionIndex < NewModel->Sections.Num(); ++SectionIndex)
- {
- MeshBoneReductionInterface->FixUpSectionBoneMaps(NewModel->Sections[SectionIndex], BonesToRemove);
- }
- }
-
- if (OldLodWasFromFile)
- {
- SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Empty();
- }
-
- // If base lod has a customized LODMaterialMap and this LOD doesn't (could have if changes are applied instead of freshly generated, copy over the data into new new LOD
- if (SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Num() == 0 && SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap.Num() != 0)
- {
- SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap = SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap;
- }
- else
- {
- // Assuming the reducing step has set all material indices correctly, we double check if something went wrong
- // make sure we don't have more materials
- int32 TotalSectionCount = NewModel->Sections.Num();
- if (SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Num() > TotalSectionCount)
- {
- SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap = SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap;
- // Something went wrong during the reduce step during regenerate
- check(SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap.Num() == TotalSectionCount || SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap.Num() == 0);
- }
- }
-
- // Flag this LOD as having been simplified.
- SkeletalMesh->GetLODInfo(LODIndex)->bHasBeenSimplified = true;
- SkeletalMesh->bHasBeenSimplified = true;
- }
- else
- {
- // Bulk data arrays need to be locked before a copy can be made.
- SrcModel->RawPointIndices.Lock(LOCK_READ_ONLY);
- SrcModel->LegacyRawPointIndices.Lock(LOCK_READ_ONLY);
- *NewModel = *SrcModel;
- SrcModel->RawPointIndices.Unlock();
- SrcModel->LegacyRawPointIndices.Unlock();
-
- // See if we'd like to remove extra bones first
- if (MeshBoneReductionInterface->GetBoneReductionData(SkeletalMesh, LODIndex, BonesToRemove))
- {
- // fix up chunks to remove the bones that set to be removed
- for (int32 SectionIndex = 0; SectionIndex < NewModel->Sections.Num(); ++SectionIndex)
- {
- MeshBoneReductionInterface->FixUpSectionBoneMaps(NewModel->Sections[SectionIndex], BonesToRemove);
- }
- }
-
- //Clean up some section data
- for (int32 SectionIndex = SrcModel->Sections.Num()-1; SectionIndex >= 0; --SectionIndex)
- {
- //New model should be reset to -1 value
- NewModel->Sections[SectionIndex].GenerateUpToLodIndex = -1;
- int8 GenerateUpToLodIndex = SrcModel->Sections[SectionIndex].GenerateUpToLodIndex;
- if (GenerateUpToLodIndex != -1 && GenerateUpToLodIndex < LODIndex)
- {
- //Remove the section
- RemoveMeshSection(NewModel, SectionIndex);
- }
- }
-
- SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap = SkeletalMesh->GetLODInfo(BaseLOD)->LODMaterialMap;
- // Required bones are recalculated later on.
- NewModel->RequiredBones.Empty();
- SkeletalMesh->GetLODInfo(LODIndex)->bHasBeenSimplified = true;
- SkeletalMesh->bHasBeenSimplified = true;
- }
-
- SkeletalMesh->CalculateRequiredBones(SkeletalMeshResource->LODModels[LODIndex], SkeletalMesh->RefSkeleton, &BonesToRemove);
- }
-
- virtual void ReduceMeshDescription(
- FMeshDescription& OutReducedMesh,
- float& OutMaxDeviation,
- const FMeshDescription& InMesh,
- const FOverlappingCorners& InOverlappingCorners,
- const struct FMeshReductionSettings& InSettings
- ) override
- {
- SimplygonSDK::spGeometryData GeometryData = CreateGeometryFromRawMesh(InMesh);
- check(GeometryData);
-
- SimplygonSDK::spScene Scene = SDK->CreateScene();
-
- SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh();
- Mesh->SetGeometry(GeometryData);
- Mesh->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("UnrealMesh"))));
- Scene->GetRootNode()->AddChild(Mesh);
-
- SimplygonSDK::spReductionProcessor ReductionProcessor = SDK->CreateReductionProcessor();
- ReductionProcessor->AddObserver(&EventHandler, SimplygonSDK::SG_EVENT_PROGRESS);
- ReductionProcessor->SetScene(Scene);
-
- SimplygonSDK::spRepairSettings RepairSettings = ReductionProcessor->GetRepairSettings();
- RepairSettings->SetWeldDist(InSettings.WeldingThreshold);
- RepairSettings->SetTjuncDist(InSettings.WeldingThreshold);
-
- SimplygonSDK::spReductionSettings ReductionSettings = ReductionProcessor->GetReductionSettings();
- SetReductionSettings(ReductionSettings, InSettings, GeometryData->GetTriangleCount());
-
- //Set visibility settings
- SimplygonSDK::spVisibilitySettings VisibilitySettings = ReductionProcessor->GetVisibilitySettings();
- VisibilitySettings->SetCullOccludedGeometry(InSettings.bCullOccluded);
- int32 TempAggressiveness = InSettings.VisibilityAggressiveness + 1; //+1 because there is an offset in aggressiveness options
- VisibilitySettings->SetVisibilityWeightsPower(TempAggressiveness);
- VisibilitySettings->SetUseVisibilityWeightsInReducer(InSettings.bVisibilityAided);
-
- SimplygonSDK::spNormalCalculationSettings NormalSettings = ReductionProcessor->GetNormalCalculationSettings();
- SetNormalSettings(NormalSettings, InSettings);
-
- ReductionProcessor->RunProcessing();
-
- SimplygonSDK::spSceneMesh ReducedMesh = SimplygonSDK::Cast(Scene->GetRootNode()->GetChild(0));
- CreateRawMeshFromGeometry(OutReducedMesh, ReducedMesh->GetGeometry(), WINDING_Keep);
- OutMaxDeviation = ReductionProcessor->GetResultDeviation();
- }
-
- virtual bool ReduceSkeletalMesh(
- USkeletalMesh* SkeletalMesh,
- int32 LODIndex,
- bool bReregisterComponent = true
- ) override
- {
- check( SkeletalMesh );
- check( LODIndex >= 0 );
- check( LODIndex <= SkeletalMesh->GetLODNum() );
-
- FSkeletalMeshModel* SkeletalMeshResource = SkeletalMesh->GetImportedModel();
- check(SkeletalMeshResource);
- check( LODIndex <= SkeletalMeshResource->LODModels.Num() );
-
- if (bReregisterComponent)
- {
- TComponentReregisterContext ReregisterContext;
- SkeletalMesh->ReleaseResources();
- SkeletalMesh->ReleaseResourcesFence.Wait();
-
- Reduce(SkeletalMesh, SkeletalMeshResource, LODIndex);
-
- SkeletalMesh->PostEditChange();
- SkeletalMesh->InitResources();
- }
- else
- {
- Reduce(SkeletalMesh, SkeletalMeshResource, LODIndex);
- }
-
- return true;
- }
-
- virtual bool IsSupported() const override
- {
- return true;
- }
-
- /**
- * Returns true if mesh reduction is active. Active mean there will be a reduction of the vertices or triangle number
- */
- virtual bool IsReductionActive(const FMeshReductionSettings &ReductionSettings) const
- {
- float Threshold_One = (1.0f - KINDA_SMALL_NUMBER);
- float Threshold_Zero = (0.0f + KINDA_SMALL_NUMBER);
- return ReductionSettings.PercentTriangles < Threshold_One || ReductionSettings.MaxDeviation > Threshold_Zero;
- }
-
- virtual bool IsReductionActive(const FSkeletalMeshOptimizationSettings &ReductionSettings) const
- {
- float Threshold_One = (1.0f - KINDA_SMALL_NUMBER);
- float Threshold_Zero = (0.0f + KINDA_SMALL_NUMBER);
- switch (ReductionSettings.ReductionMethod)
- {
- case SkeletalMeshOptimizationType::SMOT_NumOfTriangles:
- {
- return ReductionSettings.NumOfTrianglesPercentage < Threshold_One;
- }
- break;
- case SkeletalMeshOptimizationType::SMOT_MaxDeviation:
- {
- return ReductionSettings.MaxDeviationPercentage > Threshold_Zero;
- }
- break;
- case SkeletalMeshOptimizationType::SMOT_TriangleOrDeviation:
- {
- return ReductionSettings.NumOfTrianglesPercentage < Threshold_One || ReductionSettings.MaxDeviationPercentage > Threshold_Zero;
- }
- break;
- }
-
- return false;
- }
-
- static void Destroy()
- {
- if (GSimplygonSDKDLLHandle != nullptr)
- {
- typedef int(*DeinitializeSimplygonSDKPtr)();
- DeinitializeSimplygonSDKPtr DeinitializeSimplygonSDK = (DeinitializeSimplygonSDKPtr) FPlatformProcess::GetDllExport(GSimplygonSDKDLLHandle, TEXT("DeinitializeSimplygonSDK"));
- DeinitializeSimplygonSDK();
- FPlatformProcess::FreeDllHandle(GSimplygonSDKDLLHandle);
- GSimplygonSDKDLLHandle = nullptr;
- }
- }
-
- static FSimplygonMeshReduction* Create()
- {
- if (FParse::Param(FCommandLine::Get(), TEXT("NoSimplygon")))
- {
- //The user specified that simplygon should not be used
- UE_LOG(LogSimplygon, Log, TEXT("Simplygon is disabled with -NoSimplygon flag"));
- return NULL;
- }
- //@third party BEGIN SIMPLYGON
- const FString SUPPORTED_SIMPLYGON_VERSION = "8";
- //@third party END SIMPLYGON
-
- // Load d3d compiler first
- FString DllPath;
-#if !PLATFORM_64BITS
- DllPath = FPaths::Combine(*FPaths::EngineDir(), TEXT("Binaries/ThirdParty/Windows/DirectX/x86/d3dcompiler_47.dll"));
-#else
- DllPath = FPaths::Combine(*FPaths::EngineDir(), TEXT("Binaries/ThirdParty/Windows/DirectX/x64/d3dcompiler_47.dll"));
-#endif
- if (!FPaths::FileExists(DllPath))
- {
- UE_LOG(LogSimplygon, Log, TEXT("Could not find d3dcompiler_47 DLL, which is required for loading Simplygon."));
- return NULL;
- }
- else
- {
- void* D3DHandle = FPlatformProcess::GetDllHandle(*DllPath);
- }
-
-
- DllPath = (FPaths::Combine(*FPaths::EngineDir(), TEXT("Binaries/ThirdParty/NotForLicensees/Simplygon")));
- FString DllFilename(FPaths::Combine(*DllPath, TEXT("SimplygonSDKRuntimeReleasex64.dll")));
-
- // If the DLL just doesn't exist, fail gracefully. Licensees and Subscribers will not necessarily have Simplygon.
- if( !FPaths::FileExists(DllFilename) )
- {
- UE_LOG(LogSimplygon,Log,TEXT("Simplygon DLL not present - disabling."));
- return NULL;
- }
-
- // Otherwise fail
- GSimplygonSDKDLLHandle = FPlatformProcess::GetDllHandle(*DllFilename);
- if (GSimplygonSDKDLLHandle == NULL)
- {
- int32 ErrorNum = FPlatformMisc::GetLastError();
- TCHAR ErrorMsg[1024];
- FPlatformMisc::GetSystemErrorMessage( ErrorMsg, 1024, ErrorNum );
- UE_LOG(LogSimplygon,Error,TEXT("Failed to get Simplygon DLL handle: %s (%d)"),ErrorMsg,ErrorNum);
- return NULL;
- }
-
- // Get API function pointers of interest
- typedef void (*GetInterfaceVersionSimplygonSDKPtr)(ANSICHAR*);
- GetInterfaceVersionSimplygonSDKPtr GetInterfaceVersionSimplygonSDK = (GetInterfaceVersionSimplygonSDKPtr)FPlatformProcess::GetDllExport( GSimplygonSDKDLLHandle, TEXT( "GetInterfaceVersionSimplygonSDK" ) );
-
- typedef int (*InitializeSimplygonSDKPtr)(const char* LicenseData , SimplygonSDK::ISimplygonSDK** OutInterfacePtr);
- InitializeSimplygonSDKPtr InitializeSimplygonSDK = (InitializeSimplygonSDKPtr)FPlatformProcess::GetDllExport( GSimplygonSDKDLLHandle, TEXT( "InitializeSimplygonSDK" ) );
-
- if ((GetInterfaceVersionSimplygonSDK == NULL) || (InitializeSimplygonSDK == NULL))
- {
- // Couldn't find the functions we need.
- UE_LOG(LogSimplygon, Log,TEXT("Failed to acquire Simplygon DLL exports."));
- FPlatformProcess::FreeDllHandle( GSimplygonSDKDLLHandle );
- GSimplygonSDKDLLHandle = NULL;
- return NULL;
- }
-
- //@third party BEGIN SIMPLYGON
- //NOTE: Only major version check is required. This removes the dependency to update each time the SDK is updated to a minor release with bug fixes
- //TODO : Tokenize the version string. This would allow for better granularity when a tight coupling to the dll is required. (i.e Custom Dll Version)
- FString MajorVersion, MinorAndBuildRevision;
- FString SimplygonHeaderVersioString = ANSI_TO_TCHAR(SimplygonSDK::GetHeaderVersion());
- SimplygonHeaderVersioString.Split(TEXT("."), &MajorVersion, &MinorAndBuildRevision);
-
- if (SUPPORTED_SIMPLYGON_VERSION.Compare(MajorVersion) != 0)
- {
- UE_LOG(LogSimplygon, Log, TEXT("Simplygon version doesn't match the version expected by the Simplygon UE4 integration"));
- UE_LOG(LogSimplygon, Log, TEXT("Min version %s, found version %s"), *SUPPORTED_SIMPLYGON_VERSION, ANSI_TO_TCHAR(SimplygonSDK::GetHeaderVersion()));
- FPlatformProcess::FreeDllHandle(GSimplygonSDKDLLHandle);
- GSimplygonSDKDLLHandle = NULL;
- return NULL;
- }
- //@third party END SIMPLYGON
-
-
- ANSICHAR VersionHash[200];
- GetInterfaceVersionSimplygonSDK(VersionHash);
- if (FCStringAnsi::Strcmp(VersionHash, SimplygonSDK::GetInterfaceVersionHash()) != 0)
- {
- UE_LOG(LogSimplygon, Log,TEXT("Library version mismatch. Header=%s Lib=%s"),ANSI_TO_TCHAR(SimplygonSDK::GetInterfaceVersionHash()),ANSI_TO_TCHAR(VersionHash));
- FPlatformProcess::FreeDllHandle(GSimplygonSDKDLLHandle);
- GSimplygonSDKDLLHandle = NULL;
- return NULL;
- }
-
- // Workaround for the fact Simplygon stomps memory
- class FSimplygonLicenseData
- {
- uint8* LicenseDataMem;
- int32 RealDataOffset;
- public:
- FSimplygonLicenseData(const TCHAR* InDllPath)
- : LicenseDataMem(nullptr)
- , RealDataOffset(0)
- {
- TArray LicenseFileContents;
- if (FFileHelper::LoadFileToArray(LicenseFileContents, *FPaths::Combine(InDllPath, TEXT("Simplygon_8_license.dat")), FILEREAD_Silent))
- {
- if (LicenseFileContents.Num() > 0)
- {
- // Allocate with a big slack at the beginning and end to workaround the fact Simplygon stomps memory
- const int32 LicenseDataSizeWithSlack = LicenseFileContents.Num() * 3 * sizeof(uint8);
- LicenseDataMem = (uint8*)FMemory::Malloc(LicenseDataSizeWithSlack);
- FMemory::Memzero(LicenseDataMem, LicenseDataSizeWithSlack);
- // Copy data to somewhere in the middle of allocated memory
- RealDataOffset = LicenseFileContents.Num();
- FMemory::Memcpy(LicenseDataMem + RealDataOffset, LicenseFileContents.GetData(), LicenseFileContents.Num() * sizeof(uint8));
- }
- }
- }
- ~FSimplygonLicenseData()
- {
- FMemory::Free(LicenseDataMem);
- }
- const char* GetLicenseData() const
- {
- return (const char*)(LicenseDataMem + RealDataOffset);
- }
- bool IsValid() const
- {
- return !!LicenseDataMem;
- }
- } LicenseDataContainer(*DllPath);
-
- FSimplygonMeshReduction* Result = nullptr;
- if (LicenseDataContainer.IsValid())
- {
- SimplygonSDK::ISimplygonSDK* SDK = NULL;
- int32 InitResult = InitializeSimplygonSDK(LicenseDataContainer.GetLicenseData(), &SDK);
-
- if (InitResult != SimplygonSDK::SG_ERROR_NOERROR && InitResult != SimplygonSDK::SG_ERROR_ALREADYINITIALIZED)
- {
- UE_LOG(LogSimplygon, Log, TEXT("Failed to initialize Simplygon. Return code: %d."), InitResult);
- FPlatformProcess::FreeDllHandle(GSimplygonSDKDLLHandle);
- GSimplygonSDKDLLHandle = nullptr;
- }
- else
- {
- Result = new FSimplygonMeshReduction(SDK);
- }
- }
- else
- {
- UE_LOG(LogSimplygon, Log, TEXT("Failed to load Simplygon license file."));
- }
-
- return Result;
- }
-
- struct FMaterialCastingProperties
- {
- bool bCastMaterials;
- bool bCastNormals;
- bool bCastMetallic;
- bool bCastRoughness;
- bool bCastSpecular;
-
- FMaterialCastingProperties()
- : bCastMaterials(false)
- , bCastNormals(false)
- , bCastMetallic(false)
- , bCastRoughness(false)
- , bCastSpecular(false)
- {
- }
- };
-
- // Processor is spRemeshingProcessor or spAggregationProcessor
- template
- void SimplygonProcessLOD(
- ProcessorClass Processor,
- const TArray& DataArray,
- const TArray& FlattenedMaterials,
- const FSimplygonMaterialLODSettings& MaterialLODSettings,
- FFlattenMaterial& OutMaterial)
- {
- if (!MaterialLODSettings.bActive)
- {
- UE_LOG(LogSimplygon, Log, TEXT("Processing with %s."), *FString(Processor->GetClass()));
- Processor->RunProcessing();
- UE_LOG(LogSimplygon, Log, TEXT("Processing done."));
- return;
- }
-
- // Setup the mapping image used for casting
- SetupMappingImage(
- MaterialLODSettings,
- Processor->GetMappingImageSettings(),
- /*InAggregateProcess = */ TAreTypesEqual::Value,
- /*InRemoveUVs = */ true);
-
- // Convert FFlattenMaterial array to Simplygon materials
- SimplygonSDK::spMaterialTable InputMaterialTable = SDK->CreateMaterialTable();
- //@third party BEGIN SIMPLYGON
- SimplygonSDK::spTextureTable InputTextureTable = Processor->GetScene()->GetTextureTable();
- CreateSGMaterialFromFlattenMaterial(FlattenedMaterials, MaterialLODSettings, InputMaterialTable, InputTextureTable, true);
- //@third party END SIMPLYGON
-
-
- // Perform LOD processing
- UE_LOG(LogSimplygon, Log, TEXT("Processing with %s."), *FString(Processor->GetClass()));
- double ProcessingTime = 0.0;
- {
- FScopedDurationTimer ProcessingTimer(ProcessingTime);
- Processor->RunProcessing();
- }
- UE_LOG(LogSimplygon, Log, TEXT("Processing done in %.2f s."), ProcessingTime);
-
- // Cast input materials to output materials and convert to FFlattenMaterial
- UE_LOG(LogSimplygon, Log, TEXT("Casting materials."));
- SimplygonSDK::spMappingImage MappingImage = Processor->GetMappingImage();
- //@third party BEGIN SIMPLYGON
- SimplygonSDK::spMaterial SgMaterial = RebakeMaterials(MaterialLODSettings, MappingImage, InputMaterialTable, InputTextureTable);
- CreateFlattenMaterialFromSGMaterial(SgMaterial, InputTextureTable, OutMaterial);
- //@third party END SIMPLYGON
-
- // Fill flatten material samples alpha values with 255 (to prevent the data from being optimized away)
- OutMaterial.FillAlphaValues(255);
-
- if (FlattenedMaterials.Num())
- {
- OutMaterial.BlendMode = FlattenedMaterials[0].BlendMode;
- OutMaterial.bTwoSided = FlattenedMaterials[0].bTwoSided;
- OutMaterial.bDitheredLODTransition = FlattenedMaterials[0].bDitheredLODTransition;
- OutMaterial.EmissiveScale = FlattenedMaterials[0].EmissiveScale;
- }
-
- UE_LOG(LogSimplygon, Log, TEXT("Casting done."));
- }
-
-
- class FProxyLODTask : FRunnable
- {
- public:
- FProxyLODTask(const TArray& InData,
- const struct FMeshProxySettings& InProxySettings,
- const TArray& InputMaterials,
- const FGuid InJobGUID, FProxyCompleteDelegate& InDelegate,
- SimplygonSDK::ISimplygonSDK* InSDK, FSimplygonMeshReduction* InReduction)
- : Data(InData)
- , ProxySettings(InProxySettings)
- , Materials(InputMaterials)
- , JobGUID(InJobGUID)
- , Delegate(InDelegate)
- , SDK(InSDK)
- , Reduction(InReduction)
- {
- }
-
- virtual bool Init()
- {
- return true;
- }
-
- virtual uint32 Run()
- {
- FMeshDescription OutProxyMesh;
- FFlattenMaterial OutMaterial;
-
- if (!Data.Num())
- {
- UE_LOG(LogSimplygon, Log, TEXT("The selected meshes are not valid. Make sure to select static meshes only."));
- OutProxyMesh.Empty();
- return 1;
- }
-
- //Create a Simplygon Scene
- SimplygonSDK::spScene Scene = SDK->CreateScene();
-
- SimplygonSDK::spGeometryValidator GeometryValidator = SDK->CreateGeometryValidator();
- TArray GlobalTexcoordBounds;
-
-
- for (int32 MeshIndex = 0; MeshIndex < Data.Num(); ++MeshIndex)
- {
- GlobalTexcoordBounds.Append(Data[MeshIndex].TexCoordBounds);
- }
-
- //For each raw mesh in array create a scene mesh and populate with geometry data
- for (int32 MeshIndex = 0; MeshIndex < Data.Num(); ++MeshIndex)
- {
- SimplygonSDK::spGeometryData GeometryData = Reduction->CreateGeometryFromRawMesh(*Data[MeshIndex].RawMesh, Data[MeshIndex].TexCoordBounds, Data[MeshIndex].NewUVs);
- if (!GeometryData)
- {
- UE_LOG(LogSimplygon, Warning, TEXT("Geometry data is NULL"));
- continue;
- }
-
- GeometryData->CleanupNanValues();
-
- //Validate the geometry
- Reduction->ValidateGeometry(GeometryValidator, GeometryData);
-
- check(GeometryData)
-
-#ifdef DEBUG_PROXY_MESH
- SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter();
- objexp->SetExportFilePath("d:/BeforeProxyMesh.obj");
- objexp->SetSingleGeometry(GeometryData);
- objexp->RunExport();
-#endif
-
- SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh();
- Mesh->SetGeometry(GeometryData);
- Mesh->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("UnrealMesh%d"), MeshIndex)));
- Scene->GetRootNode()->AddChild(Mesh);
-
- }
-
- //Create a remesher
- SimplygonSDK::spRemeshingProcessor RemeshingProcessor = SDK->CreateRemeshingProcessor();
-
- //Setup the remesher
- // TODO add more settings back in
- RemeshingProcessor->GetRemeshingSettings()->SetOnScreenSize(ProxySettings.ScreenSize);
- RemeshingProcessor->GetRemeshingSettings()->SetMergeDistance(ProxySettings.MergeDistance);
- RemeshingProcessor->SetScene(Scene);
-
- FSimplygonMaterialLODSettings MaterialLODSettings(ProxySettings.MaterialSettings);
- MaterialLODSettings.bActive = true;
-
- // Process data
- Reduction->SimplygonProcessLOD(RemeshingProcessor, Data, Materials, MaterialLODSettings, OutMaterial);
-
- //Collect the proxy mesh
- SimplygonSDK::spSceneMesh ProxyMesh = SimplygonSDK::Cast(Scene->GetRootNode()->GetChild(0));
-
-#ifdef DEBUG_PROXY_MESH
- SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter();
- objexp->SetExportFilePath("d:/AfterProxyMesh.obj");
- objexp->SetSingleGeometry(ProxyMesh->GetGeometry());
- objexp->RunExport();
-#endif
-
- //Convert geometry data to raw mesh data
- SimplygonSDK::spGeometryData outGeom = ProxyMesh->GetGeometry();
- Reduction->CreateRawMeshFromGeometry(OutProxyMesh, ProxyMesh->GetGeometry(), WINDING_Keep);
-
- // Default smoothing
- TEdgeAttributesRef EdgeHardnesses = OutProxyMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard);
-
- for (const FEdgeID& EdgeID : OutProxyMesh.Edges().GetElementIDs())
- {
- EdgeHardnesses[EdgeID] = false;
- }
-
- Delegate.ExecuteIfBound(OutProxyMesh, OutMaterial, JobGUID);
-
- return 0;
- }
-
- virtual void Stop()
- {
-
- }
-
- void StartJobAsync()
- {
- Thread = FRunnableThread::Create(this, TEXT("SimplygonMeshReductionTask"), 0, TPri_BelowNormal);
- }
-
- bool IsRunning() const
- {
- return (Thread != nullptr);
- }
- void Wait()
- {
- Thread->WaitForCompletion();
- }
- private:
- FRunnableThread* Thread;
- TArray Data;
- struct FMeshProxySettings ProxySettings;
- TArray Materials;
- FGuid JobGUID;
- FProxyCompleteDelegate Delegate;
- SimplygonSDK::ISimplygonSDK* SDK;
- FSimplygonMeshReduction* Reduction;
- };
-
- virtual void ProxyLOD(const TArray& InData,
- const struct FMeshProxySettings& InProxySettings,
- const TArray& InputMaterials,
- const FGuid InJobGUID) override
- {
- FScopedSlowTask SlowTask(100.f, (LOCTEXT("SimplygonProxyLOD_ProxyLOD", "Generating Proxy Mesh using Simplygon")));
- SlowTask.MakeDialog();
-
- FMeshDescription OutProxyMesh;
- UStaticMesh::RegisterMeshAttributes(OutProxyMesh);
- FFlattenMaterial OutMaterial;
-
- if (!InData.Num())
- {
- FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("The selected meshes are not valid. Make sure to select static meshes only."));
- OutProxyMesh.Empty();
- return;
- }
-
- //Create a Simplygon Scene
- SimplygonSDK::spScene Scene = SDK->CreateScene();
-
- SimplygonSDK::spGeometryValidator GeometryValidator = SDK->CreateGeometryValidator();
- TArray GlobalTexcoordBounds;
-
- for (int32 MeshIndex = 0; MeshIndex < InData.Num(); ++MeshIndex)
- {
- GlobalTexcoordBounds.Append(InData[MeshIndex].TexCoordBounds);
- }
-
- // Create Selection Sets for tagging geometry for processing and clipping geometry
- SimplygonSDK::spSelectionSet processingSet = SDK->CreateSelectionSet();
- processingSet->SetName("RemeshingProcessingSet");
- SimplygonSDK::spSelectionSet clippingGeometrySet = SDK->CreateSelectionSet();
- clippingGeometrySet->SetName("ClippingObjectSet");
-
- //For each raw mesh in array create a scene mesh and populate with geometry data
- for (int32 MeshIndex = 0; MeshIndex < InData.Num(); ++MeshIndex)
- {
- SimplygonSDK::spGeometryData GeometryData = CreateGeometryFromRawMesh(*InData[MeshIndex].RawMesh, InData[MeshIndex].TexCoordBounds, InData[MeshIndex].NewUVs);
- if (!GeometryData)
- {
- FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("Simplygon failed to generate Geometry Data"));
- continue;
- }
-
- GeometryData->CleanupNanValues();
-
- //Validate the geometry
- ValidateGeometry(GeometryValidator, GeometryData);
-
-#ifdef DEBUG_PROXY_MESH
- SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter();
- objexp->SetExportFilePath("d:/BeforeProxyMesh.obj");
- objexp->SetSingleGeometry(GeometryData);
- objexp->RunExport();
-#endif
- SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh();
- Mesh->SetGeometry(GeometryData);
- Mesh->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("UnrealMesh%d"), MeshIndex)));
- Scene->GetRootNode()->AddChild(Mesh);
-
- // if mesh is clipping geometry add to clipping set else processing set
- if (!InData[MeshIndex].bIsClippingMesh)
- {
- processingSet->AddItem(Mesh->GetNodeGUID());
- }
- else if (InData[MeshIndex].bIsClippingMesh)
- {
- clippingGeometrySet->AddItem(Mesh->GetNodeGUID());
- }
- }
-
- //add the sets to the scene
- Scene->GetSelectionSetTable()->AddItem(processingSet);
- Scene->GetSelectionSetTable()->AddItem(clippingGeometrySet);
-
- //Create a remesher
- SimplygonSDK::spRemeshingProcessor RemeshingProcessor = SDK->CreateRemeshingProcessor();
- RemeshingProcessor->Clear();
-
- //Setup the remesher
- EventHandler.Task = &SlowTask;
- RemeshingProcessor->AddObserver(&EventHandler, SimplygonSDK::SG_EVENT_PROGRESS);
- RemeshingProcessor->GetRemeshingSettings()->SetOnScreenSize(InProxySettings.ScreenSize);
- RemeshingProcessor->GetRemeshingSettings()->SetMergeDistance(InProxySettings.MergeDistance);
- if (InProxySettings.bUseHardAngleThreshold)
- {
- // Otherwise the default Remeshing setting of 80-degree will be used.
- RemeshingProcessor->GetRemeshingSettings()->SetHardEdgeAngleInRadians(FMath::DegreesToRadians(InProxySettings.HardAngleThreshold));
- }
-
- // Setup sets for the remeshing processor
- bool bUseClippingGeometry = clippingGeometrySet->GetItemCount() > 0 ? true : false;
-
- RemeshingProcessor->GetRemeshingSettings()->SetUseClippingGeometry(bUseClippingGeometry);
- RemeshingProcessor->GetRemeshingSettings()->SetProcessSelectionSetName("RemeshingProcessingSet");
-
- if (bUseClippingGeometry)
- {
- RemeshingProcessor->GetRemeshingSettings()->SetClippingGeometrySelectionSetName("ClippingObjectSet");
- RemeshingProcessor->GetRemeshingSettings()->SetUseClippingGeometryEmptySpaceOverride(false);
- }
-
- RemeshingProcessor->SetScene(Scene);
-
- FSimplygonMaterialLODSettings MaterialLODSettings(InProxySettings.MaterialSettings);
- MaterialLODSettings.bActive = true;
-
- // Process data
- SimplygonProcessLOD(RemeshingProcessor, InData, InputMaterials, MaterialLODSettings, OutMaterial);
-
- //Collect the proxy mesh
- const uint32 ChildNodeCount = Scene->GetRootNode()->GetChildCount();
-
- SimplygonSDK::spSceneMesh ProxyMesh = nullptr;
- for (uint32 ChildNodeIndex = 0; ChildNodeIndex < ChildNodeCount; ++ChildNodeIndex)
- {
- auto ChildNode = Scene->GetRootNode()->GetChild(ChildNodeIndex);
- SimplygonSDK::spSceneMesh Mesh = SimplygonSDK::SafeCast(ChildNode);
- if (Mesh != NULL)
- {
- ProxyMesh = Mesh;
- }
- }
-
- if (ProxyMesh == nullptr)
- {
- FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("Simplygon failed to generate a proxy mesh"));
- }
- else
- {
-#ifdef DEBUG_PROXY_MESH
- SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter();
- objexp->SetExportFilePath("d:/AfterProxyMesh.obj");
- objexp->SetSingleGeometry(ProxyMesh->GetGeometry());
- objexp->RunExport();
-#endif
-
- //Convert geometry data to raw mesh data
- SimplygonSDK::spGeometryData outGeom = ProxyMesh->GetGeometry();
- CreateRawMeshFromGeometry(OutProxyMesh, ProxyMesh->GetGeometry(), WINDING_Keep);
-
- // Default smoothing
- TEdgeAttributesRef EdgeHardnesses = OutProxyMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard);
-
- for (const FEdgeID& EdgeID : OutProxyMesh.Edges().GetElementIDs())
- {
- EdgeHardnesses[EdgeID] = false;
- }
-
- if (OutProxyMesh.Polygons().Num() == 0)
- {
- FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("Simplygon failed to generate a valid proxy mesh"));
- }
- CompleteDelegate.ExecuteIfBound(OutProxyMesh, OutMaterial, InJobGUID);
- }
-
- EventHandler.Task = nullptr;
- }
-
-private:
- SimplygonSDK::ISimplygonSDK* SDK;
- FDefaultErrorHandler ErrorHandler;
- FDefaultEventHandler EventHandler;
- FString VersionString;
-
- explicit FSimplygonMeshReduction(SimplygonSDK::ISimplygonSDK* InSDK)
- : SDK(InSDK)
- {
- check(SDK);
- SDK->SetErrorHandler(&ErrorHandler);
- SDK->SetGlobalSetting("DefaultTBNType", SimplygonSDK::SG_TANGENTSPACEMETHOD_ORTHONORMAL_LEFTHANDED);
- SDK->SetGlobalSetting("AllowDirectX", true);
-
- const TCHAR* LibraryVersion = ANSI_TO_TCHAR(InSDK->GetVersion());
- const TCHAR* UnrealVersionGuid = TEXT("18f808c3cf724e5a994f57de5c83cc4b");
- VersionString = FString::Printf(TEXT("%s.%s_%s"), LibraryVersion, SG_UE_INTEGRATION_REV,UnrealVersionGuid);
- UE_LOG(LogSimplygon, Display, TEXT("Initialized with Simplygon %s"), *VersionString);
- }
-
- SimplygonSDK::spGeometryData CreateGeometryFromRawMesh(const FMeshDescription& RawMesh)
- {
- TVertexAttributesConstRef VertexPositions = RawMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position);
- TEdgeAttributesConstRef EdgeHardnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard);
- TEdgeAttributesConstRef EdgeCreaseSharpnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::CreaseSharpness);
- TPolygonGroupAttributesConstRef PolygonGroupImportedMaterialSlotNames = RawMesh.PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
- TVertexInstanceAttributesConstRef VertexInstanceNormals = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal);
- TVertexInstanceAttributesConstRef VertexInstanceTangents = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent);
- TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign);
- TVertexInstanceAttributesConstRef VertexInstanceColors = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color);
- TVertexInstanceAttributesConstRef VertexInstanceUVs = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate);
-
- int32 NumVertices = RawMesh.Vertices().Num();
- int32 NumWedges = RawMesh.VertexInstances().Num();
-
- if (NumWedges == 0)
- {
- return NULL;
- }
-
- int32 NumTris = 0;
- for (const FPolygonID& PolygonID : RawMesh.Polygons().GetElementIDs())
- {
- NumTris += RawMesh.GetPolygon(PolygonID).Triangles.Num();
- }
-
- TArray FaceSmoothingMasks;
- FaceSmoothingMasks.AddZeroed(NumTris);
- FMeshDescriptionOperations::ConvertHardEdgesToSmoothGroup(RawMesh, FaceSmoothingMasks);
-
- SimplygonSDK::spGeometryData GeometryData = SDK->CreateGeometryData();
- GeometryData->SetVertexCount(NumVertices);
- GeometryData->SetTriangleCount(NumTris);
-
- TMap VertexIDToDstVertexIndex;
- VertexIDToDstVertexIndex.Reserve(NumVertices);
- int32 VertexCount = 0;
- SimplygonSDK::spRealArray Positions = GeometryData->GetCoords();
- for (const FVertexID& VertexID : RawMesh.Vertices().GetElementIDs())
- {
- FVector TempPos = VertexPositions[VertexID];
- TempPos = GetConversionMatrix().TransformPosition(TempPos);
- Positions->SetTuple(VertexID.GetValue(), (float*)&TempPos);
- VertexIDToDstVertexIndex.Add(VertexID, VertexCount);
- VertexCount++;
- }
-
- //Prepare the tex coord
- TArray TexCoordsArray;
- for (int32 TexCoordIndex = 0; TexCoordIndex < VertexInstanceUVs.GetNumIndices(); ++TexCoordIndex)
- {
- GeometryData->AddTexCoords(TexCoordIndex);
- SimplygonSDK::spRealArray TexCoords = GeometryData->GetTexCoords(TexCoordIndex);
- check(TexCoords->GetTupleSize() == 2);
- TexCoordsArray.Add(TexCoords);
- }
-
- //Prepare the vertex color
- GeometryData->AddColors(0);
- SimplygonSDK::spRealArray LinearColors = GeometryData->GetColors(0);
- check(LinearColors);
- check(LinearColors->GetTupleSize() == 4);
-
- //Prepare the tangent space
- GeometryData->AddTangents(0);
- GeometryData->AddNormals();
- SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0);
- SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0);
- SimplygonSDK::spRealArray Normals = GeometryData->GetNormals();
-
- SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds();
-
- GeometryData->AddMaterialIds();
- SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds();
-
- uint32 DstVertexIndex = 0;
- uint32 DstTriangleIndex = 0;
- for (const FPolygonID& PolygonID : RawMesh.Polygons().GetElementIDs())
- {
- for (const FMeshTriangle& Triangle : RawMesh.GetPolygon(PolygonID).Triangles)
- {
- for (int32 Corner = 0; Corner < 3; ++Corner)
- {
- const FVertexInstanceID VertexInstanceID = Triangle.GetVertexInstanceID(Corner);
- //Add all the per indice data
- Indices->SetItem(DstVertexIndex, VertexIDToDstVertexIndex[RawMesh.GetVertexInstanceVertex(VertexInstanceID)]);
-
- //UVs
- for (int32 TexCoordIndex = 0; TexCoordIndex < VertexInstanceUVs.GetNumIndices(); ++TexCoordIndex)
- {
- FVector2D Vector2D = VertexInstanceUVs.Get(VertexInstanceID, TexCoordIndex);
- TexCoordsArray[TexCoordIndex]->SetTuple(DstVertexIndex, (float*)&Vector2D);
- }
-
- //Colors
- LinearColors->SetTuple(DstVertexIndex, (float*)&VertexInstanceColors[VertexInstanceID]);
-
- //Tangents
- FVector TempTangent = VertexInstanceTangents[VertexInstanceID];
- TempTangent = GetConversionMatrix().TransformVector(TempTangent);
- Tangents->SetTuple(DstVertexIndex, (float*)&TempTangent);
-
- FVector TempBitangent = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID];
- TempBitangent = GetConversionMatrix().TransformVector(TempBitangent);
- Bitangents->SetTuple(DstVertexIndex, (float*)&TempBitangent);
-
- FVector TempNormal = VertexInstanceNormals[VertexInstanceID];
- TempNormal = GetConversionMatrix().TransformVector(TempNormal);
- Normals->SetTuple(DstVertexIndex, (float*)&TempNormal);
-
- //Increment the indice index
- DstVertexIndex++;
- }
-
- // Per-triangle data.
-
- //Materials
- const FPolygonGroupID& PolygonGroupID = RawMesh.GetPolygonPolygonGroup(PolygonID);
- MaterialIndices->SetItem(DstTriangleIndex, PolygonGroupID.GetValue());
-
- //Smooth group
- GeometryData->AddGroupIds();
- SimplygonSDK::spRidArray GroupIds = GeometryData->GetGroupIds();
- GroupIds->SetItem(DstTriangleIndex, FaceSmoothingMasks[DstTriangleIndex]);
-
- DstTriangleIndex++;
- }
- }
-
- return GeometryData;
- }
-
- // This is a copy of CreateGeometryFromRawMesh with additional features for material LOD.
- SimplygonSDK::spGeometryData CreateGeometryFromRawMesh(const FMeshDescription& RawMesh, const TArray& TextureBounds, const TArray& InTexCoords)
- {
- TVertexAttributesConstRef VertexPositions = RawMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position);
- TEdgeAttributesConstRef EdgeHardnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard);
- TEdgeAttributesConstRef EdgeCreaseSharpnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::CreaseSharpness);
- TPolygonGroupAttributesConstRef PolygonGroupImportedMaterialSlotNames = RawMesh.PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
- TVertexInstanceAttributesConstRef VertexInstanceNormals = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal);
- TVertexInstanceAttributesConstRef VertexInstanceTangents = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent);
- TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign);
- TVertexInstanceAttributesConstRef VertexInstanceColors = RawMesh.VertexInstanceAttributes().GetAttributesRef