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(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; - 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) - { - //Materials - const FPolygonGroupID& PolygonGroupID = RawMesh.GetPolygonPolygonGroup(PolygonID); - MaterialIndices->SetItem(DstTriangleIndex, PolygonGroupID.GetValue()); - int32 MaterialIndex = PolygonGroupID.GetValue(); - - 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) - { - // Compute texture bounds for current material. - float MinU = 0, ScaleU = 1; - float MinV = 0, ScaleV = 1; - - if (TextureBounds.IsValidIndex(MaterialIndex) && TexCoordIndex == 0 && InTexCoords.Num() == 0) - { - const FBox2D& Bounds = TextureBounds[MaterialIndex]; - if (Bounds.GetArea() > 0) - { - MinU = Bounds.Min.X; - MinV = Bounds.Min.Y; - ScaleU = 1.0f / (Bounds.Max.X - Bounds.Min.X); - ScaleV = 1.0f / (Bounds.Max.Y - Bounds.Min.Y); - } - } - - const FVector2D& TexCoord = VertexInstanceUVs.Get(VertexInstanceID, TexCoordIndex); - float UV[2]; - UV[0] = (TexCoord.X - MinU) * ScaleU; - UV[1] = (TexCoord.Y - MinV) * ScaleV; - TexCoordsArray[TexCoordIndex]->SetTuple(DstVertexIndex, UV); - } - - //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. - - //Smooth group - GeometryData->AddGroupIds(); - SimplygonSDK::spRidArray GroupIds = GeometryData->GetGroupIds(); - GroupIds->SetItem(DstTriangleIndex, FaceSmoothingMasks[DstTriangleIndex]); - - DstTriangleIndex++; - } - } - return GeometryData; - } - - void CreateRawMeshFromGeometry(FMeshDescription& OutRawMesh, const SimplygonSDK::spGeometryData& GeometryData, EWindingMode WindingMode) - { - check(GeometryData); - - SimplygonSDK::spRealArray Positions = GeometryData->GetCoords(); - SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds(); - SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds(); - SimplygonSDK::spRidArray GroupIds = GeometryData->GetGroupIds(); - SimplygonSDK::spRealArray LinearColors = GeometryData->GetColors(0); - SimplygonSDK::spRealArray Normals = GeometryData->GetNormals(); - SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0); - SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0); - - check(Positions); - check(Indices); - - FMeshDescription& RawMesh = OutRawMesh; - - TVertexAttributesRef VertexPositions = RawMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - TEdgeAttributesRef EdgeHardnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); - TEdgeAttributesRef EdgeCreaseSharpnesses = RawMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::CreaseSharpness); - TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = RawMesh.PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - TVertexInstanceAttributesRef VertexInstanceNormals = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - TVertexInstanceAttributesRef VertexInstanceTangents = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesRef VertexInstanceBinormalSigns = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - TVertexInstanceAttributesRef VertexInstanceColors = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - TVertexInstanceAttributesRef VertexInstanceUVs = RawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - - const bool bReverseWinding = (WindingMode == WINDING_Reverse); - int32 NumTris = GeometryData->GetTriangleCount(); - int32 NumWedges = NumTris * 3; - int32 NumVertices = GeometryData->GetVertexCount(); - - RawMesh.Empty(); - RawMesh.ReserveNewVertices(NumVertices); - RawMesh.ReserveNewPolygons(NumTris); - RawMesh.ReserveNewVertexInstances(NumWedges); - RawMesh.ReserveNewEdges(NumWedges); - - TMap GeoToRawVertexID; - SimplygonSDK::spRealData sgTuple = SDK->CreateRealData(); - for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) - { - Positions->GetTuple(VertexIndex, sgTuple); - SimplygonSDK::real* vertexPos = sgTuple->GetData(); - const FVertexID& VertexID = RawMesh.CreateVertex(); - VertexPositions[VertexID] = GetConversionMatrix().TransformPosition(FVector(vertexPos[0], vertexPos[1], vertexPos[2])); - GeoToRawVertexID.Add(VertexIndex, VertexID); - } - - //Prepare the tex coord - int32 TexCoordNum = 0; - TArray TexCoordsArray; - for (int32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS_MD; ++TexCoordIndex, ++TexCoordNum) - { - SimplygonSDK::spRealArray TexCoords = GeometryData->GetTexCoords(TexCoordIndex); - if (TexCoords == nullptr) - { - break; - } - check(TexCoords->GetTupleSize() == 2); - TexCoordsArray.Add(TexCoords); - } - VertexInstanceUVs.SetNumIndices(FMath::Max(TexCoordNum, 1)); - - //Prepare the polygongroup - TMap GeoToRawMaterial; - for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex) - { - int32 MaterialIndex = MaterialIndices != nullptr ? MaterialIndices->GetItem(TriIndex) : 0; - if (!GeoToRawMaterial.Contains(MaterialIndex)) - { - const FPolygonGroupID PolygonGroupID(MaterialIndex); - RawMesh.CreatePolygonGroupWithID(PolygonGroupID); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString(TEXT("SimplygonMeshReduction_") + FString::FromInt(PolygonGroupID.GetValue()))); - GeoToRawMaterial.Add(MaterialIndex, PolygonGroupID); - } - } - - bool bHasZeroTangent = false; - bool bHasZeroNormal = false; - for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex) - { - FVertexID VertexIndexes[3]; - FVertexInstanceID VertexInstanceIDs[3]; - for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) - { - const uint32 SrcIndex = (TriIndex * 3) + (bReverseWinding ? (2 - CornerIndex) : CornerIndex); - const FVertexID VertexID = GeoToRawVertexID[Indices->GetItem(SrcIndex)]; - const FVertexInstanceID VertexInstanceID = RawMesh.CreateVertexInstance(VertexID); - VertexIndexes[CornerIndex] = VertexID; - VertexInstanceIDs[CornerIndex] = VertexInstanceID; - //Texture coordinnates - for (int32 TexCoordIndex = 0; TexCoordIndex < TexCoordNum; ++TexCoordIndex) - { - TexCoordsArray[TexCoordIndex]->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgTextCoors = sgTuple->GetData(); - VertexInstanceUVs.Set(VertexInstanceID, TexCoordIndex, FVector2D(sgTextCoors[0], sgTextCoors[1])); - } - - //Vertex Color - if (LinearColors) - { - LinearColors->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgVertexColor = sgTuple->GetData(); - VertexInstanceColors[VertexInstanceID] = FVector4(sgVertexColor[0], sgVertexColor[1], sgVertexColor[2], sgVertexColor[3]); - } - - //Vertex Tangents - FVector Tangent(0.0f); - FVector BiTangent(0.0f); - FVector Normal(0.0f); - - if (Normals) - { - Normals->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgNormal = sgTuple->GetData(); - Normal = GetConversionMatrix().TransformVector(FVector(sgNormal[0], sgNormal[1], sgNormal[2])); - } - bHasZeroNormal |= Normal.IsNearlyZero(); - - if (Tangents && Bitangents) - { - Tangents->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgTangents = sgTuple->GetData(); - Tangent = GetConversionMatrix().TransformVector(FVector(sgTangents[0], sgTangents[1], sgTangents[2])); - - Bitangents->GetTuple(SrcIndex, sgTuple); - SimplygonSDK::real* sgBiTangents = sgTuple->GetData(); - BiTangent = GetConversionMatrix().TransformVector(FVector(sgBiTangents[0], sgBiTangents[1], sgBiTangents[2])); - } - bHasZeroTangent |= Tangent.IsNearlyZero() || BiTangent.IsNearlyZero(); - - VertexInstanceTangents[VertexInstanceID] = Tangent; - VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign(Tangent.GetSafeNormal(), BiTangent.GetSafeNormal(), Normal.GetSafeNormal()); - VertexInstanceNormals[VertexInstanceID] = Normal; - } - //Create a polygon from this triangle - TArray Contours; - for (int32 Corner = 0; Corner < 3; ++Corner) - { - int32 ContourPointIndex = Contours.AddDefaulted(); - FMeshDescription::FContourPoint& ContourPoint = Contours[ContourPointIndex]; - //Find the matching edge ID - uint32 CornerIndices[2]; - CornerIndices[0] = (Corner + 0) % 3; - CornerIndices[1] = (Corner + 1) % 3; - - FVertexID EdgeVertexIDs[2]; - EdgeVertexIDs[0] = VertexIndexes[CornerIndices[0]]; - EdgeVertexIDs[1] = VertexIndexes[CornerIndices[1]]; - - FEdgeID MatchEdgeId = RawMesh.GetVertexPairEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]); - if (MatchEdgeId == FEdgeID::Invalid) - { - MatchEdgeId = RawMesh.CreateEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]); - EdgeHardnesses[MatchEdgeId] = false; - EdgeCreaseSharpnesses[MatchEdgeId] = 0.0f; - } - ContourPoint.EdgeID = MatchEdgeId; - ContourPoint.VertexInstanceID = VertexInstanceIDs[CornerIndices[0]]; - } - // Insert a polygon into the mesh - int32 MaterialIndex = MaterialIndices != nullptr ? MaterialIndices->GetItem(TriIndex) : 0; - const FPolygonID NewPolygonID = RawMesh.CreatePolygon(GeoToRawMaterial[MaterialIndex], Contours); - //Triangulate the polygon - FMeshPolygon& Polygon = RawMesh.GetPolygon(NewPolygonID); - RawMesh.ComputePolygonTriangulation(NewPolygonID, Polygon.Triangles); - } - - TArray FaceSmoothingMasks; - FaceSmoothingMasks.AddZeroed(NumTris); - if (GroupIds) - { - for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex) - { - FaceSmoothingMasks[TriIndex] = GroupIds->GetItem(TriIndex); - } - } - FMeshDescriptionOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, RawMesh); - - if (bHasZeroTangent | bHasZeroNormal) - { - FMeshDescriptionOperations::ETangentOptions TangentOption = (FMeshDescriptionOperations::ETangentOptions)(FMeshDescriptionOperations::ETangentOptions::BlendOverlappingNormals | FMeshDescriptionOperations::ETangentOptions::IgnoreDegenerateTriangles); - if (bHasZeroNormal) - { - FMeshDescriptionOperations::CreateNormals(RawMesh, TangentOption, false); - } - FMeshDescriptionOperations::CreateMikktTangents(RawMesh, TangentOption); - } - } - - void SetReductionSettings(SimplygonSDK::spReductionSettings ReductionSettings, const FMeshReductionSettings& Settings, int32 NumTris) - { - float MaxDeviation = Settings.MaxDeviation > 0.0f ? Settings.MaxDeviation : SimplygonSDK::REAL_MAX; - float MinReductionRatio = FMath::Max(1.0f / NumTris, 0.05f); - float MaxReductionRatio = (Settings.MaxDeviation > 0.0f && Settings.PercentTriangles == 1.0f) ? MinReductionRatio : 1.0f; - float ReductionRatio = FMath::Clamp(Settings.PercentTriangles, MinReductionRatio, MaxReductionRatio); - - const float ImportanceTable[] = - { - 0.0f, // OFF - 0.125f, // Lowest - 0.35f, // Low, - 1.0f, // Normal - 2.8f, // High - 8.0f, // Highest - }; - - static_assert(ARRAY_COUNT(ImportanceTable) == (EMeshFeatureImportance::Highest + 1), "Importance table size mismatch."); // -1 because of TEMP_BROKEN(?) - check(Settings.SilhouetteImportance < EMeshFeatureImportance::Highest+1); // -1 because of TEMP_BROKEN(?) - check(Settings.TextureImportance < EMeshFeatureImportance::Highest+1); // -1 because of TEMP_BROKEN(?) - check(Settings.ShadingImportance < EMeshFeatureImportance::Highest+1); // -1 because of TEMP_BROKEN(?) - check(Settings.VertexColorImportance < EMeshFeatureImportance::Highest + 1); // -1 because of TEMP_BROKEN(?) - - ReductionSettings->SetStopCondition(SimplygonSDK::SG_STOPCONDITION_ANY); - ReductionSettings->SetReductionTargets(SimplygonSDK::SG_REDUCTIONTARGET_TRIANGLERATIO | SimplygonSDK::SG_REDUCTIONTARGET_MAXDEVIATION); - ReductionSettings->SetMaxDeviation(MaxDeviation); - ReductionSettings->SetTriangleRatio(ReductionRatio); - ReductionSettings->SetGeometryImportance(ImportanceTable[Settings.SilhouetteImportance]); - ReductionSettings->SetTextureImportance(ImportanceTable[Settings.TextureImportance]); - ReductionSettings->SetMaterialImportance(ImportanceTable[Settings.TextureImportance]); - ReductionSettings->SetShadingImportance( ImportanceTable[Settings.ShadingImportance]); - ReductionSettings->SetVertexColorImportance(ImportanceTable[Settings.VertexColorImportance]); - ////ReductionSettings->SetAllowDirectX(true); - - //Automatic Symmetry Detection - ReductionSettings->SetKeepSymmetry(Settings.bKeepSymmetry); - ReductionSettings->SetUseAutomaticSymmetryDetection(Settings.bKeepSymmetry); - - //Set reposition vertices to be enabled by default - ReductionSettings->SetDataCreationPreferences(2); //2 = reposition vertices enabled - ReductionSettings->SetGenerateGeomorphData(true); - } - - void SetNormalSettings(SimplygonSDK::spNormalCalculationSettings NormalSettings, const FMeshReductionSettings& Settings) - { - NormalSettings->SetReplaceNormals(Settings.bRecalculateNormals); - NormalSettings->SetScaleByArea(false); - NormalSettings->SetScaleByAngle(false); - NormalSettings->SetHardEdgeAngleInRadians( FMath::DegreesToRadians(Settings.HardAngleThreshold) ); - } - - /** - * Creates a Simplygon scene representation of the skeletal hierarchy. - * @param InScene The Simplygon scene. - * @param InSkeleton The skeletal hierarchy from which to create the Simplygon representation. - * @param OutBoneTableIDs A mapping of Bone IDs from RefSkeleton to Simplygon Bone Table IDs - */ - void CreateSkeletalHierarchy( SimplygonSDK::spScene& InScene, const FReferenceSkeleton& InSkeleton, TArray& OutBoneTableIDs ) - { - TArray BoneArray; - - //Create Simplygon scene nodes for each bone in our Skeletal Hierarchy - for(int32 BoneIndex=0; BoneIndex < InSkeleton.GetRawBoneNum(); ++BoneIndex) - { - SimplygonSDK::spSceneBone SceneBone = SDK->CreateSceneBone(); - BoneArray.Add(SceneBone); - } - - SimplygonSDK::spSceneBoneTable BoneTable = InScene->GetBoneTable(); - for(int32 BoneIndex=0; BoneIndex < InSkeleton.GetRawBoneNum(); ++BoneIndex) - { - int32 ParentIndex = InSkeleton.GetParentIndex(BoneIndex); - SimplygonSDK::spSceneBone CurrentBone = BoneArray[BoneIndex]; - - /*if(InBoneTree[BoneIndex].bLockBone) - { - CurrentBone->SetLockFromBoneLOD(true); - } - - if(InBoneTree[BoneIndex].bRemoveBone) - { - CurrentBone->SetForceBoneRemoval(true); - }*/ - - - //We add the bone to the scene's bone table. This returns a uid that we must apply to the geometry data. - SimplygonSDK::rid BoneID = BoneTable->AddBone(CurrentBone); - OutBoneTableIDs.Add(BoneID); - - //Handle root bone. Add to Scene root. - if(BoneIndex == 0) - { - InScene->GetRootNode()->AddChild(CurrentBone); - } - else - { - SimplygonSDK::spSceneBone ParentBone = BoneArray[ParentIndex]; - ParentBone->AddChild(CurrentBone); - } - } - } - - /** - * Creates a Simplygon geometry representation from a skeletal mesh LOD model. - * @param Scene The Simplygon scene. - * @param LODModel The skeletal mesh LOD model from which to create the geometry data. - * @param BoneIDs A maps of Bone IDs from RefSkeleton to Simplygon BoneTable IDs - * @returns a Simplygon geometry data representation of the skeletal mesh LOD. - */ - SimplygonSDK::spGeometryData CreateGeometryFromSkeletalLODModel( SimplygonSDK::spScene& Scene, const FSkeletalMeshLODModel& LODModel, const TArray& BoneIDs, const TArray& BoneMatrices, const int32 LODIndex) - { - TArray Vertices; - LODModel.GetVertices( Vertices ); - const uint32 SectionCount = (uint32)LODModel.Sections.Num(); - auto SkipSection = [&LODModel, LODIndex](int32 SectionIndex) - { - if (LODModel.Sections[SectionIndex].bDisabled) - { - return true; - } - int32 MaxLODIndex = LODModel.Sections[SectionIndex].GenerateUpToLodIndex; - return (MaxLODIndex != -1 && MaxLODIndex < LODIndex); - }; - - uint32 VertexCount = 0; - uint32 TriCount = 0; - for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex) - { - const FSkelMeshSection& Section = LODModel.Sections[SectionIndex]; - - VertexCount += Section.SoftVertices.Num(); - //Skipped section - if (!SkipSection(SectionIndex)) - { - TriCount += LODModel.Sections[SectionIndex].NumTriangles; - } - } - const uint32 TexCoordCount = LODModel.NumTexCoords; - check( TexCoordCount <= MAX_TEXCOORDS ); - - SimplygonSDK::spGeometryData GeometryData = SDK->CreateGeometryData(); - GeometryData->SetVertexCount( VertexCount ); - GeometryData->SetTriangleCount( TriCount ); - - // Per-vertex data. - GeometryData->AddBoneIds( MAX_TOTAL_INFLUENCES ); - GeometryData->AddBoneWeights( MAX_TOTAL_INFLUENCES ); - SimplygonSDK::spRealArray Positions = GeometryData->GetCoords(); - SimplygonSDK::spRidArray BoneIds = GeometryData->GetBoneIds(); - SimplygonSDK::spRealArray BoneWeights = GeometryData->GetBoneWeights(); - - // Per-corner per-triangle data. - SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds(); - SimplygonSDK::spRealArray TexCoords[MAX_TEXCOORDS]; - for( uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex ) - { - GeometryData->AddTexCoords( TexCoordIndex ); - TexCoords[TexCoordIndex] = GeometryData->GetTexCoords( TexCoordIndex ); - check( TexCoords[TexCoordIndex]->GetTupleSize() == 2 ); - } - GeometryData->AddNormals(); - GeometryData->AddTangents(0); - GeometryData->AddBitangents(0); - SimplygonSDK::spRealArray Normals = GeometryData->GetNormals(); - SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0); - SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0); - - GeometryData->AddBaseTypeUserTriangleVertexField( SimplygonSDK::TYPES_ID_UCHAR, SIMPLYGON_COLOR_CHANNEL, 4 ); - SimplygonSDK::spValueArray ColorValues = GeometryData->GetUserTriangleVertexField( SIMPLYGON_COLOR_CHANNEL ); - check( ColorValues ); - SimplygonSDK::spUnsignedCharArray Colors = SimplygonSDK::IUnsignedCharArray::SafeCast( ColorValues ); - check( Colors ); - - // Per-triangle data. - GeometryData->AddMaterialIds(); - SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds(); - - // Add per-vertex data. This data needs to be added per-section so that we can properly map bones. - uint32 FirstVertex = 0; - for ( uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex ) - { - const FSkelMeshSection& Section = LODModel.Sections[SectionIndex]; - const uint32 LastVertex = FirstVertex + (uint32)Section.SoftVertices.Num(); - for ( uint32 VertexIndex = FirstVertex; VertexIndex < LastVertex; ++VertexIndex ) - { - FSoftSkinVertex& Vertex = Vertices[ VertexIndex ]; - SimplygonSDK::rid VertexBoneIds[MAX_TOTAL_INFLUENCES]; - SimplygonSDK::real VertexBoneWeights[MAX_TOTAL_INFLUENCES]; - // get blended matrix - FMatrix BlendedMatrix = FMatrix::Identity; - uint32 TotalInfluence = 0; - for ( uint32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex ) - { - int32 BoneIndex = Vertex.InfluenceBones[InfluenceIndex]; - const uint8 BoneInfluence = Vertex.InfluenceWeights[InfluenceIndex]; - TotalInfluence += BoneInfluence; - - if ( BoneInfluence > 0 ) - { - check( BoneIndex < Section.BoneMap.Num() ); - uint32 BoneID = BoneIDs[Section.BoneMap[ BoneIndex ] ]; - VertexBoneIds[InfluenceIndex] = BoneID; - VertexBoneWeights[InfluenceIndex] = BoneInfluence / 255.0f; - - if (BoneMatrices.IsValidIndex(Section.BoneMap[BoneIndex])) - { - const FMatrix Matrix = BoneMatrices[Section.BoneMap[BoneIndex]]; - // calculate blended matrix - if (InfluenceIndex == 0) - { - BlendedMatrix = (Matrix * VertexBoneWeights[InfluenceIndex]); - } - else - { - BlendedMatrix += (Matrix * VertexBoneWeights[InfluenceIndex]); - } - } - } - else - { - //Set BoneID and BoneWeight to -1 and 0 respectively as required by simplygon. - VertexBoneIds[InfluenceIndex] = -1; - VertexBoneWeights[InfluenceIndex] = 0; - } - } - - // transform position - FVector WeightedVertex = BlendedMatrix.TransformPosition(Vertex.Position); - FVector WeightedTangentX = BlendedMatrix.TransformVector(Vertex.TangentX); - FVector WeightedTangentY = BlendedMatrix.TransformVector(Vertex.TangentY); - FVector WeightedTangentZ = BlendedMatrix.TransformVector(Vertex.TangentZ); - - check( TotalInfluence == 255 ); - Vertex.TangentX = WeightedTangentX.GetSafeNormal(); - Vertex.TangentY = WeightedTangentY.GetSafeNormal(); - uint8 WComponent = Vertex.TangentZ.W; - Vertex.TangentZ = WeightedTangentZ.GetSafeNormal(); - Vertex.TangentZ.W = WComponent; - FVector FinalVert = GetConversionMatrix().TransformPosition(WeightedVertex); - - //Vertex.Position.Z = -Vertex.Position.Z; - Positions->SetTuple( VertexIndex, (float*)&FinalVert); - BoneIds->SetTuple( VertexIndex, VertexBoneIds ); - BoneWeights->SetTuple( VertexIndex, VertexBoneWeights ); - } - FirstVertex = LastVertex; - } - - // Add per-vertex per-triangle data. - uint32 IndexCount = 0; - for ( uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex ) - { - //Skipped section - if (SkipSection(SectionIndex)) - { - continue; - } - - const FSkelMeshSection& Section = LODModel.Sections[ SectionIndex ]; - const uint32 FirstIndex = Section.BaseIndex; - const uint32 LastIndex = FirstIndex + Section.NumTriangles * 3; - - for ( uint32 Index = FirstIndex; Index < LastIndex; ++Index ) - { - uint32 VertexIndex = LODModel.IndexBuffer[ Index ]; - FSoftSkinVertex& Vertex = Vertices[ VertexIndex ]; - - FVector Normal = Vertex.TangentZ; - Normal = GetConversionMatrix().TransformPosition(Normal); - - FVector Tangent = Vertex.TangentX; - Tangent = GetConversionMatrix().TransformVector(Tangent); - - FVector Bitangent = Vertex.TangentY; - Bitangent = GetConversionMatrix().TransformVector(Bitangent); - - - Indices->SetItem(IndexCount, VertexIndex ); - Normals->SetTuple(IndexCount, (float*)&Normal ); - Tangents->SetTuple(IndexCount, (float*)&Tangent ); - Bitangents->SetTuple(IndexCount, (float*)&Bitangent ); - - for( uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex ) - { - FVector2D TexCoord = Vertex.UVs[TexCoordIndex]; - TexCoord.X = FMath::Clamp(TexCoord.X, -1024.0f, 1024.0f); - TexCoord.Y = FMath::Clamp(TexCoord.Y, -1024.0f, 1024.0f); - TexCoords[TexCoordIndex]->SetTuple(IndexCount, (float*)&TexCoord ); - } - - Colors->SetTuple(IndexCount, (UCHAR*)&Vertex.Color ); - IndexCount++; - } - } - - // Add per-triangle data. - uint32 TriangleCount = 0; - for ( uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex ) - { - //Skipped section - if (SkipSection(SectionIndex)) - { - continue; - } - const FSkelMeshSection& Section = LODModel.Sections[SectionIndex]; - const uint32 FirstTriIndex = Section.BaseIndex / 3; - const uint32 LastTriIndex = FirstTriIndex + Section.NumTriangles; - const uint16 MaterialIndex = Section.MaterialIndex; - for ( uint32 TriIndex = FirstTriIndex; TriIndex < LastTriIndex; ++TriIndex ) - { - MaterialIndices->SetItem(TriangleCount, MaterialIndex ); - check( MaterialIndices->GetItem(TriangleCount) == MaterialIndex ); - TriangleCount++; - } - } - - // Create a new simplygon scene mesh object - SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh(); - - // Assign the geometry data to the mesh - Mesh->SetGeometry( GeometryData ); - - // Add Mesh to the scene - Scene->GetRootNode()->AddChild( Mesh ); - - return GeometryData; - } - - /** - * Holds data needed to create skeletal mesh skinning streams. - */ - struct FSkeletalMeshData - { - TArray Influences; - TArray Wedges; - TArray Faces; - TArray Points; - uint32 TexCoordCount; - }; - - /** - * Extracts mesh data from the Simplygon geometry representation in to a set of data usable by skeletal meshes. - * @param GeometryData the Simplygon geometry. - * @param MeshData the skeletal mesh output data. - */ - void ExtractSkeletalDataFromGeometry( const SimplygonSDK::spGeometryData& GeometryData, FSkeletalMeshData& MeshData ) - { - TArray PointNormals; - TArray PointList; - TArray PointInfluenceMap; - - check( GeometryData ); - - SimplygonSDK::spRealArray Positions = GeometryData->GetCoords(); - SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds(); - SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds(); - SimplygonSDK::spValueArray VertexColorValues = GeometryData->GetUserTriangleVertexField( SIMPLYGON_COLOR_CHANNEL ); - SimplygonSDK::spUnsignedCharArray VertexColors = SimplygonSDK::IUnsignedCharArray::SafeCast( VertexColorValues ); - SimplygonSDK::spRealArray Normals = GeometryData->GetNormals(); - SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0); - SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0); - SimplygonSDK::spRidArray BoneIds = GeometryData->GetBoneIds(); - SimplygonSDK::spRealArray BoneWeights = GeometryData->GetBoneWeights(); - SimplygonSDK::spRealArray TexCoords[MAX_TEXCOORDS]; - uint32 TexCoordCount = 0; - for( uint32 TexCoordIndex = 0; TexCoordIndex < MAX_TEXCOORDS; ++TexCoordIndex ) - { - TexCoords[TexCoordIndex] = GeometryData->GetTexCoords(TexCoordIndex); - if ( TexCoords[TexCoordIndex] == NULL ) - { - break; - } - TexCoordCount++; - } - MeshData.TexCoordCount = TexCoordCount; - - check( Positions ); - check( Indices ); - -// check( MaterialIndices ); - - check( Normals ); - check( Tangents ); - check( Bitangents ); - check( BoneIds ); - check( BoneWeights ); - - const bool bHaveColors = (VertexColors != NULL); - const uint32 VertexCount = GeometryData->GetVertexCount(); - const uint32 TriCount = GeometryData->GetTriangleCount(); - - // Initialize the lists of points, wedges, and faces. - MeshData.Points.AddZeroed( VertexCount ); - PointNormals.AddZeroed( VertexCount ); - PointList.Reserve( VertexCount ); - PointInfluenceMap.Reserve( VertexCount ); - for ( uint32 PointIndex = 0; PointIndex < VertexCount; ++PointIndex ) - { - PointList.Add( INDEX_NONE ); - PointInfluenceMap.Add( INDEX_NONE ); - } - MeshData.Wedges.AddZeroed( TriCount * 3 ); - MeshData.Faces.AddZeroed( TriCount ); - - //The number of influences may have changed if we have specified a max number of bones per vertex. - uint32 NumOfInfluences = FMath::Min( BoneIds->GetTupleSize() , MAX_TOTAL_INFLUENCES ); - - SimplygonSDK::spRealData sgPositionData = SDK->CreateRealData(); - SimplygonSDK::spRidData sgBoneIdsData = SDK->CreateRidData(); - SimplygonSDK::spRealData sgBoneWeightsData = SDK->CreateRealData(); - SimplygonSDK::spRealData sgTangentData = SDK->CreateRealData(); - SimplygonSDK::spRealData sgTexCoordData = SDK->CreateRealData(); - SimplygonSDK::spUnsignedCharData sgVertexColorData = SDK->CreateUnsignedCharData(); - - // Per-vertex data. - for ( uint32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex ) - { - FVector& Point = MeshData.Points[ VertexIndex ]; - //SimplygonSDK::rid VertexBoneIds[MAX_TOTAL_INFLUENCES]; - //SimplygonSDK::real VertexBoneWeights[MAX_TOTAL_INFLUENCES]; - - //Positions->GetTuple( VertexIndex, (float*)&Point ); - Positions->GetTuple( VertexIndex, sgPositionData ); - SimplygonSDK::real* sgPosition = sgPositionData->GetData(); - Point = FVector(sgPosition[0], sgPosition[1], sgPosition[2]); - - - //BoneIds->GetTuple( VertexIndex, VertexBoneIds ); - BoneIds->GetTuple(VertexIndex, sgBoneIdsData); - SimplygonSDK::rid* sgBoneId = sgBoneIdsData->GetData(); - - - //BoneWeights->GetTuple( VertexIndex, VertexBoneWeights ); - BoneWeights->GetTuple(VertexIndex, sgBoneWeightsData); - SimplygonSDK::real* sgBoneWeights = sgBoneWeightsData->GetData(); - - PointInfluenceMap[ VertexIndex ] = (uint32)MeshData.Influences.Num(); - for ( uint32 InfluenceIndex = 0; InfluenceIndex < NumOfInfluences; ++InfluenceIndex ) - { - const uint16 BoneIndex = sgBoneId[InfluenceIndex]; - const float BoneWeight = sgBoneWeights[InfluenceIndex]; - if ( InfluenceIndex == 0 || BoneWeight > 0.0f ) - { - SkeletalMeshImportData::FVertInfluence* VertInfluence = new(MeshData.Influences) SkeletalMeshImportData::FVertInfluence; - VertInfluence->BoneIndex = BoneIndex; - VertInfluence->Weight = BoneWeight; - VertInfluence->VertIndex = VertexIndex; - } - } - } - - // Per-triangle and per-corner data. - for ( uint32 TriIndex = 0; TriIndex < TriCount; ++TriIndex ) - { - // Per-triangle data. - SkeletalMeshImportData::FMeshFace& Face = MeshData.Faces[ TriIndex ]; - - Face.MeshMaterialIndex = MaterialIndices ? MaterialIndices->GetItem( TriIndex ) : 0; - - - // Per-corner data. - for( uint32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex ) - { - const uint32 WedgeIndex = TriIndex * 3 + CornerIndex; - const uint32 BasePointIndex = (uint32)Indices->GetItem( WedgeIndex ); - uint32 PointIndex = BasePointIndex; - - check( BasePointIndex < (uint32)PointList.Num() ); - - // Duplicate points where needed to create hard edges. - FVector WedgeNormal; - //Normals->GetTuple( WedgeIndex, (float*)&WedgeNormal ); - Normals->GetTuple(WedgeIndex, sgTangentData); - SimplygonSDK::real* sgNormal = sgTangentData->GetData(); - WedgeNormal = FVector(sgNormal[0], sgNormal[1], sgNormal[2]); - WedgeNormal.Normalize(); - - FVector WedgeTangent, WedgeBitangent; - //Tangents->GetTuple( WedgeIndex, (float*)&WedgeTangent ); - Tangents->GetTuple(WedgeIndex, sgTangentData); - SimplygonSDK::real* sgTangent = sgTangentData->GetData(); - WedgeTangent = FVector(sgTangent[0], sgTangent[1], sgTangent[2]); - - //Bitangents->GetTuple( WedgeIndex, (float*)&WedgeBitangent ); - Bitangents->GetTuple(WedgeIndex, sgTangentData); - SimplygonSDK::real* sgBitangent = sgTangentData->GetData(); - WedgeBitangent = FVector(sgBitangent[0], sgBitangent[1], sgBitangent[2]); - - Face.TangentX[CornerIndex] = WedgeTangent; - Face.TangentY[CornerIndex] = WedgeBitangent; - Face.TangentZ[CornerIndex] = WedgeNormal; - - - FVector PointNormal = PointNormals[ PointIndex ]; - if ( PointNormal.SizeSquared() < KINDA_SMALL_NUMBER ) - { - PointNormals[ PointIndex ] = WedgeNormal; - } - else - { - while ( (PointNormal | WedgeNormal) - 1.0f < -KINDA_SMALL_NUMBER ) - { - PointIndex = PointList[ PointIndex ]; - if ( PointIndex == INDEX_NONE ) - { - break; - } - check( PointIndex < (uint32)PointList.Num() ); - PointNormal = PointNormals[ PointIndex ]; - } - - if ( PointIndex == INDEX_NONE ) - { - FVector Point = MeshData.Points[ BasePointIndex ]; - PointIndex = MeshData.Points.Add( Point ); - check( PointIndex == (uint32)PointList.Num() ); - check( PointIndex == (uint32)PointInfluenceMap.Num() ); - check( PointIndex == (uint32)PointNormals.Num() ); - PointNormals.Add( WedgeNormal ); - uint32 NextPointIndex = PointList[ BasePointIndex ]; - check( NextPointIndex < (uint32)PointList.Num() || NextPointIndex == INDEX_NONE ); - PointList[ BasePointIndex ] = PointIndex; - PointList.Add( NextPointIndex ); - PointInfluenceMap.Add( (uint32)MeshData.Influences.Num() ); - - int32 InfluenceIndex = PointInfluenceMap[ BasePointIndex ]; - while ( MeshData.Influences[ InfluenceIndex ].VertIndex == BasePointIndex ) - { - SkeletalMeshImportData::FVertInfluence* NewVertInfluence = new( MeshData.Influences ) SkeletalMeshImportData::FVertInfluence; - NewVertInfluence->BoneIndex = MeshData.Influences[ InfluenceIndex ].BoneIndex; - NewVertInfluence->Weight = MeshData.Influences[ InfluenceIndex ].Weight; - NewVertInfluence->VertIndex = PointIndex; - InfluenceIndex++; - } - - check( PointNormals.Num() == MeshData.Points.Num() ); - check( PointList.Num() == MeshData.Points.Num() ); - check( PointInfluenceMap.Num() == MeshData.Points.Num() ); - } - } - - check( PointIndex != INDEX_NONE ); - check( ( MeshData.Points[ PointIndex ] - MeshData.Points[ BasePointIndex ] ).SizeSquared() == 0.0f ); - - SkeletalMeshImportData::FMeshWedge& Wedge = MeshData.Wedges[ WedgeIndex ]; - Wedge.iVertex = PointIndex; - for( uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex ) - { - //TexCoords[TexCoordIndex]->GetTuple( WedgeIndex, (float*)&Wedge.UVs[TexCoordIndex] ); - TexCoords[TexCoordIndex]->GetTuple(WedgeIndex, sgTexCoordData); - SimplygonSDK::real* sgTexCoord = sgTexCoordData->GetData(); - Wedge.UVs[TexCoordIndex] = FVector2D(sgTexCoordData[0], sgTexCoordData[1]); - } - - if( bHaveColors ) - { - //VertexColors->GetTuple( WedgeIndex, (uint8 *)&Wedge.Color ); - VertexColors->GetTuple(WedgeIndex, sgVertexColorData); - uint8* sgColors = sgVertexColorData->GetData(); - Wedge.Color = FColor(sgColors[2], sgColors[1], sgColors[0], sgColors[3]); - } - else - { - Wedge.Color = FColor( 255, 255, 255, 255 ); - } - - Face.iWedge[CornerIndex] = WedgeIndex; - } - } - } - - /** - * Creates a skeletal mesh LOD model from the Simplygon geometry representation. - * @param GeometryData the Simplygon geometry representation from which to create a skeletal mesh LOD. - * @param SkeletalMesh the skeletal mesh in to which the LOD model will be added. - * @param NewModel the LOD model in to which the geometry will be stored. - */ - void CreateSkeletalLODModelFromGeometry( const SimplygonSDK::spGeometryData& GeometryData, const FReferenceSkeleton& RefSkeleton, FSkeletalMeshLODModel* NewModel ) - { - check( GeometryData ); - - FSkeletalMeshData MeshData; - ExtractSkeletalDataFromGeometry( GeometryData, MeshData ); - - - TArray& Vertices = MeshData.Points; - for (int32 VertexIndex = 0; VertexIndex < MeshData.Points.Num(); ++VertexIndex) - { - Vertices[VertexIndex] = GetConversionMatrix().InverseTransformPosition(Vertices[VertexIndex]); - } - - for (int32 FaceIndex = 0; FaceIndex < MeshData.Faces.Num(); ++FaceIndex) - { - SkeletalMeshImportData::FMeshFace& Face = MeshData.Faces[FaceIndex]; - for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) - { - Face.TangentX[CornerIndex] = GetConversionMatrix().InverseTransformVector(Face.TangentX[CornerIndex]); - Face.TangentY[CornerIndex] = GetConversionMatrix().InverseTransformVector(Face.TangentY[CornerIndex]); - Face.TangentZ[CornerIndex] = GetConversionMatrix().InverseTransformVector(Face.TangentZ[CornerIndex]); - } - } - - // Create dummy map of 'point to original' - TArray DummyMap; - DummyMap.AddUninitialized(MeshData.Points.Num()); - for(int32 PointIdx = 0; PointIdx("MeshUtilities"); - // Create skinning streams for NewModel. - MeshUtilities.BuildSkeletalMesh( - *NewModel, - RefSkeleton, - MeshData.Influences, - MeshData.Wedges, - MeshData.Faces, - MeshData.Points, - DummyMap, - Options - ); - - // Set texture coordinate count on the new model. - NewModel->NumTexCoords = MeshData.TexCoordCount; - } - - /** - * Sets reduction settings for Simplygon. - * @param Settings The skeletal mesh optimization settings. - * @param ReductionSettings The reduction settings to set for Simplygon. - */ - void SetReductionSettings( const FSkeletalMeshOptimizationSettings& Settings, float BoundsRadius, int32 SourceTriCount, SimplygonSDK::spReductionSettings ReductionSettings ) - { - // Compute max deviation from quality. - float MaxDeviation = (Settings.ReductionMethod != SMOT_NumOfTriangles)? Settings.MaxDeviationPercentage * BoundsRadius : SimplygonSDK::REAL_MAX; - - // Set the reduction ratio such that at least 1 triangle or 1% of the original triangles remain, whichever is larger. - float MinReductionRatio = FMath::Max(1.0f / SourceTriCount, 0.01f); - float ReductionRatio = MinReductionRatio; - - // if reduction is not deviate, we set the percentage - if (Settings.ReductionMethod != SMOT_MaxDeviation) - { - ReductionRatio = FMath::Clamp(Settings.NumOfTrianglesPercentage, MinReductionRatio, 1.f); - } - - unsigned int ReductionTarget = 0; - unsigned int StopCondition = SimplygonSDK::SG_STOPCONDITION_ANY; - switch (Settings.ReductionMethod) - { - case SMOT_MaxDeviation: - // we still give triangle ratio because otherwise you won't get any triangle back and crash render resource - ReductionTarget = SimplygonSDK::SG_REDUCTIONTARGET_TRIANGLERATIO | SimplygonSDK::SG_REDUCTIONTARGET_MAXDEVIATION; - break; - case SMOT_NumOfTriangles: - ReductionTarget = SimplygonSDK::SG_REDUCTIONTARGET_TRIANGLERATIO; - break; - case SMOT_TriangleOrDeviation: - default: - ReductionTarget = SimplygonSDK::SG_REDUCTIONTARGET_TRIANGLERATIO | SimplygonSDK::SG_REDUCTIONTARGET_MAXDEVIATION; - break; - } - - const float ImportanceTable[] = - { - 0.0f, // OFF - 0.125f, // Lowest - 0.35f, // Low, - 1.0f, // Normal - 2.8f, // High - 8.0f, // Highest - }; - - static_assert(ARRAY_COUNT(ImportanceTable) == SMOI_MAX, "Bad importance table size."); - check( Settings.SilhouetteImportance < SMOI_MAX ); - check( Settings.TextureImportance < SMOI_MAX ); - check( Settings.ShadingImportance < SMOI_MAX ); - check( Settings.SkinningImportance < SMOI_MAX ); - - ReductionSettings->SetStopCondition(StopCondition); - ReductionSettings->SetReductionTargets(ReductionTarget); - ReductionSettings->SetMaxDeviation( MaxDeviation ); - ReductionSettings->SetTriangleRatio( ReductionRatio ); - ReductionSettings->SetGeometryImportance( ImportanceTable[Settings.SilhouetteImportance] ); - ReductionSettings->SetTextureImportance( ImportanceTable[Settings.TextureImportance] ); - ReductionSettings->SetMaterialImportance( ImportanceTable[Settings.TextureImportance] ); - ReductionSettings->SetShadingImportance( ImportanceTable[Settings.ShadingImportance] ); - ReductionSettings->SetSkinningImportance( ImportanceTable[Settings.SkinningImportance] ); - - ReductionSettings->SetDataCreationPreferences(2); //2 = reposition vertices enabled - ReductionSettings->SetGenerateGeomorphData(true); - } - - /** - * Sets vertex normal settings for Simplygon. - * @param Settings The skeletal mesh optimization settings. - * @param NormalSettings The normal settings to set for Simplygon. - */ - void SetNormalSettings( const FSkeletalMeshOptimizationSettings& Settings, SimplygonSDK::spNormalCalculationSettings NormalSettings ) - { - NormalSettings->SetReplaceNormals( Settings.bRecalcNormals ); - NormalSettings->SetScaleByArea( false ); - NormalSettings->SetScaleByAngle( false ); - NormalSettings->SetHardEdgeAngleInRadians(FMath::DegreesToRadians(Settings.NormalsThreshold)); - } - - /** - * Sets Bone Lod settings for Simplygon. - * @param Settings The skeletal mesh optimization settings. - * @param NormalSettings The Bone LOD to set for Simplygon. - */ - void SetBoneSettings( const FSkeletalMeshOptimizationSettings& Settings, SimplygonSDK::spBoneSettings BoneSettings) - { - BoneSettings->SetBoneReductionTargets( SimplygonSDK::SG_BONEREDUCTIONTARGET_BONERATIO ); - BoneSettings->SetBoneRatio ( 1.f ); - BoneSettings->SetMaxBonePerVertex( Settings.MaxBonesPerVertex ); - BoneSettings->SetLimitBonesPerVertex(true); - BoneSettings->SetUseBoneReducer(true); - } - - /** - * ProxyLOD Related Methods - */ - void SetMaterialChannelData( - const TArray& InSamples, - FIntPoint InTextureSize, - SimplygonSDK::spMaterial& InSGMaterial, - //@third party BEGIN SIMPLYGON - SimplygonSDK::spTextureTable& TextureTable, - //@third party END SIMPLYGON - - const char* SGMaterialChannelName) - { - if (InSamples.Num() > 1) - { - int32 NumTexels = InTextureSize.X * InTextureSize.Y; - SimplygonSDK::spImageData ImageData = SDK->CreateImageData(); - ImageData->AddColors(SimplygonSDK::TYPES_ID_UCHAR, SimplygonSDK::SG_IMAGEDATA_FORMAT_RGBA); - ImageData->Set2DSize(InTextureSize.X, InTextureSize.Y); - SimplygonSDK::spUnsignedCharArray ImageColors = SimplygonSDK::SafeCast(ImageData->GetColors()); - - //Set the texture data to simplygon color data texel per texel - ImageColors->SetTupleCount(NumTexels); - for (int32 TexelIndex = 0; TexelIndex < NumTexels; TexelIndex++) - { - // BGRA -> RGBA - uint8 Texel[4]; - Texel[0] = InSamples[TexelIndex].R; - Texel[1] = InSamples[TexelIndex].G; - Texel[2] = InSamples[TexelIndex].B; - - if (SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE || - SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR) - { - Texel[3] = InSamples[TexelIndex].A; - } - else - { - Texel[3] = FColor::White.A; - } - - ImageColors->SetTuple(TexelIndex, Texel); - } - - //@third party BEGIN SIMPLYGON - // Need to add a texture to the texture table in order to be able - // to reference it in the shading network. - FGuid TextureGuid = FGuid::NewGuid(); - SimplygonSDK::spTexture Texture = SDK->CreateTexture(); - Texture->SetImageData(ImageData); - Texture->SetName(TCHAR_TO_ANSI(*TextureGuid.ToString())); - TextureTable->AddTexture(Texture); - - //Create texture node - SimplygonSDK::spShadingTextureNode TextureNode = SDK->CreateShadingTextureNode(); - TextureNode->SetTextureLevel(0); // UV Coordinate to use - TextureNode->SetTextureName(TCHAR_TO_ANSI(*TextureGuid.ToString())); - TextureNode->SetUseSRGB(false); - //hook node into desired channel. - InSGMaterial->SetShadingNetwork(SGMaterialChannelName, TextureNode); - //@third party END SIMPLYGON - - - - } - else if (InSamples.Num() == 1) - { - //@third party BEGIN SIMPLYGON - // Setup a color node - SimplygonSDK::spShadingColorNode ColorNode = SDK->CreateShadingColorNode(); - ColorNode->SetColor(InSamples[0].R / 255.0f, InSamples[0].G / 255.0f, InSamples[0].B / 255.0f, 1.0f); - InSGMaterial->SetShadingNetwork(SGMaterialChannelName, ColorNode); - //@third party END SIMPLYGON - - } - else - { - //@third party BEGIN SIMPLYGON - SimplygonSDK::spShadingColorNode ColorNode = SDK->CreateShadingColorNode(); - ColorNode->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - InSGMaterial->SetShadingNetwork(SGMaterialChannelName, ColorNode); - //@third party END SIMPLYGON - - } - } - - //Material conversions - //@third party BEGIN SIMPLYGON - void GetMaterialChannelData(const SimplygonSDK::spMaterial& InSGMaterial, const SimplygonSDK::spTextureTable& TextureTable, const char* SGMaterialChannelName, TArray& OutSamples, FIntPoint& OutTextureSize) - //@third party END SIMPLYGON - - { - //@third party BEGIN SIMPLYGON - SimplygonSDK::spShadingNode ExitNode = InSGMaterial->GetShadingNetwork(SGMaterialChannelName); - if (ExitNode == nullptr) - { - //Current channel has not been baked - return; - } - - SimplygonSDK::spShadingTextureNode TextureNode = SimplygonSDK::Cast(ExitNode); - SimplygonSDK::rid TextureId = TextureTable->FindTextureId(TextureNode->GetTextureName()); - - if (TextureId < 0) - { - UE_LOG(LogSimplygon, Warning, TEXT("Could not retrieve texture for texture table for channel %s"), ANSI_TO_TCHAR(SGMaterialChannelName)); - return; - } - - SimplygonSDK::spTexture Texture = TextureTable->GetTexture(TextureId); - SimplygonSDK::spImageData SGChannelData = Texture->GetImageData(); - //@third party END SIMPLYGON - - - if (SGChannelData) - { - SimplygonSDK::spUnsignedCharArray ImageColors = SimplygonSDK::SafeCast(SGChannelData->GetColors()); - - OutTextureSize.X = SGChannelData->GetXSize(); - OutTextureSize.Y = SGChannelData->GetYSize(); - - int32 TexelsCount = OutTextureSize.X*OutTextureSize.Y; - OutSamples.Empty(TexelsCount); - OutSamples.AddUninitialized(TexelsCount); - - SimplygonSDK::spUnsignedCharData sgImageCharData = SDK->CreateUnsignedCharData(); - for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex) - { - //uint8 ColorData[4]; - //ImageColors->GetTuple(TexelIndex, (unsigned char*)&ColorData); - - ImageColors->GetTuple(TexelIndex, sgImageCharData); - uint8* ColorData = sgImageCharData->GetData(); - - OutSamples[TexelIndex].R = ColorData[0]; - OutSamples[TexelIndex].G = ColorData[1]; - OutSamples[TexelIndex].B = ColorData[2]; - if (SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE || - SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR) - { - OutSamples[TexelIndex].A = ColorData[3]; - } - else - { - OutSamples[TexelIndex].A = FColor::White.A; - } - } - } - } - - void CreateFlattenMaterialFromSGMaterial( - SimplygonSDK::spMaterialTable& InSGMaterialTable, - //@third party BEGIN SIMPLYGON - SimplygonSDK::spTextureTable& TextureTable, - //@third party END SIMPLYGON - - FFlattenMaterial& OutMaterial) - { - FIntPoint Size; - SimplygonSDK::spMaterial SGMaterial = InSGMaterialTable->GetMaterial(0); - - // Diffuse - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, Size); - - // Normal - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Normal, Size); - - // Metallic - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_METALLIC, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Metallic, Size); - - // Roughness - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_ROUGHNESS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Roughness, Size); - - // Specular - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_SPECULAR, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Specular, Size); - } - - void CreateFlattenMaterialFromSGMaterial( - SimplygonSDK::spMaterial& SGMaterial, - //@third party BEGIN SIMPLYGON - SimplygonSDK::spTextureTable& TextureTable, - //@third party END SIMPLYGON - - FFlattenMaterial& OutMaterial) - { - FIntPoint Size; - - // Diffuse - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, Size); - - // Normal - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Normal, Size); - - // Metallic - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_METALNESS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Metallic, Size); - - // Roughness - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_ROUGHNESS, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Roughness, Size); - - // Specular - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_SPECULAR, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Specular, Size); - - // Opacity - Size = FIntPoint::ZeroValue; -#if USE_USER_OPACITY_CHANNEL - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_OPACITY, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), Size); -#else - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), Size); -#endif - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Opacity, Size); - - // Opacity mask - Size = FIntPoint::ZeroValue; -#if USE_USER_OPACITY_CHANNEL - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_OPACITY_MASK, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), Size); -#else - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), Size); -#endif - OutMaterial.SetPropertySize(EFlattenMaterialProperties::OpacityMask, Size); - - // Emissive - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_EMISSIVE, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Emissive, Size); - - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::SubSurface), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::SubSurface, Size); - - Size = FIntPoint::ZeroValue; - GetMaterialChannelData(SGMaterial, TextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_AMBIENT, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::AmbientOcclusion), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::AmbientOcclusion, Size); - } - - bool CreateSGMaterialFromFlattenMaterial( - const TArray& InputMaterials, - const FSimplygonMaterialLODSettings& InMaterialLODSettings, - SimplygonSDK::spMaterialTable& OutSGMaterialTable, - //@third party BEGIN SIMPLYGON - SimplygonSDK::spTextureTable& OutTextureTable, - //@third party END SIMPLYGON - - bool bReleaseInputMaterials) - { - if (InputMaterials.Num() == 0) - { - //If there are no materials, feed Simplygon with a default material instead. - UE_LOG(LogSimplygon, Log, TEXT("Input meshes do not contain any materials. A proxy without material will be generated.")); - return false; - } - - for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - SimplygonSDK::spMaterial SGMaterial = SDK->CreateMaterial(); - const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex]; - -#if USE_USER_OPACITY_CHANNEL - if (!SGMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_OPACITY)) - SGMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_OPACITY); - - if (!SGMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_OPACITY_MASK)) - SGMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_OPACITY_MASK); -#endif - SGMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR); - - SGMaterial->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("Material%d"), MaterialIndex))); - - // Does current material have BaseColor? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Diffuse)) - { - //@third party BEGIN SIMPLYGON - //NOTE: Currently bBakeVertexData is marked as deprecated in FMaterialProxySetting. We can add the support before 4.15 lockdown - - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR); - } - - // Does current material have Metallic? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Metallic)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_METALNESS); - } - - // Does current material have Specular? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Specular)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_SPECULAR); - } - - // Does current material have Roughness? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Roughness)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_ROUGHNESS); - } - - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::AmbientOcclusion)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::AmbientOcclusion), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::AmbientOcclusion), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_AMBIENT); - } - - //Does current material have a normalmap? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Normal)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS); - } - - // Does current material have Opacity? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Opacity)) - { -#if USE_USER_OPACITY_CHANNEL - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity), SGMaterial, OutTextureTable, USER_MATERIAL_CHANNEL_OPACITY); -#else - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY); -#endif - } - - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::OpacityMask)) - { -#if USE_USER_OPACITY_CHANNEL - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask), SGMaterial, OutTextureTable, USER_MATERIAL_CHANNEL_OPACITY_MASK); -#else - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY); -#endif - } - - if (FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Num()) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_EMISSIVE); - } - else - { - TArray BlackEmissive; - BlackEmissive.AddZeroed(1); - //@third party BEGIN SIMPLYGON - SetMaterialChannelData(BlackEmissive, FIntPoint(1,1), SGMaterial, OutTextureTable, SimplygonSDK::SG_MATERIAL_CHANNEL_EMISSIVE); - //@third party END SIMPLYGON - - } - - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::SubSurface)) - { - SetMaterialChannelData(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::SubSurface), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::SubSurface), SGMaterial, OutTextureTable, USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR); - } - - OutSGMaterialTable->AddMaterial(SGMaterial); - - if (bReleaseInputMaterials) - { - // Release FlattenMaterial. Using const_cast here to avoid removal of "const" from input data here - // and above the call chain. - const_cast(&FlattenMaterial)->ReleaseData(); - } - } - - return true; - } - - /** - * The method converts ESimplygonTextureSamplingQuality - * @param InSamplingQuality The Caster Settings used to setup the Simplygon Caster. - * @param InMappingImage Simplygon MappingImage. - * @return - */ - uint8 GetSamples(ESimplygonTextureSamplingQuality::Type InSamplingQuality) - { - switch (InSamplingQuality) - { - case ESimplygonTextureSamplingQuality::Poor: - return 1; - case ESimplygonTextureSamplingQuality::Low: - return 2; - case ESimplygonTextureSamplingQuality::Medium: - return 6; - case ESimplygonTextureSamplingQuality::High: - return 8; - } - return 1; - } - - uint8 ConvertColorChannelToInt(ESimplygonColorChannels::Type InSamplingQuality) - { - switch (InSamplingQuality) - { - case ESimplygonColorChannels::RGBA: - return 4; - case ESimplygonColorChannels::RGB: - return 3; - case ESimplygonColorChannels::L: - return 1; - } - - return 3; - } - - /** - * Use Simplygon Color Caster to Cast a Color Channel - * @param InCasterSettings The Caster Settings used to setup the Simplygon Caster. - * @param InMappingImage Simplygon MappingImage. - * @param InMaterialTable Input MaterialTable used by the Simplygon Caster. - * @param InTextureTable Simplygon TextureTable used by the Simplyogn Caster. - * @param OutColorData The Simplygon Output ImageData. - */ - void CastColorChannel(const FSimplygonChannelCastingSettings& InCasterSettings, - SimplygonSDK::spMappingImage& InMappingImage, - SimplygonSDK::spMaterialTable& InMaterialTable, - const SimplygonSDK::spTextureTable& InTextureTable, - SimplygonSDK::spImageData& OutColorData) - { - SimplygonSDK::spColorCaster cast = SDK->CreateColorCaster(); - - cast->SetColorType( GetSimplygonMaterialChannel(InCasterSettings.MaterialChannel) ); - cast->SetSourceMaterials( InMaterialTable ); - cast->SetSourceTextures(InTextureTable); - cast->SetMappingImage( InMappingImage ); // The mapping image we got from the remeshing process. - cast->SetOutputChannels( ConvertColorChannelToInt(InCasterSettings.ColorChannels) ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.) - cast->SetOutputChannelBitDepth( InCasterSettings.BitsPerChannel ); // 8 bits per channel. So in this case we will have 24bit colors RGB. - cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree aswell. - cast->SetOutputImage(OutColorData); - cast->SetBakeOpacityInAlpha(false); - //cast->SetBakeVertexColors(InCasterSettings.bBakeVertexColors); - //@third party BEGIN SIMPLYGON - cast->SetOutputSRGB(InCasterSettings.bUseSRGB); - //@third party END SIMPLYGON - - cast->CastMaterials(); // Fetch! - } - - /** - * Use Simplygon Normal Caster to Cast a Normals Channel - * @param InCasterSettings The Caster Settings used to setup the Simplygon Caster. - * @param InMappingImage Simplygon MappingImage. - * @param InMaterialTable Input MaterialTable used by the Simplygon Caster. - * @param InTextureTable Simplygon TextureTable used by the Simplyogn Caster. - * @param OutColorData The Simplygon Output ImageData. - */ - void CastNormalsChannel(FSimplygonChannelCastingSettings InCasterSettings, - SimplygonSDK::spMappingImage& InMappingImage, - SimplygonSDK::spMaterialTable& InMaterialTable, - const SimplygonSDK::spTextureTable& InTextureTable, - SimplygonSDK::spImageData& OutColorData, - int32 DestinationMaterialIndex = -1) - { - SimplygonSDK::spNormalCaster cast = SDK->CreateNormalCaster(); - cast->SetSourceMaterials( InMaterialTable ); - cast->SetSourceTextures(InTextureTable); - cast->SetMappingImage( InMappingImage ); // The mapping image we got from the remeshing process. - cast->SetOutputChannels( ConvertColorChannelToInt(InCasterSettings.ColorChannels) ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.) - cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB. - cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree aswell. - cast->SetFlipBackfacingNormals(InCasterSettings.bFlipBackfacingNormals); - cast->SetGenerateTangentSpaceNormals(InCasterSettings.bUseTangentSpaceNormals); - cast->SetFlipGreen(false); - //cast->SetNormalMapTextureLevel(); - cast->SetDestMaterialId(DestinationMaterialIndex); - cast->SetOutputImage(OutColorData); - cast->CastMaterials(); // Fetch! - } - - /** - * Use Simplygon Opacity Caster to Cast an Opacity Channel - * @param InCasterSettings The Caster Settings used to setup the Simplygon Caster. - * @param InMappingImage Simplygon MappingImage. - * @param InMaterialTable Input MaterialTable used by the Simplygon Caster. - * @param InTextureTable Simplygon TextureTable used by the Simplyogn Caster. - * @param OutColorData The Simplygon Output ImageData. - */ - void CastOpacityChannel(FSimplygonChannelCastingSettings InCasterSettings, - SimplygonSDK::spMappingImage& InMappingImage, - SimplygonSDK::spMaterialTable& InMaterialTable, - const SimplygonSDK::spTextureTable& InTextureTable, - SimplygonSDK::spImageData& OutColorData) - { - SimplygonSDK::spOpacityCaster cast = SDK->CreateOpacityCaster(); - cast->SetSourceMaterials( InMaterialTable ); - cast->SetSourceTextures(InTextureTable); - cast->SetMappingImage( InMappingImage ); // The mapping image we got from the remeshing process. - cast->SetOutputChannels( ConvertColorChannelToInt(InCasterSettings.ColorChannels) ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.) - cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB. - cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree aswell. - cast->SetOutputImage(OutColorData); - cast->CastMaterials(); // Fetch! - } - - /** - * Sets Mapping Image Setting for Simplygon. - * @param InMaterialLODSettings The MaterialLOD Settings used for setting up Simplygon MappingImageSetting. - * @param InMappingImageSettings The Simplygon MappingImage Settings that is being setup. - */ - void SetupMappingImage(const FSimplygonMaterialLODSettings& InMaterialLODSettings, - SimplygonSDK::spMappingImageSettings InMappingImageSettings, - bool InAggregateProcess, - bool InRemoveUVs) - { - if (!InMaterialLODSettings.bActive) - { - return; - } - - int32 NumInputMaterials = 1; - int32 NumOutputMaterials = 1; - - //if (InMaterialLODSettings.bReuseExistingCharts || InAggregateProcess) - we're always using UVs from the mesh because new UVs are already generated with GenerateUniqueUVs() call - if (InAggregateProcess || NumOutputMaterials > 1) - { - InMappingImageSettings->SetTexCoordGeneratorType(SimplygonSDK::SG_TEXCOORDGENERATORTYPE_CHARTAGGREGATOR); - //InMappingImageSettings->SetChartAggregatorMode(SimplygonSDK::SG_CHARTAGGREGATORMODE_SURFACEAREA); - } - else - { - InMappingImageSettings->SetTexCoordGeneratorType(SimplygonSDK::SG_TEXCOORDGENERATORTYPE_PARAMETERIZER); - } - - InMappingImageSettings->SetGenerateMappingImage(true); - InMappingImageSettings->SetTexCoordLevel(0); - if (NumOutputMaterials > 1) //-V547 - { - InMappingImageSettings->SetGenerateTexCoords(true); //! check if it is possible to avoid this - } - - - for (int32 MaterialIndex = 0; MaterialIndex < NumOutputMaterials; MaterialIndex++) - { - InMappingImageSettings->SetGutterSpace(MaterialIndex, InMaterialLODSettings.GutterSpace); - InMappingImageSettings->SetMultisamplingLevel(MaterialIndex, GetSamples(InMaterialLODSettings.SamplingQuality)); - } - - if (InRemoveUVs) - { - InMappingImageSettings->SetUseFullRetexturing(true); - } - - InMappingImageSettings->SetGenerateTangents(false); - - bool bAutomaticSizes = InMaterialLODSettings.bUseAutomaticSizes; - InMappingImageSettings->SetUseAutomaticTextureSize(bAutomaticSizes); - - if (!bAutomaticSizes) - { - for (int32 MaterialIndex = 0; MaterialIndex < NumOutputMaterials; MaterialIndex++) - { - InMappingImageSettings->SetWidth(MaterialIndex, InMaterialLODSettings.GetTextureResolutionFromEnum(InMaterialLODSettings.TextureWidth)); - InMappingImageSettings->SetHeight(MaterialIndex, InMaterialLODSettings.GetTextureResolutionFromEnum(InMaterialLODSettings.TextureHeight)); - } - } - else - { - InMappingImageSettings->SetForcePower2Texture(true); - } - } - - SimplygonSDK::spMaterial RebakeMaterials(const FSimplygonMaterialLODSettings& InMaterialLODSettings, - SimplygonSDK::spMappingImage& InMappingImage, - SimplygonSDK::spMaterialTable& InSGMaterialTable, - //@third party BEGIN SIMPLYGON - const SimplygonSDK::spTextureTable& TextureTable, - //@third party END SIMPLYGON - - int32 DestinationMaterialIndex = -1) - { - SimplygonSDK::spMaterial OutMaterial = SDK->CreateMaterial(); -#if USE_USER_OPACITY_CHANNEL - if(!OutMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_OPACITY)) - { - OutMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_OPACITY); - } - - if (!OutMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_OPACITY_MASK)) - { - OutMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_OPACITY_MASK); - } -#endif - OutMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR); - - for(int ChannelIndex=0; ChannelIndex < InMaterialLODSettings.ChannelsToCast.Num(); ChannelIndex++) - { - FSimplygonChannelCastingSettings CasterSetting = InMaterialLODSettings.ChannelsToCast[ChannelIndex]; - - if(CasterSetting.bActive) - { - SimplygonSDK::spImageData OutColorData = SDK->CreateImageData(); - - switch(CasterSetting.Caster) - { - case ESimplygonCasterType::Color: - CastColorChannel(CasterSetting,InMappingImage,InSGMaterialTable, TextureTable, OutColorData); - break; - case ESimplygonCasterType::Normals: - CastNormalsChannel(CasterSetting,InMappingImage,InSGMaterialTable, TextureTable, OutColorData, DestinationMaterialIndex); - break; - case ESimplygonCasterType::Opacity: -#if USE_USER_OPACITY_CHANNEL - CastColorChannel(CasterSetting,InMappingImage,InSGMaterialTable, TextureTable, OutColorData); -#else - CastOpacityChannel(CasterSetting,InMappingImage,InSGMaterialTable,OutColorData, TextureTable); -#endif - break; - default: - break; - } - - //@third party BEGIN SIMPLYGON - FGuid TextureGuid = FGuid::NewGuid(); - SimplygonSDK::spTexture BakedTexture = SDK->CreateTexture(); - BakedTexture->SetImageData(OutColorData); - BakedTexture->SetName(TCHAR_TO_ANSI(*TextureGuid.ToString())); - TextureTable->AddTexture(BakedTexture); - - SimplygonSDK::spShadingTextureNode BakedTextureNode = SDK->CreateShadingTextureNode(); - BakedTextureNode->SetTextureLevel(0); - BakedTextureNode->SetTextureName(TCHAR_TO_ANSI(*TextureGuid.ToString())); - OutMaterial->SetShadingNetwork(GetSimplygonMaterialChannel(CasterSetting.MaterialChannel), BakedTextureNode); - //@third party END SIMPLYGON - - } - } - - return OutMaterial; - } - - /* - * (1, 0, 0) - * (0, 0, 1) - * (0, 1, 0) - */ - const FMatrix& GetConversionMatrix() - { - static FMatrix m; - static bool bInitialized = false; - if (!bInitialized) - { - m.SetIdentity(); - m.M[1][1] = 0.0f; - m.M[2][1] = 1.0f; - - m.M[1][2] = 1.0f; - m.M[2][2] = 0.0f; - bInitialized = true; - } - - return m; - } - - void ValidateGeometry(SimplygonSDK::spGeometryValidator& GeometryValidator, SimplygonSDK::spGeometryData& GeometryData) - { - bool bGeometryValid = GeometryValidator->ValidateGeometry(GeometryData); - if (!bGeometryValid) - { - uint32 error_val = GeometryValidator->GetErrorValue(); - if ((error_val & SimplygonSDK::SG_VALIDATIONERROR_ZERO_LENGTH_NORMAL) != 0) - { - SimplygonSDK::spNormalRepairer rep = SDK->CreateNormalRepairer(); - rep->SetRepairOnlyInvalidNormals(true); - rep->SetGeometry(GeometryData); - rep->RunProcessing(); - } - else - { - FString ErrorMessage = ANSI_TO_TCHAR(GeometryValidator->GetErrorString()); - - // Downgrade geometry validation errors to info - ErrorMessage = ErrorMessage.Replace(TEXT("Error:"), TEXT("Info:")); - - UE_LOG(LogSimplygon, Log, TEXT("Invalid mesh data: %s."), *ErrorMessage); - } - } - } -}; - -TUniquePtr GSimplygonMeshReduction; - - -void FSimplygonMeshReductionModule::StartupModule() -{ - GSimplygonMeshReduction.Reset(FSimplygonMeshReduction::Create()); - IModularFeatures::Get().RegisterModularFeature(IMeshReductionModule::GetModularFeatureName(), this); -} - -void FSimplygonMeshReductionModule::ShutdownModule() -{ - FSimplygonMeshReduction::Destroy(); - GSimplygonMeshReduction = nullptr; - IModularFeatures::Get().UnregisterModularFeature(IMeshReductionModule::GetModularFeatureName(), this); -} - -#define USE_SIMPLYGON_SWARM 0 - -IMeshReduction* FSimplygonMeshReductionModule::GetStaticMeshReductionInterface() -{ -#if !USE_SIMPLYGON_SWARM - return GSimplygonMeshReduction.Get(); -#else - return nullptr; -#endif -} - -IMeshReduction* FSimplygonMeshReductionModule::GetSkeletalMeshReductionInterface() -{ -#if !USE_SIMPLYGON_SWARM - return GSimplygonMeshReduction.Get(); -#else - return nullptr; -#endif -} - -IMeshMerging* FSimplygonMeshReductionModule::GetMeshMergingInterface() -{ -#if !USE_SIMPLYGON_SWARM - return GSimplygonMeshReduction.Get(); -#else - return nullptr; -#endif -} - -class IMeshMerging* FSimplygonMeshReductionModule::GetDistributedMeshMergingInterface() -{ - return nullptr; -} - -FString FSimplygonMeshReductionModule::GetName() -{ - return FString("SimplygonMeshReduction"); -} - -#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/SimplygonMeshReduction/Public/SimplygonTypes.h b/Engine/Source/Developer/SimplygonMeshReduction/Public/SimplygonTypes.h deleted file mode 100644 index 3f065031114e..000000000000 --- a/Engine/Source/Developer/SimplygonMeshReduction/Public/SimplygonTypes.h +++ /dev/null @@ -1,535 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Containers/EnumAsByte.h" -#include "Engine/MaterialMerging.h" -#include "SimplygonSDK.h" - -#include "SimplygonTypes.generated.h" - -//@third party code BEGIN SIMPLYGON -#define USE_USER_OPACITY_CHANNEL 1 -#if USE_USER_OPACITY_CHANNEL -static const char* USER_MATERIAL_CHANNEL_OPACITY = "UserOpacity"; -static const char* USER_MATERIAL_CHANNEL_OPACITY_MASK = "UserOpacityMask"; -#endif -//@third party code END SIMPLYGON - -// User defined material channel used for baking out sub surface colours -static const char* USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR = "UserSubSurfaceColor"; - -UENUM() -namespace ESimplygonMaterialChannel -{ - enum Type - { - SG_MATERIAL_CHANNEL_AMBIENT UMETA(DisplayName = "Ambient", DisplayValue = "Ambient"), - SG_MATERIAL_CHANNEL_DIFFUSE UMETA(DisplayName = "Diffuse", DisplayValue = "Diffuse"), - SG_MATERIAL_CHANNEL_SPECULAR UMETA(DisplayName = "Specular", DisplayValue = "Specular"), - SG_MATERIAL_CHANNEL_OPACITY UMETA(DisplayName = "Opacity", DisplayValue = "Opacity"), - SG_MATERIAL_CHANNEL_OPACITYMASK UMETA(DisplayName = "OpacityMask", DisplayValue = "OpacityMask"), - SG_MATERIAL_CHANNEL_EMISSIVE UMETA(DisplayName = "Emissive", DisplayValue = "Emissive"), - SG_MATERIAL_CHANNEL_NORMALS UMETA(DisplayName = "Normals", DisplayValue = "Normals"), - SG_MATERIAL_CHANNEL_DISPLACEMENT UMETA(DisplayName = "Displacement", DisplayValue = "Displacement"), - SG_MATERIAL_CHANNEL_BASECOLOR UMETA(DisplayName = "Basecolor", DisplayValue = "Basecolor"), - SG_MATERIAL_CHANNEL_ROUGHNESS UMETA(DisplayName = "Roughness", DisplayValue = "Roughness"), - SG_MATERIAL_CHANNEL_METALLIC UMETA(DisplayName = "Metallic", DisplayValue = "Metallic"), - SG_MATERIAL_CHANNEL_SUBSURFACE UMETA(DisplayName = "SubsurfaceColor", DisplayValue = "SubsurfaceColor") - }; - -} - -static const char* GetSimplygonMaterialChannel(ESimplygonMaterialChannel::Type channel) -{ - if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_BASECOLOR) - return SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SPECULAR) - return SimplygonSDK::SG_MATERIAL_CHANNEL_SPECULAR; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_ROUGHNESS) - return SimplygonSDK::SG_MATERIAL_CHANNEL_ROUGHNESS; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_METALLIC) - return SimplygonSDK::SG_MATERIAL_CHANNEL_METALNESS; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_NORMALS) - return SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_OPACITY) -#if USE_USER_OPACITY_CHANNEL - return USER_MATERIAL_CHANNEL_OPACITY; -#else - return SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY; -#endif - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_OPACITYMASK) -#if USE_USER_OPACITY_CHANNEL - return USER_MATERIAL_CHANNEL_OPACITY_MASK; -#else - return SimplygonSDK::SG_MATERIAL_CHANNEL_OPACITY; -#endif - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_EMISSIVE) - return SimplygonSDK::SG_MATERIAL_CHANNEL_EMISSIVE; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SUBSURFACE) - return USER_MATERIAL_CHANNEL_SUBSURFACE_COLOR; - else if (channel == ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_AMBIENT) - return SimplygonSDK::SG_MATERIAL_CHANNEL_AMBIENT; - else - { - check(0); - return 0; - } - -} - -UENUM() -namespace ESimplygonLODType -{ - enum Type - { - Reduction, - Remeshing - }; -} - -/* -* Material LOD Type -*/ -UENUM() -namespace EMaterialLODType -{ - enum Type - { - Off, //no material lod - BakeTexture, //combine materials and cast new textures - BakeVertexColor, //combine materials and cast textures into vertex color field instead of baking new textures - Replace - }; -} - -/* -* Texture Stretch -*/ -UENUM() -namespace ESimplygonTextureStrech -{ - enum Type - { - None, - VerySmall, - Small, - Medium, - Large, - VeryLarge - }; -} - -/* -* Type of caster to use Stretch -*/ -UENUM() -namespace ESimplygonCasterType -{ - enum Type - { - None, - Color, //use Color caster - Normals, //use Normals caster - Opacity, //use Opacity caster - }; -} - -/* -* Texture Sampling Quality -*/ -UENUM() -namespace ESimplygonTextureSamplingQuality -{ - enum Type - { - Poor, - Low, - Medium, - High - }; -} - -UENUM() -namespace ESimplygonColorChannels -{ - enum Type - { - RGBA, - RGB, - L - }; -} - - -UENUM() -namespace ESimplygonTextureResolution -{ - enum Type - { - TextureResolution_64 UMETA(DisplayName = "64"), - TextureResolution_128 UMETA(DisplayName = "128"), - TextureResolution_256 UMETA(DisplayName = "256"), - TextureResolution_512 UMETA(DisplayName = "512"), - TextureResolution_1024 UMETA(DisplayName = "1024"), - TextureResolution_2048 UMETA(DisplayName = "2048"), - TextureResolution_4096 UMETA(DisplayName = "4096"), - TextureResolution_8192 UMETA(DisplayName = "8192") - }; -} - -/* -* Desc : The following class stores settings for the simplygon caster. -*/ -USTRUCT() -struct FSimplygonChannelCastingSettings -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - TEnumAsByte MaterialChannel; - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - TEnumAsByte Caster; - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - bool bActive; - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - TEnumAsByte ColorChannels; - - UPROPERTY(EditAnywhere, Category = SimplygonChannelCasterSettings) - int32 BitsPerChannel; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bUseSRGB; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bBakeVertexColors; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bFlipBackfacingNormals; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bUseTangentSpaceNormals; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bFlipGreenChannel; - - FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::Type channel, ESimplygonCasterType::Type caster, ESimplygonColorChannels::Type colorChannels) - : MaterialChannel(channel) - , Caster(caster) - , bActive(false) - , ColorChannels(colorChannels) - , BitsPerChannel(8) - , bUseSRGB(true) - , bBakeVertexColors(false) - , bFlipBackfacingNormals(false) - , bUseTangentSpaceNormals(true) - , bFlipGreenChannel(false) - { - } - - FSimplygonChannelCastingSettings() - : MaterialChannel(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_BASECOLOR) - , Caster(ESimplygonCasterType::Color) - , bActive(false) - , ColorChannels(ESimplygonColorChannels::RGB) - , BitsPerChannel(8) - , bUseSRGB(true) - , bBakeVertexColors(false) - , bFlipBackfacingNormals(false) - , bUseTangentSpaceNormals(true) - , bFlipGreenChannel(false) - { - } - - FSimplygonChannelCastingSettings(const FSimplygonChannelCastingSettings& Other) - : MaterialChannel(Other.MaterialChannel) - , Caster(Other.Caster) - , bActive(Other.bActive) - , ColorChannels(Other.ColorChannels) - , BitsPerChannel(Other.BitsPerChannel) - , bUseSRGB(Other.bUseSRGB) - , bBakeVertexColors(Other.bBakeVertexColors) - , bFlipBackfacingNormals(Other.bFlipBackfacingNormals) - , bUseTangentSpaceNormals(Other.bUseTangentSpaceNormals) - , bFlipGreenChannel(Other.bFlipGreenChannel) - { - } - - bool operator==(const FSimplygonChannelCastingSettings& Other) const - { - if (bActive == false && Other.bActive == false) - { - // Ignore other fields when both objects are inactive - return true; - } - - return MaterialChannel == Other.MaterialChannel - && Caster == Other.Caster - && bActive == Other.bActive - && ColorChannels == Other.ColorChannels - && BitsPerChannel == Other.BitsPerChannel - && bUseSRGB == Other.bUseSRGB - && bBakeVertexColors == Other.bBakeVertexColors - && bFlipBackfacingNormals == Other.bFlipBackfacingNormals - && bUseTangentSpaceNormals == Other.bUseTangentSpaceNormals - && bFlipGreenChannel == Other.bFlipGreenChannel; - } - - bool operator!=(const FSimplygonChannelCastingSettings& Other) const - { - return !(*this == Other); - } -}; - - -static const ESimplygonTextureResolution::Type GetResolutionEnum(const int32 InSize) -{ - switch (InSize) - { - case 64: - { - return ESimplygonTextureResolution::TextureResolution_64; - } - - case 128: - { - return ESimplygonTextureResolution::TextureResolution_128; - } - - case 256: - { - return ESimplygonTextureResolution::TextureResolution_256; - } - - case 512: - { - return ESimplygonTextureResolution::TextureResolution_512; - - } - case 1024: - { - return ESimplygonTextureResolution::TextureResolution_1024; - } - case 2048: - { - return ESimplygonTextureResolution::TextureResolution_2048; - } - case 4096: - { - return ESimplygonTextureResolution::TextureResolution_4096; - } - case 8192: - { - return ESimplygonTextureResolution::TextureResolution_8192; - } - - default: - { - check(false); - return ESimplygonTextureResolution::TextureResolution_64; - } - } - - return ESimplygonTextureResolution::TextureResolution_64; -} - -/* -* Desc : The following class stores settings for the simplygon material LOD. Specifically the mapping image -*/ -USTRUCT() -struct FSimplygonMaterialLODSettings -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bActive; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte MaterialLODType; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bUseAutomaticSizes; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte TextureWidth; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte TextureHeight; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte SamplingQuality; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - int32 GutterSpace; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - TEnumAsByte TextureStrech; - - UPROPERTY(EditAnywhere, Category = SimplygonMaterialLODSettings) - bool bReuseExistingCharts; - - UPROPERTY() - TArray ChannelsToCast; - - FSimplygonMaterialLODSettings() - : bActive(false) - , MaterialLODType(EMaterialLODType::BakeTexture) - , bUseAutomaticSizes(false) - , TextureWidth(ESimplygonTextureResolution::Type::TextureResolution_512) - , TextureHeight(ESimplygonTextureResolution::Type::TextureResolution_512) - , SamplingQuality(ESimplygonTextureSamplingQuality::Low) - , GutterSpace(4) - , TextureStrech(ESimplygonTextureStrech::Medium) - , bReuseExistingCharts(false) - { - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_BASECOLOR, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SPECULAR, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_ROUGHNESS, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_METALLIC, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_NORMALS, ESimplygonCasterType::Normals, ESimplygonColorChannels::RGB)); - } - - FSimplygonMaterialLODSettings(const FSimplygonMaterialLODSettings& Other) - : bActive(Other.bActive) - , MaterialLODType(Other.MaterialLODType) - , bUseAutomaticSizes(Other.bUseAutomaticSizes) - , TextureWidth(Other.TextureWidth) - , TextureHeight(Other.TextureHeight) - , SamplingQuality(Other.SamplingQuality) - , GutterSpace(Other.GutterSpace) - , TextureStrech(Other.TextureStrech) - , bReuseExistingCharts(Other.bReuseExistingCharts) - { - ChannelsToCast.Empty(); - for (int ItemIndex = 0; ItemIndex < Other.ChannelsToCast.Num(); ItemIndex++) - { - ChannelsToCast.Add(Other.ChannelsToCast[ItemIndex]); - - } - } - - - - FSimplygonMaterialLODSettings(const FMaterialProxySettings& Settings) - : bActive(true) - , MaterialLODType(EMaterialLODType::BakeTexture) - , bUseAutomaticSizes( Settings.TextureSizingType == TextureSizingType_UseSimplygonAutomaticSizing ) - , TextureWidth(GetResolutionEnum(Settings.TextureSize.X)) - , TextureHeight(GetResolutionEnum(Settings.TextureSize.Y)) - , SamplingQuality(ESimplygonTextureSamplingQuality::High) - , GutterSpace( Settings.GutterSpace ) - , TextureStrech(ESimplygonTextureStrech::Medium) - , bReuseExistingCharts(false) - { - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_BASECOLOR, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = true; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SPECULAR, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bSpecularMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_ROUGHNESS, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bRoughnessMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_METALLIC, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bMetallicMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_NORMALS, ESimplygonCasterType::Normals, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bNormalMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_EMISSIVE, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bEmissiveMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_OPACITY, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bOpacityMap; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_OPACITYMASK, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bOpacityMaskMap; - - // TODO, properly render out sub surface data/values - //ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_SUBSURFACE, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - //ChannelsToCast.Last().bUseSRGB = false; - //ChannelsToCast.Last().bActive = true; - - ChannelsToCast.Add(FSimplygonChannelCastingSettings(ESimplygonMaterialChannel::SG_MATERIAL_CHANNEL_AMBIENT, ESimplygonCasterType::Color, ESimplygonColorChannels::RGB)); - ChannelsToCast.Last().bUseSRGB = false; - ChannelsToCast.Last().bActive = Settings.bAmbientOcclusionMap; - } - - static int32 GetTextureResolutionFromEnum(ESimplygonTextureResolution::Type InResolution) - { - switch (InResolution) - { - case ESimplygonTextureResolution::TextureResolution_64: - return 64; - case ESimplygonTextureResolution::TextureResolution_128: - return 128; - case ESimplygonTextureResolution::TextureResolution_256: - return 256; - case ESimplygonTextureResolution::TextureResolution_512: - return 512; - case ESimplygonTextureResolution::TextureResolution_1024: - return 1024; - case ESimplygonTextureResolution::TextureResolution_2048: - return 2048; - case ESimplygonTextureResolution::TextureResolution_4096: - return 4096; - case ESimplygonTextureResolution::TextureResolution_8192: - return 8192; - - } - return 64; - } - - bool operator==(const FSimplygonMaterialLODSettings& Other) const - { - if (bActive == false && Other.bActive == false) - { - // Ignore other fields when both objects are inactive - return true; - } - - bool areAllElementsEqual = ChannelsToCast.Num() == Other.ChannelsToCast.Num() ? true : false; - - if (areAllElementsEqual) - { - for (int ItemIndex = 0; ItemIndex < ChannelsToCast.Num(); ItemIndex++) - { - if (ChannelsToCast[ItemIndex] != Other.ChannelsToCast[ItemIndex]) - { - areAllElementsEqual = false; - break; - } - if (!areAllElementsEqual) - break; - } - } - - return bActive == Other.bActive - && MaterialLODType == Other.MaterialLODType - && bUseAutomaticSizes == Other.bUseAutomaticSizes - && TextureWidth == Other.TextureWidth - && TextureHeight == Other.TextureHeight - && SamplingQuality == Other.SamplingQuality - && GutterSpace == Other.GutterSpace - && TextureStrech == Other.TextureStrech - && bReuseExistingCharts == Other.bReuseExistingCharts - && areAllElementsEqual == true; - } - - bool operator!=(const FSimplygonMaterialLODSettings& Other) const - { - return !(*this == Other); - } -}; \ No newline at end of file diff --git a/Engine/Source/Developer/SimplygonMeshReduction/SimplygonMeshReduction.Build.cs b/Engine/Source/Developer/SimplygonMeshReduction/SimplygonMeshReduction.Build.cs deleted file mode 100644 index b75099c95846..000000000000 --- a/Engine/Source/Developer/SimplygonMeshReduction/SimplygonMeshReduction.Build.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -using System.IO; -using UnrealBuildTool; - -public class SimplygonMeshReduction : ModuleRules -{ - public SimplygonMeshReduction(ReadOnlyTargetRules Target) : base(Target) - { - PublicIncludePaths.Add("Developer/SimplygonMeshReduction/Public"); - - PrivateDependencyModuleNames.AddRange( - new string[] { - "Core", - "CoreUObject", - "Engine", - "RenderCore", - "MeshDescription", - "MeshDescriptionOperations", - "MaterialUtilities", - "MeshBoneReduction", - "RHI", - "AnimationModifiers", - } - ); - - PrivateIncludePathModuleNames.AddRange( - new string[] { - "MeshUtilities", - "MeshReductionInterface", - } - ); - - AddEngineThirdPartyPrivateStaticDependencies(Target, "Simplygon"); - - string SimplygonPath = Target.UEThirdPartySourceDirectory + "NotForLicensees/Simplygon/Simplygon-latest/Inc/SimplygonSDK.h"; - if (Target.Platform == UnrealTargetPlatform.Win64 && File.Exists(SimplygonPath)) - { - PrecompileForTargets = PrecompileTargetsType.Editor; - } - else - { - PrecompileForTargets = PrecompileTargetsType.None; - } - } -} diff --git a/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonRESTClient.cpp b/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonRESTClient.cpp deleted file mode 100644 index 7c203cae4d19..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonRESTClient.cpp +++ /dev/null @@ -1,1341 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#include "SimplygonRESTClient.h" -#include "HAL/RunnableThread.h" -#include "Editor/EditorPerProjectUserSettings.h" -#include "Dom/JsonObject.h" -#include "Serialization/JsonReader.h" -#include "Serialization/JsonSerializer.h" -#include "HttpModule.h" -#include "HttpManager.h" - -#define HOSTNAME "http://127.0.0.1" -#define PORT ":55002" - -DEFINE_LOG_CATEGORY_STATIC(LogSimplygonRESTClient, Verbose, All); - -const TCHAR* SIMPLYGON_SWARM_REQUEST_DEBUG_TEMPALTE = TEXT("Error Processing Request %s"); - -FSimplygonSwarmTask::FSimplygonSwarmTask(const FSwarmTaskkData& InTaskData) - : - TaskData(InTaskData), - State(SRS_UNKNOWN), - bProcessingStarted(false), - TimeSinceProcessStart(0.0f) -{ - bMultiPartUploadInitialized = false; - TaskData.TaskUploadComplete = false; - DebugHttpRequestCounter.Set(0); - IsCompleted.AtomicSet(false); - APIKey = TEXT("LOCAL"); - TaskData.JobName = TaskData.JobName.IsEmpty() ? TEXT("UE4_SWARM") : TaskData.JobName; - bEnableDebugLogging = true; -} - -FSimplygonSwarmTask::~FSimplygonSwarmTask() -{ - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, Log, TEXT("Destroying Task With Job Id %s"), *JobId); - - TArray> OutPendingRequests; - if (PendingRequests.GetKeys(OutPendingRequests) > 0) - { - for (int32 ItemIndex = 0; ItemIndex < OutPendingRequests.Num(); ++ItemIndex) - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, Warning, TEXT("Cancelling pending Request for task with status %d"), (int32)OutPendingRequests[ItemIndex]->GetStatus()); - OutPendingRequests[ItemIndex]->CancelRequest(); - } - } - //check(DebugHttpRequestCounter.GetValue() == 0); - UploadParts.Empty(); - - //unbind the delegates - OnAssetDownloaded().Unbind(); - OnAssetUploaded().Unbind(); - OnSwarmTaskFailed().Unbind(); - - delete TaskData.StateLock; -} - -void FSimplygonSwarmTask::CreateUploadParts(const int32 MaxUploadPartSize) -{ - //create upload parts - UploadParts.Empty(); - TArray fileBlob; - if (FFileHelper::LoadFileToArray(fileBlob, *TaskData.ZipFilePath)) - { - UploadSize = fileBlob.Num(); - if (fileBlob.Num() > MaxUploadPartSize) - { - int32 NumberOfPartsRequried = fileBlob.Num() / MaxUploadPartSize; - int32 RemainingBytes = fileBlob.Num() % MaxUploadPartSize; - - for (int32 PartIndex = 0; PartIndex < NumberOfPartsRequried; ++PartIndex) - { - FSwarmUploadPart* UploadPartData = new (UploadParts) FSwarmUploadPart(); - int32 Offset = PartIndex * MaxUploadPartSize * sizeof(uint8); - UploadPartData->Data.AddUninitialized(MaxUploadPartSize); - FMemory::Memcpy(UploadPartData->Data.GetData(), fileBlob.GetData() + Offset, MaxUploadPartSize * sizeof(uint8)); - UploadPartData->PartNumber = PartIndex + 1; - UploadPartData->PartUploaded = false; - } - - if (RemainingBytes > 0) - { - //NOTE: need to set Offset before doing a new on UploadParts - int32 Offset = UploadParts.Num() * MaxUploadPartSize; - FSwarmUploadPart* UploadPartData = new (UploadParts) FSwarmUploadPart(); - UploadPartData->Data.AddUninitialized(RemainingBytes); - FMemory::Memcpy(UploadPartData->Data.GetData(), fileBlob.GetData() + Offset, RemainingBytes * sizeof(uint8)); - UploadPartData->PartNumber = NumberOfPartsRequried + 1; - UploadPartData->PartUploaded = false; - } - } - else - { - FSwarmUploadPart* UploadPartData = new (UploadParts) FSwarmUploadPart(); - UploadPartData->Data.AddUninitialized(fileBlob.Num()); - FMemory::Memcpy(UploadPartData->Data.GetData(), fileBlob.GetData(), fileBlob.Num() * sizeof(uint8)); - UploadPartData->PartNumber = 1; - UploadPartData->PartUploaded = false; - } - - TotalParts = UploadParts.Num(); - RemainingPartsToUpload.Add(TotalParts); - } -} - -bool FSimplygonSwarmTask::NeedsMultiPartUpload() -{ - return UploadParts.Num() > 1; -} - -SimplygonRESTState FSimplygonSwarmTask::GetState() const -{ - FScopeLock Lock(TaskData.StateLock); - return State; -} - -void FSimplygonSwarmTask::SetHost(FString InHostAddress) -{ - HostName = InHostAddress; -} - -void FSimplygonSwarmTask::EnableDebugLogging() -{ - bEnableDebugLogging = true; -} - -void FSimplygonSwarmTask::SetState(SimplygonRESTState InState) -{ - if (this != nullptr && TaskData.StateLock) - { - FScopeLock Lock(TaskData.StateLock); - - //do not update the state if either of these set is already set - if (InState != State - && (State != SRS_ASSETDOWNLOADED && State != SRS_FAILED)) - { - State = InState; - } - else if ((InState != State) && (State != SRS_FAILED) && (InState == SRS_ASSETDOWNLOADED)) - { - State = SRS_ASSETDOWNLOADED; - this->IsCompleted.AtomicSet(true); - } - else if (InState != State && InState == SRS_FAILED) - { - State = SRS_ASSETDOWNLOADED; - } - - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, Error, TEXT("Object in invalid state can not acquire state lock")); - } - -} - -bool FSimplygonSwarmTask::IsFinished() const -{ - FScopeLock Lock(TaskData.StateLock); - return IsCompleted; -} - -bool FSimplygonSwarmTask::ParseJsonMessage(FString InJsonMessage, FSwarmJsonResponse& OutResponseData) -{ - bool sucess = false; - - TSharedPtr JsonParsed; - TSharedRef> JsonReader = TJsonReaderFactory::Create(InJsonMessage); - if (FJsonSerializer::Deserialize(JsonReader, JsonParsed)) - { - FString Status; - if (JsonParsed->HasField("JobId")) - { - JsonParsed->TryGetStringField("JobId", OutResponseData.JobId); - } - - if (JsonParsed->HasField("Status")) - { - OutResponseData.Status = JsonParsed->GetStringField("Status"); - } - - if (JsonParsed->HasField("OutputAssetId")) - { - JsonParsed->TryGetStringField("OutputAssetId", OutResponseData.OutputAssetId); - } - - if (JsonParsed->HasField("AssetId")) - { - JsonParsed->TryGetStringField("AssetId", OutResponseData.AssetId); - } - - if (JsonParsed->HasField("ProgressPercentage")) - { - JsonParsed->TryGetNumberField("ProgressPercentage", OutResponseData.Progress); - } - - if (JsonParsed->HasField("ProgressPercentage")) - { - JsonParsed->TryGetNumberField("ProgressPercentage", OutResponseData.Progress); - } - - if (JsonParsed->HasField("UploadId")) - { - JsonParsed->TryGetStringField("UploadId", OutResponseData.UploadId); - } - - sucess = true; - } - - - return sucess; -} - - -// ~ HTTP Request method to communicate with Simplygon REST Interface - - -void FSimplygonSwarmTask::AccountInfo() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - AddAuthenticationHeader(request); - request->SetURL(FString::Printf(TEXT("%s/2.3/account?apikey=%s"), *HostName, *APIKey)); - request->SetVerb(TEXT("GET")); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::AccountInfo_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::AccountInfo_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response Invalid %s"), *Request->GetURL()); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response Message %s"), *msg); - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - - -void FSimplygonSwarmTask::CreateJob() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/job/create?apikey=%s&job_name=%s&asset_id=%s"), *HostName, *APIKey, *TaskData.JobName, *InputAssetId); - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::CreateJob_Response); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - SetState(SRS_JOBCREATED_PENDING); - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::CreateJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response Invalid %s"), *Request->GetURL()); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - if (ParseJsonMessage(msg, Data)) - { - if (!Data.JobId.IsEmpty()) - { - JobId = Data.JobId; - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Created JobId: %s"), *Data.JobId); - SetState(SRS_JOBCREATED); - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, Warning, TEXT("Empty JobId for task")); - SetState(SRS_FAILED); - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to parse message %s for Request %s"), *msg, *Request->GetURL()); - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::UploadJobSettings() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/job/%s/uploadsettings?apikey=%s"), *HostName, *JobId, *APIKey); - - TArray data; - FFileHelper::LoadFileToArray(data, *TaskData.SplFilePath); - - AddAuthenticationHeader(request); - request->SetHeader(TEXT("Content-Type"), TEXT("application/octet-stream")); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - request->SetContent(data); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::UploadJobSettings_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - SetState(SRS_JOBSETTINGSUPLOADED_PENDING); - } -} - -void FSimplygonSwarmTask::UploadJobSettings_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - if (!OnAssetUploaded().IsBound()) - { - UE_LOG(LogSimplygonRESTClient, Error, TEXT("OnAssetUploaded delegate not bound to any object")); - } - else - { - SetState(SRS_JOBSETTINGSUPLOADED); - OnAssetUploaded().Execute(*this); - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::ProcessJob() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/job/%s/Process?apikey=%s"), *HostName, *JobId, *APIKey); - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("PUT")); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::ProcessJob_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s Response code %d"), *request->GetURL()); - } - else - { - SetState(SRS_JOBPROCESSING_PENDING); - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } - - if ( !bProcessingStarted) - { - TimeSinceProcessStart = 0.0f; - bProcessingStarted = true; - } - else - { - TimeSinceProcessStart += 2.5f; - - if (TimeSinceProcessStart > 900.0f) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process time seconds since processing started %f"), TimeSinceProcessStart); - } - } -} - -void FSimplygonSwarmTask::ProcessJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - SetState(SRS_JOBPROCESSING); - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::GetJob() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/job/%s?apikey=%s"), *HostName, *JobId, *APIKey); - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("GET")); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::GetJob_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::GetJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - // Do not need to process jobs which already either failed or completed - if (State != SRS_JOBPROCESSING) - { - return; - } - - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - SetState(SRS_FAILED); - return; - } - else if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - if (!msg.IsEmpty() && ParseJsonMessage(msg, Data)) - { - - if (!Data.Status.IsEmpty()) - { - if (Data.Status == "Processed") - { - if (!Data.OutputAssetId.IsEmpty()) - { - OutputAssetId = Data.OutputAssetId; - SetState(SRS_JOBPROCESSED); - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Job with Id %s processed"), *Data.JobId); - } - else - { - SetState(SRS_FAILED); - } - } - else if (Data.Status == "Failed") - { - SetState(SRS_FAILED); - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Job with id %s failed with error: %s"), *Data.JobId, *Data.ErrorMessage); - } - else if (Data.Status == "Processing") - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Process update job with Id %s (%f)"), *Data.JobId, Data.Progress); - } - else if (Data.Status == "Created") - { - SetState(SRS_JOBCREATED); - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Job state was created while expecting processing %s"), *Data.JobId); - } - } - } - else - { - SetState(SRS_FAILED); - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::UploadAsset() -{ - //if multipart upload is need then use multi part request else use older request to upload data - if (NeedsMultiPartUpload()) - { - int32 PartsToUpload = RemainingPartsToUpload.GetValue(); - int32 PartIndex = TotalParts - PartsToUpload; - if (!bMultiPartUploadInitialized && PartsToUpload > 0) - { - MultiPartUploadBegin(); - } - else if (PartsToUpload > 0) - { - MultiPartUploadPart(UploadParts[PartIndex].PartNumber); - } - else if (PartsToUpload == 0) - { - if (!TaskData.TaskUploadComplete) - { - MultiPartUploadEnd(); - } - else - { - MultiPartUploadGet(); - } - } - } - else - { - //bail if part has already been uploaded - if (UploadParts[0].PartUploaded) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Skip Already Uploaded Asset.")); - return; - } - - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/upload?apikey=%s&asset_name=%s"), *HostName, *APIKey, *TaskData.JobName); - - AddAuthenticationHeader(request); - request->SetHeader(TEXT("Content-Type"), TEXT("application/octet-stream")); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - request->SetContent(UploadParts[0].Data); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::UploadAsset_Response); - - const uint32 bufferSize = UploadParts[0].Data.Num(); - - FHttpModule::Get().SetMaxReadBufferSize(bufferSize); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - SetState(SRS_ASSETUPLOADED_PENDING); - } - } -} - -void FSimplygonSwarmTask::UploadAsset_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Upload asset Response: %i"), Response->GetResponseCode()); - } - else - { - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - if (ParseJsonMessage(msg, Data)) - { - if (!Data.AssetId.IsEmpty()) - { - InputAssetId = Data.AssetId; - UploadParts[0].PartUploaded = true; - SetState(SRS_ASSETUPLOADED); - } - else - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Could not parse Input asset Id for job: %s"), *Data.JobId); - SetState(SRS_FAILED); - } - - } - else - { - SetState(SRS_FAILED); - } - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - SetState(SRS_FAILED); - } - } -} - -void FSimplygonSwarmTask::DownloadAsset() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - if (OutputAssetId.IsEmpty()) - { - SetState(SRS_FAILED); - } - - FString url = FString::Printf(TEXT("%s/2.3/asset/%s/download?apikey=%s"), *HostName, *OutputAssetId, *APIKey); - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("GET")); - FHttpModule::Get(). - - FHttpModule::Get().SetHttpTimeout(300); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::DownloadAsset_Response); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - SetState(SRS_ASSETDOWNLOADED_PENDING); - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Downloading Job with Id %s"), *JobId); - } -} - -void FSimplygonSwarmTask::DownloadAsset_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Invalid response for job %s"), *TaskData.JobName); - SetState(SRS_FAILED); - return; - } - - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - if (this == nullptr || JobId.IsEmpty()) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Object has already been destoryed or job id was empty")); - return; - } - TArray data = Response->GetContent(); - /*FString msg = Response->GetContentAsString(); - FSwarmJsonResponse Data;*/ - - if (data.Num() > 0) - { - if (!TaskData.OutputZipFilePath.IsEmpty() && !FFileHelper::SaveArrayToFile(data, *TaskData.OutputZipFilePath)) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Unable to save file %s"), *TaskData.OutputZipFilePath); - SetState(SRS_FAILED); - - } - else - { - if (!this->IsCompleted && OnAssetDownloaded().IsBound()) - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("Asset downloaded")); - OnAssetDownloaded().Execute(*this); - SetState(SRS_ASSETDOWNLOADED); - } - else - { - UE_LOG(LogSimplygonRESTClient, Display, TEXT("OnAssetDownloaded delegate not bound to any objects")); - } - } - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } -} - -void FSimplygonSwarmTask::MultiPartUploadBegin() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/uploadpart?apikey=%s&asset_name=%s"), *HostName, *APIKey, *TaskData.JobName); - - - FArrayWriter Writer; - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::MultiPartUploadBegin_Response); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - - SetState(SRS_MULTIPARTASSETUPLOAD_PENDING); - } -} - -void FSimplygonSwarmTask::MultiPartUploadBegin_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Upload asset Response: %i"), Response->GetResponseCode()); - } - else - { - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - FSwarmJsonResponse Data; - - if (ParseJsonMessage(msg, Data)) - { - if (!Data.UploadId.IsEmpty()) - { - UploadId = Data.UploadId; - bMultiPartUploadInitialized = true; - } - else - { - SetState(SRS_FAILED); - } - } - else - { - SetState(SRS_FAILED); - } - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - SetState(SRS_FAILED); - } - } -} - -void FSimplygonSwarmTask::MultiPartUploadPart(const uint32 InPartNumber) -{ - //should bailout if part has already been uploaded - int32 PartIndex = InPartNumber - 1; - if (UploadParts[PartIndex].PartUploaded) - return; - - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/uploadpart/%s/upload?apikey=%s&part_number=%d"), *HostName, *UploadId, *APIKey, UploadParts[PartIndex].PartNumber); - - FArrayWriter Writer; - AddAuthenticationHeader(request); - request->SetHeader(TEXT("Content-Type"), TEXT("application/octet-stream")); - request->SetURL(url); - request->SetVerb(TEXT("PUT")); - request->SetContent(UploadParts[PartIndex].Data); - FHttpModule::Get().SetMaxReadBufferSize(UploadParts[PartIndex].Data.Num()); - - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::MultiPartUploadPart_Response); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } - -} - -void FSimplygonSwarmTask::MultiPartUploadPart_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Upload asset Response: %i"), Response->GetResponseCode()); - } - else - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - //get part_number from query string - FString PartNumStr = Request->GetURLParameter("part_number"); - if (!PartNumStr.IsEmpty()) - { - int32 PartNum = FCString::Atoi(*PartNumStr); - PartNum -= 1; - if (!UploadParts[PartNum].PartUploaded) - { - RemainingPartsToUpload.Decrement(); - UploadParts[PartNum].PartUploaded = true; - } - } - } - else - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - } - } -} - -void FSimplygonSwarmTask::MultiPartUploadEnd() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/uploadpart/%s/Complete?apikey=%s&part_count=%d&upload_size=%d"), *HostName, *UploadId, *APIKey, TotalParts, UploadSize); - - FArrayWriter Writer; - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("POST")); - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::MultiPartUploadEnd_Response); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::MultiPartUploadEnd_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("Upload asset Response: %i"), Response->GetResponseCode()); - } - else - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - if (ParseJsonMessage(msg, Data)) - { - if (!Data.UploadId.IsEmpty()) - { - this->TaskData.TaskUploadComplete = true; - } - } - else - { - SetState(SRS_FAILED); - } - } - else - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - - } -} - -void FSimplygonSwarmTask::MultiPartUploadGet() -{ - TSharedRef request = FHttpModule::Get().CreateRequest(); - - FString url = FString::Printf(TEXT("%s/2.3/asset/uploadpart/%s?apikey=%s"), *HostName, *UploadId, *APIKey); - - FArrayWriter Writer; - AddAuthenticationHeader(request); - request->SetURL(url); - request->SetVerb(TEXT("GET")); - request->OnProcessRequestComplete().BindRaw(this, &FSimplygonSwarmTask::MultiPartUploadGet_Response); - - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("%s"), *request->GetURL()); - - if (!request->ProcessRequest()) - { - SetState(SRS_FAILED); - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to process Request %s"), *request->GetURL()); - } - else - { - DebugHttpRequestCounter.Increment(); - PendingRequests.Add(request, request->GetURL()); - } -} - -void FSimplygonSwarmTask::MultiPartUploadGet_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) -{ - DebugHttpRequestCounter.Decrement(); - FString OutUrl = PendingRequests.FindRef(Request); - - if (!OutUrl.IsEmpty()) - { - PendingRequests.Remove(Request); - } - - if (!bWasSuccessful) - { - SetState(SRS_FAILED); - if (Response.IsValid()) - { - UE_LOG(LogSimplygonRESTClient, Log, TEXT("%s: %i"), __FUNCTION__, Response->GetResponseCode()); - } - else - { - UE_LOG(LogSimplygonRESTClient, Error, TEXT("Upload asset response failed.")); - } - } - else - { - if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) - { - FString msg = Response->GetContentAsString(); - - FSwarmJsonResponse Data; - - if (ParseJsonMessage(msg, Data)) - { - if (!Data.AssetId.IsEmpty()) - { - InputAssetId = Data.AssetId; - SetState(SRS_ASSETUPLOADED); - UploadParts.Empty(); - } - else - { - SetState(SRS_FAILED); - } - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Failed to parse JSON")); - SetState(SRS_FAILED); - } - } - else - { - UE_CLOG(bEnableDebugLogging, LogSimplygonRESTClient, VeryVerbose, TEXT("Response failed %s Code %d"), *Request->GetURL(), Response->GetResponseCode()); - SetState(SRS_FAILED); - } - } -} - - -void FSimplygonSwarmTask::AddAuthenticationHeader(TSharedRef request) -{ - //Basic User:User - request->SetHeader("Authorization", "Basic dXNlcjp1c2Vy"); -} - -FSimplygonRESTClient* FSimplygonRESTClient::Runnable = nullptr; - -FSimplygonRESTClient::FSimplygonRESTClient() - : - Thread(nullptr), - HostName(TEXT(HOSTNAME)), - APIKey(TEXT("LOCAL")), - bEnableDebugging(false) -{ - FString IP = GetDefault()->SimplygonServerIP; - bEnableDebugging = GetDefault()->bEnableSwarmDebugging; - if (IP != "") - { - if (!IP.Contains("http://")) - { - HostName = "http://" + IP; - } - else - { - HostName = IP; - } - } - else - { - HostName = TEXT(HOSTNAME); - } - - HostName += TEXT(PORT); - JobLimit = GetDefault()->SwarmNumOfConcurrentJobs; - - - DelayBetweenRuns = GetDefault()->SimplygonSwarmDelay / 1000; - DelayBetweenRuns = DelayBetweenRuns <= 5 ? 5 : DelayBetweenRuns; - - Thread = FRunnableThread::Create(this, TEXT("SimplygonRESTClient")); -} - -FSimplygonRESTClient::~FSimplygonRESTClient() -{ - if (Thread != nullptr) - { - delete Thread; - Thread = nullptr; - } -} - - -bool FSimplygonRESTClient::Init() -{ - return true; -} - - -void FSimplygonRESTClient::Wait(const float InSeconds, const float InSleepTime /* = 0.1f */) -{ - // Instead of waiting the given amount of seconds doing nothing - // check periodically if there's been any Stop requests. - for (float TimeToWait = InSeconds; TimeToWait > 0.0f && ShouldStop() == false; TimeToWait -= InSleepTime) - { - FPlatformProcess::Sleep(FMath::Min(InSleepTime, TimeToWait)); - } -} - -uint32 FSimplygonRESTClient::Run() -{ - Wait(5); - //float SleepDelay = DelayBetweenRuns; - do - { - MoveItemsToBoundedArray(); - - UpdateTaskStates(); - - if (IsRunningCommandlet()) - { - FHttpModule::Get().GetHttpManager().Tick(FApp::GetDeltaTime()); - } - - Wait(DelayBetweenRuns); - - } while (ShouldStop() == false); - - - return 0; -} - -void FSimplygonRESTClient::UpdateTaskStates() -{ - TArray> TasksMarkedForRemoval; - //TasksMarkedForRemoval.AddUninitialized(JobLimit); - - FScopeLock Lock(&CriticalSectionData); - - for (int32 Index = 0; Index < JobsBuffer.Num(); Index++) - { - // - TSharedPtr& SwarmTask = JobsBuffer[Index]; - switch (SwarmTask->GetState()) - { - case SRS_UNKNOWN: - case SRS_MULTIPARTASSETUPLOAD_PENDING: - SwarmTask->UploadAsset(); - break; - case SRS_FAILED: - TasksMarkedForRemoval.Add(SwarmTask); - break; - case SRS_ASSETUPLOADED: - SwarmTask->CreateJob(); - break; - case SRS_JOBCREATED: - SwarmTask->UploadJobSettings(); - break; - case SRS_JOBSETTINGSUPLOADED: - SwarmTask->ProcessJob(); - break; - case SRS_JOBPROCESSING: - SwarmTask->GetJob(); - break; - case SRS_JOBPROCESSED: - SwarmTask->DownloadAsset(); - break; - case SRS_ASSETDOWNLOADED: - TasksMarkedForRemoval.Add(SwarmTask); - break; - } - } - - for (int32 Index = 0; Index < TasksMarkedForRemoval.Num(); Index++) - { - if (TasksMarkedForRemoval[Index]->GetState() == SRS_FAILED) - { - TasksMarkedForRemoval[Index]->OnSwarmTaskFailed().ExecuteIfBound(*TasksMarkedForRemoval[Index]); - } - JobsBuffer.Remove(TasksMarkedForRemoval[Index]); - } - - TasksMarkedForRemoval.Empty(); -} - -void FSimplygonRESTClient::MoveItemsToBoundedArray() -{ - if (!PendingJobs.IsEmpty()) - { - int32 ItemsToInsert = 0; - if (JobsBuffer.Num() >= JobLimit) - { - ItemsToInsert = JobLimit; - } - else if (JobsBuffer.Num() < JobLimit) - { - JobsBuffer.Shrink(); - ItemsToInsert = JobLimit - JobsBuffer.Num(); - - FScopeLock Lock(&CriticalSectionData); - do - { - TSharedPtr OutItem; - if (PendingJobs.Dequeue(OutItem)) - { - OutItem->CreateUploadParts(MaxUploadSizeInBytes); - JobsBuffer.Add(OutItem); - } - - } while (!PendingJobs.IsEmpty() && JobsBuffer.Num() < JobLimit); - } - } -} - -FSimplygonRESTClient* FSimplygonRESTClient::Get() -{ - if (!Runnable && FPlatformProcess::SupportsMultithreading()) - { - Runnable = new FSimplygonRESTClient(); - } - return Runnable; -} - -void FSimplygonRESTClient::Shutdown() -{ - if (Runnable) - { - Runnable->EnusureCompletion(); - delete Runnable; - Runnable = nullptr; - } -} - -void FSimplygonRESTClient::Stop() -{ - StopTaskCounter.Increment(); -} - -void FSimplygonRESTClient::EnusureCompletion() -{ - Stop(); - Thread->WaitForCompletion(); -} - -void FSimplygonRESTClient::AddSwarmTask(TSharedPtr& InTask) -{ - InTask->SetHost(HostName); - PendingJobs.Enqueue(InTask); -} - -void FSimplygonRESTClient::SetMaxUploadSizeInBytes(int32 InMaxUploadSizeInBytes) -{ - MaxUploadSizeInBytes = InMaxUploadSizeInBytes; -} - -void FSimplygonRESTClient::Exit() -{ - -} - diff --git a/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonSwarm.cpp b/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonSwarm.cpp deleted file mode 100644 index 0236ff7764d5..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Private/SimplygonSwarm.cpp +++ /dev/null @@ -1,1759 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#include "SimplygonSwarmCommon.h" -#include "SimplygonSwarmHelpers.h" -#include "SimplygonRESTClient.h" -#include "Modules/ModuleManager.h" -#include "UObject/Object.h" -#include "Misc/Paths.h" -#include "Misc/ScopedSlowTask.h" -#include "IImageWrapper.h" -#include "IImageWrapperModule.h" -#include "Editor/EditorPerProjectUserSettings.h" -#include "Misc/EngineVersion.h" -#include "Misc/MonitoredProcess.h" -#include "Templates/UniquePtr.h" -THIRD_PARTY_INCLUDES_START -#include -THIRD_PARTY_INCLUDES_END - -#define LOCTEXT_NAMESPACE "SimplygonSwarm" - -#include "IMeshReductionInterfaces.h" - -#include "MeshMergeData.h" -#include "Features/IModularFeatures.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* BASECOLOR_CHANNEL = TEXT("Basecolor"); -static const TCHAR* METALLIC_CHANNEL = TEXT("Metallic"); -static const TCHAR* SPECULAR_CHANNEL = TEXT("Specular"); -static const TCHAR* ROUGHNESS_CHANNEL = TEXT("Roughness"); -static const TCHAR* NORMAL_CHANNEL = TEXT("Normals"); -static const TCHAR* OPACITY_CHANNEL = TEXT("Opacity"); -static const TCHAR* EMISSIVE_CHANNEL = TEXT("Emissive"); -static const TCHAR* OPACITY_MASK_CHANNEL = TEXT("OpacityMask"); -static const TCHAR* AO_CHANNEL = TEXT("AmbientOcclusion"); -static const TCHAR* MATERIAL_MASK_CHANNEL = TEXT("MaterialMask"); -static const TCHAR* OUTPUT_LOD = TEXT("outputlod_0"); -static const TCHAR* SSF_FILE_TYPE = TEXT("ssf"); -static const TCHAR* REMESHING_PROCESSING_SETNAME = TEXT("RemeshingProcessingSet"); -static const TCHAR* CLIPPING_GEOMETRY_SETNAME = TEXT("ClippingObjectSet"); - - -#define SIMPLYGON_COLOR_CHANNEL "VertexColors" - -#define KEEP_SIMPLYGON_SWARM_TEMPFILES - -static const TCHAR* SG_UE_INTEGRATION_REV = TEXT("#SG_UE_INTEGRATION_REV"); - -#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 - -#ifdef __clang__ - #pragma clang diagnostic pop -#endif - -#define MAX_UPLOAD_PART_SIZE_MB 1024 -#define MAX_UPLOAD_PART_SIZE_BYTES ( MAX_UPLOAD_PART_SIZE_MB * 1024 * 1024 ) - -static const TCHAR SHADING_NETWORK_TEMPLATE[] = TEXT("\n\t\n\t\t\n\t\t\t1 1 1 1\n\t\t\n\t\t%s\n\t\t%s\n\t\t%d\n\t\t1.000000\n\t\t1.000000\n\t\n"); - -ssf::pssfMeshData CreateSSFMeshDataFromRawMesh(const FMeshDescription& InRawMesh, TArray InTextureBounds, TArray InTexCoords); - -class FSimplygonSwarmModule : 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; - -private: -}; - -DEFINE_LOG_CATEGORY_STATIC(LogSimplygonSwarm, Log, All); -IMPLEMENT_MODULE(FSimplygonSwarmModule, SimplygonSwarm); - -class FSimplygonSwarm - : public IMeshMerging -{ -public: - virtual ~FSimplygonSwarm() - { - } - - static FSimplygonSwarm* Create() - { - return new FSimplygonSwarm(); - } - - virtual FString GetName() override - { - return FString("SimplygonSwarm"); - } - - struct FMaterialCastingProperties - { - bool bCastMaterials; - bool bCastNormals; - bool bCastMetallic; - bool bCastRoughness; - bool bCastSpecular; - - FMaterialCastingProperties() - : bCastMaterials(false) - , bCastNormals(false) - , bCastMetallic(false) - , bCastRoughness(false) - , bCastSpecular(false) - { - } - }; - - /** - * Method used to generate ProxyLOD either using Remeshing or Aggregation - * @param InData Mesh Merge Data - * @param InProxySettings Settings to use for proxy generation - * @param InputMaterials Flattened materials - * @param InJobGUID Job GUID - */ - virtual void ProxyLOD(const TArray& InData, - const struct FMeshProxySettings& InProxySettings, - const TArray& InputMaterials, - const FGuid InJobGUID) - { - FScopedSlowTask SlowTask(3.f, (LOCTEXT("SimplygonSwarm_ProxyLOD", "Generating Proxy Mesh using Simplygon Swarm"))); - SlowTask.MakeDialog(); - - FMeshDescription OutProxyMesh; - FFlattenMaterial OutMaterial; - - //setup path variables - FString JobPath = FGuid::NewGuid().ToString(); - FString JobDirectory = FString::Printf(TEXT("%s%s"), *GetMutableDefault()->SwarmIntermediateFolder, *JobPath); - FString InputFolderPath = FString::Printf(TEXT("%s/Input"), *JobDirectory); - - FString ZipFileName = FString::Printf(TEXT("%s/%s.zip"), *JobDirectory, *JobPath); - FString OutputZipFileName = FString::Printf(TEXT("%s/%s_output.zip"), *JobDirectory, *JobPath); - FString SPLFileOutputFullPath = FString::Printf(TEXT("%s/input.spl"), *InputFolderPath); - FString SPLSettingsText; - - EBlendMode OutputMaterialBlendMode = BLEND_Opaque; - bool bHasMaked = false; - bool bHasOpacity = false; - - for (int MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - if (InputMaterials[MaterialIndex].BlendMode == BLEND_Translucent) - { - bHasOpacity = true; - } - - if (InputMaterials[MaterialIndex].BlendMode == BLEND_Masked) - { - bHasMaked = true; - } - } - - if ( (bHasMaked && bHasOpacity) || bHasOpacity) - { - OutputMaterialBlendMode = BLEND_Translucent; - } - else if (bHasMaked && !bHasOpacity) - { - OutputMaterialBlendMode = BLEND_Masked; - } - - //scan for clipping geometry - bool bHasClippingGeometry = false; - if (InData.FindByPredicate([](const FMeshMergeData InMeshData) {return InMeshData.bIsClippingMesh == true; })) - { - bHasClippingGeometry = true; - } - - SPL::SPL* spl = new SPL::SPL(); - spl->Header.ClientName = TCHAR_TO_ANSI(TEXT("UE4")); - spl->Header.ClientVersion = TCHAR_TO_ANSI(*FEngineVersion::Current().ToString()); - spl->Header.SimplygonVersion = TCHAR_TO_ANSI(TEXT("8.0")); - SPL::ProcessNode* splProcessNode = new SPL::ProcessNode(); - spl->ProcessGraph = splProcessNode; - - SlowTask.EnterProgressFrame(1.0f, LOCTEXT("SimplygonSwarm_CreateSPL", "Generating Simplygon Processing Settings")); - - CreateRemeshingProcess(InProxySettings, *splProcessNode, OutputMaterialBlendMode, bHasClippingGeometry); - - ssf::pssfScene SsfScene; - - TArray InputMeshes; - - for (auto Data : InData) - { - InputMeshes.Push(Data.RawMesh); - } - - bool bDiscardEmissive = true; - for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex]; - bDiscardEmissive &= ((!FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive) || (FlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive) && FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive)[0] == FColor::Black))); - } - - SlowTask.EnterProgressFrame(1.0f, LOCTEXT("SimplygonSwarm_GenerateData", "Generating Simplygon Processing Data")); - - //converts UE entities to ssf, Textures will be exported to file - ConvertMeshMergeDataToSsfScene(InData, InputMaterials, InProxySettings, InputFolderPath, SsfScene); - - SsfScene->CoordinateSystem->Value = 1; - SsfScene->WorldOrientation->Value = 3; - - FString SsfOuputPath = FString::Printf(TEXT("%s/input.ssf"), *InputFolderPath); - - //save out ssf file. - WriteSsfFile(SsfScene, SsfOuputPath); - - spl->Save(TCHAR_TO_ANSI(*SPLFileOutputFullPath)); - - SlowTask.EnterProgressFrame(1.0f, LOCTEXT("SimplygonSwarm_UploadData", "Uploading Processing Data to Simplygon Swarm Server")); - //zip contents and spawn a task - if (ZipContentsForUpload(InputFolderPath, ZipFileName)) - { - //validate if patch exist - if (!FPaths::FileExists(*FPaths::ConvertRelativePathToFull(ZipFileName))) - { - UE_LOG(LogSimplygonSwarm, Error , TEXT("Could not find zip file for uploading %s"), *ZipFileName); - FailedDelegate.ExecuteIfBound(InJobGUID, TEXT("Could not find zip file for uploading")); - return; //-V773 - } - - FSwarmTaskkData TaskData; - TaskData.ZipFilePath = ZipFileName; - TaskData.SplFilePath = SPLFileOutputFullPath; - TaskData.OutputZipFilePath = OutputZipFileName; - TaskData.JobDirectory = JobDirectory; - TaskData.StateLock = new FCriticalSection(); - TaskData.ProcessorJobID = InJobGUID; - TaskData.bDitheredTransition = (InputMaterials.Num() > 0) ? InputMaterials[0].bDitheredLODTransition : false; - TaskData.bEmissive = !bDiscardEmissive; - TaskData.JobName = InData[0].DebugJobName; - - int32 MaxUploadSizeInBytes = GetMutableDefault()->SwarmMaxUploadChunkSizeInMB * 1024 * 1024; - FSimplygonRESTClient::Get()->SetMaxUploadSizeInBytes(MaxUploadSizeInBytes); - TSharedPtr SwarmTask = MakeShareable(new FSimplygonSwarmTask(TaskData)); - SwarmTask->OnAssetDownloaded().BindRaw(this, &FSimplygonSwarm::ImportFile); - SwarmTask->OnAssetUploaded().BindRaw(this, &FSimplygonSwarm::Cleanup); - SwarmTask->OnSwarmTaskFailed().BindRaw(this, &FSimplygonSwarm::OnSimplygonSwarmTaskFailed); - FSimplygonRESTClient::Get()->AddSwarmTask(SwarmTask); - } - - delete spl; - } - - /** - * The following method is called when a swarm task fails. This forwards the call to external module - * @param InSwarmTask The completed swarm task - */ - void OnSimplygonSwarmTaskFailed(const FSimplygonSwarmTask& InSwarmTask) - { - FailedDelegate.ExecuteIfBound(InSwarmTask.TaskData.ProcessorJobID, TEXT("Simplygon Swarm Proxy Generation failed.")); - } - - /** - * Method to clean up temporary files after uploading the job to Simplygon Grid Server - * @param InSwarmTask The completed swarm task - */ - void Cleanup(const FSimplygonSwarmTask& InSwarmTask) - { - bool bDebuggingEnabled = GetDefault()->bEnableSwarmDebugging; - - if(!bDebuggingEnabled) - { - FString InputFolderPath = FPaths::ConvertRelativePathToFull(FString::Printf(TEXT("%s/Input"), *InSwarmTask.TaskData.JobDirectory)); - //remove folder folder - if (FPaths::DirectoryExists(InputFolderPath)) - { - if (!IFileManager::Get().DeleteDirectory(*InputFolderPath, true, true)) - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("Failed to remove simplygon swarm task temp directory %s"), *InputFolderPath); - } - } - FString FullZipPath = FPaths::ConvertRelativePathToFull(*InSwarmTask.TaskData.ZipFilePath); - //remove uploaded zip file - if (FPaths::FileExists(FullZipPath)) - { - if (!IFileManager::Get().Delete(*FullZipPath)) - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("Failed to remove Simplygon Swarm Task temp file %s"), *InSwarmTask.TaskData.ZipFilePath); - } - } - } - } - - - /** - * Fired when the Server returns the completed job to the client. Called from RESTClient - * @param InSwarmTask The completed swarm task - */ - void ImportFile(const FSimplygonSwarmTask& InSwarmTask) - { - - FMeshDescription OutProxyMesh; - FFlattenMaterial OutMaterial; - bool bDebuggingEnabled = GetDefault()->bEnableSwarmDebugging; - FString OutputFolderPath = FString::Printf(TEXT("%s/Output"), *InSwarmTask.TaskData.JobDirectory); - FString ParentDirForOutputSsf = FString::Printf(TEXT("%s/outputlod_0"), *OutputFolderPath); - - //for import the file back in uncomment - FString ZipFileFullPath = FPaths::ConvertRelativePathToFull(InSwarmTask.TaskData.OutputZipFilePath); - FString UnzipOutputFullPath = FPaths::ConvertRelativePathToFull(OutputFolderPath); - if (UnzipDownloadedContent(ZipFileFullPath, UnzipOutputFullPath)) - { - FString InOuputSsfPath = FString::Printf(TEXT("%s/output.ssf"), *ParentDirForOutputSsf); - ssf::pssfScene OutSsfScene = new ssf::ssfScene(); - FString SsfFullPath = FPaths::ConvertRelativePathToFull(InOuputSsfPath); - - if (!FPaths::FileExists(SsfFullPath)) - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("Ssf file not found %s"), *SsfFullPath); - FailedDelegate.ExecuteIfBound(InSwarmTask.TaskData.ProcessorJobID, TEXT("Ssf file not found")); - return; - } - - ReadSsfFile(SsfFullPath, OutSsfScene); - ConvertFromSsfSceneToRawMesh(OutSsfScene, OutProxyMesh, OutMaterial, ParentDirForOutputSsf); - OutMaterial.bDitheredLODTransition = InSwarmTask.TaskData.bDitheredTransition; - - if (!InSwarmTask.TaskData.bEmissive) - { - OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Empty(); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Emissive, FIntPoint(0,0)); - } - - if (!OutProxyMesh.VertexInstances().Num()) - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("RawMesh is invalid.")); - FailedDelegate.ExecuteIfBound(InSwarmTask.TaskData.ProcessorJobID, TEXT("Invalid FMeshDescription data")); - } - - - //do cleanup work - if (!bDebuggingEnabled) - { - FString FullOutputFolderPath = FPaths::ConvertRelativePathToFull(*OutputFolderPath); - if (!IFileManager::Get().DeleteDirectory(*FullOutputFolderPath, true, true)) - UE_LOG(LogSimplygonSwarm, Error, TEXT("Failed to remove simplygon swarm task temp directory %s"), *FullOutputFolderPath); - - FString FullOutputFileName = FPaths::ConvertRelativePathToFull(*InSwarmTask.TaskData.OutputZipFilePath); - //remove uploaded zip file - if (!IFileManager::Get().Delete(*FullOutputFileName, true, true, false)) - { - UE_LOG(LogSimplygonSwarm, Error, TEXT("Failed to remove Simplygon Swarm Task temp file %s"), *FullOutputFileName); - } - } - - //if is bound then execute - if (CompleteDelegate.IsBound()) - { - CompleteDelegate.Execute(OutProxyMesh, OutMaterial, InSwarmTask.TaskData.ProcessorJobID); - } - else - { - UE_LOG(LogSimplygonSwarm, Error, TEXT("No valid complete delegate is currently bounded. ")); - } - - } - else - { - UE_LOG(LogSimplygonSwarm, Log, TEXT("Failed to unzip downloaded content %s"), *ZipFileFullPath); - FailedDelegate.ExecuteIfBound(InSwarmTask.TaskData.ProcessorJobID, TEXT("Invalid FMeshDescription data")); - } - } - -private: - FString VersionString; - FSimplygonRESTClient* SgRESTInterface; - //FRunnableThread *Thread; - uint8 ToolMajorVersion; - uint8 ToolMinorVersion; - uint16 ToolBuildVersion; - - explicit FSimplygonSwarm() - { - VersionString = FString::Printf(TEXT("%s"), SG_UE_INTEGRATION_REV); - ToolMajorVersion = FEngineVersion::Current().GetMajor(); - ToolMinorVersion = FEngineVersion::Current().GetMinor(); - ToolBuildVersion = FEngineVersion::Current().GetPatch(); - } - - /** - * Read in ssf file from disk - * @param InSsfFilePath Ssf file to read in - * @param SsfScene SsfScene that the ssf file is read into - */ - void ReadSsfFile(FString InSsfFilePath, ssf::pssfScene& SsfScene) - { - ssf::ssfString ToolName = FSimplygonSSFHelper::TCHARToSSFString(TEXT("UE4")); - - ssf::ssfBinaryInputStream InputStream; - InputStream.OpenFile(FSimplygonSSFHelper::TCHARToSSFString(*InSsfFilePath)); - SsfScene->ReadFile(&InputStream, ToolName, ToolMajorVersion, ToolMinorVersion, ToolBuildVersion); - } - - /** - * Write out ssf scene to disk - * @param SsfScene SsfScene to write out - * @param InSsfFilePath Path to ssf file - */ - void WriteSsfFile(ssf::pssfScene SsfScene, FString InSsfFilePath) - { - ssf::ssfString ToolName = FSimplygonSSFHelper::TCHARToSSFString(TEXT("UE4")); - ssf::ssfBinaryOutputStream theOutputStream; - theOutputStream.OpenFile(FSimplygonSSFHelper::TCHARToSSFString(*InSsfFilePath)); - SsfScene->WriteFile(&theOutputStream, ToolName, ToolMajorVersion, ToolMinorVersion, ToolBuildVersion); - theOutputStream.CloseFile(); - } - - /** - * Setup spl mapping image object used for material baking - * @param InMaterialProxySettings Proxy Settings to use for setting up remeshing process node - * @param InMappingImageSettings Mapping image setting object - */ - void SetupSplMappingImage(const struct FMaterialProxySettings& InMaterialProxySettings, SPL::MappingImageSettings& InMappingImageSettings) - { - FIntPoint ImageSizes = ComputeMappingImageSize(InMaterialProxySettings); - bool bAutomaticTextureSize = InMaterialProxySettings.TextureSizingType == TextureSizingType_UseSimplygonAutomaticSizing; - - InMappingImageSettings.GenerateMappingImage = true; - InMappingImageSettings.GutterSpace = InMaterialProxySettings.GutterSpace; - InMappingImageSettings.UseAutomaticTextureSize = bAutomaticTextureSize; - InMappingImageSettings.Height = ImageSizes.X; - InMappingImageSettings.Width = ImageSizes.Y; - InMappingImageSettings.UseFullRetexturing = true; - InMappingImageSettings.GenerateTangents = true; - InMappingImageSettings.GenerateTexCoords = true; - InMappingImageSettings.TexCoordLevel = 255; - InMappingImageSettings.MultisamplingLevel = 3; - InMappingImageSettings.TexCoordGeneratorType = SPL::TexCoordGeneratorType::SG_TEXCOORDGENERATORTYPE_PARAMETERIZER; - InMappingImageSettings.Enabled = true; - - } - - /** - * Create Spl Process node for Remeshing - * @param InProxySettings Proxy Settings to use for setting up remeshing process node - * @param InProcessNodeSpl SplProcessNode object - * @param InOutputMaterialBlendMode Output Material Blend mode - * @param InHasClippingGeometry Weather or the scene being processed has clipping geometry - */ - void CreateRemeshingProcess(const struct FMeshProxySettings& InProxySettings, SPL::ProcessNode& InProcessNodeSpl, EBlendMode InOutputMaterialBlendMode = BLEND_Opaque, bool InHasClippingGeometry = false) - { - SPL::RemeshingProcessor* processor = new SPL::RemeshingProcessor(); - processor->RemeshingSettings = new SPL::RemeshingSettings(); - - processor->RemeshingSettings->OnScreenSize = InProxySettings.ScreenSize; - processor->RemeshingSettings->SurfaceTransferMode = SPL::SurfaceTransferMode::SG_SURFACETRANSFER_ACCURATE; - processor->RemeshingSettings->ProcessSelectionSetName = TCHAR_TO_ANSI(REMESHING_PROCESSING_SETNAME); - - if (InHasClippingGeometry) - { - processor->RemeshingSettings->UseClippingGeometryEmptySpaceOverride = false; - processor->RemeshingSettings->UseClippingGeometry = InHasClippingGeometry; - processor->RemeshingSettings->ClippingGeometrySelectionSetName = TCHAR_TO_ANSI(CLIPPING_GEOMETRY_SETNAME); - } - - if (InProxySettings.bRecalculateNormals) - processor->RemeshingSettings->HardEdgeAngleInRadians = FMath::DegreesToRadians(InProxySettings.HardAngleThreshold); - - processor->RemeshingSettings->MergeDistance = InProxySettings.MergeDistance; - processor->RemeshingSettings->Enabled = true; - - FIntPoint ImageSizes = ComputeMappingImageSize(InProxySettings.MaterialSettings); - - //mapping image settings - processor->MappingImageSettings = new SPL::MappingImageSettings(); - SetupSplMappingImage(InProxySettings.MaterialSettings, *processor->MappingImageSettings); - - SetupSplMaterialCasters(InProxySettings.MaterialSettings, InProcessNodeSpl, InOutputMaterialBlendMode); - - InProcessNodeSpl.Processor = processor; - InProcessNodeSpl.DefaultTBNType = SPL::SG_TANGENTSPACEMETHOD_ORTHONORMAL_LEFTHANDED; - - SPL::WriteNode* splWriteNode = new SPL::WriteNode(); - splWriteNode->Format = TCHAR_TO_ANSI(SSF_FILE_TYPE); - splWriteNode->Name = TCHAR_TO_ANSI(OUTPUT_LOD); - - - InProcessNodeSpl.Children.push_back(splWriteNode); - } - - /** - * Create Spl Process node for Remeshing - * @param InSplText Save SPL Text - * @param InOutputFilePath SplProcessNode object - */ - void SaveSPL(FString InSplText, FString InOutputFilePath) - { - FArchive* SPLFile = IFileManager::Get().CreateFileWriter(*InOutputFilePath); - SPLFile->Logf(TEXT("%s"), *InSplText); - SPLFile->Close(); - } - - /** - * Convert collection of FMeshMergeData to SsfScene - * @param InMeshMergeData Meshes to merge - * @param InputMaterials Flattened Materials - * @param InProxySettings Proxy Settings - * @param InputFolderPath Input Folder Path - * @param OutSsfScene Out SsfScene - */ - void ConvertMeshMergeDataToSsfScene(const TArray& InMeshMergeData, - const TArray& InputMaterials, - const struct FMeshProxySettings& InProxySettings, FString InputFolderPath, ssf::pssfScene& OutSsfScene) - { - //create the ssf scene - OutSsfScene = new ssf::ssfScene(); - - OutSsfScene->CoordinateSystem.Set(1); - OutSsfScene->WorldOrientation.Set(2); - OutSsfScene->TextureTable->TexturesDirectory.Set(FSimplygonSSFHelper::TCHARToSSFString(TEXT("/Textures"))); - - //set processing and clipping geometry sets - - //processing set - ssf::ssfNamedIdList ProcessingObjectsSet; - ssf::ssfNamedIdList ClippingGeometrySet; - - ProcessingObjectsSet.Name = FSimplygonSSFHelper::TCHARToSSFString(REMESHING_PROCESSING_SETNAME); - ProcessingObjectsSet.ID = FSimplygonSSFHelper::SSFNewGuid(); - ClippingGeometrySet.Name = FSimplygonSSFHelper::TCHARToSSFString(CLIPPING_GEOMETRY_SETNAME); - ClippingGeometrySet.ID = FSimplygonSSFHelper::SSFNewGuid(); - - - TMap MaterialMap; - - CreateSSFMaterialFromFlattenMaterial(InputMaterials, InProxySettings.MaterialSettings, OutSsfScene->MaterialTable, OutSsfScene->TextureTable, InputFolderPath, true, MaterialMap); - - //create the root node - ssf::pssfNode SsfRootNode = new ssf::ssfNode(); - SsfRootNode->Id.Set(FSimplygonSSFHelper::SSFNewGuid()); - SsfRootNode->ParentId.Set(FSimplygonSSFHelper::SFFEmptyGuid()); - - //add root node to scene - OutSsfScene->NodeTable->NodeList.push_back(SsfRootNode); - - int32 Count = 0; - for (FMeshMergeData MergeData : InMeshMergeData) - { - //create a the node that will contain the mesh - ssf::pssfNode SsfNode = new ssf::ssfNode(); - SsfNode->Id.Set(FSimplygonSSFHelper::SSFNewGuid()); - SsfNode->ParentId.Set(SsfRootNode->Id.Get()); - FString NodeName = FString::Printf(TEXT("Node%i"), Count); - - SsfNode->Name.Set(FSimplygonSSFHelper::TCHARToSSFString(*NodeName)); - ssf::ssfMatrix4x4 IdenMatrix; - IdenMatrix.M[0][0] = IdenMatrix.M[1][1] = IdenMatrix.M[2][2] = IdenMatrix.M[3][3] = 1; - SsfNode->LocalTransform.Set(IdenMatrix); - - //create the mesh object - ssf::pssfMesh SsfMesh = new ssf::ssfMesh(); - SsfMesh->Id.Set(FSimplygonSSFHelper::SSFNewGuid()); - FString MeshName = FString::Printf(TEXT("Mesh%i"), Count); - SsfMesh->Name.Set(FSimplygonSSFHelper::TCHARToSSFString(*MeshName)); - - Count++; - - //setup mesh data - ssf::pssfMeshData SsfMeshData = CreateSSFMeshDataFromRawMesh(*MergeData.RawMesh, MergeData.TexCoordBounds, MergeData.NewUVs); - SsfMesh->MeshDataList.push_back(SsfMeshData); - - //setup mesh material information - SsfMesh->MaterialIds.Create(); - TArray UniqueMaterialIds; - UniqueMaterialIds.Reserve(InputMaterials.Num()); - - //get unqiue material ids - GetUniqueMaterialIndices(*(MergeData.RawMesh), UniqueMaterialIds); - - SsfMesh->MaterialIds->Items.reserve(UniqueMaterialIds.Num()); - - TMap GlobalToLocal; - //map ssfmesh local materials - for (int32 GlobalMaterialId : UniqueMaterialIds) - { - SsfMesh->MaterialIds->Items.push_back(FSimplygonSSFHelper::TCHARToSSFString(*MaterialMap[GlobalMaterialId])); - int32 localIndex = SsfMesh->MaterialIds->Items.size() - 1; - //replace - GlobalToLocal.Add(GlobalMaterialId, localIndex); - } - - for (ssf::pssfMeshData MeshData : SsfMesh->MeshDataList) - { - for (int Index = 0; Index < MeshData->MaterialIndices.Get().Items.size(); Index++) - { - MeshData->MaterialIndices.Get().Items[Index] = GlobalToLocal[MeshData->MaterialIndices.Get().Items[Index]]; - } - } - - //link mesh to node - SsfNode->MeshId.Set(SsfMesh->Id.Get().Value); - - //add mesh and node to their respective tables - OutSsfScene->NodeTable->NodeList.push_back(SsfNode); - OutSsfScene->MeshTable->MeshList.push_back(SsfMesh); - - if (MergeData.bIsClippingMesh) - { - ClippingGeometrySet.Items.push_back(SsfNode->Id->ToCharString()); - } - else - { - ProcessingObjectsSet.Items.push_back(SsfNode->Id->ToCharString()); - } - - - } - - if(ClippingGeometrySet.Items.size() > 0) - OutSsfScene->SelectionGroupSetsList.push_back(ClippingGeometrySet); - - if (ProcessingObjectsSet.Items.size() > 0) - OutSsfScene->SelectionGroupSetsList.push_back(ProcessingObjectsSet); - - } - - /** - * Convert SsfScnee to RawMesh. Currently assumes that only a single mesh will be present in the SsfScene - * @param SsfScene SsfScene - * @param OutProxyMesh Converted SsfMeshData to RawMesh - * @param OutMaterial Converted SsfMaterial to Flattened Material - * @param BaseTexturesPath Base Path for textures - */ - void ConvertFromSsfSceneToRawMesh(ssf::pssfScene SsfScene, FMeshDescription& OutProxyMesh, FFlattenMaterial& OutMaterial, const FString BaseTexturesPath) - { - TVertexAttributesRef VertexPositions = OutProxyMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - TEdgeAttributesRef EdgeHardnesses = OutProxyMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); - TEdgeAttributesRef EdgeCreaseSharpnesses = OutProxyMesh.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::CreaseSharpness); - TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = OutProxyMesh.PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - TVertexInstanceAttributesRef VertexInstanceNormals = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - TVertexInstanceAttributesRef VertexInstanceTangents = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesRef VertexInstanceBinormalSigns = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - TVertexInstanceAttributesRef VertexInstanceColors = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - TVertexInstanceAttributesRef VertexInstanceUVs = OutProxyMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - - bool bReverseWinding = true; - - for (ssf::pssfMesh Mesh : SsfScene->MeshTable->MeshList) - { - //extract geometry data - for (ssf::pssfMeshData MeshData : Mesh->MeshDataList) - { - int32 TotalVertices = MeshData->GetVerticesCount(); - int32 TotalCorners = MeshData->GetCornersCount(); - int32 TotalTriangles = MeshData->GetTrianglesCount(); - - //Assuming only one mesh - OutProxyMesh.Empty(); - OutProxyMesh.ReserveNewVertices(TotalVertices); - OutProxyMesh.ReserveNewPolygons(TotalTriangles); - OutProxyMesh.ReserveNewVertexInstances(TotalCorners); - OutProxyMesh.ReserveNewEdges(TotalCorners); - - TMap SsfToMeshDescriptionVertexID; - SsfToMeshDescriptionVertexID.Reserve(TotalVertices); - int32 VertexIndex = 0; - for (ssf::ssfVector3 VertexCoord : MeshData->Coordinates.Get().Items) - { - const FVertexID VertexID = OutProxyMesh.CreateVertex(); - VertexPositions[VertexID] = GetConversionMatrixYUP().InverseTransformPosition(FVector(VertexCoord.V[0], VertexCoord.V[1], VertexCoord.V[2])); - SsfToMeshDescriptionVertexID.Add(VertexIndex, VertexID); - VertexIndex++; - } - - //Prepare the tex coord - int32 TexCoordIndex = 0; - ssf::ssfNamedList BakedMaterialUVs = FSimplygonSSFHelper::GetBakedMaterialUVs(MeshData->TextureCoordinatesList); - VertexInstanceUVs.SetNumIndices(1); - - //Is buffer has some data? - bool Normals = !MeshData->Normals.IsEmpty() && MeshData->Normals.Get().Items.size() > 0; - bool Tangents = !MeshData->Tangents.IsEmpty() && MeshData->Tangents.Get().Items.size() > 0; - bool Bitangents = !MeshData->Bitangents.IsEmpty() && MeshData->Bitangents.Get().Items.size() > 0; - bool MaterialIndices = !MeshData->MaterialIndices.IsEmpty() && MeshData->MaterialIndices.Get().Items.size() > 0; - bool GroupIds = !MeshData->SmoothingGroup.IsEmpty() && MeshData->SmoothingGroup.Get().Items.size() > 0; - - //Setup PolygonGroup - //Prepare the polygongroup - TMap SsfToRawMaterial; - for (int32 TriIndex = 0; TriIndex < TotalTriangles; ++TriIndex) - { - int32 MaterialIndex = MaterialIndices ? (int32)MeshData->MaterialIndices.Get().Items[TriIndex].Value : 0; - if (!SsfToRawMaterial.Contains(MaterialIndex)) - { - const FPolygonGroupID PolygonGroupID(MaterialIndex); - OutProxyMesh.CreatePolygonGroupWithID(PolygonGroupID); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString(TEXT("SimplygonSwarm_") + FString::FromInt(PolygonGroupID.GetValue()))); - SsfToRawMaterial.Add(MaterialIndex, PolygonGroupID); - } - } - - for (int32 TriIndex = 0; TriIndex < TotalTriangles; ++TriIndex) - { - FVertexID VertexIndexes[3]; - FVertexInstanceID VertexInstanceIDs[3]; - for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) - { - int32 SrcCornerIndex = bReverseWinding ? 2 - CornerIndex : CornerIndex; - int32 SrcIndex = (3 * TriIndex) + SrcCornerIndex; - VertexIndexes[CornerIndex] = SsfToMeshDescriptionVertexID[MeshData->TriangleIndices.Get().Items[TriIndex].V[SrcCornerIndex]]; - VertexInstanceIDs[CornerIndex] = OutProxyMesh.CreateVertexInstance(VertexIndexes[CornerIndex]); - - //Texture Coordinates, copy baked material UV's only discard the rest - VertexInstanceUVs.Get(VertexInstanceIDs[CornerIndex], 0) = FVector2D(BakedMaterialUVs.Items[SrcIndex].V[0], BakedMaterialUVs.Items[SrcIndex].V[1]); - - //Vertex Color, SSF can store multiple color channels. However UE only supports one color channel - for (ssf::ssfNamedList TexColorChannel : MeshData->ColorsList) - { - VertexInstanceColors[VertexInstanceIDs[CornerIndex]] = FVector4(TexColorChannel.Items[SrcIndex].V[0], TexColorChannel.Items[SrcIndex].V[1], TexColorChannel.Items[SrcIndex].V[2], TexColorChannel.Items[SrcIndex].V[3]); - break; //UE support only one - } - - //Tangents - if (Normals) - { - FVector NormalValue = FVector(MeshData->Normals.Get().Items[SrcIndex].V[0], MeshData->Normals.Get().Items[SrcIndex].V[1], MeshData->Normals.Get().Items[SrcIndex].V[2]); - NormalValue = GetConversionMatrixYUP().InverseTransformPosition(NormalValue); - VertexInstanceNormals[VertexInstanceIDs[CornerIndex]] = NormalValue; - if (Tangents && Bitangents) - { - FVector TangentValue = FVector(MeshData->Tangents.Get().Items[SrcIndex].V[0], MeshData->Tangents.Get().Items[SrcIndex].V[1], MeshData->Tangents.Get().Items[SrcIndex].V[2]); - TangentValue = GetConversionMatrixYUP().InverseTransformPosition(TangentValue); - VertexInstanceTangents[VertexInstanceIDs[CornerIndex]] = TangentValue; - - FVector BiTangentValue = FVector(MeshData->Bitangents.Get().Items[SrcIndex].V[0], MeshData->Bitangents.Get().Items[SrcIndex].V[1], MeshData->Bitangents.Get().Items[SrcIndex].V[2]); - BiTangentValue = GetConversionMatrixYUP().InverseTransformPosition(BiTangentValue); - VertexInstanceBinormalSigns[VertexInstanceIDs[CornerIndex]] = GetBasisDeterminantSign(TangentValue.GetSafeNormal(), BiTangentValue.GetSafeNormal(), NormalValue.GetSafeNormal()); - } - } - } - //Create a polygon from this triangle - TArray Contours; - for (int32 Corner = 0; Corner < 3; ++Corner) - { - int32 ContourPointIndex = Contours.AddDefaulted(); - FMeshDescription::FContourPoint& ContourPoint = Contours[ContourPointIndex]; - //Find the matching edge ID - uint32 CornerIndices[2]; - CornerIndices[0] = (Corner + 0) % 3; - CornerIndices[1] = (Corner + 1) % 3; - - FVertexID EdgeVertexIDs[2]; - EdgeVertexIDs[0] = VertexIndexes[CornerIndices[0]]; - EdgeVertexIDs[1] = VertexIndexes[CornerIndices[1]]; - - FEdgeID MatchEdgeId = OutProxyMesh.GetVertexPairEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]); - if (MatchEdgeId == FEdgeID::Invalid) - { - MatchEdgeId = OutProxyMesh.CreateEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]); - EdgeHardnesses[MatchEdgeId] = false; - EdgeCreaseSharpnesses[MatchEdgeId] = 0.0f; - } - ContourPoint.EdgeID = MatchEdgeId; - ContourPoint.VertexInstanceID = VertexInstanceIDs[CornerIndices[0]]; - } - // Insert a polygon into the mesh - const FPolygonID NewPolygonID = OutProxyMesh.CreatePolygon(SsfToRawMaterial[MeshData->MaterialIndices.Get().Items[TriIndex].Value], Contours); - //Triangulate the polygon - FMeshPolygon& Polygon = OutProxyMesh.GetPolygon(NewPolygonID); - OutProxyMesh.ComputePolygonTriangulation(NewPolygonID, Polygon.Triangles); - } - - TArray FaceSmoothingMasks; - FaceSmoothingMasks.AddZeroed(TotalTriangles); - if (GroupIds) - { - for (int32 TriIndex = 0; TriIndex < TotalTriangles; ++TriIndex) - { - FaceSmoothingMasks[TriIndex] = MeshData->SmoothingGroup.Get().Items[TriIndex].Value; - } - } - FMeshDescriptionOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, OutProxyMesh); - } - - - - //since its a proxy will only contain one material on it - ssf::ssfString ProxyMaterialGuid = Mesh->MaterialIds.Get().Items[0].Value; - ssf::pssfMaterial ProxyMaterial = FSimplygonSSFHelper::FindMaterialById(SsfScene, ProxyMaterialGuid); - if (ProxyMaterial != nullptr) - { - SetupMaterial(SsfScene, ProxyMaterial, OutMaterial, BaseTexturesPath); - } - } - } - - /** - * Extracts texture from a material channel's textures. Currently only returns one Samples - * @param SsfMaterialChannel SsfMaterialChannel pointer - * @param BaseTexturesPath Base folder path where textures are located - * @param ChannelName Channel name - * @param OutSamples Out Pixel samples from the texture - * @param OutTextureSize Out TextureSizes - */ - void ExtractTextureDescriptors(ssf::pssfScene SceneGraph, - ssf::pssfMaterialChannel SsfMaterialChannel, - FString BaseTexturesPath, - FString ChannelName, - TArray& OutSamples, - FIntPoint& OutTextureSize) - { - for (ssf::pssfMaterialChannelTextureDescriptor TextureDescriptor : SsfMaterialChannel->MaterialChannelTextureDescriptorList) - { - ssf::pssfTexture Texture = FSimplygonSSFHelper::FindTextureById(SceneGraph, TextureDescriptor->TextureID.Get().Value); - - if (Texture != nullptr) - { - FString TextureFilePath = FString::Printf(TEXT("%s/%s"), *BaseTexturesPath, ANSI_TO_TCHAR(Texture->Path.Get().Value.c_str())); - CopyTextureData(OutSamples, OutTextureSize, ChannelName, TextureFilePath); - } - } - } - - /** - * Setup material will extract material information from SsfMaterial and create a flattened material from it. - * @param InSsfScene Base folder path where textures are located - * @param InSsfMaterial Channel name - * @param OutMaterial Out Pixel samples from the texture - * @param InBaseTexturesPath Out TextureSizes - */ - void SetupMaterial(ssf::pssfScene SceneGraph, ssf::pssfMaterial InSsfMaterial, FFlattenMaterial &OutMaterial, FString InBaseTexturesPath) - { - - bool bHasOpacityMask = false; - bool bHasOpacity = false; - for (ssf::pssfMaterialChannel Channel : InSsfMaterial->MaterialChannelList) - { - const FString ChannelName(ANSI_TO_TCHAR(Channel->ChannelName.Get().Value.c_str())); - - if (ChannelName.Compare(BASECOLOR_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, Size); - } - else if (ChannelName.Compare(NORMAL_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Normal); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Normal, Size); - } - else if (ChannelName.Compare(SPECULAR_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Specular); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Specular, Size); - } - else if (ChannelName.Compare(ROUGHNESS_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness),Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Roughness, Size); - } - else if (ChannelName.Compare(METALLIC_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Metallic, Size); - } - else if (ChannelName.Compare(OPACITY_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), Size); - bHasOpacity = true; - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Opacity, Size); - } - else if (ChannelName.Compare(OPACITY_MASK_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), Size); - bHasOpacityMask = true; - OutMaterial.SetPropertySize(EFlattenMaterialProperties::OpacityMask, Size); - }/**/ - else if (ChannelName.Compare(AO_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::AmbientOcclusion); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::AmbientOcclusion), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::AmbientOcclusion, Size); - } - else if (ChannelName.Compare(EMISSIVE_CHANNEL) == 0) - { - FIntPoint Size = OutMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive); - ExtractTextureDescriptors(SceneGraph, Channel, InBaseTexturesPath, ChannelName, OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), Size); - OutMaterial.SetPropertySize(EFlattenMaterialProperties::Emissive, Size); - } - } - - if ( (bHasOpacity && bHasOpacityMask) || bHasOpacity) - { - OutMaterial.BlendMode = BLEND_Translucent; - } - else if (bHasOpacityMask) - { - OutMaterial.BlendMode = BLEND_Masked; - } - - //NOTE: this feature is provided in the advance integration. - // Simplygon can bake both worldspace and tangentspace normal maps. - // worldspace normal maps are better in certain cases. - // We will move the functionality in a separate CL. - - //OutMaterial.bTangentspaceNormalmap = InSsfMaterial->TangentSpaceNormals->Value; - } - - /** - * Wrapper method which calls UAT with ZipUtils to unzip files. - * @param ZipFileName Path to zip file to be extracted. - * @param OutputFolderPath Path to folder where the contents of the zip will be extracted. - */ - bool UnzipDownloadedContent(FString ZipFileName, FString OutputFolderPath) - { - if (!FPaths::FileExists(FPaths::ConvertRelativePathToFull(ZipFileName))) - { - return false; - } - - FString CmdExe = TEXT("cmd.exe"); - - bool bEnableDebugging = GetDefault()->bEnableSwarmDebugging; - - FString CommandLine = FString::Printf(TEXT("ZipUtils -archive=\"%s\" -extract=\"%s\" -nocompile"), *ZipFileName, *OutputFolderPath); - UatTask(CommandLine); - - return true; - } - - /** - * Wrapper method which call UAT with the ZipUtils to zip files. - * @param InputDirectoryPath Directory to zip - * @param OutputFileName Output zipfile path - */ -bool ZipContentsForUpload(FString InputDirectoryPath, FString OutputFileName) -{ - bool bEnableDebugging = GetDefault()->bEnableSwarmDebugging; - FString CmdExe = TEXT("cmd.exe"); - FString CommandLine = FString::Printf(TEXT("ZipUtils -archive=\"%s\" -add=\"%s\" -compression=0 -nocompile"), *FPaths::ConvertRelativePathToFull(OutputFileName), *FPaths::ConvertRelativePathToFull(InputDirectoryPath)); - UE_CLOG(bEnableDebugging, LogSimplygonSwarm, Log, TEXT("Uat command line %s"), *CommandLine); - - return UatTask(CommandLine); -} - - /** - * Takes in a UAT Command and executes it. Is based on MainFrameAction CreateUatTask. A very minimalistic version. - * @param CommandLine Commandline argument to run against RunUAT.bat - */ - bool UatTask(FString CommandLine) - { -#if PLATFORM_WINDOWS - FString RunUATScriptName = TEXT("RunUAT.bat"); - FString CmdExe = TEXT("cmd.exe"); -#elif PLATFORM_LINUX - FString RunUATScriptName = TEXT("RunUAT.sh"); - FString CmdExe = TEXT("/bin/bash"); -#else - FString RunUATScriptName = TEXT("RunUAT.command"); - FString CmdExe = TEXT("/bin/sh"); -#endif - const bool bEnableDebugging = GetDefault()->bEnableSwarmDebugging; - - const FString UatPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Build/BatchFiles") / RunUATScriptName); - - if (!FPaths::FileExists(UatPath)) - { - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("File"), FText::FromString(UatPath)); - FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("RequiredFileNotFoundMessage", "A required file could not be found:\n{File}"), Arguments)); - - return false; - } - -#if PLATFORM_WINDOWS - FString FullCommandLine = FString::Printf(TEXT("/c \"\"%s\" %s\""), *UatPath, *CommandLine); -#else - FString FullCommandLine = FString::Printf(TEXT("\"%s\" %s"), *UatPath, *CommandLine); -#endif - while (FPlatformProcess::IsApplicationRunning(TEXT("AutomationTool.exe"))) - { - static const float SleepTime = 0.5f; - FPlatformProcess::Sleep(SleepTime); - UE_CLOG(bEnableDebugging, LogSimplygonSwarm, Log, TEXT("UAT already running sleeping for %f seconds"), SleepTime); - } - - TSharedPtr UatProcess = MakeShareable(new FMonitoredProcess(CmdExe, FullCommandLine, true)); - UatProcess->SetSleepInterval(0.1f); - - // create notification item - - const bool bLaunched = UatProcess->Launch(); - - UatProcess->OnOutput().BindLambda([&](FString Message) {UE_CLOG(bEnableDebugging, LogSimplygonSwarm, Log, TEXT("UatTask Output %s"), *Message); }); - - while (UatProcess->Update()) {} - - return bLaunched; - - } - - /** - * Get Unique Mateiral Inidices - * @param OriginalMaterialIds Original Material Indicies - * @param ChannelUniqueMaterialIds OutUniqueMaterialIds - */ - void GetUniqueMaterialIndices(const FMeshDescription& MeshDescription, TArray& UniqueMaterialIds) - { - int32 index = 0; - for (const FPolygonGroupID PolygonGroupID : MeshDescription.PolygonGroups().GetElementIDs()) - { - UniqueMaterialIds.AddUnique(index); - index++; - } - } - - struct FSkeletalMeshData - { - TArray Influences; - TArray Wedges; - TArray Faces; - TArray Points; - uint32 TexCoordCount; - }; - - /** - * Method to setup a color caster spl object and attach it to the given process node. - * @param InSplProcessNode SplProcessNode to attach the caster to. - * @param Channel Channel name to cast (i.e Basecolor, Specular, Roughness) - */ - void SetupColorCaster(SPL::ProcessNode& InSplProcessNode, FString Channel) - { - SPL::ColorCaster* colorCaster = new SPL::ColorCaster(); - colorCaster->Dilation = 10; - colorCaster->OutputChannels = 4; - colorCaster->OutputSRGB = false; - colorCaster->FillMode = SPL::FillMode::SG_ATLASFILLMODE_INTERPOLATE; - colorCaster->ColorType = TCHAR_TO_ANSI(*Channel); - colorCaster->Name = TCHAR_TO_ANSI(*Channel); - colorCaster->Channel = TCHAR_TO_ANSI(*Channel); - colorCaster->DitherType = SPL::DitherType::SG_DITHERPATTERNS_FLOYDSTEINBERG; - //for spl we need to expliclity set the enabled flag. - colorCaster->Enabled = true; - - InSplProcessNode.MaterialCaster.push_back(colorCaster); - } - - /** - * Method to setup a normal caster spl object and attach it to the given process node. - * Note : You can use this method to define custom normal channels as well. - * @param InSplProcessNode SplProcessNode to attach the caster to. - * @param Channel Channel name to cast (i.e Normal) - * @param bTangentspaceNormals Channel name to cast (i.e Normal) - */ - void SetupNormalCaster(SPL::ProcessNode& InSplProcessNode, FString Channel, bool bTangentspaceNormals = true) - { - SPL::NormalCaster* normalCaster = new SPL::NormalCaster(); - normalCaster->Name = TCHAR_TO_ANSI(*Channel); - normalCaster->Channel = TCHAR_TO_ANSI(*Channel); - normalCaster->GenerateTangentSpaceNormals = bTangentspaceNormals; - normalCaster->OutputChannels = 3; - normalCaster->Dilation = 10; - normalCaster->FlipGreen = false; - normalCaster->FillMode = SPL::FillMode::SG_ATLASFILLMODE_NEARESTNEIGHBOR; - normalCaster->DitherType = SPL::DitherType::SG_DITHERPATTERNS_NO_DITHER; - normalCaster->Enabled = true; - - InSplProcessNode.MaterialCaster.push_back(normalCaster); - } - - /** - * Method to setup a normal caster spl object and attach it to the given process node. - * Note : You can use this method to define custom normal channels as well. - * @param InSplProcessNode SplProcessNode to attach the caster to. - * @param Channel Channel name to cast (i.e Normal) - * @param bTangentspaceNormals Channel name to cast (i.e Normal) - */ - void SetupOpacityCaster(SPL::ProcessNode& InSplProcessNode, FString Channel) - { - SPL::OpacityCaster* opacityCaster = new SPL::OpacityCaster(); - opacityCaster->Dilation = 10; - opacityCaster->OutputChannels = 4; - opacityCaster->FillMode = SPL::FillMode::SG_ATLASFILLMODE_INTERPOLATE; - opacityCaster->ColorType = TCHAR_TO_ANSI(*Channel); - opacityCaster->Name = TCHAR_TO_ANSI(*Channel); - opacityCaster->Channel = TCHAR_TO_ANSI(*Channel); - opacityCaster->DitherType = SPL::DitherType::SG_DITHERPATTERNS_FLOYDSTEINBERG; - - //for spl we need to expliclity set the enabled flag. - opacityCaster->Enabled = true; - - InSplProcessNode.MaterialCaster.push_back(opacityCaster); - } - - /** - * Setup Material caster for a spl process node - * @param InMaterialProxySettings Material proxy settings - * @param InSplProcessNode SplProcess node to attach casters to - * @param InOutputMaterialBlendMode EBlendMode (Opaque, Translucent, Masked) are supported - * @returns The calculated view distance - */ - void SetupSplMaterialCasters(const FMaterialProxySettings& InMaterialProxySettings, SPL::ProcessNode& InSplProcessNode, EBlendMode InOutputMaterialBlendMode = BLEND_Opaque) - { - SetupColorCaster(InSplProcessNode, BASECOLOR_CHANNEL); - - if (InMaterialProxySettings.bRoughnessMap) - { - SetupColorCaster(InSplProcessNode, ROUGHNESS_CHANNEL); - } - if (InMaterialProxySettings.bSpecularMap) - { - SetupColorCaster(InSplProcessNode, SPECULAR_CHANNEL); - } - if (InMaterialProxySettings.bMetallicMap) - { - SetupColorCaster(InSplProcessNode, METALLIC_CHANNEL); - } - - if (InMaterialProxySettings.bNormalMap) - { - SetupNormalCaster(InSplProcessNode, NORMAL_CHANNEL, true/*InMaterialProxySettings.bUseTangentSpace*/); - } - - if (InMaterialProxySettings.bOpacityMap) - { - SetupOpacityCaster(InSplProcessNode, OPACITY_CHANNEL); - } - else if (InMaterialProxySettings.bOpacityMaskMap) - { - SetupColorCaster(InSplProcessNode, OPACITY_MASK_CHANNEL); - } - - //NOTE: Enable this block once AO feature is moved into vanilla integration. - if (InMaterialProxySettings.bAmbientOcclusionMap) - { - SetupColorCaster(InSplProcessNode, AO_CHANNEL); - } - - if (InMaterialProxySettings.bEmissiveMap) - { - SetupColorCaster(InSplProcessNode, EMISSIVE_CHANNEL); - } - } - - /** - * Calculates the view distance that a mesh should be displayed at. - * @param MaxDeviation - The maximum surface-deviation between the reduced geometry and the original. This value should be acquired from Simplygon - * @returns The calculated view distance - */ - float CalculateViewDistance( float MaxDeviation ) - { - // We want to solve for the depth in world space given the screen space distance between two pixels - // - // Assumptions: - // 1. There is no scaling in the view matrix. - // 2. The horizontal FOV is 90 degrees. - // 3. The backbuffer is 1920x1080. - // - // If we project two points at (X,Y,Z) and (X',Y,Z) from view space, we get their screen - // space positions: (X/Z, Y'/Z) and (X'/Z, Y'/Z) where Y' = Y * AspectRatio. - // - // The distance in screen space is then sqrt( (X'-X)^2/Z^2 + (Y'-Y')^2/Z^2 ) - // or (X'-X)/Z. This is in clip space, so PixelDist = 1280 * 0.5 * (X'-X)/Z. - // - // Solving for Z: ViewDist = (X'-X * 640) / PixelDist - - const float ViewDistance = (MaxDeviation * 960.0f); - return ViewDistance; - } - - /** - * Compute mapping image size from the given material proxy settings - * @param Settings Material Proxy Settings - */ - static FIntPoint ComputeMappingImageSize(const FMaterialProxySettings& Settings) - { - FIntPoint ImageSize = Settings.TextureSize; - - return ImageSize; - } - - /** - * Method to swap axis - * (1,0,0) - * (0,0,1) - * (0,1,0) - */ - const FMatrix& GetConversionMatrixYUP() - { - - static FMatrix m; - static bool bInitialized = false; - if (!bInitialized) - { - m.SetIdentity(); - - bInitialized = true; - } - return m; - } - - /** - * Method to create a SsfMeshData from FMeshDescription - * @param InRawMesh Rawmesh to create SsfMeshData from - * @param InTextureBounds Texture bounds - * @param InTexCoords Corrected texture coordinates generated after material flattening. - */ - ssf::pssfMeshData CreateSSFMeshDataFromRawMesh(const FMeshDescription& SrcRawMesh, TArray InTextureBounds, TArray InTexCoords) - { - TVertexAttributesConstRef VertexPositions = SrcRawMesh.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - TVertexInstanceAttributesConstRef VertexInstanceNormals = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - TVertexInstanceAttributesConstRef VertexInstanceTangents = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - TVertexInstanceAttributesConstRef VertexInstanceColors = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - TVertexInstanceAttributesConstRef VertexInstanceUVs = SrcRawMesh.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - - int32 NumVertices = SrcRawMesh.Vertices().Num(); - int32 NumWedges = 0; - for (const FPolygonID& PolygonID : SrcRawMesh.Polygons().GetElementIDs()) - { - const FMeshPolygon& Polygon = SrcRawMesh.GetPolygon(PolygonID); - NumWedges += Polygon.Triangles.Num() * 3; - } - int32 NumTris = NumWedges / 3; - - if (NumWedges == 0) - { - return nullptr; - } - - //assuming everything is left-handed so no need to change winding order and handedness. SSF supports both - - ssf::pssfMeshData SgMeshData = new ssf::ssfMeshData(); - - //setup vertex coordinates - ssf::ssfList & SsfCoorinates = SgMeshData->Coordinates.Create(); - SsfCoorinates.Items.resize(NumVertices); - TMap MeshToSsfVertexID; - MeshToSsfVertexID.Reserve(NumVertices); - int32 VertexIndex = 0; - for(const FVertexID& VertexID : SrcRawMesh.Vertices().GetElementIDs()) - { - ssf::ssfVector3 CurrentVertex; - FVector4 Position = GetConversionMatrixYUP().TransformPosition(VertexPositions[VertexID]); - CurrentVertex.V[0] = double(Position.X); - CurrentVertex.V[1] = double(Position.Y); - CurrentVertex.V[2] = double(Position.Z); - SsfCoorinates.Items[VertexIndex] = CurrentVertex; - MeshToSsfVertexID.Add(VertexID, VertexIndex); - VertexIndex++; - } - - //setup triangle data - ssf::ssfList& SsfTriangleIndices = SgMeshData->TriangleIndices.Create(); - ssf::ssfList& SsfMaterialIndices = SgMeshData->MaterialIndices.Create(); - ssf::ssfList& SsfSmoothingGroups = SgMeshData->SmoothingGroup.Create(); - - SsfTriangleIndices.Items.resize(NumTris); - SsfMaterialIndices.Items.resize(NumTris); - SsfSmoothingGroups.Items.resize(NumTris); - - bool bHasNormals = VertexInstanceNormals.GetNumElements() == NumWedges; - bool bHasTangents = bHasNormals && (VertexInstanceTangents.GetNumElements() == NumWedges) && (VertexInstanceBinormalSigns.GetNumElements() == NumWedges); - - ssf::ssfList EmptyList; - ssf::ssfList& SsfTangents = bHasTangents ? SgMeshData->Tangents.Create() : EmptyList; - ssf::ssfList& SsfBitangents = bHasTangents ? SgMeshData->Bitangents.Create() : EmptyList; - ssf::ssfList& SsfNormals = bHasNormals ? SgMeshData->Normals.Create() : EmptyList; - if (bHasNormals) - { - if (bHasTangents) - { - SsfTangents.Items.resize(NumWedges); - SsfBitangents.Items.resize(NumWedges); - } - SsfNormals.Items.resize(NumWedges); - } - - const int32 TexCoordNumber = FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_MESH_TEXTURE_COORDS); - ssf::ssfNamedList SsfTextureCoordinates[MAX_MESH_TEXTURE_COORDS]; - for (int32 TexCoordIndex = 0; TexCoordIndex < TexCoordNumber; ++TexCoordIndex) - { - //Since SSF uses Named Channels - SsfTextureCoordinates[TexCoordIndex].Name = FSimplygonSSFHelper::TCHARToSSFString(*FString::Printf(TEXT("TexCoord%d"), TexCoordIndex)); - SsfTextureCoordinates[TexCoordIndex].Items.resize(NumWedges); - } - - ssf::ssfNamedList SsfColorMap; - bool bHasVertexColor = VertexInstanceColors.GetNumElements() == NumWedges; - if (bHasVertexColor) - { - //setup the color named channel . Currently its se to index zero. If multiple colors channel are need then use an index instead of 0 - SsfColorMap.Name = FSimplygonSSFHelper::TCHARToSSFString(*FString::Printf(TEXT("Colors%d"), 0)); - SsfColorMap.Items.resize(NumWedges); - } - - //Smooth group - TArray FaceSmoothingMasks; - FMeshDescriptionOperations::ConvertHardEdgesToSmoothGroup(SrcRawMesh, FaceSmoothingMasks); - - //Reverse winding switches - bool bReverseWinding = true; - - int32 TriangleIndex = 0; - for (const FPolygonID& PolygonID : SrcRawMesh.Polygons().GetElementIDs()) - { - const FMeshPolygon& Polygon = SrcRawMesh.GetPolygon(PolygonID); - - FPolygonGroupID PolygonGroupID = SrcRawMesh.GetPolygonPolygonGroup(PolygonID); - int32 MaterialIndex = PolygonGroupID.GetValue(); - - for (const FMeshTriangle& Triangle : Polygon.Triangles) - { - for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) - { - int32 DestCornerIndex = bReverseWinding ? 2 - CornerIndex : CornerIndex; - FVertexInstanceID VertexInstanceID = Triangle.GetVertexInstanceID(CornerIndex); - SsfTriangleIndices.Items[TriangleIndex].V[DestCornerIndex] = MeshToSsfVertexID[SrcRawMesh.GetVertexInstanceVertex(VertexInstanceID)]; - - //NTBs - if (bHasNormals) - { - FVector Normal = VertexInstanceNormals[VertexInstanceID]; - if (bHasTangents) - { - FVector Tangent = VertexInstanceTangents[VertexInstanceID]; - FVector Bitangent = FVector::CrossProduct(Normal, Tangent).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID]; - - ssf::ssfVector3 SsfTangent; - FVector4 Tangent4 = GetConversionMatrixYUP().TransformPosition(Tangent); - SsfTangent.V[0] = double(Tangent4.X); - SsfTangent.V[1] = double(Tangent4.Y); - SsfTangent.V[2] = double(Tangent4.Z); - SsfTangents.Items[TriangleIndex * 3 + DestCornerIndex] = SsfTangent; - - ssf::ssfVector3 SsfBitangent; - FVector4 Bitangent4 = GetConversionMatrixYUP().TransformPosition(Bitangent); - SsfBitangent.V[0] = double(Bitangent4.X); - SsfBitangent.V[1] = double(Bitangent4.Y); - SsfBitangent.V[2] = double(Bitangent4.Z); - SsfBitangents.Items[TriangleIndex * 3 + DestCornerIndex] = SsfBitangent; - } - - ssf::ssfVector3 SsfNormal; - FVector4 Normal4 = GetConversionMatrixYUP().TransformPosition(Normal); - SsfNormal.V[0] = double(Normal4.X); - SsfNormal.V[1] = double(Normal4.Y); - SsfNormal.V[2] = double(Normal4.Z); - SsfNormals.Items[TriangleIndex * 3 + DestCornerIndex] = SsfNormal; - } - - //Vertex color - if (bHasVertexColor) - { - FLinearColor LinearColor(VertexInstanceColors[VertexInstanceID]); - SsfColorMap.Items[TriangleIndex * 3 + DestCornerIndex].V[0] = LinearColor.R; - SsfColorMap.Items[TriangleIndex * 3 + DestCornerIndex].V[1] = LinearColor.G; - SsfColorMap.Items[TriangleIndex * 3 + DestCornerIndex].V[2] = LinearColor.B; - SsfColorMap.Items[TriangleIndex * 3 + DestCornerIndex].V[3] = LinearColor.A; - } - - - //Texcoords - for (int32 TexCoordIndex = 0; TexCoordIndex < TexCoordNumber; ++TexCoordIndex) - { - bool bUseInTexCoord = (TexCoordIndex == 0 && InTexCoords.Num() == NumWedges); - int32 NumTexCoord = bUseInTexCoord ? InTexCoords.Num() : VertexInstanceUVs.GetNumElements(); - if (NumTexCoord == NumWedges) - { - // Compute texture bounds for current material. - float MinU = 0, ScaleU = 1; - float MinV = 0, ScaleV = 1; - if (InTextureBounds.IsValidIndex(MaterialIndex) && TexCoordIndex == 0 && InTexCoords.Num() == 0) - { - const FBox2D& Bounds = InTextureBounds[MaterialIndex]; - if (Bounds.GetArea() > 0) - { - MinU = Bounds.Min.X; - MinV = Bounds.Min.Y; - ScaleU = 1.0f / (Bounds.Max.X - Bounds.Min.X); - ScaleV = 1.0f / (Bounds.Max.Y - Bounds.Min.Y); - } - } - - const FVector2D& TexCoord = bUseInTexCoord ? InTexCoords[TriangleIndex * 3 + CornerIndex] : VertexInstanceUVs.Get(VertexInstanceID, TexCoordIndex); - ssf::ssfVector2 temp; - temp.V[0] = (TexCoord.X - MinU) * ScaleU; - temp.V[1] = (TexCoord.Y - MinV) * ScaleV; - SsfTextureCoordinates[TexCoordIndex].Items[TriangleIndex * 3 + DestCornerIndex] = temp; - } - } - - } - - //Material - SsfMaterialIndices.Items[TriangleIndex] = MaterialIndex; - - //Smooth group - SsfSmoothingGroups.Items[TriangleIndex] = FaceSmoothingMasks[TriangleIndex]; - - TriangleIndex++; - } - } - - SgMeshData->MaterialIndices.Create(); - - //Push back all the data... - - for (int32 TexCoordIndex = 0; TexCoordIndex < TexCoordNumber; ++TexCoordIndex) - { - SgMeshData->TextureCoordinatesList.push_back(SsfTextureCoordinates[TexCoordIndex]); - } - if (bHasVertexColor) - { - SgMeshData->ColorsList.push_back(SsfColorMap); - } - - - return SgMeshData; - } - - /** - * Method to copy texture's pixel data into a FColor array - * @param OutSamples Out TArray where texture data is copied to. - * @param OutTextureSize Out Texture sizes - * @param TexturePath Path to Texture - * @param IsNormalMap Is this normalmap that we are reading - */ - void CopyTextureData( - TArray& OutSamples, - FIntPoint& OutTextureSize, - FString ChannelName, - FString TexturePath, - bool IsNormalMap = false - ) - { - IImageWrapperModule& ImageWrapperModule = FModuleManager::GetModuleChecked(FName("ImageWrapper")); - TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); - - TArray TextureData; - if (!FFileHelper::LoadFileToArray(TextureData, *FPaths::ConvertRelativePathToFull(TexturePath)) && TextureData.Num() > 0) - { - UE_LOG(LogSimplygonSwarm, Warning, TEXT("Unable to find Texture file %s"), *TexturePath); - } - else - { - const TArray* RawData = NULL; - - if (ImageWrapper->SetCompressed(TextureData.GetData(), TextureData.Num()) && ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, RawData)) - { - OutTextureSize.X = ImageWrapper->GetHeight(); - OutTextureSize.Y = ImageWrapper->GetWidth(); - int32 TexelsCount = ImageWrapper->GetHeight() * ImageWrapper->GetWidth(); - OutSamples.Empty(TexelsCount); - OutSamples.AddUninitialized(TexelsCount); - - for (int32 X = 0; X < ImageWrapper->GetHeight(); ++X) - { - for (int32 Y = 0; Y < ImageWrapper->GetWidth(); ++Y) - { - int32 PixelIndex = ImageWrapper->GetHeight() * X + Y; - - OutSamples[PixelIndex].B = (*RawData)[PixelIndex*sizeof(FColor) + 0]; - OutSamples[PixelIndex].G = (*RawData)[PixelIndex*sizeof(FColor) + 1]; - OutSamples[PixelIndex].R = (*RawData)[PixelIndex*sizeof(FColor) + 2]; - OutSamples[PixelIndex].A = (*RawData)[PixelIndex*sizeof(FColor) + 3]; - } - } - } - - } - } - - /** - * Method to create a SsfMaterialChannel object - * @param InSamples Color data to output to texture. - * @param InTextureSize Texture size - * @param SsfTextureTable SsfTexture Table - * @param TextureName Texture name - * @param BaseTexturePath Texture base folder to use - * @param IsSRGB Texture is SRGB based or not. - */ - ssf::pssfMaterialChannel CreateSsfMaterialChannel( - const TArray& InSamples, - FIntPoint InTextureSize, - ssf::pssfTextureTable SsfTextureTable, - FString ChannelName, FString TextureName, FString BaseTexturePath, bool IsSRGB = true) - { - - ssf::pssfMaterialChannel SsfMaterialChannel = new ssf::ssfMaterialChannel(); - SsfMaterialChannel->ChannelName.Set(FSimplygonSSFHelper::TCHARToSSFString(*ChannelName)); - - bool bDebuggingEnabled = GetDefault()->bEnableSwarmDebugging; - - if (InSamples.Num() >= 1) - { - - IImageWrapperModule& ImageWrapperModule = FModuleManager::GetModuleChecked(FName("ImageWrapper")); - TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); - - FString TextureOutputRelative = FString::Printf(TEXT("%s/%s.png"), ANSI_TO_TCHAR(SsfTextureTable->TexturesDirectory->Value.c_str()), *TextureName); - FString TextureOutputPath = FString::Printf(TEXT("%s%s"), *BaseTexturePath, *TextureOutputRelative); - - if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(&InSamples[0], InSamples.Num() * sizeof(FColor), InTextureSize.X, InTextureSize.Y, ERGBFormat::BGRA, 8)) - { - if (FFileHelper::SaveArrayToFile(ImageWrapper->GetCompressed(), *TextureOutputPath)) - { - ssf::pssfTexture SsfTexture = new ssf::ssfTexture(); - ssf::pssfMaterialChannelTextureDescriptor SsfTextureDescriptor = new ssf::ssfMaterialChannelTextureDescriptor(); - SsfTexture->Id.Set(FSimplygonSSFHelper::SSFNewGuid()); - SsfTexture->Name.Set(FSimplygonSSFHelper::TCHARToSSFString(*TextureName)); - SsfTexture->Path.Set(FSimplygonSSFHelper::TCHARToSSFString(*TextureOutputRelative)); - SsfTextureDescriptor->TextureID.Set(SsfTexture->Id.Get()); - - FString TexCoordText = TEXT("TexCoord0"); - SsfTextureDescriptor->TexCoordSet.Set(FSimplygonSSFHelper::TCHARToSSFString(*TexCoordText)); - - SsfMaterialChannel->MaterialChannelTextureDescriptorList.push_back(SsfTextureDescriptor); - FString ShadingNetwork = FString::Printf(SHADING_NETWORK_TEMPLATE, *TextureName, *TexCoordText, 0); - SsfMaterialChannel->ShadingNetwork.Set(FSimplygonSSFHelper::TCHARToSSFString(*ShadingNetwork)); - SsfTextureTable->TextureList.push_back(SsfTexture); - } - else - { - UE_LOG(LogSimplygonSwarm, Error, TEXT("Could not save to file %s"), *TextureOutputPath); - } - - } - - } - else - { - SsfMaterialChannel->Color.Create(); - SsfMaterialChannel->Color->V[0] = 1.0f; - SsfMaterialChannel->Color->V[1] = 1.0f; - SsfMaterialChannel->Color->V[2] = 1.0f; - SsfMaterialChannel->Color->V[3] = 1.0f; - } - - return SsfMaterialChannel; - } - - /** - * Method to create a SsfMaterialChannel object - * @param InputMaterials List of flatten materials. - * @param InMaterialLODSettings Material Proxy Settings - * @param SsfMaterialTable Material Table - * @param SsfTextureTable Texture Table - * @param BaseTexturePath Base Texture Path - * @param bReleaseInputMaterials Wether or not release Flatten Material you are done. - * @param OutMaterialMapping Id to Guid mapping. - */ - bool CreateSSFMaterialFromFlattenMaterial( - const TArray& InputMaterials, - const FMaterialProxySettings& InMaterialLODSettings, - ssf::pssfMaterialTable SsfMaterialTable, - ssf::pssfTextureTable SsfTextureTable, - FString BaseTexturePath, - bool bReleaseInputMaterials, TMap& OutMaterialMapping) -{ - if (InputMaterials.Num() == 0) - { - //If there are no materials, feed Simplygon with a default material instead. - UE_LOG(LogSimplygonSwarm, Log, TEXT("Input meshes do not contain any materials. A proxy without material will be generated.")); - return false; - } - - bool bFillEmptyEmissive = false; - bool bDiscardEmissive = true; - for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex]; - if (FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Num() > 1 || (FlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive) && FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive)[0] != FColor::Black)) - { - bFillEmptyEmissive = true; - } - - bDiscardEmissive &= ((FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive)) || (FlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive) && FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive)[0] == FColor::Black)); - } - - for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++) - { - FString MaterialGuidString = FGuid::NewGuid().ToString(); - const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex]; - FString MaterialName = FString::Printf(TEXT("Material%d"), MaterialIndex); - - ssf::pssfMaterial SsfMaterial = new ssf::ssfMaterial(); - SsfMaterial->Id.Set(FSimplygonSSFHelper::TCHARToSSFString(*MaterialGuidString)); - SsfMaterial->Name.Set(FSimplygonSSFHelper::TCHARToSSFString(*MaterialName)); - - OutMaterialMapping.Add(MaterialIndex, MaterialGuidString); - - // Does current material have BaseColor? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Diffuse)) - { - FString ChannelName(BASECOLOR_CHANNEL); - ssf::pssfMaterialChannel BaseColorChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - - SsfMaterial->MaterialChannelList.push_back(BaseColorChannel); - - //NOTE: use the commented setting once switching between tangentspace/worldspace is added into the vanilla version of the engine. - SsfMaterial->TangentSpaceNormals->Create(true /*InMaterialLODSettings.bUseTangentSpace*/); - } - - // Does current material have Metallic? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Metallic)) - { - FString ChannelName(METALLIC_CHANNEL); - ssf::pssfMaterialChannel MetallicChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(MetallicChannel); - } - - // Does current material have Specular? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Specular)) - { - FString ChannelName(SPECULAR_CHANNEL); - ssf::pssfMaterialChannel SpecularChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(SpecularChannel); - } - - // Does current material have Roughness? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Roughness)) - { - FString ChannelName(ROUGHNESS_CHANNEL); - ssf::pssfMaterialChannel RoughnessChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(RoughnessChannel); - } - - //Does current material have a normalmap? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Normal)) - { - FString ChannelName(NORMAL_CHANNEL); - SsfMaterial->TangentSpaceNormals.Create(); - SsfMaterial->TangentSpaceNormals.Set(true); - ssf::pssfMaterialChannel NormalChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath, false); - SsfMaterial->MaterialChannelList.push_back(NormalChannel); - } - - // Does current material have Opacity? - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Opacity)) - { - FString ChannelName(OPACITY_CHANNEL); - ssf::pssfMaterialChannel OpacityChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(OpacityChannel); - } - - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::OpacityMask)) - { - FString ChannelName(OPACITY_MASK_CHANNEL); - ssf::pssfMaterialChannel OpacityChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(OpacityChannel); - } - - // Emissive could have been outputted by the shader/swarm due to various reasons, however we don't always need the data that was created so we discard it - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive) || (FlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive) && FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive)[0] == FColor::Black)) - { - FString ChannelName(EMISSIVE_CHANNEL); - ssf::pssfMaterialChannel EmissiveChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(EmissiveChannel); - } - else if (bFillEmptyEmissive && !FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive)) - { - TArray Sample; - Sample.Add(FColor::Black); - FIntPoint Size(1, 1); - FString ChannelName(EMISSIVE_CHANNEL); - TArray BlackEmissive; - BlackEmissive.AddZeroed(1); - ssf::pssfMaterialChannel EmissiveChannel = CreateSsfMaterialChannel(Sample, Size, SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(EmissiveChannel); - } - - //NOTE: Enable this once AO baking functionality is moved into the engine. - if (FlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::AmbientOcclusion)) - { - FString ChannelName(AO_CHANNEL); - ssf::pssfMaterialChannel AOChannel = CreateSsfMaterialChannel(FlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::AmbientOcclusion), FlattenMaterial.GetPropertySize(EFlattenMaterialProperties::AmbientOcclusion), SsfTextureTable, ChannelName, FString::Printf(TEXT("%s%s"), *MaterialName, *ChannelName), BaseTexturePath); - SsfMaterial->MaterialChannelList.push_back(AOChannel); - } - - SsfMaterialTable->MaterialList.push_back(SsfMaterial); - - if (bReleaseInputMaterials) - { - // Release FlattenMaterial. Using const_cast here to avoid removal of "const" from input data here - // and above the call chain. - const_cast(&FlattenMaterial)->ReleaseData(); - } - } - - return true; - } -}; - -TUniquePtr GSimplygonMeshReduction; - - -void FSimplygonSwarmModule::StartupModule() -{ - GSimplygonMeshReduction.Reset(FSimplygonSwarm::Create()); - FModuleManager::Get().LoadModule(FName("ImageWrapper")); - IModularFeatures::Get().RegisterModularFeature(IMeshReductionModule::GetModularFeatureName(), this); -} - -void FSimplygonSwarmModule::ShutdownModule() -{ - FSimplygonRESTClient::Shutdown(); - IModularFeatures::Get().UnregisterModularFeature(IMeshReductionModule::GetModularFeatureName(), this); -} - -IMeshReduction* FSimplygonSwarmModule::GetStaticMeshReductionInterface() -{ - return nullptr; -} - -IMeshReduction* FSimplygonSwarmModule::GetSkeletalMeshReductionInterface() -{ - return nullptr; -} - -IMeshMerging* FSimplygonSwarmModule::GetMeshMergingInterface() -{ - return nullptr; -} - -class IMeshMerging* FSimplygonSwarmModule::GetDistributedMeshMergingInterface() -{ - return GSimplygonMeshReduction.Get(); -} - -FString FSimplygonSwarmModule::GetName() -{ - return FString("SimplygonSwarm"); -} - -#undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonRESTClient.h b/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonRESTClient.h deleted file mode 100644 index ba32fd557124..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonRESTClient.h +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "HAL/Runnable.h" -#include "HAL/ThreadSafeBool.h" -#include "HAL/FileManager.h" -#include "Misc/MessageDialog.h" -#include "Misc/SlowTask.h" -#include "Misc/FileHelper.h" -#include "Misc/ScopeLock.h" -#include "Serialization/ArrayWriter.h" -#include "Containers/Queue.h" -#include "SimplygonSwarmCommon.h" - -#include "MeshUtilities.h" - -#include "Runtime/Online/HTTP/Public/Interfaces/IHttpResponse.h" -#include "Runtime/Online/HTTP/Public/Interfaces/IHttpRequest.h" -#include "Runtime/Online/HTTP/Public/HttpModule.h" - -/** Enum representing state used by Simplygon Grid Server */ -enum SimplygonRESTState -{ - SRS_UNKNOWN, - SRS_FAILED, - SRS_ASSETUPLOADED_PENDING, - SRS_MULTIPARTASSETUPLOAD_PENDING, - SRS_ASSETUPLOADED, - SRS_JOBCREATED_PENDING, - SRS_JOBCREATED, - SRS_JOBSETTINGSUPLOADED_PENDING, - SRS_JOBSETTINGSUPLOADED, - SRS_JOBPROCESSING_PENDING, - SRS_JOBPROCESSING, - SRS_JOBPROCESSED, - SRS_ASSETDOWNLOADED_PENDING, - SRS_ASSETDOWNLOADED -}; - -/** Enum representing state used internally by REST Client to manage multi-part asset uploading to Simplygon Grid */ -enum EUploadPartState -{ - UPS_BEGIN, // start uploading (hand shake) - UPS_UPLOADING_PART, // upload part - UPS_END // upload transaction completed -}; - -/** -Intermediate struct to hold upload file chunks for multi-part upload -Note : Multi part upload are required as the Simplygon Grid Server has a 2GB file upload limitation -*/ -struct FSwarmUploadPart -{ - /** Upload part binary chunck */ - TArray Data; - - /** Part number */ - int32 PartNumber; - - /** Bool if part has been uploaded */ - FThreadSafeBool PartUploaded; - - ~FSwarmUploadPart() - { - Data.Empty(); - } - -}; - -/** Struct holding essential task data for task management. */ -struct FSwarmTaskkData -{ - /** Path to the zip file that needs to be uploaded */ - FString ZipFilePath; - - /** Path to spl file that needs to be uploaded */ - FString SplFilePath; - - /** Path to zip file containing resulting geometry */ - FString OutputZipFilePath; - - /** Swarm Job Directory */ - FString JobDirectory; - - /** Swarm Job Name - Can be used to track jobs using the Admin Utility*/ - FString JobName; - - /** Lock for synchornization between threads */ - FCriticalSection* StateLock; - - /** Unique Job Id */ - FGuid ProcessorJobID; - - /** Set if the task upload has been completed */ - FThreadSafeBool TaskUploadComplete; - - /** Supports Dithered Transition */ - bool bDitheredTransition; - // Whether or not emissive should be outputted - bool bEmissive; -}; - -/** Struct that hold intermediate data used to communicate next state to Simplygon Grid Server */ -struct FSwarmJsonResponse -{ - /** Unique JobId */ - FString JobId; - - /** Unique asset id returned from server. - Note : This can be used to check if asset already is available on the server to save network bandwidth - */ - FString AssetId; - - /** Supports Dithered Transition */ - FString ErrorMessage; - - /** Supports Dithered Transition */ - uint32 Progress; - - /** Supports Dithered Transition */ - FString Status; - - /** Supports Dithered Transition */ - FString OutputAssetId; - - /** Supports Dithered Transition */ - FString UploadId; -}; - -/** Simplygon Swarm Task. Responsible for communicating with the Grid Server */ -class FSimplygonSwarmTask -{ - -public: - DECLARE_DELEGATE_OneParam(FSimplygonSwarmTaskDelegate, const FSimplygonSwarmTask&); - - FSimplygonSwarmTask(const FSwarmTaskkData& InTaskData); - ~FSimplygonSwarmTask(); - - /* Method to get/set Task Sate */ - SimplygonRESTState GetState() const; - void SetState(SimplygonRESTState InState); - - bool IsFinished() const; - - /*~ Events */ - FSimplygonSwarmTaskDelegate& OnAssetDownloaded() { return OnAssetDownloadedDelegate; } - FSimplygonSwarmTaskDelegate& OnAssetUploaded() { return OnAssetUploadedDelegate; } - FSimplygonSwarmTaskDelegate& OnSwarmTaskFailed() { return OnTaskFailedDelegate; } - - void EnableDebugLogging(); - - /** - Method to setup the server the task should use. - Currently only Single host is supported. - */ - void SetHost(FString InHostAddress); - - /* - The following method is used to split the asset into multiple parts for a multi part upload. - */ - void CreateUploadParts(const int32 MaxUploadFileSize); - - /* - Tells if the current task needs to be uploaded as seperate part due to data size limits - */ - bool NeedsMultiPartUpload(); - - //~Being Rest methods - void AccountInfo(); - void CreateJob(); - void UploadJobSettings(); - void ProcessJob(); - void GetJob(); - void UploadAsset(); - void DownloadAsset(); - - /* - Note for multi part upload the following need to happen - Call upload being to setup the multi part upload - upload parts using MultiPartUplaodPart - Call MultiPartupload end - */ - void MultiPartUploadBegin(); - void MultiPartUploadPart(const uint32 partNo); - void MultiPartUploadEnd(); - void MultiPartUploadGet(); - - //~Being Response methods - void AccountInfo_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void CreateJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void UploadJobSettings_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void ProcessJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void GetJob_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void UploadAsset_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void DownloadAsset_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void AddAuthenticationHeader(TSharedRef request); - - //Multi part upload responses - void MultiPartUploadBegin_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void MultiPartUploadPart_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void MultiPartUploadEnd_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - void MultiPartUploadGet_Response(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - - //~End Rest methods - - /** Helper method use to deserialze json respone from SimplygonGRid and popupate the FSwarmJsonRepose Struct*/ - bool ParseJsonMessage(FString InJsonMessage, FSwarmJsonResponse& OutResponseData); - - /** Essential Task Data */ - FSwarmTaskkData TaskData; - -private: - - /** Task State */ - SimplygonRESTState State; - - /** Job Id */ - FString JobId; - - /** Asset Id returned from the server */ - FString InputAssetId; - - /** Output Asset id */ - FString OutputAssetId; - - /** Is Completed */ - FThreadSafeBool IsCompleted; - - /** Enable Debug Logging */ - bool bEnableDebugLogging; - - /** Parts left to upload (Multi-part Uploading) */ - FThreadSafeCounter RemainingPartsToUpload; - - /** Debug HttpRequestCounter - Note: This was added to track issues when two resposnes came for a completed job. - Since the job was completed before the object is partially destoreyd when a new response came in. - The import file method failed. This was added for debugging. The most likely cause if that the respose delegate is never cleaned up. - (This must be zero else, Sometime two responses for job completed arrive which caused issue) */ - FThreadSafeCounter DebugHttpRequestCounter; - - /** Multipart upload has been initalized*/ - bool bMultiPartUploadInitialized; - - /** Multi part upload data*/ - TIndirectArray UploadParts; - - /** Total number of parts to upload*/ - uint32 TotalParts; - - /** Simplygon Grid Server IP address */ - FString HostName; - - /** API Key used to communicate with the Grid Server */ - FString APIKey; - - /** Upload Id used for multipart upload */ - FString UploadId; - - /** Total upload size */ - int32 UploadSize; - - //~ Delegates Begin - FSimplygonSwarmTaskDelegate OnAssetDownloadedDelegate; - FSimplygonSwarmTaskDelegate OnAssetUploadedDelegate; - FSimplygonSwarmTaskDelegate OnProgressUpdated; - FSimplygonSwarmTaskDelegate OnTaskFailedDelegate; - //~ Delegates End - - /** Map that stores pending request. They need to be cleaned up when destroying the instance. Especially if job has completed*/ - TMap, FString> PendingRequests; - - bool bProcessingStarted; - float TimeSinceProcessStart; -}; - -/* -Simplygon REST Based Clinet. Responsible for managing/controlling task. Runs on its own thread. -*/ -class FSimplygonRESTClient : public FRunnable -{ -public: - - /* Add a swarm task to the Queue*/ - void AddSwarmTask(TSharedPtr& InTask); - - void SetMaxUploadSizeInBytes(int32 InMaxUploadSizeInBytes); - - /*Get pointer to the runnable*/ - static FSimplygonRESTClient* Get(); - - static void Shutdown(); - -private: - - - FSimplygonRESTClient(); - - ~FSimplygonRESTClient(); - - //~ Begin FRunnable Interface - virtual bool Init(); - - virtual uint32 Run(); - - /* Method that goes through all task and checks their status*/ - void UpdateTaskStates(); - - /* Moves jobs into the Jobs Buffer*/ - void MoveItemsToBoundedArray(); - - virtual void Stop(); - - virtual void Exit(); - //~ End FRunnable Interface - - - void Wait(const float InSeconds, const float InSleepTime = 0.1f); - - void EnusureCompletion(); - - /** Checks if there's been any Stop requests */ - FORCEINLINE bool ShouldStop() const - { - return StopTaskCounter.GetValue() > 0; - } - -private: - - /*Static instance*/ - static FSimplygonRESTClient* Runnable; - - /*Critical Section*/ - FCriticalSection CriticalSectionData; - - FThreadSafeCounter StopTaskCounter; - - /* a local buffer as to limit the number of concurrent jobs. */ - TArray> JobsBuffer; - - /* Pending Jobs Queue */ - TQueue, EQueueMode::Mpsc> PendingJobs; - - /* Thread*/ - FRunnableThread* Thread; - - /* Simplygon Grid Server IP Address*/ - FString HostName; - - /* API Key*/ - FString APIKey; - - /* Add a swarm task to the Queue*/ - bool bEnableDebugging; - - /* Sleep time between status updates*/ - float DelayBetweenRuns; - - /* Number of Simultaneous Jobs to Manage*/ - int32 JobLimit; - - /* Max Upload size in bytes . Should not be more than the 2GB data limit for the Grid Server*/ - int32 MaxUploadSizeInBytes; -}; \ No newline at end of file diff --git a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmCommon.h b/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmCommon.h deleted file mode 100644 index 6cb6ff051511..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmCommon.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -#pragma once - -#include "CoreMinimal.h" -#include "RawMesh.h" -#include "MeshUtilities.h" -#include "MaterialUtilities.h" -#include "MeshBoneReduction.h" -#include "ComponentReregisterContext.h" -#include "ImageUtils.h" -THIRD_PARTY_INCLUDES_START -#include "ssf.h" -THIRD_PARTY_INCLUDES_END -#include "IImageWrapper.h" -#include "SPLInclude.h" - -namespace SPL = Simplygon::SPL::v80; - -#ifndef SPL_VERSION - #define SPL_VERSION 8 -#endif // !SPL_VERSION - -#define SPL_CURRENT_VERSION SPL_VERSION - -// You should place include statements to your module's private header files here. You only need to -// add includes for headers that are used in most of your module's source files though. diff --git a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmHelpers.h b/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmHelpers.h deleted file mode 100644 index 83c279ccf166..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/Public/SimplygonSwarmHelpers.h +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -#pragma once - -#include "SimplygonSwarmCommon.h" -#include - -struct FSimplygonSSFHelper -{ -public: - - /** - * Return a new GUID as SsfString - */ - static ssf::ssfString SSFNewGuid() - { - return TCHARToSSFString(*FGuid::NewGuid().ToString()); - } - - /** - * Return a empty GUID as SsfString - */ - static ssf::ssfString SFFEmptyGuid() - { - return TCHARToSSFString(*FGuid().ToString()); - } - - /** - * Convert TCHAR to SsfString - */ - static ssf::ssfString TCHARToSSFString(const TCHAR* str) - { - return ssf::ssfString(std::basic_string(str)); - } - - /** - * Compare two ssf strings - */ - static bool CompareSSFStr(ssf::ssfString lhs, ssf::ssfString rhs) - { - if (lhs.Value == rhs.Value) - return true; - - return false; - } - - /** - * Get TextureCoordinate from SsfTexcoordinateList based on TextureSetName - * @param TextureCoordsList List of TextureCoorniates - * @result a ssf::ssfNamedList object containing uv data nullptr otherwise - */ - static ssf::ssfNamedList FindByTextureSetName(std::list < ssf::ssfNamedList > TextureCoordsList, ssf::ssfString Name) - { - auto UVSet = std::find_if(TextureCoordsList.begin(), TextureCoordsList.end(), - [Name](const ssf::ssfNamedList InTextureSet) - { - if (FSimplygonSSFHelper::CompareSSFStr(Name, InTextureSet.Name)) - { - return true; - } - return false; - }); - - if (UVSet != std::end(TextureCoordsList)) - { - return *UVSet; - } - - return ssf::ssfNamedList < ssf::ssfVector2>(); - } - - /** - * Get SsfTexture by Guid - * @param SsfScene SsfScene - * @param TextureId Texture Guid String - * @result a ssf::pssfTexture object nullptr otherwise - */ - static ssf::pssfTexture FindTextureById(ssf::pssfScene SsfScene, ssf::ssfString TextureId) - { - auto Texture = std::find_if(SsfScene->TextureTable->TextureList.begin(), SsfScene->TextureTable->TextureList.end(), - [TextureId](const ssf::pssfTexture tex) - { - if (FSimplygonSSFHelper::CompareSSFStr(tex->Id.Get().Value, TextureId)) - { - return true; - } - return false; - }); - - - if (Texture != std::end(SsfScene->TextureTable->TextureList)) - { - if (!Texture->IsNull()) - { - return *Texture; - } - } - - return nullptr; - } - - /** - * Get TextureCoordinate from SsfTexcoordinateList based on TextureSetName - * @param SsfScene Input SsfScene - * @param MaterailId Input SsfScene - * @result a ssf::pssfMaterial object nullptr otherwise - */ - static ssf::pssfMaterial FindMaterialById(ssf::pssfScene SsfScene, ssf::ssfString MaterailId) - { - auto ProxyMaterial = std::find_if(SsfScene->MaterialTable->MaterialList.begin(), SsfScene->MaterialTable->MaterialList.end(), - [MaterailId](const ssf::pssfMaterial mat) - { - if (FSimplygonSSFHelper::CompareSSFStr(mat->Id.Get().Value, MaterailId)) - { - return true; - } - return false; - }); - - if (ProxyMaterial != std::end(SsfScene->MaterialTable->MaterialList)) - { - if (!ProxyMaterial->IsNull()) - { - return *ProxyMaterial; - } - } - - return nullptr; - } - - /** - * Get Relavant Set for Baked Materials from the SsfTexcoordsList. - * @param TextureCoordsList List of TextureCoorniates - * @result a ssf::ssfNamedList object containing uv data nullptr otherwise - */ - static ssf::ssfNamedList GetBakedMaterialUVs(std::list < ssf::ssfNamedList > TextureCoordsList) - { - return FindByTextureSetName(TextureCoordsList, ssf::ssfString("MaterialLOD")); - } - -}; \ No newline at end of file diff --git a/Engine/Source/Developer/SimplygonSwarm/SimplygonSwarm.Build.cs b/Engine/Source/Developer/SimplygonSwarm/SimplygonSwarm.Build.cs deleted file mode 100644 index e1366d325f6b..000000000000 --- a/Engine/Source/Developer/SimplygonSwarm/SimplygonSwarm.Build.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.IO; - -public class SimplygonSwarm : ModuleRules -{ - public SimplygonSwarm(ReadOnlyTargetRules Target) : base(Target) - { - PublicIncludePaths.Add("Developer/SimplygonSwarm/Public"); - PrivateIncludePaths.Add("Developer/SimplygonSwarm/Private"); - - PublicDependencyModuleNames.AddRange( - new string[] { - "Core", - "CoreUObject", - "InputCore", - "Json", - "RHI", - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] { - "Core", - "CoreUObject", - "Engine", - "RenderCore", - "MeshDescription", - "MeshDescriptionOperations", - "MeshBoneReduction", - "ImageWrapper", - "HTTP", - "Json", - "UnrealEd", - "MaterialUtilities", - "MeshMergeUtilities" - } - ); - - PrivateIncludePathModuleNames.AddRange( - new string[] { - "MeshUtilities", - "MaterialUtilities", - "SimplygonMeshReduction", - "MeshReductionInterface" - } - ); - - PublicIncludePathModuleNames.AddRange( - new string[] { - "MeshReductionInterface" - } - ); - - - AddEngineThirdPartyPrivateStaticDependencies(Target, "Simplygon"); - AddEngineThirdPartyPrivateStaticDependencies(Target, "SSF"); - AddEngineThirdPartyPrivateStaticDependencies(Target, "SPL"); - AddEngineThirdPartyPrivateDynamicDependencies(Target, "PropertyEditor"); - - string SimplygonPath = Target.UEThirdPartySourceDirectory + "NotForLicensees/Simplygon/Simplygon-latest/Inc/SimplygonSDK.h"; - if (Target.Platform == UnrealTargetPlatform.Win64 && File.Exists(SimplygonPath)) - { - PrecompileForTargets = PrecompileTargetsType.Editor; - } - else - { - PrecompileForTargets = PrecompileTargetsType.None; - } - } -} diff --git a/Engine/Source/Developer/SlateReflector/Private/VisualTreeCapture.cpp b/Engine/Source/Developer/SlateReflector/Private/VisualTreeCapture.cpp index ae445ab72176..65da2148ca0a 100644 --- a/Engine/Source/Developer/SlateReflector/Private/VisualTreeCapture.cpp +++ b/Engine/Source/Developer/SlateReflector/Private/VisualTreeCapture.cpp @@ -120,6 +120,11 @@ void FVisualTreeCapture::Disable() #endif } +void FVisualTreeCapture::Reset() +{ + VisualTrees.Reset(); +} + TSharedPtr FVisualTreeCapture::GetVisualTreeForWindow(SWindow* InWindow) { return VisualTrees.FindRef(InWindow); diff --git a/Engine/Source/Developer/SlateReflector/Private/VisualTreeCapture.h b/Engine/Source/Developer/SlateReflector/Private/VisualTreeCapture.h index ccbfe3c0c250..3a3ffd94e962 100644 --- a/Engine/Source/Developer/SlateReflector/Private/VisualTreeCapture.h +++ b/Engine/Source/Developer/SlateReflector/Private/VisualTreeCapture.h @@ -52,6 +52,7 @@ public: void Enable(); void Disable(); + void Reset(); TSharedPtr GetVisualTreeForWindow(SWindow* InWindow); diff --git a/Engine/Source/Developer/SlateReflector/Private/Widgets/SWidgetReflector.cpp b/Engine/Source/Developer/SlateReflector/Private/Widgets/SWidgetReflector.cpp index edff72c04fff..a95a51f84086 100644 --- a/Engine/Source/Developer/SlateReflector/Private/Widgets/SWidgetReflector.cpp +++ b/Engine/Source/Developer/SlateReflector/Private/Widgets/SWidgetReflector.cpp @@ -281,22 +281,24 @@ private: { if (PickingMode != InMode) { - { - SInvalidationPanel::SetEnableWidgetCaching(true); - - VisualCapture.Disable(); - } + // Disable visual picking, and renable widget caching. + SInvalidationPanel::SetEnableWidgetCaching(true); + VisualCapture.Disable(); + // Enable the picking mode. PickingMode = InMode; - if (PickingMode == EWidgetPickingMode::Drawable || PickingMode == EWidgetPickingMode::HitTesting) + // If we're enabling hit test, reset the visual capture entirely, we don't want to use the visual tree. + if (PickingMode == EWidgetPickingMode::HitTesting) { + VisualCapture.Reset(); SInvalidationPanel::SetEnableWidgetCaching(false); } - - if (PickingMode == EWidgetPickingMode::Drawable) + // If we're using the drawing picking mode enable it! + else if (PickingMode == EWidgetPickingMode::Drawable) { VisualCapture.Enable(); + SInvalidationPanel::SetEnableWidgetCaching(false); } } } diff --git a/Engine/Source/Editor/AdvancedPreviewScene/Private/AdvancedPreviewScene.cpp b/Engine/Source/Editor/AdvancedPreviewScene/Private/AdvancedPreviewScene.cpp index a177be710801..756ecad5fcf6 100644 --- a/Engine/Source/Editor/AdvancedPreviewScene/Private/AdvancedPreviewScene.cpp +++ b/Engine/Source/Editor/AdvancedPreviewScene/Private/AdvancedPreviewScene.cpp @@ -42,9 +42,28 @@ FAdvancedPreviewScene::FAdvancedPreviewScene(ConstructionValues CVS, float InFlo const FTransform Transform(FRotator(0, 0, 0), FVector(0, 0, 0), FVector(1)); - // Set up sky light using the set cube map texture, reusing the sky light from PreviewScene class - SetSkyCubemap(Profile.EnvironmentCubeMap.Get()); - SetSkyBrightness(Profile.SkyLightIntensity); + static const auto CVarSupportStationarySkylight = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.SupportStationarySkylight")); + bUseSkylight = CVarSupportStationarySkylight->GetValueOnAnyThread() != 0; + + if (bUseSkylight) + { + // Set up sky light using the set cube map texture, reusing the sky light from PreviewScene class + SetSkyCubemap(Profile.EnvironmentCubeMap.Get()); + SetSkyBrightness(Profile.SkyLightIntensity); + } + else + { + // Hide the inherited skylight + SkyLight->SetVisibility(false); + + // Setup sphere reflection + SphereReflectionComponent = NewObject(); + SphereReflectionComponent->Cubemap = Profile.EnvironmentCubeMap.Get(); + SphereReflectionComponent->ReflectionSourceType = EReflectionSourceType::SpecifiedCubemap; + SphereReflectionComponent->Brightness = Profile.SkyLightIntensity; + AddComponent(SphereReflectionComponent, Transform); + SphereReflectionComponent->UpdateReflectionCaptureContents(PreviewWorld); + } // Large scale to prevent sphere from clipping const FTransform SphereTransform(FRotator(0, 0, 0), FVector(0, 0, 0), FVector(2000)); @@ -89,7 +108,6 @@ FAdvancedPreviewScene::FAdvancedPreviewScene(ConstructionValues CVS, float InFlo bRotateLighting = Profile.bRotateLightingRig; CurrentRotationSpeed = Profile.RotationSpeed; bSkyChanged = false; - bUseSkylight = true; BindCommands(); @@ -201,7 +219,7 @@ void FAdvancedPreviewScene::UpdateScene(FPreviewSceneProfile& Profile, bool bUpd SkyComponent->SetVisibility(Profile.bShowEnvironment, true); if (bUseSkylight) { - SkyLight->SetVisibility(Profile.bShowEnvironment, true); + SkyLight->SetVisibility(Profile.bUseSkyLighting, true); } else { @@ -367,7 +385,7 @@ void FAdvancedPreviewScene::SetFloorVisibility(const bool bVisible, const bool b } else { - // Otherwise set visiblity directly on the component + // Otherwise set visibility directly on the component FloorMeshComponent->SetVisibility(bVisible ? DefaultSettings->Profiles[CurrentProfileIndex].bShowFloor : bVisible); } } @@ -386,14 +404,10 @@ void FAdvancedPreviewScene::SetEnvironmentVisibility(const bool bVisible, const else { // Otherwise set visibility directly on the component - SkyComponent->SetVisibility(bVisible ? DefaultSettings->Profiles[CurrentProfileIndex].bShowEnvironment : bVisible); - if (bUseSkylight) + SkyComponent->SetVisibility(bVisible); + if (!bUseSkylight) { - SkyLight->SetVisibility(bVisible ? DefaultSettings->Profiles[CurrentProfileIndex].bShowEnvironment : bVisible); - } - else - { - SphereReflectionComponent->SetVisibility(bVisible ? DefaultSettings->Profiles[CurrentProfileIndex].bShowEnvironment : bVisible); + SphereReflectionComponent->SetVisibility(bVisible); } } } @@ -479,7 +493,7 @@ void FAdvancedPreviewScene::OnAssetViewerSettingsRefresh(const FName& InProperty const bool bNameNone = InPropertyName == NAME_None; const bool bUpdateEnvironment = (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, EnvironmentCubeMap)) || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, LightingRigRotation) || (InPropertyName == GET_MEMBER_NAME_CHECKED(UAssetViewerSettings, Profiles))); - const bool bUpdateSkyLight = bUpdateEnvironment || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, SkyLightIntensity) || (InPropertyName == GET_MEMBER_NAME_CHECKED(UAssetViewerSettings, Profiles))); + const bool bUpdateSkyLight = bUpdateEnvironment || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, SkyLightIntensity) || InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, bUseSkyLighting) || (InPropertyName == GET_MEMBER_NAME_CHECKED(UAssetViewerSettings, Profiles))); const bool bUpdateDirectionalLight = (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, DirectionalLightIntensity)) || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, DirectionalLightColor)); const bool bUpdatePostProcessing = (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, PostProcessingSettings)) || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, bPostProcessingEnabled)); diff --git a/Engine/Source/Editor/AdvancedPreviewScene/Public/AssetViewerSettings.h b/Engine/Source/Editor/AdvancedPreviewScene/Public/AssetViewerSettings.h index 70ca35bea13a..4c56b314de92 100644 --- a/Engine/Source/Editor/AdvancedPreviewScene/Public/AssetViewerSettings.h +++ b/Engine/Source/Editor/AdvancedPreviewScene/Public/AssetViewerSettings.h @@ -23,6 +23,7 @@ struct FPreviewSceneProfile FPreviewSceneProfile() { bSharedProfile = false; + bUseSkyLighting = true; bShowFloor = true; bShowEnvironment = true; bRotateLightingRig = false; @@ -48,6 +49,10 @@ struct FPreviewSceneProfile UPROPERTY(EditAnywhere, config, Category = Profile) bool bSharedProfile; + /** Whether or not image based lighting is enabled for the environment cube map */ + UPROPERTY(EditAnywhere, AdvancedDisplay, config, Category = Lighting) + bool bUseSkyLighting; + /** Manually set the directional light intensity (0.0 - 20.0) */ UPROPERTY(EditAnywhere, config, Category = Lighting, meta = (UIMin = "0.0", UIMax = "20.0")) float DirectionalLightIntensity; diff --git a/Engine/Source/Editor/AnimGraph/Private/AnimPreviewInstance.cpp b/Engine/Source/Editor/AnimGraph/Private/AnimPreviewInstance.cpp index 19f49f0fff32..9191d888e1b0 100644 --- a/Engine/Source/Editor/AnimGraph/Private/AnimPreviewInstance.cpp +++ b/Engine/Source/Editor/AnimGraph/Private/AnimPreviewInstance.cpp @@ -144,6 +144,11 @@ void FAnimPreviewInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float D { FAnimSingleNodeInstanceProxy::PreUpdate(InAnimInstance, DeltaSeconds); + if (CopyPoseNode.SourceMeshComponent.IsValid()) + { + CopyPoseNode.PreUpdate(InAnimInstance); + } + if (!bForceRetargetBasePose) { CurveSource.PreUpdate(InAnimInstance); diff --git a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp index a353aaf14154..207c1495f6af 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp +++ b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp @@ -39,7 +39,9 @@ void UAnimationModifier::ApplyToAnimationSequence(class UAnimSequence* InAnimati OnApply(CurrentAnimSequence); // Apply transaction + ModifierTransaction.BeginOperation(); ModifierTransaction.Apply(); + ModifierTransaction.EndOperation(); GLog->RemoveOutputDevice(&OutputLog); @@ -59,7 +61,9 @@ void UAnimationModifier::ApplyToAnimationSequence(class UAnimSequence* InAnimati // Revert changes if necessary, otherwise post edit and refresh animation data if (bShouldRevert) { + AnimationDataTransaction.BeginOperation(); AnimationDataTransaction.Apply(); + AnimationDataTransaction.EndOperation(); CurrentAnimSequence->RefreshCacheData(); CurrentAnimSequence->RefreshCurveData(); } @@ -112,7 +116,9 @@ void UAnimationModifier::RevertFromAnimationSequence(class UAnimSequence* InAnim OnRevert(CurrentAnimSequence); // Apply transaction + Transaction.BeginOperation(); Transaction.Apply(); + Transaction.EndOperation(); UpdateCompressedAnimationData(); diff --git a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifierHelpers.h b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifierHelpers.h new file mode 100644 index 000000000000..b0ac81bf7f18 --- /dev/null +++ b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifierHelpers.h @@ -0,0 +1,63 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "ClassViewerModule.h" +#include "Widgets/SWidget.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/SBoxPanel.h" +#include "Modules/ModuleManager.h" +#include "Templates/SharedPointer.h" +#include "ClassViewerFilter.h" +#include "AnimationModifier.h" + +class FAnimationModifierHelpers +{ +public: + /** ClassViewerFilter for Animation Modifier classes */ + class FModifierClassFilter : public IClassViewerFilter + { + public: + bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override + { + return InClass->IsChildOf(UAnimationModifier::StaticClass()); + } + + virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override + { + return InClass->IsChildOf(UAnimationModifier::StaticClass()); + } + }; + + static TSharedRef GetModifierPicker(const FOnClassPicked& OnClassPicked) + { + FClassViewerInitializationOptions Options; + Options.bShowUnloadedBlueprints = true; + Options.bShowNoneOption = false; + TSharedPtr ClassFilter = MakeShareable(new FModifierClassFilter); + Options.ClassFilter = ClassFilter; + + return SNew(SBox) + .WidthOverride(280) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .MaxHeight(500) + [ + FModuleManager::LoadModuleChecked("ClassViewer").CreateClassViewer(Options, OnClassPicked) + ] + ]; + } + + /** Creates a new Modifier instance to store with the current asset */ + static UAnimationModifier* CreateModifierInstance(UObject* Outer, UClass* InClass, UObject* Template = nullptr) + { + checkf(Outer, TEXT("Invalid outer value for modifier instantiation")); + UAnimationModifier* ProcessorInstance = NewObject(Outer, InClass, NAME_None, RF_NoFlags, Template); + checkf(ProcessorInstance, TEXT("Unable to instantiate modifier class")); + ProcessorInstance->SetFlags(RF_Transactional); + return ProcessorInstance; + } + +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.cpp b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.cpp index 3ef91833204a..3449ed5c2662 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.cpp +++ b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.cpp @@ -12,6 +12,9 @@ #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" +#include "SAnimationModifierContentBrowserWindow.h" +#include "Framework/Application/SlateApplication.h" +#include "Interfaces/IMainFrameModule.h" #define LOCTEXT_NAMESPACE "AnimationModifiersModule" @@ -65,4 +68,31 @@ void FAnimationModifiersModule::ShutdownModule() RegisteredApplicationModes.Empty(); } +void FAnimationModifiersModule::ShowAddAnimationModifierWindow(const TArray& InSequences) +{ + TSharedPtr WindowContent; + + TSharedRef Window = SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Add Animation Modifier(s)")) + .SizingRule(ESizingRule::UserSized) + .ClientSize(FVector2D(500, 500)); + + Window->SetContent + ( + SAssignNew(WindowContent, SAnimationModifierContentBrowserWindow) + .WidgetWindow(Window) + .AnimSequences(InSequences) + ); + + TSharedPtr ParentWindow; + + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); +} + #undef LOCTEXT_NAMESPACE // "AnimationModifiersModule" diff --git a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.h b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.h index f51a38f931fa..9b9e8601c0d0 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.h +++ b/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifiersModule.h @@ -18,6 +18,10 @@ public: /** Called before the module is unloaded, right before the module object is destroyed */ virtual void ShutdownModule() override; + /** Begin IAnimationModifiersModule overrides */ + virtual void ShowAddAnimationModifierWindow(const TArray& InSequences) override; + /** End IAnimationModifiersModule overrides */ + protected: /** Callback for extending an application mode */ TSharedRef ExtendApplicationMode(const FName ModeName, TSharedRef InMode); diff --git a/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.cpp b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.cpp new file mode 100644 index 000000000000..075b6c0520c0 --- /dev/null +++ b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.cpp @@ -0,0 +1,267 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SAnimationModifierContentBrowserWindow.h" +#include "AnimationModifierHelpers.h" +#include "Widgets/SOverlay.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/Input/SComboButton.h" +#include "Widgets/Text/STextBlock.h" + +#include "EditorStyleSet.h" +#include "SModifierListview.h" +#include "PropertyEditorModule.h" + +#include "Animation/AnimSequence.h" +#include "AnimationModifiersAssetUserData.h" +#include "Dialogs/Dialogs.h" +#include "ScopedTransaction.h" + +#define LOCTEXT_NAMESPACE "AnimationModifierContentBrowserWindow" + +void SAnimationModifierContentBrowserWindow::Construct(const FArguments& InArgs) +{ + CreateInstanceDetailsView(); + + WidgetWindow = InArgs._WidgetWindow; + AnimSequences = InArgs._AnimSequences; + + FOnGetContent GetContent = FOnGetContent::CreateLambda( + [this]() + { + return FAnimationModifierHelpers::GetModifierPicker(FOnClassPicked::CreateRaw(this, &SAnimationModifierContentBrowserWindow::OnModifierPicked)); + }); + + this->ChildSlot + [ + SNew(SOverlay) + +SOverlay::Slot() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBorder) + .Padding(2.0f) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.0f, 3.0f) + .AutoWidth() + [ + SAssignNew(AddModifierCombobox, SComboButton) + .OnGetMenuContent(GetContent) + .ButtonContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("AnimationModifierWindow_AddModifier", "Add Modifier")) + ] + ] + ] + ] + + + SVerticalBox::Slot() + [ + SNew(SBorder) + .Padding(2.0f) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SSplitter) + .Orientation(EOrientation::Orient_Vertical) + + + SSplitter::Slot() + .Value(.5f) + [ + SNew(SBox) + .Padding(2.0f) + [ + SAssignNew(ModifierListView, SModifierListView) + .Items(&ModifierItems) + .InstanceDetailsView(ModifierInstanceDetailsView) + .OnRemoveModifier(FOnModifierArray::CreateSP(this, &SAnimationModifierContentBrowserWindow::RemoveModifiersCallback)) + ] + ] + + + SSplitter::Slot() + .Value(.5f) + [ + SNew(SBox) + .Padding(2.0f) + [ + ModifierInstanceDetailsView->AsShared() + ] + ] + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Right) + .Padding(2) + [ + SNew(SUniformGridPanel) + .SlotPadding(2) + + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .Text(LOCTEXT("AnimationModifierWindow_Import", "Apply")) + .ToolTipText(LOCTEXT("AnimationModifierWindow_Import_ToolTip", "Apply adding modifiers(s).")) + .IsEnabled(this, &SAnimationModifierContentBrowserWindow::CanApply) + .OnClicked(this, &SAnimationModifierContentBrowserWindow::OnApply) + ] + + SUniformGridPanel::Slot(1, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .Text(LOCTEXT("AnimationModifierWindow_Cancel", "Cancel")) + .ToolTipText(LOCTEXT("AnimationModifierWindow_Cancel_ToolTip", "Cancels adding modifiers(s).")) + .OnClicked(this, &SAnimationModifierContentBrowserWindow::OnCancel) + ] + ] + ] + ]; +} + +FReply SAnimationModifierContentBrowserWindow::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + if (InKeyEvent.GetKey() == EKeys::Escape) + { + return OnCancel(); + } + + return FReply::Unhandled(); +} + +void SAnimationModifierContentBrowserWindow::RemoveModifiersCallback(const TArray>& ModifiersToRemove) +{ + Modifiers.RemoveAll([&ModifiersToRemove](UAnimationModifier* Modifier) { return ModifiersToRemove.Contains(Modifier); }); + ModifierItems.RemoveAll([&ModifiersToRemove](TSharedPtr ModifierItem) { return ModifiersToRemove.Contains(ModifierItem->Instance); }); + ModifierListView->Refresh(); +} + +void SAnimationModifierContentBrowserWindow::OnModifierPicked(UClass* PickedClass) +{ + UAnimationModifier* Processor = FAnimationModifierHelpers::CreateModifierInstance(GetTransientPackage(), PickedClass); + + Modifiers.Add(Processor); + + FModifierListviewItem* Item = new FModifierListviewItem(); + Item->Instance = Processor; + Item->Class = Processor->GetClass(); + Item->Index = Modifiers.Num() - 1; + Item->OuterClass = nullptr; + ModifierItems.Add(ModifierListviewItem(Item)); + + // Close the combo box + AddModifierCombobox->SetIsOpen(false); + + ModifierListView->Refresh(); +} + +void SAnimationModifierContentBrowserWindow::CreateInstanceDetailsView() +{ + // Create a property view + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + + FDetailsViewArgs DetailsViewArgs( + /*bUpdateFromSelection=*/ false, + /*bLockable=*/ false, + /*bAllowSearch=*/ false, + FDetailsViewArgs::HideNameArea, + /*bHideSelectionTip=*/ true, + /*InNotifyHook=*/ nullptr, + /*InSearchInitialKeyFocus=*/ false, + /*InViewIdentifier=*/ NAME_None); + DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic; + DetailsViewArgs.bShowOptions = false; + + ModifierInstanceDetailsView = EditModule.CreateDetailView(DetailsViewArgs); + ModifierInstanceDetailsView->SetDisableCustomDetailLayouts(true); +} + +FReply SAnimationModifierContentBrowserWindow::OnApply() +{ + const FScopedTransaction Transaction(LOCTEXT("UndoAction_ApplyModifiers", "Applying Animation Modifier(s) to Animation Sequence(s)")); + + TArray AssetUserData; + + bool bCloseWindow = true; + + // Retrieve or create asset user data + for (UAnimSequence* AnimationSequence : AnimSequences) + { + UAnimationModifiersAssetUserData* UserData = AnimationSequence->GetAssetUserData(); + if (!UserData) + { + UserData = NewObject(AnimationSequence, UAnimationModifiersAssetUserData::StaticClass()); + checkf(UserData, TEXT("Unable to instantiate AssetUserData class")); + UserData->SetFlags(RF_Transactional); + AnimationSequence->AddAssetUserData(UserData); + } + + AssetUserData.Add(UserData); + } + + // For each added modifier create add a new instance to each of the user data entries, using the one(s) set up in the window as template(s) + for (UAnimationModifier* Modifier : Modifiers) + { + for (UAnimationModifiersAssetUserData* UserData : AssetUserData) + { + const bool bAlreadyContainsModifier = UserData->GetAnimationModifierInstances().ContainsByPredicate([Modifier](UAnimationModifier* TestModifier) { return Modifier->GetClass() == TestModifier->GetClass(); }); + static const FText MessageFormat = LOCTEXT("AnimationModifierWindow_AlreadyContainsModifierDialogText", "{0} already contains Animation Modifier {1}, are you sure you want to add another instance?"); + + const bool bUserInputResult = bAlreadyContainsModifier ? OpenMsgDlgInt(EAppMsgType::YesNo, FText::FormatOrdered(MessageFormat, FText::FromString(UserData->GetOuter()->GetName()), FText::FromString(Modifier->GetClass()->GetName())), LOCTEXT("AnimationModifierWindow_AlreadyContainsModifierTitle", "Already contains Animation Modifier!")) == EAppReturnType::Yes : true; + bCloseWindow = bUserInputResult; + if (!bAlreadyContainsModifier || bUserInputResult) + { + UObject* Outer = UserData; + UAnimationModifier* Processor = FAnimationModifierHelpers::CreateModifierInstance(Outer, Modifier->GetClass(), Modifier); + UserData->Modify(); + UserData->AddAnimationModifier(Processor); + } + } + } + + /** For each user data retrieve all modifiers and apply them */ + for (int32 Index = 0; Index < AssetUserData.Num(); ++Index) + { + UAnimationModifiersAssetUserData* UserData = AssetUserData[Index]; + if (UserData) + { + UAnimSequence* AnimSequence = AnimSequences[Index]; + AnimSequence->Modify(); + + const TArray& ModifierInstances = UserData->GetAnimationModifierInstances(); + for (UAnimationModifier* Modifier : ModifierInstances) + { + Modifier->ApplyToAnimationSequence(AnimSequence); + } + } + } + + if (bCloseWindow && WidgetWindow.IsValid()) + { + WidgetWindow.Pin()->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply SAnimationModifierContentBrowserWindow::OnCancel() +{ + if (WidgetWindow.IsValid()) + { + WidgetWindow.Pin()->RequestDestroyWindow(); + } + return FReply::Handled(); +} + +bool SAnimationModifierContentBrowserWindow::CanApply() const +{ + return Modifiers.Num() > 0; +} + +#undef LOCTEXT_NAMESPACE // "AnimationModifierContentBrowserWindow" \ No newline at end of file diff --git a/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.h b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.h new file mode 100644 index 000000000000..ed5a92d8cc24 --- /dev/null +++ b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifierContentBrowserWindow.h @@ -0,0 +1,67 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWindow.h" +#include "Widgets/Input/SButton.h" + +#include "Templates/SharedPointer.h" +#include "Containers/Array.h" + +class SButton; +class SComboButton; +class SModifierListView; +class SMenuAnchor; +class UAnimationModifier; +class UAnimSequence; +class IDetailsView; + +struct FModifierListviewItem; + +/** UI slate widget allowing the user to add Animation Modifier(s) to a selection of Animation Sequences */ +class SAnimationModifierContentBrowserWindow : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SAnimationModifierContentBrowserWindow) + : _WidgetWindow() + {} + SLATE_ARGUMENT(TSharedPtr, WidgetWindow) + SLATE_ARGUMENT(TArray, AnimSequences) + SLATE_END_ARGS() + +public: + SAnimationModifierContentBrowserWindow() {} + void Construct(const FArguments& InArgs); + virtual bool SupportsKeyboardFocus() const override { return true; } + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; +protected: + /** Callback for when the user picks a specific animation modifier class */ + void OnModifierPicked(UClass* PickedClass); + /** Callback for when the user wants to remove modifier(s) from the listview */ + void RemoveModifiersCallback(const TArray>& ModifiersToRemove); + + /** Creates the details view widget used to show Animation Modifier object details */ + void CreateInstanceDetailsView(); + + /** Button callback, this applies all currently set up Animation Modifiers to the previously selected Animation Sequences */ + FReply OnApply(); + /** Button callback, closes the dialog/window */ + FReply OnCancel(); + /** Check to see whether or not the user can apply the modifiers in the current state */ + bool CanApply() const; +private: + /** Window owning this window */ + TWeakPtr WidgetWindow; + + TSharedPtr ModifierInstanceDetailsView; + TSharedPtr AddModifierCombobox; + TSharedPtr ModifierListView; + + /** Data structures used by the Modifier List View widget */ + TArray> ModifierItems; + /** Current set of Animation Modifiers that would be added during Apply */ + TArray Modifiers; + /** Previously user-selected Animation Sequences */ + TArray AnimSequences; +}; \ No newline at end of file diff --git a/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifiersTab.cpp b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifiersTab.cpp index 072936062ce4..cab5f4f02101 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifiersTab.cpp +++ b/Engine/Source/Editor/AnimationModifiers/Private/SAnimationModifiersTab.cpp @@ -25,24 +25,10 @@ #include "ScopedTransaction.h" #include "Widgets/Input/SMenuAnchor.h" #include "Engine/BlueprintGeneratedClass.h" +#include "AnimationModifierHelpers.h" #define LOCTEXT_NAMESPACE "SAnimationModifiersTab" -/** ClassViewerFilter for Animation Modifier classes */ -class FModifierClassFilter : public IClassViewerFilter -{ -public: - bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override - { - return InClass->IsChildOf(UAnimationModifier::StaticClass()); - } - - virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override - { - return InClass->IsChildOf(UAnimationModifier::StaticClass()); - } -}; - SAnimationModifiersTab::SAnimationModifiersTab() : Skeleton(nullptr), AnimationSequence(nullptr), AssetUserData(nullptr), bDirty(false) { @@ -58,16 +44,6 @@ SAnimationModifiersTab::~SAnimationModifiersTab() FAssetEditorManager::Get().OnAssetOpenedInEditor().RemoveAll(this); } -UAnimationModifier* SAnimationModifiersTab::CreateModifierInstance(UObject* Outer, UClass* InClass) -{ - checkf(Outer, TEXT("Invalid outer value for modifier instantiation")); - UAnimationModifier* ProcessorInstance = NewObject(Outer, InClass); - checkf(ProcessorInstance, TEXT("Unable to instantiate modifier class")); - ProcessorInstance->SetFlags(RF_Transactional); - return ProcessorInstance; -} - - void SAnimationModifiersTab::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { if (bDirty) @@ -97,6 +73,12 @@ void SAnimationModifiersTab::Construct(const FArguments& InArgs) RetrieveModifierData(); CreateInstanceDetailsView(); + + FOnGetContent GetContent = FOnGetContent::CreateLambda( + [this]() + { + return FAnimationModifierHelpers::GetModifierPicker(FOnClassPicked::CreateRaw(this, &SAnimationModifiersTab::OnModifierPicked)); + }); this->ChildSlot [ @@ -117,7 +99,7 @@ void SAnimationModifiersTab::Construct(const FArguments& InArgs) .AutoWidth() [ SAssignNew(AddModifierCombobox, SComboButton) - .OnGetMenuContent(this, &SAnimationModifiersTab::GetModifierPicker) + .OnGetMenuContent(GetContent) .ButtonContent() [ SNew(STextBlock) @@ -193,36 +175,12 @@ void SAnimationModifiersTab::Construct(const FArguments& InArgs) FAssetEditorManager::Get().OnAssetOpenedInEditor().AddSP(this, &SAnimationModifiersTab::OnAssetOpened); } - -TSharedRef SAnimationModifiersTab::GetModifierPicker() -{ - FClassViewerInitializationOptions Options; - Options.bShowUnloadedBlueprints = true; - Options.bShowNoneOption = false; - TSharedPtr ClassFilter = MakeShareable(new FModifierClassFilter); - Options.ClassFilter = ClassFilter; - - FOnClassPicked OnPicked(FOnClassPicked::CreateRaw(this, &SAnimationModifiersTab::OnModifierPicked)); - - return SNew(SBox) - .WidthOverride(280) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .AutoHeight() - .MaxHeight(500) - [ - FModuleManager::LoadModuleChecked("ClassViewer").CreateClassViewer(Options, OnPicked) - ] - ]; -} - void SAnimationModifiersTab::OnModifierPicked(UClass* PickedClass) { FScopedTransaction Transaction(LOCTEXT("AddModifierTransaction", "Adding Animation Modifier")); UObject* Outer = AssetUserData; - UAnimationModifier* Processor = CreateModifierInstance(Outer, PickedClass); + UAnimationModifier* Processor = FAnimationModifierHelpers::CreateModifierInstance(Outer, PickedClass); AssetUserData->Modify(); AssetUserData->AddAnimationModifier(Processor); @@ -403,7 +361,7 @@ void SAnimationModifiersTab::OnOpenModifier(const TWeakObjectPtr GetModifierPicker(); /** Callback for when user has picked a modifier to add */ void OnModifierPicked(UClass* PickedClass); @@ -70,8 +68,7 @@ protected: /** Retrieves the currently opened animation asset type and modifier user data */ void RetrieveAnimationAsset(); - /** Creates a new Modifier instance to store with the current asset */ - UAnimationModifier* CreateModifierInstance(UObject* Outer, UClass* InClass); + /** Retrieves all animation sequences which are dependent on the current opened skeleton */ void FindAnimSequencesForSkeleton(TArray &ReferencedAnimSequences); protected: diff --git a/Engine/Source/Editor/AnimationModifiers/Private/SModifierListview.cpp b/Engine/Source/Editor/AnimationModifiers/Private/SModifierListview.cpp index f9c80abc5eb0..4f741c80181a 100644 --- a/Engine/Source/Editor/AnimationModifiers/Private/SModifierListview.cpp +++ b/Engine/Source/Editor/AnimationModifiers/Private/SModifierListview.cpp @@ -138,22 +138,31 @@ TSharedPtr SModifierListView::OnContextMenuOpening() MenuBuilder.BeginSection(NAME_None); { - if (Listview->GetNumItemsSelected() == 1) + if (Listview->GetNumItemsSelected() == 1 && OnOpenModifierDelegate.IsBound()) { MenuBuilder.AddMenuEntry(LOCTEXT("OpenModifierLabel", "Open Blueprint"), LOCTEXT("OpenModifierToolTip", "Open selected Modifier Blueprint"), FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.Blueprint"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnOpenModifier))); } const FText ApplyLabel = FText::FormatOrdered(LOCTEXT("ApplyModifierLabel", "Apply {0}|plural(one=Modifier,other=Modifiers)"), NumItems); const FText ApplyTooltip = FText::FormatOrdered(LOCTEXT("ApplyModifierToolTip", "Apply selected {0}|plural(one=Modifier,other=Modifiers)"), NumItems); - MenuBuilder.AddMenuEntry(ApplyLabel, ApplyTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCommands.Redo"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnApplyModifier))); - + if (OnApplyModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(ApplyLabel, ApplyTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCommands.Redo"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnApplyModifier))); + } + const FText RevertLabel = FText::FormatOrdered(LOCTEXT("ApplyRevertLabel", "Revert {0}|plural(one=Modifier,other=Modifiers)"), NumItems); const FText RevertTooltip = FText::FormatOrdered(LOCTEXT("ApplyRevertToolTip", "Revert selected {0}|plural(one=Modifier,other=Modifiers)"), NumItems); - MenuBuilder.AddMenuEntry(RevertLabel, RevertTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCommands.Undo"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnRevertModifier))); + if (OnRevertModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(RevertLabel, RevertTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "GenericCommands.Undo"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnRevertModifier))); + } const FText RemoveLabel = FText::FormatOrdered(LOCTEXT("RemoveModifierLabel", "Remove {0}|plural(one=Modifier,other=Modifiers)"), NumItems); const FText RemoveTooltip = FText::FormatOrdered(LOCTEXT("RemoveModifierToolTip", "Remove selected {0}|plural(one=Modifier,other=Modifiers)"), NumItems); - MenuBuilder.AddMenuEntry(RemoveLabel, RemoveTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Delete"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnRemoveModifier))); + if (OnRemoveModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(RemoveLabel, RemoveTooltip, FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.AssetActions.Delete"), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnRemoveModifier))); + } } MenuBuilder.EndSection(); @@ -161,9 +170,15 @@ TSharedPtr SModifierListView::OnContextMenuOpening() { MenuBuilder.BeginSection(NAME_None); { - MenuBuilder.AddMenuEntry(LOCTEXT("MoveUpModifierLabel", "Move Up"), LOCTEXT("MoveUpModifierToolTip", "Move selected Modifier Up in list"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnMoveUpModifier), FCanExecuteAction::CreateSP( this, &SModifierListView::CanMoveSelectedItemUp))); + if (OnMoveUpModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(LOCTEXT("MoveUpModifierLabel", "Move Up"), LOCTEXT("MoveUpModifierToolTip", "Move selected Modifier Up in list"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnMoveUpModifier), FCanExecuteAction::CreateSP(this, &SModifierListView::CanMoveSelectedItemUp))); + } - MenuBuilder.AddMenuEntry(LOCTEXT("MoveDownModifierLabel", "Move Down"), LOCTEXT("MoveDownModifierToolTip", "Move selected Modifier Down in list"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnMoveDownModifier), FCanExecuteAction::CreateSP(this, &SModifierListView::CanMoveSelectedItemDown))); + if (OnMoveDownModifierDelegate.IsBound()) + { + MenuBuilder.AddMenuEntry(LOCTEXT("MoveDownModifierLabel", "Move Down"), LOCTEXT("MoveDownModifierToolTip", "Move selected Modifier Down in list"), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SModifierListView::OnMoveDownModifier), FCanExecuteAction::CreateSP(this, &SModifierListView::CanMoveSelectedItemDown))); + } } MenuBuilder.EndSection(); } diff --git a/Engine/Source/Editor/AnimationModifiers/Public/IAnimationModifiersModule.h b/Engine/Source/Editor/AnimationModifiers/Public/IAnimationModifiersModule.h index aac763449b44..04d36ba16b6a 100644 --- a/Engine/Source/Editor/AnimationModifiers/Public/IAnimationModifiersModule.h +++ b/Engine/Source/Editor/AnimationModifiers/Public/IAnimationModifiersModule.h @@ -3,7 +3,13 @@ #pragma once #include "Modules/ModuleInterface.h" +#include "Containers/Array.h" + +class UAnimSequence; class IAnimationModifiersModule : public IModuleInterface { +public: + /** Shows a new modal dialog allowing the user to setup Animation Modifiers to be added for all AnimSequences part of InSequences */ + virtual void ShowAddAnimationModifierWindow(const TArray& InSequences) = 0; }; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp index 2f61536650d2..46acc38abfb4 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.cpp @@ -15,6 +15,7 @@ #include "K2Node.h" #include "Widgets/Input/SButton.h" #include "K2Node_BreakStruct.h" +#include "K2Node_GetClassDefaults.h" #include "ScopedTransaction.h" #define LOCTEXT_NAMESPACE "SkeletalControlNodeDetails" @@ -36,16 +37,16 @@ void FSkeletalControlNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& D const TArray< TWeakObjectPtr >& SelectedObjects = DetailBuilder.GetSelectedObjects(); for (const TWeakObjectPtr& CurrentObject : SelectedObjects) { - if (UK2Node_BreakStruct* CurrBreakStruct = Cast(CurrentObject.Get())) + if (Cast(CurrentObject.Get()) || Cast(CurrentObject.Get())) { - if (BreakStructNode.IsValid()) + if (HideUnconnectedPinsNode.IsValid()) { // Have more than one break struct node, don't cache so we don't // create the hide unconnected pins UI - BreakStructNode = nullptr; + HideUnconnectedPinsNode = nullptr; break; } - BreakStructNode = CurrBreakStruct; + HideUnconnectedPinsNode = Cast(CurrentObject.Get()); } } @@ -90,7 +91,7 @@ void FSkeletalControlNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& D } // Add the action buttons - if(BreakStructNode.IsValid()) + if(HideUnconnectedPinsNode.IsValid()) { FDetailWidgetRow& GroupActionsRow = DetailCategory->AddCustomRow(LOCTEXT("GroupActionsSearchText", "Split Sort")) .ValueContent() @@ -99,7 +100,7 @@ void FSkeletalControlNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& D [ SNew(SButton) .OnClicked(this, &FSkeletalControlNodeDetails::HideAllUnconnectedPins) - .ToolTipText(LOCTEXT("HideAllUnconnectedPinsTooltip", "All unconnected pins of the structure get hidden (removed from the graph node)")) + .ToolTipText(LOCTEXT("HideAllUnconnectedPinsTooltip", "All unconnected pins get hidden (removed from the graph node)")) [ SNew(STextBlock) .Text(LOCTEXT("HideAllUnconnectedPins", "Hide Unconnected Pins")) @@ -246,7 +247,7 @@ void FSkeletalControlNodeDetails::OnGenerateElementForPropertyPin(TSharedRefGetNumElements(NumChildren); @@ -261,7 +262,7 @@ FReply FSkeletalControlNodeDetails::HideAllUnconnectedPins() FName ActualPropertyName; if (PropertyNameHandle.IsValid() && PropertyNameHandle->GetValue(ActualPropertyName) == FPropertyAccess::Success) { - const UEdGraphPin* Pin = BreakStructNode->FindPin(ActualPropertyName.ToString(), EGPD_Output); + const UEdGraphPin* Pin = HideUnconnectedPinsNode->FindPin(ActualPropertyName.ToString(), EGPD_Output); if (Pin && Pin->LinkedTo.Num() <= 0) { OnShowPinChanged(ECheckBoxState::Unchecked, ElementHandle); diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.h b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.h index cbc175c40148..fd7316b3aaae 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.h +++ b/Engine/Source/Editor/DetailCustomizations/Private/SkeletalControlNodeDetails.h @@ -37,6 +37,6 @@ protected: private: IDetailCategoryBuilder* DetailCategory; - TWeakObjectPtr BreakStructNode; + TWeakObjectPtr HideUnconnectedPinsNode; TSharedPtr ArrayProperty; }; diff --git a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp index 24855ec5e2d6..4a1968899d72 100644 --- a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp +++ b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp @@ -5369,8 +5369,11 @@ void FSlateEditorStyle::FStyle::SetupPersonaStyle() // Anim Slot Manager Set("AnimSlotManager.SaveSkeleton", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_SaveSkeleton_40x", Icon40x40)); + Set("AnimSlotManager.SaveSkeleton.Small", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_SaveSkeleton_40x", Icon20x20)); Set("AnimSlotManager.AddGroup", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_AddGroup_40x", Icon40x40)); + Set("AnimSlotManager.AddGroup.Small", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_AddGroup_40x", Icon20x20)); Set("AnimSlotManager.AddSlot", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_AddSlot_40x", Icon40x40)); + Set("AnimSlotManager.AddSlot.Small", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_AddSlot_40x", Icon20x20)); Set("AnimSlotManager.Warning", new IMAGE_BRUSH("Persona/AnimSlotManager/icon_Warning_14x", Icon16x16)); // Anim Notify Editor @@ -6170,6 +6173,7 @@ void FSlateEditorStyle::FStyle::SetupClassIconsAndThumbnails() TEXT("AnimComposite"), TEXT("AnimMontage"), TEXT("AnimSequence"), + TEXT("AnimationSharingSetup"), TEXT("ApplicationLifecycleComponent"), TEXT("AtmosphericFog"), TEXT("BehaviorTree"), diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp index 113944f0def1..7941e6729c1a 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintEditor.cpp @@ -5303,6 +5303,18 @@ void FBlueprintEditor::DeleteSelectedNodes() { if (Node->CanUserDeleteNode()) { + // if it's state based anim node, make sure we close the sub graph first + // otherwise, we leave the orphan graph around + UAnimStateNodeBase* StateNode = Cast(Node); + if (StateNode) + { + UEdGraph* NodeGraph = StateNode->GetBoundGraph(); + if (NodeGraph) + { + CloseDocumentTab(NodeGraph); + } + } + UK2Node* K2Node = Cast(Node); if (K2Node != NULL && K2Node->NodeCausesStructuralBlueprintChange()) { diff --git a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp index 8b3438cbea82..36c174cd9582 100644 --- a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp +++ b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp @@ -1228,97 +1228,99 @@ public: { FAssetRegistryModule* AssetRegistryModule = &FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); FAssetData AssetData = AssetRegistryModule->Get().GetAssetByObjectPath(UncachedAssets[TickCacheIndex]); - - const bool bIsWorldAsset = AssetData.AssetClass == UWorld::StaticClass()->GetFName(); - - // Construct a full package filename with path so we can query the read only status and save to disk - FString FinalPackageFilename = FPackageName::LongPackageNameToFilename(AssetData.PackageName.ToString()); - if( FinalPackageFilename.Len() > 0 && FPaths::GetExtension(FinalPackageFilename).Len() == 0 ) + if (AssetData.IsValid()) { - FinalPackageFilename += bIsWorldAsset ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension(); - } - FText ErrorMessage; - bool bValidFilename = FFileHelper::IsFilenameValidForSaving( FinalPackageFilename, ErrorMessage ); - if ( bValidFilename ) - { - bValidFilename = bIsWorldAsset ? FEditorFileUtils::IsValidMapFilename( FinalPackageFilename, ErrorMessage ) : FPackageName::IsValidLongPackageName( FinalPackageFilename, false, &ErrorMessage ); - } + const bool bIsWorldAsset = AssetData.AssetClass == UWorld::StaticClass()->GetFName(); - bool bIsAssetReadOnlyOnDisk = IFileManager::Get().IsReadOnly( *FinalPackageFilename ); - bool bFailedToCache = CacheParams.bCheckOutAndSave; - - if (!bIsAssetReadOnlyOnDisk || !CacheParams.bCheckOutAndSave) - { - if (!FFindInBlueprintSearchManager::Get().IsUnindexedCacheInProgress()) + // Construct a full package filename with path so we can query the read only status and save to disk + FString FinalPackageFilename = FPackageName::LongPackageNameToFilename(AssetData.PackageName.ToString()); + if (FinalPackageFilename.Len() > 0 && FPaths::GetExtension(FinalPackageFilename).Len() == 0) { - // Re-index and update the cached value for loaded Blueprint assets only - if (AssetData.IsAssetLoaded()) + FinalPackageFilename += bIsWorldAsset ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension(); + } + FText ErrorMessage; + bool bValidFilename = FFileHelper::IsFilenameValidForSaving(FinalPackageFilename, ErrorMessage); + if (bValidFilename) + { + bValidFilename = bIsWorldAsset ? FEditorFileUtils::IsValidMapFilename(FinalPackageFilename, ErrorMessage) : FPackageName::IsValidLongPackageName(FinalPackageFilename, false, &ErrorMessage); + } + + bool bIsAssetReadOnlyOnDisk = IFileManager::Get().IsReadOnly(*FinalPackageFilename); + bool bFailedToCache = CacheParams.bCheckOutAndSave; + + if (!bIsAssetReadOnlyOnDisk || !CacheParams.bCheckOutAndSave) + { + if (!FFindInBlueprintSearchManager::Get().IsUnindexedCacheInProgress()) { - if (UBlueprint* LoadedBlueprintAsset = Cast(AssetData.GetAsset())) + // Re-index and update the cached value for loaded Blueprint assets only + if (AssetData.IsAssetLoaded()) { - FFindInBlueprintSearchManager::Get().AddOrUpdateBlueprintSearchMetadata(LoadedBlueprintAsset, true); + if (UBlueprint* LoadedBlueprintAsset = Cast(AssetData.GetAsset())) + { + FFindInBlueprintSearchManager::Get().AddOrUpdateBlueprintSearchMetadata(LoadedBlueprintAsset, true); + } } } - } - else - { - UObject* Asset = AssetData.GetAsset(); - if (Asset && CacheParams.bCheckOutAndSave) + else { - if (UBlueprint* BlueprintAsset = Cast(Asset)) + UObject* Asset = AssetData.GetAsset(); + if (Asset && CacheParams.bCheckOutAndSave) { - if (BlueprintAsset->SkeletonGeneratedClass == nullptr) + if (UBlueprint* BlueprintAsset = Cast(Asset)) { - // There is no skeleton class, something was wrong with the Blueprint during compile on load. This asset will be marked as failing to cache. - bFailedToCache = false; - } - } - - // Still good to attempt to save - if (bFailedToCache) - { - // Assume the package was correctly checked out from SCC - bool bOutPackageLocallyWritable = true; - - UPackage* Package = AssetData.GetPackage(); - - ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); - // Trusting the SCC status in the package file cache to minimize network activity during save. - const FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(Package, EStateCacheUsage::Use); - // If the package is in the depot, and not recognized as editable by source control, and not read-only, then we know the user has made the package locally writable! - const bool bSCCCanEdit = !SourceControlState.IsValid() || SourceControlState->CanCheckIn() || SourceControlState->IsIgnored() || SourceControlState->IsUnknown(); - const bool bSCCIsCheckedOut = SourceControlState.IsValid() && SourceControlState->IsCheckedOut(); - const bool bInDepot = SourceControlState.IsValid() && SourceControlState->IsSourceControlled(); - if (!bSCCCanEdit && bInDepot && !bIsAssetReadOnlyOnDisk && SourceControlProvider.UsesLocalReadOnlyState() && !bSCCIsCheckedOut) - { - bOutPackageLocallyWritable = false; - } - - // Save the package if the file is writable - if (bOutPackageLocallyWritable) - { - UWorld* WorldAsset = Cast(Asset); - - // Save the package - EObjectFlags ObjectFlags = (WorldAsset == nullptr) ? RF_Standalone : RF_NoFlags; - - if (GEditor->SavePackage(Package, WorldAsset, ObjectFlags, *FinalPackageFilename, GError, nullptr, false, true, SAVE_NoError)) + if (BlueprintAsset->SkeletonGeneratedClass == nullptr) { + // There is no skeleton class, something was wrong with the Blueprint during compile on load. This asset will be marked as failing to cache. bFailedToCache = false; } } + + // Still good to attempt to save + if (bFailedToCache) + { + // Assume the package was correctly checked out from SCC + bool bOutPackageLocallyWritable = true; + + UPackage* Package = AssetData.GetPackage(); + + ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); + // Trusting the SCC status in the package file cache to minimize network activity during save. + const FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(Package, EStateCacheUsage::Use); + // If the package is in the depot, and not recognized as editable by source control, and not read-only, then we know the user has made the package locally writable! + const bool bSCCCanEdit = !SourceControlState.IsValid() || SourceControlState->CanCheckIn() || SourceControlState->IsIgnored() || SourceControlState->IsUnknown(); + const bool bSCCIsCheckedOut = SourceControlState.IsValid() && SourceControlState->IsCheckedOut(); + const bool bInDepot = SourceControlState.IsValid() && SourceControlState->IsSourceControlled(); + if (!bSCCCanEdit && bInDepot && !bIsAssetReadOnlyOnDisk && SourceControlProvider.UsesLocalReadOnlyState() && !bSCCIsCheckedOut) + { + bOutPackageLocallyWritable = false; + } + + // Save the package if the file is writable + if (bOutPackageLocallyWritable) + { + UWorld* WorldAsset = Cast(Asset); + + // Save the package + EObjectFlags ObjectFlags = (WorldAsset == nullptr) ? RF_Standalone : RF_NoFlags; + + if (GEditor->SavePackage(Package, WorldAsset, ObjectFlags, *FinalPackageFilename, GError, nullptr, false, true, SAVE_NoError)) + { + bFailedToCache = false; + } + } + } } } } - } - if (bFailedToCache) - { - FailedToCacheList.Add(UncachedAssets[TickCacheIndex]); - } - else - { - CacheParams.OnCached.ExecuteIfBound(UncachedAssets[TickCacheIndex]); + if (bFailedToCache) + { + FailedToCacheList.Add(UncachedAssets[TickCacheIndex]); + } + else + { + CacheParams.OnCached.ExecuteIfBound(UncachedAssets[TickCacheIndex]); + } } ++TickCacheIndex; diff --git a/Engine/Source/Editor/MergeActors/Private/MergeProxyUtils/Utils.cpp b/Engine/Source/Editor/MergeActors/Private/MergeProxyUtils/Utils.cpp index 1c2762d88aa0..89c7f723fb78 100644 --- a/Engine/Source/Editor/MergeActors/Private/MergeProxyUtils/Utils.cpp +++ b/Engine/Source/Editor/MergeActors/Private/MergeProxyUtils/Utils.cpp @@ -122,28 +122,11 @@ void FComponentSelectionControl::UpdateSelectedStaticMeshComponents() TSharedRef FComponentSelectionControl::MakeComponentListItemWidget(TSharedPtr ComponentData, const TSharedRef& OwnerTable) { check(ComponentData->PrimComponent != nullptr); - - // Retrieve information about the mesh component - const FString OwningActorName = ComponentData->PrimComponent->GetOwner()->GetName(); - + // If box should be enabled bool bEnabled = true; bool bIsMesh = false; - FString ComponentInfo; - if (UStaticMeshComponent* StaticMeshComponent = Cast(ComponentData->PrimComponent.Get())) - { - ComponentInfo = (StaticMeshComponent->GetStaticMesh() != nullptr) ? StaticMeshComponent->GetStaticMesh()->GetName() : TEXT("No Static Mesh Available"); - bEnabled = (StaticMeshComponent->GetStaticMesh() != nullptr); - bIsMesh = true; - } - else if (UShapeComponent* ShapeComponent = Cast(ComponentData->PrimComponent.Get())) - { - ComponentInfo = ShapeComponent->GetClass()->GetName(); - } - - const FString ComponentName = ComponentData->PrimComponent->GetName(); - // See if we stored a checkbox state for this mesh component, and set accordingly ECheckBoxState State = bEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; ECheckBoxState* StoredState = StoredCheckBoxStates.Find(ComponentData->PrimComponent.Get()); @@ -152,7 +135,6 @@ TSharedRef FComponentSelectionControl::MakeComponentListItemWidget(TS State = *StoredState; } - return SNew(STableRow>, OwnerTable) [ SNew(SBox) diff --git a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/AudioTrackEditor.cpp b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/AudioTrackEditor.cpp index 0f9302460804..513011a99d50 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/AudioTrackEditor.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/TrackEditors/AudioTrackEditor.cpp @@ -306,7 +306,7 @@ void FAudioThumbnail::GenerateWaveformPreview(TArray& OutData, TRangeInitAudioResource( AudioDevice->GetRuntimeFormat( SoundWave ) ) && (SoundWave->DecompressionType != DTYPE_RealTime || SoundWave->CachedRealtimeFirstBuffer == nullptr ) ) { - FAsyncAudioDecompress TempDecompress(SoundWave, 0); + FAsyncAudioDecompress TempDecompress(SoundWave, AudioDevice->NumPrecacheFrames); TempDecompress.StartSynchronousTask(); } diff --git a/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.cpp b/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.cpp index b98c6b1a0120..83975b37f2d2 100644 --- a/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.cpp +++ b/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.cpp @@ -32,8 +32,6 @@ void FAnimViewportShowCommands::RegisterCommands() UI_COMMAND( ShowBoneWeight, "Selected Bone Weight", "Display color overlay of the weight from selected bone in the viewport", EUserInterfaceActionType::RadioButton, FInputChord() ); UI_COMMAND( ShowMorphTargetVerts, "Selected Morph Target Vertices", "Display color overlay with the change of selected morph target in the viewport", EUserInterfaceActionType::RadioButton, FInputChord()); - UI_COMMAND(ShowVertexColors, "Vertex Colors", "Display mesh vertex colors", EUserInterfaceActionType::ToggleButton, FInputChord()); - UI_COMMAND( ShowRawAnimation, "Uncompressed Animation", "Display skeleton with uncompressed animation data", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( ShowNonRetargetedAnimation, "Non-Retargeted Animation", "Display Skeleton With non-retargeted animation data", EUserInterfaceActionType::ToggleButton, FInputChord() ); UI_COMMAND( ShowAdditiveBaseBones, "Additive Base", "Display skeleton in additive base pose", EUserInterfaceActionType::ToggleButton, FInputChord() ); diff --git a/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.h b/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.h index 6b2d021c91aa..36a9976f0f6a 100644 --- a/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.h +++ b/Engine/Source/Editor/Persona/Private/AnimViewportShowCommands.h @@ -98,9 +98,6 @@ public: TSharedPtr< FUICommandInfo > ShowBoneWeight; TSharedPtr< FUICommandInfo > ShowMorphTargetVerts; - /** Show mesh vertex colors */ - TSharedPtr< FUICommandInfo > ShowVertexColors; - /** Show socket hit point diamonds */ TSharedPtr< FUICommandInfo > ShowSockets; diff --git a/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp b/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp index e3cc37f339f9..afcaa939672c 100644 --- a/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp +++ b/Engine/Source/Editor/Persona/Private/AnimationEditorViewportClient.cpp @@ -1901,6 +1901,12 @@ void FAnimationViewportClient::SetupViewForRendering( FSceneViewFamily& ViewFami void FAnimationViewportClient::HandleToggleShowFlag(FEngineShowFlags::EShowFlag EngineShowFlagIndex) { FEditorViewportClient::HandleToggleShowFlag(EngineShowFlagIndex); + + if (UDebugSkelMeshComponent* Component = GetAnimPreviewScene()->GetPreviewMeshComponent()) + { + Component->bDisplayVertexColors = EngineShowFlags.VertexColors; + Component->MarkRenderStateDirty(); + } ConfigOption->SetShowGrid(EngineShowFlags.Grid); } diff --git a/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp b/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp index bdf57b7bf423..4aa884491f97 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimCurvePanel.cpp @@ -164,7 +164,8 @@ public: UpdateNameInternal(AnimSequenceBase.Get()->RawCurveData, RequestedNameUID, RequestedName); if (UAnimSequence* Seq = AnimSequence.Get()) { - UpdateNameInternal(Seq->CompressedCurveData, RequestedNameUID, RequestedName); + FSmartName NewCurveName(RequestedName, RequestedNameUID); + Seq->UpdateCompressedCurveName(CurveUID, NewCurveName); } CurveUID = RequestedNameUID; } @@ -175,13 +176,6 @@ public: void SetCurveTypeFlag(EAnimAssetCurveFlags InFlag, bool bValue) { AnimSequenceBase.Get()->RawCurveData.GetCurveData(CurveUID)->SetCurveTypeFlag(InFlag, bValue); - if (UAnimSequence* Seq = AnimSequence.Get()) - { - if (FAnimCurveBase* CompressedCurve = Seq->CompressedCurveData.GetCurveData(CurveUID)) - { - CompressedCurve->SetCurveTypeFlag(InFlag, bValue); - } - } } /** @@ -190,13 +184,6 @@ public: void ToggleCurveTypeFlag(EAnimAssetCurveFlags InFlag) { AnimSequenceBase.Get()->RawCurveData.GetCurveData(CurveUID)->ToggleCurveTypeFlag(InFlag); - if (UAnimSequence* Seq = AnimSequence.Get()) - { - if (FAnimCurveBase* CompressedCurve = Seq->CompressedCurveData.GetCurveData(CurveUID)) - { - CompressedCurve->ToggleCurveTypeFlag(InFlag); - } - } } /** diff --git a/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp b/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp index 6f0385c1eff5..fa1c4dd2a7fb 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimViewportToolBar.cpp @@ -604,7 +604,6 @@ TSharedRef SAnimViewportToolBar::GenerateCharacterMenu() const SubMenuBuilder.AddMenuEntry(FAnimViewportShowCommands::Get().UseFixedBounds); SubMenuBuilder.AddMenuEntry(FAnimViewportShowCommands::Get().ShowPreviewMesh ); SubMenuBuilder.AddMenuEntry(FAnimViewportShowCommands::Get().ShowMorphTargets ); - SubMenuBuilder.AddMenuEntry(FAnimViewportShowCommands::Get().ShowVertexColors ); } SubMenuBuilder.EndSection(); @@ -842,6 +841,7 @@ TSharedRef SAnimViewportToolBar::GenerateShowMenu() const .IncludeFlag(FEngineShowFlags::SF_SeparateTranslucency) .IncludeFlag(FEngineShowFlags::SF_TemporalAA) .IncludeFlag(FEngineShowFlags::SF_Tessellation) + .IncludeFlag(FEngineShowFlags::SF_VertexColors) ; FShowFlagMenuCommands::Get().BuildShowFlagsMenu(InMenuBuilder, ShowFlagFilter); diff --git a/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.cpp b/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.cpp index 57ea5b0f646c..dd20ad400298 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.cpp +++ b/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.cpp @@ -737,13 +737,7 @@ void SAnimationEditorViewportTabBody::BindCommands() FExecuteAction::CreateSP(this, &SAnimationEditorViewportTabBody::OnShowOverlayMorphTargetVert), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SAnimationEditorViewportTabBody::IsShowingOverlayMorphTargetVerts)); - - CommandList.MapAction( - ViewportShowMenuCommands.ShowVertexColors, - FExecuteAction::CreateSP(this, &SAnimationEditorViewportTabBody::OnShowVertexColorsChanged), - FCanExecuteAction(), - FIsActionChecked::CreateSP(this, &SAnimationEditorViewportTabBody::IsShowingVertexColors)); - + CommandList.EndGroup(); // Show sockets @@ -1715,38 +1709,6 @@ bool SAnimationEditorViewportTabBody::IsPreviewingRootMotion() const return false; } -bool SAnimationEditorViewportTabBody::IsShowingVertexColors() const -{ - return GetAnimationViewportClient()->EngineShowFlags.VertexColors; -} - -void SAnimationEditorViewportTabBody::OnShowVertexColorsChanged() -{ - FEngineShowFlags& ShowFlags = GetAnimationViewportClient()->EngineShowFlags; - - if(UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent()) - { - if(!ShowFlags.VertexColors) - { - ShowFlags.SetVertexColors(true); - ShowFlags.SetLighting(false); - ShowFlags.SetIndirectLightingCache(false); - PreviewComponent->bDisplayVertexColors = true; - } - else - { - ShowFlags.SetVertexColors(false); - ShowFlags.SetLighting(true); - ShowFlags.SetIndirectLightingCache(true); - PreviewComponent->bDisplayVertexColors = false; - } - - PreviewComponent->RecreateRenderState_Concurrent(); - } - - RefreshViewport(); -} - #if WITH_APEX_CLOTHING bool SAnimationEditorViewportTabBody::IsClothSimulationEnabled() const { diff --git a/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.h b/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.h index aad79bf6d27b..b519d90c81a0 100644 --- a/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.h +++ b/Engine/Source/Editor/Persona/Private/SAnimationEditorViewport.h @@ -461,13 +461,6 @@ private: /** Whether or not we are previewing root motion */ bool IsPreviewingRootMotion() const; - - /** Callback when user checks the vertex colors box in the show menu */ - void OnShowVertexColorsChanged(); - - /** Whether or not vertex color display is enabled */ - bool IsShowingVertexColors() const; - private: /** Selected Turn Table speed */ EAnimationPlaybackSpeeds::Type SelectedTurnTableSpeed; diff --git a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.cpp b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.cpp index bd4321eb4adb..2b5558dfa837 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.cpp +++ b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.cpp @@ -43,6 +43,7 @@ #include "ComponentReregisterContext.h" #include "EditorFramework/AssetImportData.h" #include "Factories/FbxSkeletalMeshImportData.h" +#include "AnimPreviewInstance.h" const FName SkeletalMeshEditorAppIdentifier = FName(TEXT("SkeletalMeshEditorApp")); @@ -176,6 +177,9 @@ void FSkeletalMeshEditor::BindCommands() ToolkitCommands->MapAction(FPersonaCommonCommands::Get().TogglePlay, FExecuteAction::CreateRaw(&GetPersonaToolkit()->GetPreviewScene().Get(), &IPersonaPreviewScene::TogglePlayback)); + + ToolkitCommands->MapAction(FSkeletalMeshEditorCommands::Get().UpdateRefPose, + FExecuteAction::CreateSP(this, &FSkeletalMeshEditor::HandleUpdateRefPose)); } void FSkeletalMeshEditor::ExtendToolbar() @@ -812,6 +816,25 @@ void FSkeletalMeshEditor::ExtendMenu() { MenuExtender = MakeShareable(new FExtender); + struct Local + { + static void AddAssetMenu(FMenuBuilder& MenuBuilder, FSkeletalMeshEditor* InSkeletalMeshEditor) + { + MenuBuilder.BeginSection("SkeletalMeshEditor", LOCTEXT("SkeletalMeshEditorAssetMenu_Animation", "SkeletalMesh")); + { + MenuBuilder.AddMenuEntry(FSkeletalMeshEditorCommands::Get().UpdateRefPose); + } + MenuBuilder.EndSection(); + } + }; + + MenuExtender->AddMenuExtension( + "AssetEditorActions", + EExtensionHook::After, + GetToolkitCommands(), + FMenuExtensionDelegate::CreateStatic(&Local::AddAssetMenu, this) + ); + AddMenuExtender(MenuExtender); ISkeletalMeshEditorModule& SkeletalMeshEditorModule = FModuleManager::GetModuleChecked("SkeletalMeshEditor"); @@ -943,6 +966,56 @@ void ReimportAllCustomLODs(USkeletalMesh* SkeletalMesh, UDebugSkelMeshComponent* } } +void FSkeletalMeshEditor::HandleUpdateRefPose() +{ + UDebugSkelMeshComponent* Component = GetPersonaToolkit()->GetPreviewMeshComponent(); + + if (SkeletalMesh && Component) + { + FScopedTransaction Transaction(LOCTEXT("ModifyRefPose", "Update Reference Pose")); + SkeletalMesh->Modify(); + + // need to be in the scope so that it SkeletonModifier to update the transform of RefSkeleton + { + USkeleton* Skeleton = SkeletalMesh->Skeleton; + FReferenceSkeleton& CurrentRefSkeleton = SkeletalMesh->RefSkeleton; + FReferenceSkeletonModifier RefSkeletonModifier(CurrentRefSkeleton, Skeleton); + // get current preview mesh transform + TArray CurrentPose = Component->GetComponentSpaceTransforms(); + + // we want to only apply these to raw ref pose transform + const TArray& RefPose = CurrentRefSkeleton.GetRawRefBonePose(); + // make sure ref pose count is smaller than current pose + check(RefPose.Num() <= CurrentPose.Num()); + + for (int32 Index = 0; Index < RefPose.Num(); ++Index) + { + FTransform LocalTransform; + if (Index == 0) + { + LocalTransform = CurrentPose[Index]; + } + else + { + const int32 ParentIndex = CurrentRefSkeleton.GetParentIndex(Index); + LocalTransform = CurrentPose[Index].GetRelativeTransform(CurrentPose[ParentIndex]); + } + + RefSkeletonModifier.UpdateRefPoseTransform(Index, LocalTransform); + } + } + + // calculate inverse ref matrices + SkeletalMesh->CalculateInvRefMatrices(); + + // now clear any bone transform modified + if (Component && Component->PreviewInstance) + { + Component->PreviewInstance->ResetModifiedBone(); + } + } +} + void FSkeletalMeshEditor::HandleReimportAllMesh(int32 SourceFileIndex /*= INDEX_NONE*/) { // Reimport the asset @@ -972,6 +1045,7 @@ void FSkeletalMeshEditor::HandleReimportAllMeshWithNewFile(int32 SourceFileIndex } } + void FSkeletalMeshEditor::ToggleMeshSectionSelection() { TSharedRef PreviewScene = GetPersonaToolkit()->GetPreviewScene(); diff --git a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.h b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.h index 294842ee4b66..242af1efb35b 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.h +++ b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditor.h @@ -103,6 +103,8 @@ private: void HandleReimportAllMesh(int32 SourceFileIndex = INDEX_NONE); void HandleReimportAllMeshWithNewFile(int32 SourceFileIndex = INDEX_NONE); + void HandleUpdateRefPose(); + /** Callback for toggling UV drawing in the viewport */ void ToggleMeshSectionSelection(); diff --git a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.cpp b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.cpp index eca608f481f5..c7fc2f539e61 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.cpp +++ b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.cpp @@ -13,6 +13,8 @@ void FSkeletalMeshEditorCommands::RegisterCommands() UI_COMMAND(ReimportAllMeshWithNewFile, "Reimport All Mesh With New File", "Reimport the current mesh using a new source file and all the custom LODs (No new source file for LODs).", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(MeshSectionSelection, "Section Selection", "Enables selecting Mesh Sections in the viewport (disables selecting bones using their physics shape).", EUserInterfaceActionType::ToggleButton, FInputChord()); + + UI_COMMAND(UpdateRefPose, "Update Reference Pose", "Update Reference Pose with current pose", EUserInterfaceActionType::Button, FInputChord()); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.h b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.h index a5bbb9f7968c..9ab4c4d4406a 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.h +++ b/Engine/Source/Editor/SkeletalMeshEditor/Private/SkeletalMeshEditorCommands.h @@ -28,4 +28,7 @@ public: // selecting mesh section using hit proxies TSharedPtr MeshSectionSelection; + + // update ref pose + TSharedPtr UpdateRefPose; }; diff --git a/Engine/Source/Editor/SkeletalMeshEditor/SkeletalMeshEditor.Build.cs b/Engine/Source/Editor/SkeletalMeshEditor/SkeletalMeshEditor.Build.cs index 4530203af5f5..a2488538a415 100644 --- a/Engine/Source/Editor/SkeletalMeshEditor/SkeletalMeshEditor.Build.cs +++ b/Engine/Source/Editor/SkeletalMeshEditor/SkeletalMeshEditor.Build.cs @@ -22,7 +22,6 @@ public class SkeletalMeshEditor : ModuleRules "SlateCore", "EditorStyle", "UnrealEd", - "SkeletonEditor", "Kismet", "KismetWidgets", @@ -34,7 +33,8 @@ public class SkeletalMeshEditor : ModuleRules "RHI", "ClothingSystemRuntime", "ClothingSystemEditorInterface", - "ClothingSystemRuntimeInterface" + "ClothingSystemRuntimeInterface", + "AnimGraph" } ); diff --git a/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp b/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp index 988e6b80cf77..ad2e74ad2432 100644 --- a/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp +++ b/Engine/Source/Editor/SkeletonEditor/Private/EditableSkeleton.cpp @@ -297,7 +297,9 @@ void FEditableSkeleton::RenameSmartname(const FName InContainerName, SmartName:: if (UAnimSequence* Seq = Cast(SequenceBase)) { SequencesToRecompress.Add(Seq); - Seq->CompressedCurveData.Empty(); + + Seq->CompressedCurveByteStream.Empty(); + Seq->CurveCompressionCodec = nullptr; } } } diff --git a/Engine/Source/Editor/UMGEditor/Private/Settings/UMGEditorProjectSettings.cpp b/Engine/Source/Editor/UMGEditor/Private/Settings/UMGEditorProjectSettings.cpp index 5563a1cf92cf..fbae977e254d 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Settings/UMGEditorProjectSettings.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Settings/UMGEditorProjectSettings.cpp @@ -5,6 +5,7 @@ #include "WidgetCompilerRule.h" #include "UObject/Package.h" #include "UObject/UObjectIterator.h" +#include "Components/CanvasPanel.h" UUMGEditorProjectSettings::UUMGEditorProjectSettings() { @@ -13,6 +14,8 @@ UUMGEditorProjectSettings::UUMGEditorProjectSettings() bShowWidgetsFromEngineContent = false; bShowWidgetsFromDeveloperContent = true; + DefaultRootWidget = UCanvasPanel::StaticClass(); + // Deprecated bCookSlowConstructionWidgetTree_DEPRECATED = true; bWidgetSupportsDynamicCreation_DEPRECATED = true; diff --git a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintFactory.cpp b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintFactory.cpp index 17735b2edc5e..84cf7e1c32bd 100644 --- a/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintFactory.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/WidgetBlueprintFactory.cpp @@ -8,10 +8,8 @@ #include "WidgetBlueprint.h" #include "Kismet2/KismetEditorUtilities.h" - - -#include "Components/CanvasPanel.h" #include "Blueprint/WidgetTree.h" +#include "UMGEditorProjectSettings.h" #define LOCTEXT_NAMESPACE "UWidgetBlueprintFactory" @@ -53,19 +51,22 @@ UObject* UWidgetBlueprintFactory::FactoryCreateNew(UClass* Class, UObject* InPar if ( ( ParentClass == NULL ) || !FKismetEditorUtilities::CanCreateBlueprintOfClass(ParentClass) || !ParentClass->IsChildOf(UUserWidget::StaticClass()) ) { FFormatNamedArguments Args; - Args.Add( TEXT("ClassName"), (ParentClass != NULL) ? FText::FromString( ParentClass->GetName() ) : LOCTEXT("Null", "(null)") ); + Args.Add( TEXT("ClassName"), ParentClass ? FText::FromString( ParentClass->GetName() ) : LOCTEXT("Null", "(null)") ); FMessageDialog::Open( EAppMsgType::Ok, FText::Format( LOCTEXT("CannotCreateWidgetBlueprint", "Cannot create a Widget Blueprint based on the class '{ClassName}'."), Args ) ); - return NULL; + return nullptr; } else { UWidgetBlueprint* NewBP = CastChecked(FKismetEditorUtilities::CreateBlueprint(ParentClass, InParent, Name, BlueprintType, UWidgetBlueprint::StaticClass(), UWidgetBlueprintGeneratedClass::StaticClass(), CallingContext)); - // Create a CanvasPanel to use as the default root widget + // Create the desired root widget specified by the project if ( NewBP->WidgetTree->RootWidget == nullptr ) { - UWidget* Root = NewBP->WidgetTree->ConstructWidget(UCanvasPanel::StaticClass()); - NewBP->WidgetTree->RootWidget = Root; + if (TSubclassOf RootWidgetClass = GetDefault()->DefaultRootWidget) + { + UWidget* Root = NewBP->WidgetTree->ConstructWidget(RootWidgetClass); + NewBP->WidgetTree->RootWidget = Root; + } } return NewBP; diff --git a/Engine/Source/Editor/UMGEditor/Public/UMGEditorProjectSettings.h b/Engine/Source/Editor/UMGEditor/Public/UMGEditorProjectSettings.h index 78bce44f2cdd..2b821ae85958 100644 --- a/Engine/Source/Editor/UMGEditor/Public/UMGEditorProjectSettings.h +++ b/Engine/Source/Editor/UMGEditor/Public/UMGEditorProjectSettings.h @@ -13,6 +13,7 @@ class UWidgetCompilerRule; class UUserWidget; class UWidgetBlueprint; +class UPanelWidget; USTRUCT() struct FDebugResolution @@ -174,6 +175,10 @@ public: public: + /** The panel widget to place at the root of all newly constructed widget blueprints. Can be empty. */ + UPROPERTY(EditAnywhere, config, Category = Designer) + TSubclassOf DefaultRootWidget; + UPROPERTY(EditAnywhere, config, Category=Designer) TArray DebugResolutions; diff --git a/Engine/Source/Editor/UnrealEd/Classes/Factories/AnimCurveCompressionSettingsFactory.h b/Engine/Source/Editor/UnrealEd/Classes/Factories/AnimCurveCompressionSettingsFactory.h new file mode 100644 index 000000000000..d9ff9ce1418c --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Classes/Factories/AnimCurveCompressionSettingsFactory.h @@ -0,0 +1,17 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "AnimCurveCompressionSettingsFactory.generated.h" + +UCLASS(HideCategories = Object, MinimalAPI) +class UAnimCurveCompressionSettingsFactory : public UFactory +{ + GENERATED_UCLASS_BODY() + + //~ Begin UFactory Interface + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + //~ Begin UFactory Interface +}; diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp index 32c51a3208ff..2713d723abb6 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp @@ -435,36 +435,41 @@ void FAssetRegistryGenerator::InjectEncryptionData(FAssetRegistryState& TargetSt for (FName EncryptedRootPackageName : EncryptedRootAssets) { - FAssetData* AssetData = const_cast(TargetState.GetAssetByObjectPath(EncryptedRootPackageName)); + const TArray& PackageAssets = TargetState.GetAssetsByPackageName(EncryptedRootPackageName); - if (AssetData) + for (const FAssetData* PackageAsset : PackageAssets) { - FString GuidString; + FAssetData* AssetData = const_cast(PackageAsset); - if (AssetData->ChunkIDs.Num() > 1) + if (AssetData) { - UE_LOG(LogAssetRegistryGenerator, Error, TEXT("Encrypted primary asset '%s' exists in two chunks. Only secondary assets should be shared between chunks.")); - } - else if (AssetData->ChunkIDs.Num() == 1) - { - int32 ChunkID = AssetData->ChunkIDs[0]; - FGuid Guid; + FString GuidString; - if (GuidCache.Contains(ChunkID)) + if (AssetData->ChunkIDs.Num() > 1) { - Guid = GuidCache[ChunkID]; + UE_LOG(LogAssetRegistryGenerator, Error, TEXT("Encrypted primary asset '%s' exists in two chunks. Only secondary assets should be shared between chunks.")); } - else + else if (AssetData->ChunkIDs.Num() == 1) { - Guid = GuidCache.Add(ChunkID, AssetManager.GetChunkEncryptionKeyGuid(ChunkID)); - } + int32 ChunkID = AssetData->ChunkIDs[0]; + FGuid Guid; - if (Guid.IsValid()) - { - FAssetDataTagMap TagsAndValues = AssetData->TagsAndValues.GetMap(); - TagsAndValues.Add(UAssetManager::GetEncryptionKeyAssetTagName(), Guid.ToString()); - FAssetData NewAssetData = FAssetData(AssetData->PackageName, AssetData->PackagePath, AssetData->AssetName, AssetData->AssetClass, TagsAndValues, AssetData->ChunkIDs, AssetData->PackageFlags); - TargetState.UpdateAssetData(AssetData, NewAssetData); + if (GuidCache.Contains(ChunkID)) + { + Guid = GuidCache[ChunkID]; + } + else + { + Guid = GuidCache.Add(ChunkID, AssetManager.GetChunkEncryptionKeyGuid(ChunkID)); + } + + if (Guid.IsValid()) + { + FAssetDataTagMap TagsAndValues = AssetData->TagsAndValues.GetMap(); + TagsAndValues.Add(UAssetManager::GetEncryptionKeyAssetTagName(), Guid.ToString()); + FAssetData NewAssetData = FAssetData(AssetData->PackageName, AssetData->PackagePath, AssetData->AssetName, AssetData->AssetClass, TagsAndValues, AssetData->ChunkIDs, AssetData->PackageFlags); + TargetState.UpdateAssetData(AssetData, NewAssetData); + } } } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp index d464b18937c2..918906135f50 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Commandlets/ContentCommandlets.cpp @@ -1357,7 +1357,8 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, ABrush::OnRebuildDone(); const bool bShouldBuildTextureStreamingForWorld = bShouldBuildTextureStreaming && !bShouldBuildTextureStreamingForAll; - if (bShouldBuildLighting || bShouldBuildTextureStreamingForWorld || bShouldBuildHLOD || bShouldBuildReflectionCaptures) + const bool bBuildingNonHLODData = (bShouldBuildLighting || bShouldBuildTextureStreamingForWorld || bShouldBuildReflectionCaptures); + if (bBuildingNonHLODData || bShouldBuildHLOD) { bool bShouldProceedWithRebuild = true; @@ -1415,12 +1416,13 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, FString WorldPackageCheckedOutUser; if (FPackageName::DoesPackageExist(World->GetOutermost()->GetName(), NULL, &WorldPackageName)) { - if(bShouldBuildHLOD) + // If we are only building HLODs check if level can be checked out, if so add to list of files that will be saved/checked-out after rebuilding the data + if(bShouldBuildHLOD && !bBuildingNonHLODData) { if (CanCheckoutFile(WorldPackageName, WorldPackageCheckedOutUser) || !bSkipCheckedOutFiles) { - SublevelFilenames.Add(WorldPackageName); - } + SublevelFilenames.Add(WorldPackageName); + } else { bShouldProceedWithRebuild = false; @@ -1452,8 +1454,8 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, for (ULevelStreaming* NextStreamingLevel : World->GetStreamingLevels()) { - // If we are building HLODs, we dont check out ahead of time - if(!bShouldBuildHLOD) + // If we are not building HLODs or are but also rebuilding lighting we check out the level file, otherwise we don't to try and ensure a minimal HLOD rebuild + if (!bShouldBuildHLOD || bBuildingNonHLODData) { CheckOutLevelFile(NextStreamingLevel->GetLoadedLevel()); } @@ -1462,14 +1464,14 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, const FString StreamingLevelWorldAssetPackageName = NextStreamingLevel->GetWorldAssetPackageName(); if (FPackageName::DoesPackageExist(StreamingLevelWorldAssetPackageName, NULL, &StreamingLevelPackageFilename)) { - // If we are building HLODs, we dont check out ahead of time - if(bShouldBuildHLOD) + // If we are building HLODs only, we dont check out the files ahead of rebuilding the data + if(bShouldBuildHLOD && !bBuildingNonHLODData) { FString CurrentlyCheckedOutUser; if (CanCheckoutFile(StreamingLevelPackageFilename, CurrentlyCheckedOutUser) || !bSkipCheckedOutFiles) { - SublevelFilenames.Add(StreamingLevelPackageFilename); - } + SublevelFilenames.Add(StreamingLevelPackageFilename); + } else { UE_LOG(LogContentCommandlet, Warning, TEXT("[REPORT] Skipping %s as it is checked out by %s"), *StreamingLevelPackageFilename, *CurrentlyCheckedOutUser); @@ -1484,6 +1486,7 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, } else { + UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] %s is currently already checked out, cannot continue resaving"), *StreamingLevelPackageFilename); bShouldProceedWithRebuild = false; break; } @@ -1696,11 +1699,11 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, { FString StreamingLevelPackageFilename; const FString StreamingLevelWorldAssetPackageName = NextStreamingLevel->GetWorldAssetPackageName(); - if (FPackageName::DoesPackageExist(StreamingLevelWorldAssetPackageName, NULL, &StreamingLevelPackageFilename) && SublevelFilenames.Contains(StreamingLevelWorldAssetPackageName)) + if (FPackageName::DoesPackageExist(StreamingLevelWorldAssetPackageName, NULL, &StreamingLevelPackageFilename) && SublevelFilenames.Contains(StreamingLevelPackageFilename)) { UPackage* SubLevelPackage = NextStreamingLevel->GetLoadedLevel()->GetOutermost(); bool bSaveSubLevelPackage = true; - if(bShouldBuildHLOD) + if(bShouldBuildHLOD && !bBuildingNonHLODData) { // If we are building HLOD, only save packages that were dirtied bSaveSubLevelPackage = SubLevelPackage->IsDirty(); @@ -1710,12 +1713,14 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, { // When building HLODs we dont check out/modify maps unless dirty bool bFileCheckedOut = true; - if(bShouldBuildHLOD) + if(bShouldBuildHLOD && !bBuildingNonHLODData) { bFileCheckedOut = CheckoutFile(StreamingLevelPackageFilename, true); } - if (!bFileCheckedOut || !SavePackageHelper(SubLevelPackage, StreamingLevelPackageFilename)) + // Try to save the level package + const bool bSavePackageResult = SavePackageHelper(SubLevelPackage, StreamingLevelPackageFilename); + if (!bFileCheckedOut && !bSavePackageResult) { UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] Failed to save sub level: %s"), *StreamingLevelPackageFilename); } @@ -1734,8 +1739,8 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, } else { - UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] Failed to complete steps necessary to start a lightmass or texture streaming build of %s"), *World->GetName()); - } + UE_LOG(LogContentCommandlet, Error, TEXT("[REPORT] Failed to complete steps necessary to start a lightmass or texture streaming build of %s"), *World->GetName()); + } } if ((bShouldProceedWithRebuild == false)||(bSavePackage == false)) @@ -1759,9 +1764,9 @@ void UResavePackagesCommandlet::PerformAdditionalOperations(class UWorld* World, FilesToSubmit.AddUnique(SublevelFilename); } - if(bShouldBuildHLOD) + if(bShouldBuildHLOD && !bBuildingNonHLODData) { - // Don't save outer package if it isn't dirty + // Don't save outer package if it isn't dirty when doing a HLOD rebuild only bSavePackage = World->GetOutermost()->IsDirty(); } } diff --git a/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp b/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp index dc0e1161ac54..c2749ece71c0 100644 --- a/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp @@ -850,6 +850,7 @@ void UEditorEngine::Init(IEngineLoop* InEngineLoop) TEXT("KismetCompiler"), TEXT("Kismet"), TEXT("Persona"), + TEXT("AnimationBlueprintEditor"), TEXT("LevelEditor"), TEXT("MainFrame"), TEXT("PropertyEditor"), diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/AnimCurveCompressionSettingsFactory.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/AnimCurveCompressionSettingsFactory.cpp new file mode 100644 index 000000000000..3bb7968b487e --- /dev/null +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/AnimCurveCompressionSettingsFactory.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= +AnimCurveCompressionSettingsFactory.cpp: Factory for animation curve compression settings assets +=============================================================================*/ + +#include "Factories/AnimCurveCompressionSettingsFactory.h" +#include "Animation/AnimCurveCompressionSettings.h" + +UAnimCurveCompressionSettingsFactory::UAnimCurveCompressionSettingsFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bCreateNew = true; + SupportedClass = UAnimCurveCompressionSettings::StaticClass(); +} + +UObject* UAnimCurveCompressionSettingsFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, Class, Name, Flags); +} diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Platform/IOS/Gauntlet.IOSBuildSource.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Platform/IOS/Gauntlet.IOSBuildSource.cs index 372c0c9c1fdf..dfb0c05105e3 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Platform/IOS/Gauntlet.IOSBuildSource.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Platform/IOS/Gauntlet.IOSBuildSource.cs @@ -141,8 +141,8 @@ namespace Gauntlet string AbsPath = Fi.Directory.FullName; - // IOS builds are always packaged, and we can always replace the command line - BuildFlags Flags = BuildFlags.Packaged | BuildFlags.CanReplaceCommandLine; + // IOS builds are always packaged, and can always replace the command line and executable as we cache the unzip'd IPA + BuildFlags Flags = BuildFlags.Packaged | BuildFlags.CanReplaceCommandLine | BuildFlags.CanReplaceExecutable; if (AbsPath.Contains("Bulk")) { diff --git a/Engine/Source/Programs/AutomationTool/Gauntlet/Platform/IOS/Gauntlet.TargetDeviceIOS.cs b/Engine/Source/Programs/AutomationTool/Gauntlet/Platform/IOS/Gauntlet.TargetDeviceIOS.cs index 60402adb2443..02118d9d3b64 100644 --- a/Engine/Source/Programs/AutomationTool/Gauntlet/Platform/IOS/Gauntlet.TargetDeviceIOS.cs +++ b/Engine/Source/Programs/AutomationTool/Gauntlet/Platform/IOS/Gauntlet.TargetDeviceIOS.cs @@ -1,985 +1,1097 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Linq; -using System.Security.Cryptography; -using AutomationTool; -using UnrealBuildTool; -using System.Text; -using System.Text.RegularExpressions; - -/* - -General Device Notes (and areas for improvement): - -1) We don't currently support parallel iOS tests, see https://jira.it.epicgames.net/browse/UEATM-219 -2) Device Farm devices should be in airplane mode + wifi to avoid No Sim warning notification - -*/ - -namespace Gauntlet -{ - - class IOSAppInstance : IAppInstance - { - protected IOSAppInstall Install; - public IOSAppInstance(IOSAppInstall InInstall, IProcessResult InProcess, string InCommandLine) - { - Install = InInstall; - this.CommandLine = InCommandLine; - this.ProcessResult = InProcess; - } - - public string ArtifactPath - { - get - { - if (bHaveSavedArtifacts == false) - { - if (HasExited) - { - SaveArtifacts(); - bHaveSavedArtifacts = true; - } - } - - return Install.IOSDevice.LocalCachePath + "/" + Install.IOSDevice.DeviceArtifactPath; - } - } - - public ITargetDevice Device - { - get - { - return Install.Device; - } - } - - protected void SaveArtifacts() - { - TargetDeviceIOS Device = Install.IOSDevice; - - // copy remote artifacts to local - string CommandLine = String.Format("--bundle_id {0} --download={1} --to {2}", Install.PackageName, Device.DeviceArtifactPath, Device.LocalCachePath); - - IProcessResult DownloadCmd = Device.ExecuteIOSDeployCommand(CommandLine, 120); - - if (DownloadCmd.ExitCode != 0) - { - Log.Warning("Failed to retrieve artifacts. {0}", DownloadCmd.Output); - } - - } - - public IProcessResult ProcessResult { get; private set; } - - public bool HasExited { get { return ProcessResult.HasExited; } } - - public bool WasKilled { get; protected set; } - - public int ExitCode { get { return ProcessResult.ExitCode; } } - - public string CommandLine { get; private set; } - - public string StdOut - { - get - { - if (HasExited) - { - // The ios application is being run under lldb by ios-deploy - // lldb catches crashes and we have it setup to dump thread callstacks - // parse any crash dumps into Unreal crash format and append to output - string CrashLog = LLDBCrashParser.GenerateCrashLog(ProcessResult.Output); - - if (!string.IsNullOrEmpty(CrashLog)) - { - return String.Format("{0}\n{1}", ProcessResult.Output, CrashLog); - } - } - - return ProcessResult.Output; - - } - } - - public int WaitForExit() - { - if (!HasExited) - { - ProcessResult.WaitForExit(); - } - - return ExitCode; - } - - public void Kill() - { - if (!HasExited) - { - WasKilled = true; - ProcessResult.ProcessObject.Kill(); - } - } - - - internal bool bHaveSavedArtifacts; - } - - - class IOSAppInstall : IAppInstall - { - public string Name { get; protected set; } - - public string CommandLine { get; protected set; } - - public string PackageName { get; protected set; } - - public ITargetDevice Device { get { return IOSDevice; } } - - public TargetDeviceIOS IOSDevice; - - public IOSAppInstall(string InName, TargetDeviceIOS InDevice, string InPackageName, string InCommandLine) - { - Name = InName; - CommandLine = InCommandLine; - PackageName = InPackageName; - - IOSDevice = InDevice; - } - - public IAppInstance Run() - { - return Device.Run(this); - } - } - - public class IOSDeviceFactory : IDeviceFactory - { - public bool CanSupportPlatform(UnrealTargetPlatform Platform) - { - return Platform == UnrealTargetPlatform.IOS; - } - - public ITargetDevice CreateDevice(string InRef, string InParam) - { - return new TargetDeviceIOS(InRef); - } - } - - /// - /// iOS implementation of a device to run applications - /// - public class TargetDeviceIOS : ITargetDevice - { - public string Name { get; protected set; } - - /// - /// Low-level device name (uuid) - /// - public string DeviceName { get; protected set; } - - protected Dictionary LocalDirectoryMappings { get; set; } - - public TargetDeviceIOS(string InName) - { - IsDefaultDevice = (String.IsNullOrEmpty(InName) || InName.Equals("default", StringComparison.OrdinalIgnoreCase)); - - Name = InName; - LocalDirectoryMappings = new Dictionary(); - - var DefaultDevices = GetConnectedDeviceUUID(); - - // If no device name or its 'default' then use the first default device - if (IsDefaultDevice) - { - if (DefaultDevices.Count() == 0) - { - throw new AutomationException("No default device available"); - } - - DeviceName = DefaultDevices.First(); - - Log.Verbose("Selected device {0} as default", DeviceName); - } - else - { - DeviceName = InName.Trim(); - if (!DefaultDevices.Contains(DeviceName)) - { - throw new AutomationException("Device with UUID {0} not found in device list", DeviceName); - } - } - - // setup local cache - LocalCachePath = Path.Combine(GauntletAppCache, "Device_" + Name); - if (Directory.Exists(LocalCachePath)) - { - Directory.Delete(LocalCachePath, true); - } - - Directory.CreateDirectory(LocalCachePath); - } - - bool IsDefaultDevice = false; - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - try - { - if (Directory.Exists(LocalCachePath)) - { - Directory.Delete(LocalCachePath, true); - } - } - catch (Exception Ex) - { - Log.Warning("TargetDeviceIOS.Dispose() threw: {0}", Ex.Message); - } - finally - { - disposedValue = true; - } - - } - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - GC.SuppressFinalize(this); - } - #endregion - - public CommandUtils.ERunOptions RunOptions { get; set; } - - public IAppInstance Run(IAppInstall App) - { - IOSAppInstall IOSApp = App as IOSAppInstall; - - if (IOSApp == null) - { - throw new DeviceException("AppInstance is of incorrect type!"); - } - - string CommandLine = IOSApp.CommandLine.Replace("\"", "\\\""); - - Log.Info("Launching {0} on {1}", App.Name, ToString()); - Log.Verbose("\t{0}", CommandLine); - - // ios-deploy notes: -L launches detached, -I non-interactive (exits when app exits), -r uninstalls before install (removes app Documents folder) - // -t number of seconds to wait for device to be connected - - // setup symbols if available - string DSymBundle = ""; - string DSymDir = Path.Combine(GauntletAppCache, "Symbols"); - - if (Directory.Exists(DSymDir)) - { - DSymBundle = Directory.GetDirectories(DSymDir).Where(D => Path.GetExtension(D).ToLower() == ".dsym").FirstOrDefault(); - DSymBundle = string.IsNullOrEmpty(DSymBundle) ? "" : DSymBundle = " -s \"" + DSymBundle + "\""; - } - - string CL = "--noinstall -I" + DSymBundle + " -b \"" + LocalAppBundle + "\" --args '" + CommandLine.Trim() + "'"; - - IProcessResult Result = ExecuteIOSDeployCommand(CL, 0); - - Thread.Sleep(5000); - - // Give ios-deploy a chance to throw out any errors... - if (Result.HasExited) - { - Log.Warning("ios-deploy exited early: " + Result.Output); - throw new DeviceException("Failed to launch on {0}. {1}", Name, Result.Output); - } - - return new IOSAppInstance(IOSApp, Result, IOSApp.CommandLine); - } - - /// - /// Remove the application entirely from the iOS device, this includes any persistent app data in /Documents - /// - private void RemoveApplication(IOSBuild Build) - { - string CommandLine = String.Format("--bundle_id {0} --uninstall_only", Build.PackageName); - ExecuteIOSDeployCommand(CommandLine); - } - - /// - /// Remove artifacts from device - /// - private bool CleanDeviceArtifacts(IOSBuild Build) - { - try - { - Log.Verbose("Cleaning device artifacts"); - - string CleanCommand = String.Format("--bundle_id {0} --rm_r {1}", Build.PackageName, DeviceArtifactPath); - IProcessResult Result = ExecuteIOSDeployCommand(CleanCommand, 120); - - if (Result.ExitCode != 0) - { - Log.Warning("Failed to clean artifacts from device"); - return false; - } - - } - catch (Exception Ex) - { - Log.Verbose("Exception while cleaning artifacts from device: {0}", Ex.Message); - } - - return true; - - } - - /// - /// Checks whether version of deployed bundle matches local IPA - /// - bool CheckDeployedIPA(IOSBuild Build) - { - try - { - Log.Verbose("Checking deployed IPA hash"); - - string CommandLine = String.Format("--bundle_id {0} --download={1} --to {2}", Build.PackageName, "/Documents/IPAHash.txt", LocalCachePath); - IProcessResult Result = ExecuteIOSDeployCommand(CommandLine, 120); - - if (Result.ExitCode != 0) - { - return false; - } - - string Hash = File.ReadAllText(LocalCachePath + "/Documents/IPAHash.txt").Trim(); - string StoredHash = File.ReadAllText(IPAHashFilename).Trim(); - - if (Hash == StoredHash) - { - Log.Verbose("Deployed app hash matched cached IPA hash"); - return true; - } - - } - catch (Exception Ex) - { - if (!Ex.Message.Contains("is denied")) - { - Log.Verbose("Unable to pull cached IPA cache from device, cached file may not exist: {0}", Ex.Message); - } - } - - Log.Verbose("Deployed app hash doesn't match, IPA will be installed"); - return false; - - } - - public IAppInstall InstallApplication(UnrealAppConfig AppConfig) - { - IOSBuild Build = AppConfig.Build as IOSBuild; - - // Ensure Build exists - if (Build == null) - { - throw new AutomationException("Invalid build for IOS!"); - } - - Log.Info("Installing using IPA {0}", Build.SourceIPAPath); - - // device artifact path - DeviceArtifactPath = string.Format("/Documents/{0}/Saved", AppConfig.ProjectName); - - PrepareIPA(Build); - - if (!CheckDeployedIPA(Build)) - { - // uninstall will clean all device artifacts - ExecuteIOSDeployCommand(String.Format("--uninstall -b \"{0}\"", LocalAppBundle), 10 * 60); - } - else - { - // remove device artifacts - CleanDeviceArtifacts(Build); - } - - // parallel iOS tests use same app install folder, so lock it as setup is quick - lock (Globals.MainLock) - { - // local app install with additional files, this directory will be mirrored to device in a single operation - string AppInstallPath; - - AppInstallPath = Path.Combine(Globals.TempDir, "iOSAppInstall"); - - if (Directory.Exists(AppInstallPath)) - { - Directory.Delete(AppInstallPath, true); - } - - Directory.CreateDirectory(AppInstallPath); - - if (LocalDirectoryMappings.Count == 0) - { - PopulateDirectoryMappings(AppInstallPath); - } - - //@todo: Combine Build and AppConfig files, this should be done in higher level code, not per device implementation - - if (AppConfig.FilesToCopy != null) - { - foreach (UnrealFileToCopy FileToCopy in AppConfig.FilesToCopy) - { - string PathToCopyTo = Path.Combine(LocalDirectoryMappings[FileToCopy.TargetBaseDirectory], FileToCopy.TargetRelativeLocation); - - if (File.Exists(FileToCopy.SourceFileLocation)) - { - FileInfo SrcInfo = new FileInfo(FileToCopy.SourceFileLocation); - SrcInfo.IsReadOnly = false; - string DirectoryToCopyTo = Path.GetDirectoryName(PathToCopyTo); - if (!Directory.Exists(DirectoryToCopyTo)) - { - Directory.CreateDirectory(DirectoryToCopyTo); - } - if (File.Exists(PathToCopyTo)) - { - FileInfo ExistingFile = new FileInfo(PathToCopyTo); - ExistingFile.IsReadOnly = false; - } - - SrcInfo.CopyTo(PathToCopyTo, true); - Log.Verbose("Copying app install: {0} to {1}", FileToCopy, DirectoryToCopyTo); - } - else - { - Log.Warning("File to copy {0} not found", FileToCopy); - } - } - } - - // copy mapped files in a single pass - string CopyCommand = String.Format("--bundle_id {0} --upload={1} --to {2}", Build.PackageName, AppInstallPath, DeviceArtifactPath); - ExecuteIOSDeployCommand(CopyCommand, 120); - - // store the IPA hash to avoid redundant deployments - CopyCommand = String.Format("--bundle_id {0} --upload={1} --to {2}", Build.PackageName, IPAHashFilename, "/Documents/IPAHash.txt"); - ExecuteIOSDeployCommand(CopyCommand, 120); - } - - IOSAppInstall IOSApp = new IOSAppInstall(AppConfig.Name, this, Build.PackageName, AppConfig.CommandLine); - return IOSApp; - } - - public void PopulateDirectoryMappings(string ProjectDir) - { - LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Binaries, Path.Combine(ProjectDir, "Binaries")); - LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Config, Path.Combine(ProjectDir, "Config")); - LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Content, Path.Combine(ProjectDir, "Content")); - LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Demos, Path.Combine(ProjectDir, "Demos")); - LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Profiling, Path.Combine(ProjectDir, "Profiling")); - LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Saved, ProjectDir); - } - - - public UnrealTargetPlatform Platform { get { return UnrealTargetPlatform.IOS; } } - - /// - /// Temp path we use to push/pull things from the device - /// - public string LocalCachePath { get; protected set; } - - - /// - /// Artifact (e.g. Saved) path on the device - /// - public string DeviceArtifactPath { get; protected set; } - - - #region Device State Management - - // NOTE: We check that a default device UUID or the one specifed is connected with 'ios-deploy --detect' at device creation time - // otherwise, ios-deploy doesn't currently support this style of queries - // It might be possible to add additional lldb queries/commands through the python interface (there are various solutions for reboot, though the ones I have found require jailbreaking) - public bool IsAvailable { get { return true; } } - public bool IsConnected { get { return Connected; } } - public bool IsOn { get { return true; } } - public bool PowerOn() { return true; } - public bool PowerOff() { return true; } - public bool Reboot() { return true; } - - // catch attempts to connect multiple iOS devices in parallel, see https://jira.it.epicgames.net/browse/UEATM-219 - static int ConnectedDeviceCount = 0; - bool Connected = false; - - public bool Connect() - { - lock (Globals.MainLock) - { - if (Connected) - { - return true; - } - - ConnectedDeviceCount++; - - if (ConnectedDeviceCount > 1) - { - - throw new AutomationException("Parallel iOS device connections are not currently supported, see https://jira.it.epicgames.net/browse/UEATM-219"); - } - - Connected = true; - - // Get rid of any zombies, this needs to be reworked to use tracked process id's when running parallel tests - // may need to kill by processid for spawned children - IOSBuild.ExecuteCommand("killall", "ios-deploy"); - Thread.Sleep(2500); - IOSBuild.ExecuteCommand("killall", "lldb"); - Thread.Sleep(2500); - } - - return true; - } - public bool Disconnect() - { - lock (Globals.MainLock) - { - if (!Connected) - { - return true; - } - - Connected = false; - - ConnectedDeviceCount--; - - if (ConnectedDeviceCount < 0) - { - throw new AutomationException("iOS device connection mismatch"); - } - } - - return true; - } - - #endregion - - public override string ToString() - { - return Name; - } - - - /// - /// Get UUID of all connected iOS devices - /// - List GetConnectedDeviceUUID() - { - var Result = ExecuteIOSDeployCommand("--detect"); - - if (Result.ExitCode != 0) - { - return new List(); - } - - MatchCollection DeviceMatches = Regex.Matches(Result.Output, @"(.?)Found\ ([a-z0-9]{40})"); - - return DeviceMatches.Cast().Select( - M => M.Groups[2].ToString() - ).ToList(); - } - - // Gauntlet cache folder for tracking device/ipa state - string GauntletAppCache { get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".gauntletappcache");} } - - // path to locally extracted (and possibly resigned) app bundle - // Note: ios-deploy works with app bundles, which requires the IPA be unzipped for deployment (this will allow us to resign in the future as well) - string LocalAppBundle = null; - - // the current IPA MD5 hash, which is tracked to avoid unneccessary deployments and unzip operations - string IPAHashFilename { get { return Path.Combine(GauntletAppCache, "IPAHash.txt"); } } - - /// - /// Generate MD5 and cache IPA bundle files - /// - private bool PrepareIPA(IOSBuild Build) - { - Log.Info("Preparing IPA {0}", Build.SourceIPAPath); - - try - { - // cache the unzipped app using a MD5 checksum, avoiding needing to unzip - string Hash = null; - string StoredHash = null; - using (var MD5Hash = MD5.Create()) - { - using (var Stream = File.OpenRead(Build.SourceIPAPath)) - { - Hash = BitConverter.ToString(MD5Hash.ComputeHash(Stream)).Replace("-", "").ToLowerInvariant(); - } - } - string PayloadDir = Path.Combine(GauntletAppCache, "Payload"); - string SymbolsDir = Path.Combine(GauntletAppCache, "Symbols"); - - if (File.Exists(IPAHashFilename) && Directory.Exists(PayloadDir)) - { - StoredHash = File.ReadAllText(IPAHashFilename).Trim(); - if (Hash != StoredHash) - { - Log.Verbose("IPA hash out of date, clearing cache"); - StoredHash = null; - } - } - - if (String.IsNullOrEmpty(StoredHash) || Hash != StoredHash) - { - if (Directory.Exists(PayloadDir)) - { - Directory.Delete(PayloadDir, true); - } - - if (Directory.Exists(SymbolsDir)) - { - Directory.Delete(SymbolsDir, true); - } - - Log.Verbose("Unzipping IPA {0} to cache at: {1}", Build.SourceIPAPath, GauntletAppCache); - - IProcessResult Result = IOSBuild.ExecuteCommand("unzip", String.Format("{0} -d {1}", Build.SourceIPAPath, GauntletAppCache)); - if (Result.ExitCode != 0 || !Directory.Exists(PayloadDir)) - { - throw new Exception(String.Format("Unable to extract IPA {0}", Build.SourceIPAPath)); - } - - // Cache symbols for symbolicated callstacks - string SymbolsZipFile = string.Format("{0}/../../Symbols/{1}.dSYM.zip", Path.GetDirectoryName(Build.SourceIPAPath), Path.GetFileNameWithoutExtension(Build.SourceIPAPath)); - - Log.Verbose("Checking Symbols at {0}", SymbolsZipFile); - - if (File.Exists(SymbolsZipFile)) - { - Log.Verbose("Unzipping Symbols {0} to cache at: {1}", SymbolsZipFile, SymbolsDir); - - Result = IOSBuild.ExecuteCommand("unzip", String.Format("{0} -d {1}", SymbolsZipFile, SymbolsDir)); - if (Result.ExitCode != 0 || !Directory.Exists(SymbolsDir)) - { - throw new Exception(String.Format("Unable to extract build symbols {0} -> {1}", SymbolsZipFile, SymbolsDir)); - } - } - - // store hash - File.WriteAllText(IPAHashFilename, Hash); - - Log.Verbose("IPA cached"); - } - else - { - Log.Verbose("Using cached IPA"); - } - - LocalAppBundle = Directory.GetDirectories(PayloadDir).Where(D => Path.GetExtension(D) == ".app").FirstOrDefault(); - - if (String.IsNullOrEmpty(LocalAppBundle)) - { - throw new Exception(String.Format("Unable to find app in local app bundle {0}", PayloadDir)); - } - - } - catch (Exception Ex) - { - throw new AutomationException("Unable to prepare {0} : {1}", Build.SourceIPAPath, Ex.Message); - } - - return true; - } - - public Dictionary GetPlatformDirectoryMappings() - { - if (LocalDirectoryMappings.Count == 0) - { - Log.Warning("Platform directory mappings have not been populated for this platform! This should be done within InstallApplication()"); - } - return LocalDirectoryMappings; - } - - public IProcessResult ExecuteIOSDeployCommand(String CommandLine, int WaitTime = 60, bool WarnOnTimeout = true) - { - if (!IsDefaultDevice) - { - CommandLine = String.Format("--id {0} {1}", DeviceName, CommandLine); - } - - String IOSDeployPath = Path.Combine(Globals.UE4RootDir, "Engine/Extras/ThirdPartyNotUE/ios-deploy/bin/ios-deploy"); - - if (!File.Exists(IOSDeployPath)) - { - throw new AutomationException("Unable to run ios-deploy binary at {0}", IOSDeployPath); - } - - CommandUtils.ERunOptions RunOptions = CommandUtils.ERunOptions.NoWaitForExit; - - if (Log.IsVeryVerbose) - { - RunOptions |= CommandUtils.ERunOptions.AllowSpew; - } - else - { - RunOptions |= CommandUtils.ERunOptions.NoLoggingOfRunCommand; - } - - Log.Verbose("ios-deploy executing '{0}'", CommandLine); - - IProcessResult Result = CommandUtils.Run(IOSDeployPath, CommandLine, Options: RunOptions); - - if (WaitTime > 0) - { - DateTime StartTime = DateTime.Now; - - Result.ProcessObject.WaitForExit(WaitTime * 1000); - - if (Result.HasExited == false) - { - if ((DateTime.Now - StartTime).TotalSeconds >= WaitTime) - { - string Message = String.Format("IOSDeployPath timeout after {0} secs: {1}, killing process", WaitTime, CommandLine); - - if (WarnOnTimeout) - { - Log.Warning(Message); - } - else - { - Log.Info(Message); - } - - Result.ProcessObject.Kill(); - // wait up to 15 seconds for process exit - Result.ProcessObject.WaitForExit(15000); - } - } - } - - return Result; - } - - } - - - /// - /// Helper class to parses LLDB crash threads and generate Unreal compatible log callstack - /// - static class LLDBCrashParser - { - // Frame in callstack - class FrameInfo - { - public string Module; - public string Symbol; - public string Address; - public string Offset; - public string Source; - public string Line; - - public override string ToString() - { - // symbolicated - if (!String.IsNullOrEmpty(Source)) - { - return string.Format("Error: [Callstack] 0x{0} {1}!{2} [{3}{4}]", Address, Module, Symbol.Replace(" ", "^"), Source, String.IsNullOrEmpty(Line) ? "" : ":" + Line); - } - - // unsymbolicated - return string.Format("Error: [Callstack] 0x{0} {1}!{2} [???]", Address, Module, Symbol.Replace(" ", "^")); - } - - } - - // Parsed thread callstack - class ThreadInfo - { - public int Num; - public string Status; - public bool Current; - public List Frames = new List(); - - public override string ToString() - { - return string.Format("{0}{1}{2}\n{3}", Num, string.IsNullOrEmpty(Status) ? "" : " " + Status + " ", Current ? " (Current)" : "", string.Join("\n", Frames)); - } - } - - /// - /// Parse lldb thread crash dump to Unreal log format - /// - public static string GenerateCrashLog(string LogOutput) - { - DateTime TimeStamp; - int Frame; - ThreadInfo Thread = ParseCallstack(LogOutput, out TimeStamp, out Frame); - if (Thread == null) - { - return null; - } - - StringBuilder CrashLog = new StringBuilder(); - CrashLog.Append(string.Format("[{0}:000][{1}]LogCore: === Fatal Error: ===\n", TimeStamp.ToString("yyyy.mm.dd - H.mm.ss"), Frame)); - CrashLog.Append(string.Format("Error: Thread #{0} {1}\n", Thread.Num, Thread.Status)); - CrashLog.Append(string.Join("\n", Thread.Frames)); - - return CrashLog.ToString(); - - } - - static ThreadInfo ParseCallstack(string LogOutput, out DateTime Timestamp, out int FrameNum) - { - Timestamp = DateTime.UtcNow; - FrameNum = 0; - - Regex LogLineRegex = new Regex(@"(?\s\[\d.+\]\[\s*\d+\])(?.*)"); - Regex TimeRegex = new Regex(@"\[(?\d+)\.(?\d+)\.(?\d+)-(?\d+)\.(?\d+)\.(?\d+):(?\d+)\]\[(?\s*\d+)\]", RegexOptions.IgnoreCase); - Regex ThreadRegex = new Regex(@"(thread\s#)(?\d+),?(?.+)"); - Regex SymbolicatedFrameRegex = new Regex(@"\s#(?\d+):\s0x(?
[\da-f]+)\s(?.+)\`(?.+)(\sat\s)(?.+)\s\[opt\]"); - Regex UnsymbolicatedFrameRegex = new Regex(@"frame\s#(?\d+):\s0x(?
[\da-f]+)\s(?.+)\`(?.+)\s\+\s(?\d+)"); - - LinkedList CrashLog = new LinkedList(Regex.Split(LogOutput, "\r\n|\r|\n")); - - List Threads = new List(); - ThreadInfo Thread = null; - ThreadInfo CrashThread = null; - - var LineNode = CrashLog.First; - while (LineNode != null) - { - string Line = LineNode.Value.Trim(); - - // If Gauntlet marks the test as complete, ignore any thread dumps from forcing process to exit - if (Line.Contains("**** TEST COMPLETE. EXIT CODE: 0 ****")) - { - return null; - } - - // Parse log timestamps - if (LogLineRegex.IsMatch(Line)) - { - GroupCollection LogGroups = LogLineRegex.Match(Line).Groups; - if (TimeRegex.IsMatch(LogGroups["timestamp"].Value)) - { - GroupCollection TimeGroups = TimeRegex.Match(LogGroups["timestamp"].Value).Groups; - int Year = int.Parse(TimeGroups["year"].Value); - int Month = int.Parse(TimeGroups["month"].Value); - int Day = int.Parse(TimeGroups["day"].Value); - int Hour = int.Parse(TimeGroups["hour"].Value); - int Minute = int.Parse(TimeGroups["minute"].Value); - int Second = int.Parse(TimeGroups["second"].Value); - FrameNum = int.Parse(TimeGroups["frame"].Value); - Timestamp = new DateTime(Year, Month, Day, Hour, Minute, Second); - } - - LineNode = LineNode.Next; - continue; - } - - if (Thread != null) - { - FrameInfo Frame = null; - GroupCollection FrameGroups = null; - - // Parse symbolicated frame - if (SymbolicatedFrameRegex.IsMatch(Line)) - { - FrameGroups = SymbolicatedFrameRegex.Match(Line).Groups; - - Frame = new FrameInfo() - { - Address = FrameGroups["address"].Value, - Module = FrameGroups["module"].Value, - Symbol = FrameGroups["symbol"].Value, - }; - - Frame.Source = FrameGroups["source"].Value; - if (Frame.Source.Contains(":")) - { - Frame.Source = FrameGroups["source"].Value.Split(':')[0]; - Frame.Line = FrameGroups["source"].Value.Split(':')[1]; - } - } - - // Parse unsymbolicated frame - if (UnsymbolicatedFrameRegex.IsMatch(Line)) - { - FrameGroups = UnsymbolicatedFrameRegex.Match(Line).Groups; - - Frame = new FrameInfo() - { - Address = FrameGroups["address"].Value, - Offset = FrameGroups["offset"].Value, - Module = FrameGroups["module"].Value, - Symbol = FrameGroups["symbol"].Value - }; - } - - if (Frame != null) - { - Thread.Frames.Add(Frame); - } - else - { - Thread = null; - } - - } - - // Parse thread - if (ThreadRegex.IsMatch(Line)) - { - GroupCollection ThreadGroups = ThreadRegex.Match(Line).Groups; - Thread = new ThreadInfo() - { - Num = int.Parse(ThreadGroups["threadnum"].Value), - Status = ThreadGroups["status"].Value.Trim() - }; - - if (Line.StartsWith("*")) - { - Thread.Current = true; - } - - if (CrashThread == null) - { - CrashThread = Thread; - } - else - { - Threads.Add(Thread); - } - } - - LineNode = LineNode.Next; - } - - if (CrashThread == null) - { - return null; - } - - Thread = Threads.Single(T => T.Num == CrashThread.Num); - - if (Thread == null) - { - Log.Warning("Unable to parse full crash callstack"); - Thread = CrashThread; - } - - return Thread; - - } - - } +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Linq; +using System.Security.Cryptography; +using AutomationTool; +using UnrealBuildTool; +using System.Text; +using System.Text.RegularExpressions; + +/* + +General Device Notes (and areas for improvement): + +1) We don't currently support parallel iOS tests, see https://jira.it.epicgames.net/browse/UEATM-219 +2) Device Farm devices should be in airplane mode + wifi to avoid No Sim warning notification + +*/ + +namespace Gauntlet +{ + + class IOSAppInstance : IAppInstance + { + protected IOSAppInstall Install; + public IOSAppInstance(IOSAppInstall InInstall, IProcessResult InProcess, string InCommandLine) + { + Install = InInstall; + this.CommandLine = InCommandLine; + this.ProcessResult = InProcess; + } + + public string ArtifactPath + { + get + { + if (bHaveSavedArtifacts == false) + { + if (HasExited) + { + SaveArtifacts(); + bHaveSavedArtifacts = true; + } + } + + return Install.IOSDevice.LocalCachePath + "/" + Install.IOSDevice.DeviceArtifactPath; + } + } + + public ITargetDevice Device + { + get + { + return Install.Device; + } + } + + protected void SaveArtifacts() + { + TargetDeviceIOS Device = Install.IOSDevice; + + // copy remote artifacts to local + string CommandLine = String.Format("--bundle_id {0} --download={1} --to {2}", Install.PackageName, Device.DeviceArtifactPath, Device.LocalCachePath); + + IProcessResult DownloadCmd = Device.ExecuteIOSDeployCommand(CommandLine, 120); + + if (DownloadCmd.ExitCode != 0) + { + Log.Warning("Failed to retrieve artifacts. {0}", DownloadCmd.Output); + } + + } + + public IProcessResult ProcessResult { get; private set; } + + public bool HasExited { get { return ProcessResult.HasExited; } } + + public bool WasKilled { get; protected set; } + + public int ExitCode { get { return ProcessResult.ExitCode; } } + + public string CommandLine { get; private set; } + + public string StdOut + { + get + { + if (HasExited) + { + // The ios application is being run under lldb by ios-deploy + // lldb catches crashes and we have it setup to dump thread callstacks + // parse any crash dumps into Unreal crash format and append to output + string CrashLog = LLDBCrashParser.GenerateCrashLog(ProcessResult.Output); + + if (!string.IsNullOrEmpty(CrashLog)) + { + return String.Format("{0}\n{1}", ProcessResult.Output, CrashLog); + } + } + + return ProcessResult.Output; + + } + } + + public int WaitForExit() + { + if (!HasExited) + { + ProcessResult.WaitForExit(); + } + + return ExitCode; + } + + public void Kill() + { + if (!HasExited) + { + WasKilled = true; + ProcessResult.ProcessObject.Kill(); + } + } + + + internal bool bHaveSavedArtifacts; + } + + + class IOSAppInstall : IAppInstall + { + public string Name { get; protected set; } + + public string CommandLine { get; protected set; } + + public string PackageName { get; protected set; } + + public ITargetDevice Device { get { return IOSDevice; } } + + public TargetDeviceIOS IOSDevice; + + public IOSAppInstall(string InName, TargetDeviceIOS InDevice, string InPackageName, string InCommandLine) + { + Name = InName; + CommandLine = InCommandLine; + PackageName = InPackageName; + + IOSDevice = InDevice; + } + + public IAppInstance Run() + { + return Device.Run(this); + } + } + + public class IOSDeviceFactory : IDeviceFactory + { + public bool CanSupportPlatform(UnrealTargetPlatform Platform) + { + return Platform == UnrealTargetPlatform.IOS; + } + + public ITargetDevice CreateDevice(string InRef, string InParam) + { + return new TargetDeviceIOS(InRef); + } + } + + /// + /// iOS implementation of a device to run applications + /// + public class TargetDeviceIOS : ITargetDevice + { + public string Name { get; protected set; } + + /// + /// Low-level device name (uuid) + /// + public string DeviceName { get; protected set; } + + protected Dictionary LocalDirectoryMappings { get; set; } + + public TargetDeviceIOS(string InName) + { + IsDefaultDevice = (String.IsNullOrEmpty(InName) || InName.Equals("default", StringComparison.OrdinalIgnoreCase)); + + Name = InName; + LocalDirectoryMappings = new Dictionary(); + + var DefaultDevices = GetConnectedDeviceUUID(); + + // If no device name or its 'default' then use the first default device + if (IsDefaultDevice) + { + if (DefaultDevices.Count() == 0) + { + throw new AutomationException("No default device available"); + } + + DeviceName = DefaultDevices.First(); + + Log.Verbose("Selected device {0} as default", DeviceName); + } + else + { + DeviceName = InName.Trim(); + if (!DefaultDevices.Contains(DeviceName)) + { + throw new AutomationException("Device with UUID {0} not found in device list", DeviceName); + } + } + + // setup local cache + LocalCachePath = Path.Combine(GauntletAppCache, "Device_" + Name); + if (Directory.Exists(LocalCachePath)) + { + Directory.Delete(LocalCachePath, true); + } + + Directory.CreateDirectory(LocalCachePath); + } + + bool IsDefaultDevice = false; + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + try + { + if (Directory.Exists(LocalCachePath)) + { + Directory.Delete(LocalCachePath, true); + } + } + catch (Exception Ex) + { + Log.Warning("TargetDeviceIOS.Dispose() threw: {0}", Ex.Message); + } + finally + { + disposedValue = true; + } + + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + public CommandUtils.ERunOptions RunOptions { get; set; } + + public IAppInstance Run(IAppInstall App) + { + IOSAppInstall IOSApp = App as IOSAppInstall; + + if (IOSApp == null) + { + throw new DeviceException("AppInstance is of incorrect type!"); + } + + string CommandLine = IOSApp.CommandLine.Replace("\"", "\\\""); + + Log.Info("Launching {0} on {1}", App.Name, ToString()); + Log.Verbose("\t{0}", CommandLine); + + // ios-deploy notes: -L launches detached, -I non-interactive (exits when app exits), -r uninstalls before install (removes app Documents folder) + // -t number of seconds to wait for device to be connected + + // setup symbols if available + string DSymBundle = ""; + string DSymDir = Path.Combine(GauntletAppCache, "Symbols"); + + if (Directory.Exists(DSymDir)) + { + DSymBundle = Directory.GetDirectories(DSymDir).Where(D => Path.GetExtension(D).ToLower() == ".dsym").FirstOrDefault(); + DSymBundle = string.IsNullOrEmpty(DSymBundle) ? "" : DSymBundle = " -s \"" + DSymBundle + "\""; + } + + string CL = "--noinstall -I" + DSymBundle + " -b \"" + LocalAppBundle + "\" --args '" + CommandLine.Trim() + "'"; + + IProcessResult Result = ExecuteIOSDeployCommand(CL, 0); + + Thread.Sleep(5000); + + // Give ios-deploy a chance to throw out any errors... + if (Result.HasExited) + { + Log.Warning("ios-deploy exited early: " + Result.Output); + throw new DeviceException("Failed to launch on {0}. {1}", Name, Result.Output); + } + + return new IOSAppInstance(IOSApp, Result, IOSApp.CommandLine); + } + + /// + /// Remove the application entirely from the iOS device, this includes any persistent app data in /Documents + /// + private void RemoveApplication(IOSBuild Build) + { + string CommandLine = String.Format("--bundle_id {0} --uninstall_only", Build.PackageName); + ExecuteIOSDeployCommand(CommandLine); + } + + /// + /// Remove artifacts from device + /// + private bool CleanDeviceArtifacts(IOSBuild Build) + { + try + { + Log.Verbose("Cleaning device artifacts"); + + string CleanCommand = String.Format("--bundle_id {0} --rm_r {1}", Build.PackageName, DeviceArtifactPath); + IProcessResult Result = ExecuteIOSDeployCommand(CleanCommand, 120); + + if (Result.ExitCode != 0) + { + Log.Warning("Failed to clean artifacts from device"); + return false; + } + + } + catch (Exception Ex) + { + Log.Verbose("Exception while cleaning artifacts from device: {0}", Ex.Message); + } + + return true; + + } + + /// + /// Checks whether version of deployed bundle matches local IPA + /// + bool CheckDeployedIPA(IOSBuild Build) + { + try + { + Log.Verbose("Checking deployed IPA hash"); + + string CommandLine = String.Format("--bundle_id {0} --download={1} --to {2}", Build.PackageName, "/Documents/IPAHash.txt", LocalCachePath); + IProcessResult Result = ExecuteIOSDeployCommand(CommandLine, 120); + + if (Result.ExitCode != 0) + { + return false; + } + + string Hash = File.ReadAllText(LocalCachePath + "/Documents/IPAHash.txt").Trim(); + string StoredHash = File.ReadAllText(IPAHashFilename).Trim(); + + if (Hash == StoredHash) + { + Log.Verbose("Deployed app hash matched cached IPA hash"); + return true; + } + + } + catch (Exception Ex) + { + if (!Ex.Message.Contains("is denied")) + { + Log.Verbose("Unable to pull cached IPA cache from device, cached file may not exist: {0}", Ex.Message); + } + } + + Log.Verbose("Deployed app hash doesn't match, IPA will be installed"); + return false; + + } + + /// + /// Resign application using local executable and update debug symbols + /// + void ResignApplication(UnrealAppConfig AppConfig) + { + // check that we have the signing stuff we need + string SignProvision = Globals.Params.ParseValue("signprovision", String.Empty); + string SignEntitlements = Globals.Params.ParseValue("signentitlements", String.Empty); + string SigningIdentity = Globals.Params.ParseValue("signidentity", String.Empty); + + // handle signing provision + if (string.IsNullOrEmpty(SignProvision) || !File.Exists(SignProvision)) + { + throw new AutomationException("Absolute path to existing provision must be specified, example: -signprovision=/path/to/myapp.provision"); + } + + // handle entitlements + // Note this extracts entitlements: which may be useful when using same provision/entitlements?: codesign -d --entitlements :entitlements.plist ~/.gauntletappcache/Payload/Example.app/ + + if (string.IsNullOrEmpty(SignEntitlements) || !File.Exists(SignEntitlements)) + { + throw new AutomationException("Absolute path to existing entitlements must be specified, example: -signprovision=/path/to/entitlements.plist"); + } + + // signing identity + if (string.IsNullOrEmpty(SigningIdentity)) + { + throw new AutomationException("Signing identity must be specified, example: -signidentity=\"iPhone Developer: John Smith\""); + } + + string ProjectName = AppConfig.ProjectName; + string BundleName = Path.GetFileNameWithoutExtension(LocalAppBundle); + string ExecutableName = UnrealHelpers.GetExecutableName(ProjectName, UnrealTargetPlatform.IOS, AppConfig.Configuration, AppConfig.ProcessType, ""); + string CachedAppPath = Path.Combine(GauntletAppCache, "Payload", string.Format("{0}.app", BundleName)); + + string LocalExecutable = Path.Combine(Environment.CurrentDirectory, ProjectName, string.Format("Binaries/IOS/{0}", ExecutableName)); + if (!File.Exists(LocalExecutable)) + { + throw new AutomationException("Local executable not found for -dev argument: {0}", LocalExecutable); + } + + File.WriteAllText(CacheResignedFilename, "The application has been resigned"); + + // copy local executable + FileInfo SrcInfo = new FileInfo(LocalExecutable); + string DestPath = Path.Combine(CachedAppPath, BundleName); + SrcInfo.CopyTo(DestPath, true); + Log.Verbose("Copied local executable from {0} to {1}", LocalExecutable, DestPath); + + // copy provision + SrcInfo = new FileInfo(SignProvision); + DestPath = Path.Combine(CachedAppPath, "embedded.mobileprovision"); + SrcInfo.CopyTo(DestPath, true); + Log.Verbose("Copied provision from {0} to {1}", SignProvision, DestPath); + + // handle symbols + string LocalSymbolsDir = Path.Combine(Environment.CurrentDirectory, ProjectName, string.Format("Binaries/IOS/{0}.dSYM", ExecutableName)); + DestPath = Path.Combine(GauntletAppCache, string.Format("Symbols/{0}.dSYM", ExecutableName)); + + if (Directory.Exists(DestPath)) + { + Directory.Delete(DestPath, true); + } + + if (Directory.Exists(LocalSymbolsDir)) + { + CommandUtils.CopyDirectory_NoExceptions(LocalSymbolsDir, DestPath, true); + } + else + { + Log.Warning("No symbols found for local build at {0}, removing cached app symbols", LocalSymbolsDir); + } + + // resign application + // @todo: this asks for password unless "Always Allow" is selected, also for builders, document how to permanently grant codesign access to keychain + string SignArgs = string.Format("-f -s \"{0}\" --entitlements \"{1}\" \"{2}\"", SigningIdentity, SignEntitlements, CachedAppPath); + Log.Info("\nResigning app, please enter keychain password if prompted:\n\ncodesign {0}", SignArgs); + var Result = IOSBuild.ExecuteCommand("codesign", SignArgs); + if (Result.ExitCode != 0) + { + throw new AutomationException("Failed to resign application"); + } + + } + + public IAppInstall InstallApplication(UnrealAppConfig AppConfig) + { + IOSBuild Build = AppConfig.Build as IOSBuild; + + // Ensure Build exists + if (Build == null) + { + throw new AutomationException("Invalid build for IOS!"); + } + + Log.Info("Installing using IPA {0}", Build.SourceIPAPath); + + // device artifact path + DeviceArtifactPath = string.Format("/Documents/{0}/Saved", AppConfig.ProjectName); + + bool CacheResigned = File.Exists(CacheResignedFilename); + bool UseLocalExecutable = Globals.Params.ParseParam("dev"); + + if (CacheResigned && !UseLocalExecutable) + { + if (File.Exists(IPAHashFilename)) + { + Log.Verbose("App was resigned, invalidating app cache"); + File.Delete(IPAHashFilename); + } + } + + PrepareIPA(Build); + + // local executable support + if (UseLocalExecutable) + { + ResignApplication(AppConfig); + } + + if (CacheResigned || UseLocalExecutable || !CheckDeployedIPA(Build)) + { + // uninstall will clean all device artifacts + ExecuteIOSDeployCommand(String.Format("--uninstall -b \"{0}\"", LocalAppBundle), 10 * 60); + } + else + { + // remove device artifacts + CleanDeviceArtifacts(Build); + } + + // parallel iOS tests use same app install folder, so lock it as setup is quick + lock (Globals.MainLock) + { + // local app install with additional files, this directory will be mirrored to device in a single operation + string AppInstallPath; + + AppInstallPath = Path.Combine(Globals.TempDir, "iOSAppInstall"); + + if (Directory.Exists(AppInstallPath)) + { + Directory.Delete(AppInstallPath, true); + } + + Directory.CreateDirectory(AppInstallPath); + + if (LocalDirectoryMappings.Count == 0) + { + PopulateDirectoryMappings(AppInstallPath); + } + + //@todo: Combine Build and AppConfig files, this should be done in higher level code, not per device implementation + + if (AppConfig.FilesToCopy != null) + { + foreach (UnrealFileToCopy FileToCopy in AppConfig.FilesToCopy) + { + string PathToCopyTo = Path.Combine(LocalDirectoryMappings[FileToCopy.TargetBaseDirectory], FileToCopy.TargetRelativeLocation); + + if (File.Exists(FileToCopy.SourceFileLocation)) + { + FileInfo SrcInfo = new FileInfo(FileToCopy.SourceFileLocation); + SrcInfo.IsReadOnly = false; + string DirectoryToCopyTo = Path.GetDirectoryName(PathToCopyTo); + if (!Directory.Exists(DirectoryToCopyTo)) + { + Directory.CreateDirectory(DirectoryToCopyTo); + } + if (File.Exists(PathToCopyTo)) + { + FileInfo ExistingFile = new FileInfo(PathToCopyTo); + ExistingFile.IsReadOnly = false; + } + + SrcInfo.CopyTo(PathToCopyTo, true); + Log.Verbose("Copying app install: {0} to {1}", FileToCopy, DirectoryToCopyTo); + } + else + { + Log.Warning("File to copy {0} not found", FileToCopy); + } + } + } + + // copy mapped files in a single pass + string CopyCommand = String.Format("--bundle_id {0} --upload={1} --to {2}", Build.PackageName, AppInstallPath, DeviceArtifactPath); + ExecuteIOSDeployCommand(CopyCommand, 120); + + // store the IPA hash to avoid redundant deployments + CopyCommand = String.Format("--bundle_id {0} --upload={1} --to {2}", Build.PackageName, IPAHashFilename, "/Documents/IPAHash.txt"); + ExecuteIOSDeployCommand(CopyCommand, 120); + } + + IOSAppInstall IOSApp = new IOSAppInstall(AppConfig.Name, this, Build.PackageName, AppConfig.CommandLine); + return IOSApp; + } + + public void PopulateDirectoryMappings(string ProjectDir) + { + LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Binaries, Path.Combine(ProjectDir, "Binaries")); + LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Config, Path.Combine(ProjectDir, "Config")); + LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Content, Path.Combine(ProjectDir, "Content")); + LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Demos, Path.Combine(ProjectDir, "Demos")); + LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Profiling, Path.Combine(ProjectDir, "Profiling")); + LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Saved, ProjectDir); + } + + + public UnrealTargetPlatform Platform { get { return UnrealTargetPlatform.IOS; } } + + /// + /// Temp path we use to push/pull things from the device + /// + public string LocalCachePath { get; protected set; } + + + /// + /// Artifact (e.g. Saved) path on the device + /// + public string DeviceArtifactPath { get; protected set; } + + + #region Device State Management + + // NOTE: We check that a default device UUID or the one specifed is connected with 'ios-deploy --detect' at device creation time + // otherwise, ios-deploy doesn't currently support this style of queries + // It might be possible to add additional lldb queries/commands through the python interface (there are various solutions for reboot, though the ones I have found require jailbreaking) + public bool IsAvailable { get { return true; } } + public bool IsConnected { get { return Connected; } } + public bool IsOn { get { return true; } } + public bool PowerOn() { return true; } + public bool PowerOff() { return true; } + public bool Reboot() { return true; } + + // catch attempts to connect multiple iOS devices in parallel, see https://jira.it.epicgames.net/browse/UEATM-219 + static int ConnectedDeviceCount = 0; + bool Connected = false; + + public bool Connect() + { + lock (Globals.MainLock) + { + if (Connected) + { + return true; + } + + ConnectedDeviceCount++; + + if (ConnectedDeviceCount > 1) + { + + throw new AutomationException("Parallel iOS device connections are not currently supported, see https://jira.it.epicgames.net/browse/UEATM-219"); + } + + Connected = true; + + // Get rid of any zombies, this needs to be reworked to use tracked process id's when running parallel tests + // may need to kill by processid for spawned children + IOSBuild.ExecuteCommand("killall", "ios-deploy"); + Thread.Sleep(2500); + IOSBuild.ExecuteCommand("killall", "lldb"); + Thread.Sleep(2500); + } + + return true; + } + public bool Disconnect() + { + lock (Globals.MainLock) + { + if (!Connected) + { + return true; + } + + Connected = false; + + ConnectedDeviceCount--; + + if (ConnectedDeviceCount < 0) + { + throw new AutomationException("iOS device connection mismatch"); + } + } + + return true; + } + + #endregion + + public override string ToString() + { + return Name; + } + + + /// + /// Get UUID of all connected iOS devices + /// + List GetConnectedDeviceUUID() + { + var Result = ExecuteIOSDeployCommand("--detect"); + + if (Result.ExitCode != 0) + { + return new List(); + } + + MatchCollection DeviceMatches = Regex.Matches(Result.Output, @"(.?)Found\ ([a-z0-9]{40})"); + + return DeviceMatches.Cast().Select( + M => M.Groups[2].ToString() + ).ToList(); + } + + // Gauntlet cache folder for tracking device/ipa state + string GauntletAppCache { get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".gauntletappcache");} } + + // path to locally extracted (and possibly resigned) app bundle + // Note: ios-deploy works with app bundles, which requires the IPA be unzipped for deployment (this will allow us to resign in the future as well) + string LocalAppBundle = null; + + // the current IPA MD5 hash, which is tracked to avoid unneccessary deployments and unzip operations + string IPAHashFilename { get { return Path.Combine(GauntletAppCache, "IPAHash.txt"); } } + + // file whose presence signals that cache was resigned + string CacheResignedFilename { get { return Path.Combine(GauntletAppCache, "Resigned.txt"); } } + + + /// + /// Generate MD5 and cache IPA bundle files + /// + private bool PrepareIPA(IOSBuild Build) + { + Log.Info("Preparing IPA {0}", Build.SourceIPAPath); + + try + { + // cache the unzipped app using a MD5 checksum, avoiding needing to unzip + string Hash = null; + string StoredHash = null; + using (var MD5Hash = MD5.Create()) + { + using (var Stream = File.OpenRead(Build.SourceIPAPath)) + { + Hash = BitConverter.ToString(MD5Hash.ComputeHash(Stream)).Replace("-", "").ToLowerInvariant(); + } + } + string PayloadDir = Path.Combine(GauntletAppCache, "Payload"); + string SymbolsDir = Path.Combine(GauntletAppCache, "Symbols"); + + if (File.Exists(IPAHashFilename) && Directory.Exists(PayloadDir)) + { + StoredHash = File.ReadAllText(IPAHashFilename).Trim(); + if (Hash != StoredHash) + { + Log.Verbose("IPA hash out of date, clearing cache"); + StoredHash = null; + } + } + + if (String.IsNullOrEmpty(StoredHash) || Hash != StoredHash) + { + if (Directory.Exists(PayloadDir)) + { + Directory.Delete(PayloadDir, true); + } + + if (Directory.Exists(SymbolsDir)) + { + Directory.Delete(SymbolsDir, true); + } + + if (File.Exists(CacheResignedFilename)) + { + File.Delete(CacheResignedFilename); + } + + Log.Verbose("Unzipping IPA {0} to cache at: {1}", Build.SourceIPAPath, GauntletAppCache); + + IProcessResult Result = IOSBuild.ExecuteCommand("unzip", String.Format("{0} -d {1}", Build.SourceIPAPath, GauntletAppCache)); + if (Result.ExitCode != 0 || !Directory.Exists(PayloadDir)) + { + throw new Exception(String.Format("Unable to extract IPA {0}", Build.SourceIPAPath)); + } + + // Cache symbols for symbolicated callstacks + string SymbolsZipFile = string.Format("{0}/../../Symbols/{1}.dSYM.zip", Path.GetDirectoryName(Build.SourceIPAPath), Path.GetFileNameWithoutExtension(Build.SourceIPAPath)); + + Log.Verbose("Checking Symbols at {0}", SymbolsZipFile); + + if (File.Exists(SymbolsZipFile)) + { + Log.Verbose("Unzipping Symbols {0} to cache at: {1}", SymbolsZipFile, SymbolsDir); + + Result = IOSBuild.ExecuteCommand("unzip", String.Format("{0} -d {1}", SymbolsZipFile, SymbolsDir)); + if (Result.ExitCode != 0 || !Directory.Exists(SymbolsDir)) + { + throw new Exception(String.Format("Unable to extract build symbols {0} -> {1}", SymbolsZipFile, SymbolsDir)); + } + } + + // store hash + File.WriteAllText(IPAHashFilename, Hash); + + Log.Verbose("IPA cached"); + } + else + { + Log.Verbose("Using cached IPA"); + } + + LocalAppBundle = Directory.GetDirectories(PayloadDir).Where(D => Path.GetExtension(D) == ".app").FirstOrDefault(); + + if (String.IsNullOrEmpty(LocalAppBundle)) + { + throw new Exception(String.Format("Unable to find app in local app bundle {0}", PayloadDir)); + } + + } + catch (Exception Ex) + { + throw new AutomationException("Unable to prepare {0} : {1}", Build.SourceIPAPath, Ex.Message); + } + + return true; + } + + public Dictionary GetPlatformDirectoryMappings() + { + if (LocalDirectoryMappings.Count == 0) + { + Log.Warning("Platform directory mappings have not been populated for this platform! This should be done within InstallApplication()"); + } + return LocalDirectoryMappings; + } + + public IProcessResult ExecuteIOSDeployCommand(String CommandLine, int WaitTime = 60, bool WarnOnTimeout = true) + { + if (!IsDefaultDevice) + { + CommandLine = String.Format("--id {0} {1}", DeviceName, CommandLine); + } + + String IOSDeployPath = Path.Combine(Globals.UE4RootDir, "Engine/Extras/ThirdPartyNotUE/ios-deploy/bin/ios-deploy"); + + if (!File.Exists(IOSDeployPath)) + { + throw new AutomationException("Unable to run ios-deploy binary at {0}", IOSDeployPath); + } + + CommandUtils.ERunOptions RunOptions = CommandUtils.ERunOptions.NoWaitForExit; + + if (Log.IsVeryVerbose) + { + RunOptions |= CommandUtils.ERunOptions.AllowSpew; + } + else + { + RunOptions |= CommandUtils.ERunOptions.NoLoggingOfRunCommand; + } + + Log.Verbose("ios-deploy executing '{0}'", CommandLine); + + IProcessResult Result = CommandUtils.Run(IOSDeployPath, CommandLine, Options: RunOptions); + + if (WaitTime > 0) + { + DateTime StartTime = DateTime.Now; + + Result.ProcessObject.WaitForExit(WaitTime * 1000); + + if (Result.HasExited == false) + { + if ((DateTime.Now - StartTime).TotalSeconds >= WaitTime) + { + string Message = String.Format("IOSDeployPath timeout after {0} secs: {1}, killing process", WaitTime, CommandLine); + + if (WarnOnTimeout) + { + Log.Warning(Message); + } + else + { + Log.Info(Message); + } + + Result.ProcessObject.Kill(); + // wait up to 15 seconds for process exit + Result.ProcessObject.WaitForExit(15000); + } + } + } + + return Result; + } + + } + + + /// + /// Helper class to parses LLDB crash threads and generate Unreal compatible log callstack + /// + static class LLDBCrashParser + { + // Frame in callstack + class FrameInfo + { + public string Module; + public string Symbol; + public string Address; + public string Offset; + public string Source; + public string Line; + + public override string ToString() + { + // symbolicated + if (!String.IsNullOrEmpty(Source)) + { + return string.Format("Error: [Callstack] 0x{0} {1}!{2} [{3}{4}]", Address, Module, Symbol.Replace(" ", "^"), Source, String.IsNullOrEmpty(Line) ? "" : ":" + Line); + } + + // unsymbolicated + return string.Format("Error: [Callstack] 0x{0} {1}!{2} [???]", Address, Module, Symbol.Replace(" ", "^")); + } + + } + + // Parsed thread callstack + class ThreadInfo + { + public int Num; + public string Status; + public bool Current; + public List Frames = new List(); + + public override string ToString() + { + return string.Format("{0}{1}{2}\n{3}", Num, string.IsNullOrEmpty(Status) ? "" : " " + Status + " ", Current ? " (Current)" : "", string.Join("\n", Frames)); + } + } + + /// + /// Parse lldb thread crash dump to Unreal log format + /// + public static string GenerateCrashLog(string LogOutput) + { + DateTime TimeStamp; + int Frame; + ThreadInfo Thread = ParseCallstack(LogOutput, out TimeStamp, out Frame); + if (Thread == null) + { + return null; + } + + StringBuilder CrashLog = new StringBuilder(); + CrashLog.Append(string.Format("[{0}:000][{1}]LogCore: === Fatal Error: ===\n", TimeStamp.ToString("yyyy.mm.dd - H.mm.ss"), Frame)); + CrashLog.Append(string.Format("Error: Thread #{0} {1}\n", Thread.Num, Thread.Status)); + CrashLog.Append(string.Join("\n", Thread.Frames)); + + return CrashLog.ToString(); + + } + + static ThreadInfo ParseCallstack(string LogOutput, out DateTime Timestamp, out int FrameNum) + { + Timestamp = DateTime.UtcNow; + FrameNum = 0; + + Regex LogLineRegex = new Regex(@"(?\s\[\d.+\]\[\s*\d+\])(?.*)"); + Regex TimeRegex = new Regex(@"\[(?\d+)\.(?\d+)\.(?\d+)-(?\d+)\.(?\d+)\.(?\d+):(?\d+)\]\[(?\s*\d+)\]", RegexOptions.IgnoreCase); + Regex ThreadRegex = new Regex(@"(thread\s#)(?\d+),?(?.+)"); + Regex SymbolicatedFrameRegex = new Regex(@"\s#(?\d+):\s0x(?
[\da-f]+)\s(?.+)\`(?.+)(\sat\s)(?.+)\s\[opt\]"); + Regex UnsymbolicatedFrameRegex = new Regex(@"frame\s#(?\d+):\s0x(?
[\da-f]+)\s(?.+)\`(?.+)\s\+\s(?\d+)"); + + LinkedList CrashLog = new LinkedList(Regex.Split(LogOutput, "\r\n|\r|\n")); + + List Threads = new List(); + ThreadInfo Thread = null; + ThreadInfo CrashThread = null; + + var LineNode = CrashLog.First; + while (LineNode != null) + { + string Line = LineNode.Value.Trim(); + + // If Gauntlet marks the test as complete, ignore any thread dumps from forcing process to exit + if (Line.Contains("**** TEST COMPLETE. EXIT CODE: 0 ****")) + { + return null; + } + + // Parse log timestamps + if (LogLineRegex.IsMatch(Line)) + { + GroupCollection LogGroups = LogLineRegex.Match(Line).Groups; + if (TimeRegex.IsMatch(LogGroups["timestamp"].Value)) + { + GroupCollection TimeGroups = TimeRegex.Match(LogGroups["timestamp"].Value).Groups; + int Year = int.Parse(TimeGroups["year"].Value); + int Month = int.Parse(TimeGroups["month"].Value); + int Day = int.Parse(TimeGroups["day"].Value); + int Hour = int.Parse(TimeGroups["hour"].Value); + int Minute = int.Parse(TimeGroups["minute"].Value); + int Second = int.Parse(TimeGroups["second"].Value); + FrameNum = int.Parse(TimeGroups["frame"].Value); + Timestamp = new DateTime(Year, Month, Day, Hour, Minute, Second); + } + + LineNode = LineNode.Next; + continue; + } + + if (Thread != null) + { + FrameInfo Frame = null; + GroupCollection FrameGroups = null; + + // Parse symbolicated frame + if (SymbolicatedFrameRegex.IsMatch(Line)) + { + FrameGroups = SymbolicatedFrameRegex.Match(Line).Groups; + + Frame = new FrameInfo() + { + Address = FrameGroups["address"].Value, + Module = FrameGroups["module"].Value, + Symbol = FrameGroups["symbol"].Value, + }; + + Frame.Source = FrameGroups["source"].Value; + if (Frame.Source.Contains(":")) + { + Frame.Source = FrameGroups["source"].Value.Split(':')[0]; + Frame.Line = FrameGroups["source"].Value.Split(':')[1]; + } + } + + // Parse unsymbolicated frame + if (UnsymbolicatedFrameRegex.IsMatch(Line)) + { + FrameGroups = UnsymbolicatedFrameRegex.Match(Line).Groups; + + Frame = new FrameInfo() + { + Address = FrameGroups["address"].Value, + Offset = FrameGroups["offset"].Value, + Module = FrameGroups["module"].Value, + Symbol = FrameGroups["symbol"].Value + }; + } + + if (Frame != null) + { + Thread.Frames.Add(Frame); + } + else + { + Thread = null; + } + + } + + // Parse thread + if (ThreadRegex.IsMatch(Line)) + { + GroupCollection ThreadGroups = ThreadRegex.Match(Line).Groups; + Thread = new ThreadInfo() + { + Num = int.Parse(ThreadGroups["threadnum"].Value), + Status = ThreadGroups["status"].Value.Trim() + }; + + if (Line.StartsWith("*")) + { + Thread.Current = true; + } + + if (CrashThread == null) + { + CrashThread = Thread; + } + else + { + Threads.Add(Thread); + } + } + + LineNode = LineNode.Next; + } + + if (CrashThread == null) + { + return null; + } + + Thread = Threads.Single(T => T.Num == CrashThread.Num); + + if (Thread == null) + { + Log.Warning("Unable to parse full crash callstack"); + Thread = CrashThread; + } + + return Thread; + + } + + } } \ No newline at end of file diff --git a/Engine/Source/Programs/AutomationTool/Scripts/BuildPhysX.Automation.cs b/Engine/Source/Programs/AutomationTool/Scripts/BuildPhysX.Automation.cs index 3339c0245411..ac3d3efd225c 100644 --- a/Engine/Source/Programs/AutomationTool/Scripts/BuildPhysX.Automation.cs +++ b/Engine/Source/Programs/AutomationTool/Scripts/BuildPhysX.Automation.cs @@ -1683,7 +1683,7 @@ class BuildPhysX : BuildCommand { RobomergeLine = Environment.NewLine + RobomergeCommand; } - P4ChangeList = P4.CreateChange(P4Env.Client, String.Format("BuildPhysX.Automation: Deploying {0} libs.", LibDeploymentDesc) + Environment.NewLine + "#rb none" + Environment.NewLine + "#lockdown Nick.Penwarden" + Environment.NewLine + "#tests none" + Environment.NewLine + "#jira none" + RobomergeLine); + P4ChangeList = P4.CreateChange(P4Env.Client, String.Format("BuildPhysX.Automation: Deploying {0} libs.", LibDeploymentDesc) + Environment.NewLine + "#rb none" + Environment.NewLine + "#lockdown Nick.Penwarden" + Environment.NewLine + "#tests none" + Environment.NewLine + "#jira none" + Environment.NewLine + "#okforgithub ignore" + RobomergeLine); } diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs index bd1c3de00f74..6f4833e681b6 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/TargetRules.cs @@ -405,49 +405,6 @@ namespace UnrealBuildTool public bool bForceBuildShaderFormats = false; /// - /// Cached value for whether Simplygon is available - /// - static Lazy HasSimplygon = new Lazy(() => FileReference.Exists(FileReference.Combine(UnrealBuildTool.EngineDirectory, "Source", "ThirdParty", "NotForLicensees", "Simplygon", "Simplygon-latest", "Inc", "SimplygonSDK.h")), LazyThreadSafetyMode.PublicationOnly); - - /// - /// Optional override for whether to use Simplygon. Allows the default value to be computed dynamically if unspecified. - /// - [CommandLine("-WithSimplygon")] - [ConfigFile(ConfigHierarchyType.Engine, "/Script/BuildSettings.BuildSettings", "bCompileSimplygon")] - bool? bOverrideCompileSimplygon; - - /// - /// Whether we should compile in support for Simplygon or not. - /// - [RequiresUniqueBuildEnvironment] - public bool bCompileSimplygon - { - set { bOverrideCompileSimplygon = value; } - get { return bOverrideCompileSimplygon ?? (Type == TargetType.Editor && (Platform == UnrealTargetPlatform.Win32 || Platform == UnrealTargetPlatform.Win64) && HasSimplygon.Value); } - } - - /// - /// Cached value for whether SimplygonSSF is available - /// - static Lazy HasSimplygonSSF = new Lazy(() => FileReference.Exists(FileReference.Combine(UnrealBuildTool.EngineDirectory, "Source", "ThirdParty", "NotForLicensees", "SSF", "Public", "ssf.h")), LazyThreadSafetyMode.PublicationOnly); - - /// - /// Manually specified value for bCompileSimplygonSSF. Allows the default to be determined based on the target type. - /// - [ConfigFile(ConfigHierarchyType.Engine, "/Script/BuildSettings.BuildSettings", "bCompileSimplygonSSF")] - bool? bOverrideCompileSimplygonSSF; - - /// - /// Whether we should compile in support for Simplygon's SSF library or not. - /// - [RequiresUniqueBuildEnvironment] - public bool bCompileSimplygonSSF - { - set { bOverrideCompileSimplygonSSF = value; } - get { return bOverrideCompileSimplygonSSF ?? (Type == TargetType.Editor && (Platform == UnrealTargetPlatform.Win32 || Platform == UnrealTargetPlatform.Win64) && HasSimplygonSSF.Value); } - } - - /// /// Whether we should compile SQLite using the custom "Unreal" platform (true), or using the native platform (false). /// [RequiresUniqueBuildEnvironment] @@ -1736,16 +1693,6 @@ namespace UnrealBuildTool get { return Inner.bForceBuildShaderFormats; } } - public bool bCompileSimplygon - { - get { return Inner.bCompileSimplygon; } - } - - public bool bCompileSimplygonSSF - { - get { return Inner.bCompileSimplygonSSF; } - } - public bool bCompileCustomSQLitePlatform { get { return Inner.bCompileCustomSQLitePlatform; } diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs index aab267445dca..acd6dbe1bf34 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildPlatform.cs @@ -729,7 +729,7 @@ namespace UnrealBuildTool public virtual bool HasDefaultBuildConfig(UnrealTargetPlatform Platform, DirectoryReference ProjectDirectoryName) { string[] BoolKeys = new string[] { - "bCompileApex", "bCompileICU", "bCompileSimplygon", "bCompileSimplygonSSF", + "bCompileApex", "bCompileICU", "bCompileRecast", "bCompileSpeedTree", "bCompileWithPluginSupport", "bCompilePhysXVehicle", "bCompileFreeType", "bCompileForSize", "bCompileCEF3", "bCompileCustomSQLitePlatform" diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/IOSToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/IOSToolChain.cs index 460bac6a28d9..307685bf4de4 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/IOSToolChain.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/IOS/IOSToolChain.cs @@ -220,15 +220,6 @@ namespace UnrealBuildTool Result += " -fno-exceptions"; } - if (CompileEnvironment.bEnableObjCExceptions) - { - Result += " -fobjc-exceptions"; - } - else - { - Result += " -fno-objc-exceptions"; - } - string SanitizerMode = Environment.GetEnvironmentVariable("ENABLE_ADDRESS_SANITIZER"); if(SanitizerMode != null && SanitizerMode == "YES") { @@ -404,6 +395,23 @@ namespace UnrealBuildTool return Result; } + // Conditionally enable (default disabled) Objective-C exceptions + static string GetObjCExceptionsFlag(CppCompileEnvironment CompileEnvironment) + { + string Result = ""; + + if (CompileEnvironment.bEnableObjCExceptions) + { + Result += " -fobjc-exceptions"; + } + else + { + Result += " -fno-objc-exceptions"; + } + + return Result; + } + static void CleanIntermediateDirectory(string Path) { string ResultsText; @@ -590,6 +598,7 @@ namespace UnrealBuildTool // Compile the file as a C++ PCH. FileArguments += GetCompileArguments_PCH(); FileArguments += GetRTTIFlag(CompileEnvironment); + FileArguments += GetObjCExceptionsFlag(CompileEnvironment); } else if (Extension == ".C") { @@ -601,17 +610,20 @@ namespace UnrealBuildTool // Compile the file as Objective-C++ code. FileArguments += GetCompileArguments_MM(); FileArguments += GetRTTIFlag(CompileEnvironment); + FileArguments += GetObjCExceptionsFlag(CompileEnvironment); } else if (Extension == ".M") { - // Compile the file as Objective-C++ code. + // Compile the file as Objective-C code. FileArguments += GetCompileArguments_M(); + FileArguments += GetObjCExceptionsFlag(CompileEnvironment); } else { // Compile the file as C++ code. FileArguments += GetCompileArguments_CPP(); FileArguments += GetRTTIFlag(CompileEnvironment); + FileArguments += GetObjCExceptionsFlag(CompileEnvironment); // only use PCH for .cpp files FilePCHArguments = PCHArguments; @@ -707,12 +719,12 @@ namespace UnrealBuildTool CompileAction.bShouldOutputStatusDescription = true; foreach(UEBuildFramework Framework in CompileEnvironment.AdditionalFrameworks) - { + { if(Framework.ZipFile != null) - { + { FileItem ExtractedTokenFile = ExtractFramework(Framework, Actions); CompileAction.PrerequisiteItems.Add(ExtractedTokenFile); - } + } } Actions.Add(CompileAction); @@ -979,7 +991,7 @@ namespace UnrealBuildTool GenDebugAction.WorkingDirectory = DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Binaries", "Mac"); GenDebugAction.CommandPath = BuildHostPlatform.Current.Shell; - GenDebugAction.CommandArguments = string.Format("-c 'rm -rf \"{1}\"; dwarfdump --uuid \"{3}\" | cut -d\" \" -f2; chmod 777 ./DsymExporter; ./DsymExporter -UUID=$(dwarfdump --uuid \"{3}\" | cut -d\" \" -f2) \"{0}\" \"{2}\"'", + GenDebugAction.CommandArguments = string.Format("-c 'rm -rf \"{1}\"; dwarfdump --uuid \"{3}\" | cut -d\" \" -f2; chmod 777 ./DsymExporter; ./DsymExporter -UUID=$(dwarfdump --uuid \"{3}\" | cut -d\" \" -f2) \"{0}\" \"{2}\"'", DWARFFile.AbsolutePath, OutputFile.AbsolutePath, Path.GetDirectoryName(OutputFile.AbsolutePath), @@ -1114,7 +1126,7 @@ namespace UnrealBuildTool throw new BuildException("Unable to extract framework '{0}' - no zip file specified", Framework.Name); } if(Framework.ExtractedTokenFile == null) - { + { FileItem InputFile = FileItem.GetItemByFileReference(Framework.ZipFile); Framework.ExtractedTokenFile = FileItem.GetItemByFileReference(new FileReference(Framework.OutputDirectory.FullName + ".extracted")); @@ -1495,7 +1507,7 @@ namespace UnrealBuildTool { ProjectFileGenerator.bGenerateProjectFiles = false; } - } + } public static FileReference GetStagedExecutablePath(FileReference Executable, string TargetName) { @@ -1503,7 +1515,7 @@ namespace UnrealBuildTool } public static void PostBuildSync(IOSPostBuildSyncTarget Target) - { + { IOSProjectSettings ProjectSettings = ((IOSPlatform)UEBuildPlatform.GetBuildPlatform(Target.Platform)).ReadProjectSettings(Target.ProjectFile); string AppName = Target.TargetName; @@ -1707,7 +1719,7 @@ namespace UnrealBuildTool CleanIntermediateDirectory(LocalFrameworkAssets); foreach (KeyValuePair Pair in Target.FrameworkNameToSourceDir) - { + { string UnpackedZipPath = Pair.Value.FullName; // For now, this is hard coded, but we need to loop over all modules, and copy bundled assets that need it diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs index cf04e68cd16c..0593cf570f3f 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/UEBuildLinux.cs @@ -245,11 +245,8 @@ namespace UnrealBuildTool Target.bCompileAPEX = Target.Architecture.StartsWith("x86_64"); Target.bCompileNvCloth = Target.Architecture.StartsWith("x86_64"); - // Disable Simplygon support if compiling against the NULL RHI. if (Target.GlobalDefinitions.Contains("USE_NULL_RHI=1")) - { - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; + { Target.bCompileCEF3 = false; } diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs index 360fad857618..e3a7ce623395 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Mac/UEBuildMac.cs @@ -122,9 +122,7 @@ namespace UnrealBuildTool } public override void ResetTarget(TargetRules Target) - { - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; + { } public override void ValidateTarget(TargetRules Target) diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs index fce740f8bb72..3c8b3d5c4618 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/UEBuildWindows.cs @@ -493,11 +493,8 @@ namespace UnrealBuildTool /// public override void ValidateTarget(TargetRules Target) { - // Disable Simplygon support if compiling against the NULL RHI. if (Target.GlobalDefinitions.Contains("USE_NULL_RHI=1")) - { - Target.bCompileSimplygon = false; - Target.bCompileSimplygonSSF = false; + { Target.bCompileCEF3 = false; } diff --git a/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs b/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs index d44721337f6f..0ef6a5c13a79 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs @@ -518,7 +518,7 @@ namespace UnrealBuildTool if (Rules.bDisableUnverifiedCertificates) { Rules.GlobalDefinitions.Add("DISABLE_UNVERIFIED_CERTIFICATE_LOADING=1"); - } + } // Allow the platform to finalize the settings UEBuildPlatform Platform = UEBuildPlatform.GetBuildPlatform(Rules.Platform); @@ -526,7 +526,7 @@ namespace UnrealBuildTool // Some platforms may *require* monolithic compilation... if (Rules.LinkType != TargetLinkType.Monolithic && UEBuildPlatform.PlatformRequiresMonolithicBuilds(Rules.Platform, Rules.Configuration)) - { + { throw new BuildException(String.Format("{0} does not support modular builds", Rules.Platform)); } diff --git a/Engine/Source/Programs/UnrealMultiUserServer/UnrealMultiUserServer.Target.cs b/Engine/Source/Programs/UnrealMultiUserServer/UnrealMultiUserServer.Target.cs index 395212bc555c..9e2dd423ccf4 100644 --- a/Engine/Source/Programs/UnrealMultiUserServer/UnrealMultiUserServer.Target.cs +++ b/Engine/Source/Programs/UnrealMultiUserServer/UnrealMultiUserServer.Target.cs @@ -17,8 +17,6 @@ public class UnrealMultiUserServerTarget : TargetRules // Lean and mean also override the build developer switch, so just set all the switch it sets manually since we want developer tools (i.e. concert plugins) //bCompileLeanAndMeanUE = true; - bCompileSimplygon = false; - bCompileSimplygonSSF = false; bCompileSpeedTree = false; // Editor-only data, however, is needed diff --git a/Engine/Source/Runtime/Core/Private/HAL/LowLevelMemTracker.cpp b/Engine/Source/Runtime/Core/Private/HAL/LowLevelMemTracker.cpp index 5b1d3748d030..8f943f5d88d7 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/LowLevelMemTracker.cpp +++ b/Engine/Source/Runtime/Core/Private/HAL/LowLevelMemTracker.cpp @@ -132,7 +132,7 @@ struct FLLMTagInfo extern const TCHAR* LLMGetTagName(ELLMTag Tag) { -#define LLM_TAG_NAME_ARRAY(Enum,Str,Stat,Group) TEXT(Str), +#define LLM_TAG_NAME_ARRAY(Enum,Str,Stat,Group,ParentTag) TEXT(Str), static TCHAR const* Names[] = { LLM_ENUM_GENERIC_TAGS(LLM_TAG_NAME_ARRAY) }; #undef LLM_TAG_NAME_ARRAY @@ -149,7 +149,7 @@ extern const TCHAR* LLMGetTagName(ELLMTag Tag) extern const ANSICHAR* LLMGetTagNameANSI(ELLMTag Tag) { -#define LLM_TAG_NAME_ARRAY(Enum,Str,Stat,Group) Str, +#define LLM_TAG_NAME_ARRAY(Enum,Str,Stat,Group,ParentTag) Str, static ANSICHAR const* Names[] = { LLM_ENUM_GENERIC_TAGS(LLM_TAG_NAME_ARRAY) }; #undef LLM_TAG_NAME_ARRAY @@ -166,7 +166,7 @@ extern const ANSICHAR* LLMGetTagNameANSI(ELLMTag Tag) extern FName LLMGetTagStat(ELLMTag Tag) { -#define LLM_TAG_STAT_ARRAY(Enum,Str,Stat,Group) Stat, +#define LLM_TAG_STAT_ARRAY(Enum,Str,Stat,Group,ParentTag) Stat, static FName Names[] = { LLM_ENUM_GENERIC_TAGS(LLM_TAG_STAT_ARRAY) }; #undef LLM_TAG_STAT_ARRAY @@ -183,7 +183,7 @@ extern FName LLMGetTagStat(ELLMTag Tag) extern FName LLMGetTagStatGroup(ELLMTag Tag) { -#define LLM_TAG_STATGROUP_ARRAY(Enum,Str,Stat,Group) Group, +#define LLM_TAG_STATGROUP_ARRAY(Enum,Str,Stat,Group,ParentTag) Group, static FName Names[] = { LLM_ENUM_GENERIC_TAGS(LLM_TAG_STATGROUP_ARRAY) }; #undef LLM_TAG_STAT_ARRAY @@ -198,6 +198,23 @@ extern FName LLMGetTagStatGroup(ELLMTag Tag) } } +extern int32 LLMGetTagParent(ELLMTag Tag) +{ +#define LLM_TAG_NAME_ARRAY(Enum,Str,Stat,Group,ParentTag) (int32)ParentTag, + static const int32 ParentTags [] = { LLM_ENUM_GENERIC_TAGS(LLM_TAG_NAME_ARRAY) }; +#undef LLM_TAG_NAME_ARRAY + + int32 Index = (int32)Tag; + if( Index >= 0 && Index < ARRAY_COUNT(ParentTags)) + { + return ParentTags[Index]; + } + else + { + return -1; + } +} + /** * FLLMCsvWriter: class for writing out the LLM stats to a csv file every few seconds */ @@ -230,16 +247,16 @@ public: void CatchRunawayTags(FLLMPlatformTag* PlatformTags); - void Update(FLLMPlatformTag* PlatformTags); + void Update(FLLMPlatformTag* PlatformTags, const int32* ParentTags); void SetEnabled(bool value) { Enabled = value; } private: - void WriteGraph(FLLMPlatformTag* PlatformTags); + void WriteGraph(FLLMPlatformTag* PlatformTags, const int32* ParentTags); void Write(const FString& Text); - static FString GetTagName(int64 Tag, FLLMPlatformTag* PlatformTags); + static FString GetTagName(int64 Tag, FLLMPlatformTag* PlatformTags, const int32* ParentTags); static const TCHAR* GetTrackerCsvName(ELLMTracker InTracker); @@ -304,7 +321,7 @@ public: void SetCSVEnabled(bool Value); - void WriteCsv(FLLMPlatformTag* PlatformTags); + void WriteCsv(FLLMPlatformTag* PlatformTags, const int32* ParentTags); #define LLM_USE_ALLOC_INFO_STRUCT (LLM_STAT_TAGS_ENABLED || LLM_ALLOW_ASSETS_TAGS) @@ -329,7 +346,7 @@ public: } void SetTotalTags(ELLMTag Untagged, ELLMTag Tracked); - void Update(FLLMPlatformTag* PlatformTags); + void Update(FLLMPlatformTag* PlatformTags, const int32* ParentTags); void UpdateTotals(); void CatchRunawayTags(FLLMPlatformTag* PlatformTags); @@ -376,6 +393,8 @@ protected: int64* EnumTagAmounts, int64* OutAllocTypeAmounts); + void UpdateFrameStatGroups( FLLMPlatformTag* PlatformTags, const int32* ParentTags ); + static void IncMemoryStatByFName(FName Name, int64 Amount); void ClearAllocTypeAmounts(); @@ -499,6 +518,19 @@ FLowLevelMemTracker::FLowLevelMemTracker() { PlatformTags[Index].Name = nullptr; } + + for (int32 Index = 0; Index < (int32)ELLMTag::PlatformTagEnd; Index++ ) + { + ParentTags[Index] = LLMGetTagParent((ELLMTag)Index); + } + for (int32 Index = 0; Index < (int32)ELLMTag::PlatformTagEnd; Index++ ) + { + if (ParentTags[Index] != -1) + { + int32 GrandparentTag = ParentTags[ParentTags[Index]]; + LLMCheckf( GrandparentTag == -1, TEXT("can only have one level of tag parent") ); + } + } } FLowLevelMemTracker::~FLowLevelMemTracker() @@ -552,7 +584,7 @@ void FLowLevelMemTracker::UpdateStatsPerFrame(const TCHAR* LogName) // update the trackers for (int32 TrackerIndex = 0; TrackerIndex < (int32)ELLMTracker::Max; TrackerIndex++) { - GetTracker((ELLMTracker)TrackerIndex)->Update(PlatformTags); + GetTracker((ELLMTracker)TrackerIndex)->Update(PlatformTags,ParentTags); #if ENABLE_CATCH_RUNAWAY_TAGS GetTracker((ELLMTracker)TrackerIndex)->CatchRunawayTags(PlatformTags); #endif @@ -602,8 +634,8 @@ void FLowLevelMemTracker::UpdateStatsPerFrame(const TCHAR* LogName) if (bCsvWriterEnabled) { - GetTracker(ELLMTracker::Default)->WriteCsv(PlatformTags); - GetTracker(ELLMTracker::Platform)->WriteCsv(PlatformTags); + GetTracker(ELLMTracker::Default)->WriteCsv(PlatformTags,ParentTags); + GetTracker(ELLMTracker::Platform)->WriteCsv(PlatformTags,ParentTags); } if (LogName != nullptr) @@ -813,7 +845,7 @@ static bool IsAssetTagForAssets(ELLMTagSet Set) return Set == ELLMTagSet::Assets || Set == ELLMTagSet::AssetClasses; } -void FLowLevelMemTracker::RegisterPlatformTag(int32 Tag, const TCHAR* Name, FName StatName, FName SummaryStatName) +void FLowLevelMemTracker::RegisterPlatformTag(int32 Tag, const TCHAR* Name, FName StatName, FName SummaryStatName, int32 ParentTag) { LLMCheck(Tag >= (int32)ELLMTag::PlatformTagStart && Tag <= (int32)ELLMTag::PlatformTagEnd); FLLMPlatformTag& PlatformTag = PlatformTags[Tag - (int32)ELLMTag::PlatformTagStart]; @@ -821,6 +853,12 @@ void FLowLevelMemTracker::RegisterPlatformTag(int32 Tag, const TCHAR* Name, FNam PlatformTag.Name = Name; PlatformTag.StatName = StatName; PlatformTag.SummaryStatName = SummaryStatName; + ParentTags[Tag] = ParentTag; + if (ParentTag != -1) + { + int32 GrandparentTag = ParentTags[ParentTag]; + LLMCheckf( GrandparentTag == -1, TEXT("can only have one level of tag parent") ); + } } bool FLowLevelMemTracker::FindTagByName( const TCHAR* Name, uint64& OutTag ) const @@ -1241,7 +1279,7 @@ void FLLMTracker::SetTotalTags(ELLMTag InUntaggedTotalTag, ELLMTag InTrackedTota TrackedTotalTag = InTrackedTotalTag; } -void FLLMTracker::Update(FLLMPlatformTag* PlatformTags) +void FLLMTracker::Update(FLLMPlatformTag* PlatformTags, const int32* ParentTags) { // protect any accesses to the ThreadStates array FScopeLock Lock(&ThreadArraySection); @@ -1250,6 +1288,7 @@ void FLLMTracker::Update(FLLMPlatformTag* PlatformTags) int ThreadStateNum = ThreadStates.Num(); for (int32 ThreadIndex = 0; ThreadIndex < ThreadStateNum; ThreadIndex++) { + ThreadStates[ThreadIndex]->UpdateFrameStatGroups(PlatformTags,ParentTags); ThreadStates[ThreadIndex]->GetFrameStatTotals(UntaggedTotalTag, StateCopy, CsvWriter, PlatformTags, EnumTagAmounts, AllocTypeAmounts); } @@ -1290,9 +1329,9 @@ void FLLMTracker::CatchRunawayTags(FLLMPlatformTag* PlatformTags) CsvWriter.CatchRunawayTags(PlatformTags); } -void FLLMTracker::WriteCsv(FLLMPlatformTag* PlatformTags) +void FLLMTracker::WriteCsv(FLLMPlatformTag* PlatformTags, const int32* ParentTags) { - CsvWriter.Update(PlatformTags); + CsvWriter.Update(PlatformTags,ParentTags); } int64 FLLMTracker::GetActiveTag() @@ -1458,7 +1497,11 @@ void FLLMTracker::SetTagAmount(ELLMTag Tag, int64 Amount, bool AddToTotal) FPlatformAtomics::InterlockedAdd(&TrackedMemoryOverFrames, (int64)(Amount - EnumTagAmounts[(int32)Tag])); } - SET_MEMORY_STAT_FName(LLMGetTagStat(Tag), Amount); + FName StatName = LLMGetTagStat(Tag); + if (StatName != NAME_None) + { + SET_MEMORY_STAT_FName(StatName, Amount); + } EnumTagAmounts[(int32)Tag] = Amount; @@ -1610,6 +1653,31 @@ void FLLMTracker::FLLMThreadState::GetFrameStatTotals( InStateCopy.Clear(); } +void FLLMTracker::FLLMThreadState::UpdateFrameStatGroups( FLLMPlatformTag* PlatformTags, const int32* ParentTags ) +{ + FScopeLock Lock(&TagSection); + + uint32 MaxTagIndex = TaggedAllocTags.Num(); //group tags will be added at the end of the array. we don't want groups of groups so don't include them in the loop + for (uint32 TagIndex = 0; TagIndex < MaxTagIndex; TagIndex++) + { + int64 Amount = TaggedAllocs[TagIndex]; + if( Amount != 0 ) + { + int64 Tag = TaggedAllocTags[TagIndex]; + if( Tag >= 0 && Tag < (int32)ELLMTag::PlatformTagEnd) + { + int32 ParentTag = ParentTags[Tag]; + if( ParentTag != -1 ) + { + IncrTag( ParentTag, Amount, false ); + } + } + } + } +} + + + void FLLMTracker::FLLMThreadState::IncMemoryStatByFName(FName Name, int64 Amount) { if (Name != NAME_None) @@ -1753,7 +1821,7 @@ void FLLMCsvWriter::CatchRunawayTags(FLLMPlatformTag* PlatformTags) for (uint32 i = 0; i < StatValues.Num(); i++) { - if (GetTagName(StatValues[i].Tag, PlatformTags) == StatsTagName) + if (GetTagName(StatValues[i].Tag, PlatformTags,nullptr) == StatsTagName) { StatsTag = StatValues[i].Tag; } @@ -1772,12 +1840,12 @@ void FLLMCsvWriter::CatchRunawayTags(FLLMPlatformTag* PlatformTags) /* * memory can be allocated in this function */ -void FLLMCsvWriter::Update(FLLMPlatformTag* PlatformTags) +void FLLMCsvWriter::Update(FLLMPlatformTag* PlatformTags, const int32* ParentTags) { double Now = FPlatformTime::Seconds(); if (Now - LastWriteTime >= (double)CVarLLMWriteInterval.GetValueOnGameThread()) { - WriteGraph(PlatformTags); + WriteGraph(PlatformTags, ParentTags); LastWriteTime = Now; } @@ -1805,7 +1873,7 @@ void FLLMCsvWriter::Write(const FString& Text) * create the csv file on the first call. When it finds a new stat name it seeks * back to the start of the file and re-writes the column names. */ -void FLLMCsvWriter::WriteGraph(FLLMPlatformTag* PlatformTags) +void FLLMCsvWriter::WriteGraph(FLLMPlatformTag* PlatformTags, const int32* ParentTags) { // create the csv file if (!Archive) @@ -1842,7 +1910,7 @@ void FLLMCsvWriter::WriteGraph(FLLMPlatformTag* PlatformTags) for (int32 i = 0; i < StatValueCountLocal; ++i) { - FString StatName = GetTagName(StatValuesForWrite[i].Tag, PlatformTags); + FString StatName = GetTagName(StatValuesForWrite[i].Tag, PlatformTags, ParentTags); FString Text = FString::Printf(TEXT("%s,"), *StatName); Write(Text); } @@ -1877,8 +1945,10 @@ void FLLMCsvWriter::WriteGraph(FLLMPlatformTag* PlatformTags) /* * convert a Tag to a string. If the Tag is actually a Stat then extract the name of the stat. */ -FString FLLMCsvWriter::GetTagName(int64 Tag, FLLMPlatformTag* PlatformTags) +FString FLLMCsvWriter::GetTagName(int64 Tag, FLLMPlatformTag* PlatformTags, const int32* ParentTags) { + FString Result; + if (Tag > (int64)ELLMTag::PlatformTagEnd) { FString Name = TagToFName(Tag).ToString(); @@ -1895,17 +1965,30 @@ FString FLLMCsvWriter::GetTagName(int64 Tag, FLLMPlatformTag* PlatformTags) } } - return Name; + Result = Name; } else if (Tag >= (int32)ELLMTag::PlatformTagStart && Tag <= (int32)ELLMTag::PlatformTagEnd) { - return PlatformTags[Tag - (int32)ELLMTag::PlatformTagStart].Name; + if (ParentTags != nullptr && ParentTags[Tag] != -1) + { + Result = GetTagName( ParentTags[Tag], PlatformTags, nullptr ) + TEXT("/"); + } + + Result += PlatformTags[Tag - (int32)ELLMTag::PlatformTagStart].Name; } else { LLMCheck(Tag >= 0 && LLMGetTagName((ELLMTag)Tag) != nullptr); - return LLMGetTagName((ELLMTag)Tag); + + if (ParentTags != nullptr && ParentTags[Tag] != -1) + { + Result = GetTagName( ParentTags[Tag], PlatformTags, nullptr ) + TEXT("/"); + } + + Result += LLMGetTagName((ELLMTag)Tag); } + + return Result; } #endif // #if ENABLE_LOW_LEVEL_MEM_TRACKER diff --git a/Engine/Source/Runtime/Core/Private/HAL/MallocBinned2.cpp b/Engine/Source/Runtime/Core/Private/HAL/MallocBinned2.cpp index 234bf0111ca1..b5b6e27c9fac 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/MallocBinned2.cpp +++ b/Engine/Source/Runtime/Core/Private/HAL/MallocBinned2.cpp @@ -1056,11 +1056,11 @@ void FMallocBinned2::FlushCurrentThreadCache() #include "Async/TaskGraphInterfaces.h" -void FMallocBinned2::Trim() +void FMallocBinned2::Trim(bool bTrimThreadCaches) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FMallocBinned2_Trim); - if (GMallocBinned2PerThreadCaches) + if (GMallocBinned2PerThreadCaches && bTrimThreadCaches) { //double StartTime = FPlatformTime::Seconds(); TFunction Broadcast = diff --git a/Engine/Source/Runtime/Core/Private/HAL/MallocBinned3.cpp b/Engine/Source/Runtime/Core/Private/HAL/MallocBinned3.cpp index 154c0c862753..6028b9733b52 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/MallocBinned3.cpp +++ b/Engine/Source/Runtime/Core/Private/HAL/MallocBinned3.cpp @@ -1756,11 +1756,11 @@ void FMallocBinned3::FlushCurrentThreadCache() #include "Async/TaskGraphInterfaces.h" -void FMallocBinned3::Trim() +void FMallocBinned3::Trim(bool bTrimThreadCaches) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FMallocBinned3_Trim); - if (GMallocBinned3PerThreadCaches) + if (GMallocBinned3PerThreadCaches && bTrimThreadCaches) { //double StartTime = FPlatformTime::Seconds(); TFunction Broadcast = diff --git a/Engine/Source/Runtime/Core/Private/HAL/MallocLeakDetectionProxy.h b/Engine/Source/Runtime/Core/Private/HAL/MallocLeakDetectionProxy.h index 5731f0db17d7..183d3c2b3094 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/MallocLeakDetectionProxy.h +++ b/Engine/Source/Runtime/Core/Private/HAL/MallocLeakDetectionProxy.h @@ -104,9 +104,9 @@ public: return UsedMalloc->QuantizeSize(Count, Alignment); } - virtual void Trim() override + virtual void Trim(bool bTrimThreadCaches) override { - return UsedMalloc->Trim(); + return UsedMalloc->Trim(bTrimThreadCaches); } virtual void SetupTLSCachesOnCurrentThread() override { diff --git a/Engine/Source/Runtime/Core/Private/HAL/MallocReplayProxy.cpp b/Engine/Source/Runtime/Core/Private/HAL/MallocReplayProxy.cpp index 34e0b515505f..f59745259966 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/MallocReplayProxy.cpp +++ b/Engine/Source/Runtime/Core/Private/HAL/MallocReplayProxy.cpp @@ -156,9 +156,9 @@ const TCHAR* FMallocReplayProxy::GetDescriptiveName() return UsedMalloc->GetDescriptiveName(); } -void FMallocReplayProxy::Trim() +void FMallocReplayProxy::Trim(bool bTrimThreadCaches) { - UsedMalloc->Trim(); + UsedMalloc->Trim(bTrimThreadCaches); } void FMallocReplayProxy::SetupTLSCachesOnCurrentThread() diff --git a/Engine/Source/Runtime/Core/Private/HAL/UnrealMemory.cpp b/Engine/Source/Runtime/Core/Private/HAL/UnrealMemory.cpp index 2b8982fff4c0..975b2c4aba13 100644 --- a/Engine/Source/Runtime/Core/Private/HAL/UnrealMemory.cpp +++ b/Engine/Source/Runtime/Core/Private/HAL/UnrealMemory.cpp @@ -197,9 +197,9 @@ public: { return UsedMalloc->QuantizeSize(Count, Alignment); } - virtual void Trim() override + virtual void Trim(bool bTrimThreadCaches) override { - return UsedMalloc->Trim(); + return UsedMalloc->Trim(bTrimThreadCaches); } virtual void SetupTLSCachesOnCurrentThread() override { @@ -504,7 +504,7 @@ SIZE_T FMemory::QuantizeSizeExternal(SIZE_T Count, uint32 Alignment) } -void FMemory::Trim() +void FMemory::Trim(bool bTrimThreadCaches) { if (!GMalloc) { @@ -517,7 +517,7 @@ void FMemory::Trim() FCoreDelegates::GetMemoryTrimDelegate().Broadcast(); } QUICK_SCOPE_CYCLE_COUNTER(STAT_FMemory_Trim_GMalloc); - GMalloc->Trim(); + GMalloc->Trim(bTrimThreadCaches); } void FMemory::SetupTLSCachesOnCurrentThread() diff --git a/Engine/Source/Runtime/Core/Public/HAL/LowLevelMemTracker.h b/Engine/Source/Runtime/Core/Public/HAL/LowLevelMemTracker.h index 72dff10d2cda..84809419c923 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/LowLevelMemTracker.h +++ b/Engine/Source/Runtime/Core/Public/HAL/LowLevelMemTracker.h @@ -153,78 +153,78 @@ enum class ELLMTagSet : uint8 }; #define LLM_ENUM_GENERIC_TAGS(macro) \ - macro(Untagged, "Untagged", NAME_None, NAME_None) \ - macro(Paused, "Paused", NAME_None, NAME_None) \ - macro(Total, "Total", GET_STATFNAME(STAT_TotalLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM)) \ - macro(Untracked, "Untracked", GET_STATFNAME(STAT_UntrackedLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM)) \ - macro(PlatformTotal, "Total", GET_STATFNAME(STAT_PlatformTotalLLM), NAME_None) \ - macro(TrackedTotal, "TrackedTotal", GET_STATFNAME(STAT_TrackedTotalLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM)) \ - macro(UntaggedTotal, "Untagged", GET_STATFNAME(STAT_UntaggedTotalLLM), NAME_None) \ - macro(WorkingSetSize, "WorkingSetSize", GET_STATFNAME(STAT_WorkingSetSizeLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM)) \ - macro(PagefileUsed, "PagefileUsed", GET_STATFNAME(STAT_PagefileUsedLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM)) \ - macro(PlatformTrackedTotal, "TrackedTotal", GET_STATFNAME(STAT_PlatformTrackedTotalLLM), NAME_None) \ - macro(PlatformUntaggedTotal, "Untagged", GET_STATFNAME(STAT_PlatformUntaggedTotalLLM), NAME_None) \ - macro(PlatformUntracked, "Untracked", GET_STATFNAME(STAT_PlatformUntrackedLLM), NAME_None) \ - macro(PlatformOverhead, "LLMOverhead", GET_STATFNAME(STAT_PlatformOverheadLLM), NAME_None) \ - macro(FMalloc, "FMalloc", GET_STATFNAME(STAT_FMallocLLM), NAME_None) \ - macro(FMallocUnused, "FMallocUnused", GET_STATFNAME(STAT_FMallocUnusedLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(ThreadStack, "ThreadStack", GET_STATFNAME(STAT_ThreadStackLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(ThreadStackPlatform, "ThreadStack", GET_STATFNAME(STAT_ThreadStackPlatformLLM), NAME_None) \ - macro(ProgramSizePlatform, "ProgramSize", GET_STATFNAME(STAT_ProgramSizePlatformLLM), NAME_None) \ - macro(ProgramSize, "ProgramSize", GET_STATFNAME(STAT_ProgramSizeLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(BackupOOMMemoryPoolPlatform, "OOMBackupPool", GET_STATFNAME(STAT_OOMBackupPoolPlatformLLM), NAME_None) \ - macro(BackupOOMMemoryPool, "OOMBackupPool", GET_STATFNAME(STAT_OOMBackupPoolLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(GenericPlatformMallocCrash, "GenericPlatformMallocCrash", GET_STATFNAME(STAT_GenericPlatformMallocCrashLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(GenericPlatformMallocCrashPlatform, "GenericPlatformMallocCrash", GET_STATFNAME(STAT_GenericPlatformMallocCrashPlatformLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(EngineMisc, "EngineMisc", GET_STATFNAME(STAT_EngineMiscLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(TaskGraphTasksMisc, "TaskGraphMiscTasks", GET_STATFNAME(STAT_TaskGraphTasksMiscLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(Audio, "Audio", GET_STATFNAME(STAT_AudioLLM), GET_STATFNAME(STAT_AudioSummaryLLM)) \ - macro(AudioMixer, "AudioMixer", GET_STATFNAME(STAT_AudioMixerLLM), GET_STATFNAME(STAT_AudioSummaryLLM)) \ - macro(AudioPrecache, "AudioPrecache", GET_STATFNAME(STAT_AudioPrecacheLLM), GET_STATFNAME(STAT_AudioSummaryLLM)) \ - macro(AudioDecompress, "AudioDecompress", GET_STATFNAME(STAT_AudioDecompressLLM), GET_STATFNAME(STAT_AudioSummaryLLM)) \ - macro(AudioRealtimePrecache, "AudioRealtimePrecache", GET_STATFNAME(STAT_AudioRealtimePrecacheLLM), GET_STATFNAME(STAT_AudioSummaryLLM)) \ - macro(AudioFullDecompress, "AudioFullDecompress", GET_STATFNAME(STAT_AudioFullDecompressLLM), GET_STATFNAME(STAT_AudioSummaryLLM)) \ - macro(FName, "FName", GET_STATFNAME(STAT_FNameLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(Networking, "Networking", GET_STATFNAME(STAT_NetworkingLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(Meshes, "Meshes", GET_STATFNAME(STAT_MeshesLLM), GET_STATFNAME(STAT_MeshesSummaryLLM)) \ - macro(Stats, "Stats", GET_STATFNAME(STAT_StatsLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(Shaders, "Shaders", GET_STATFNAME(STAT_ShadersLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(PSO, "PSO", GET_STATFNAME(STAT_PSOLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(Textures, "Textures", GET_STATFNAME(STAT_TexturesLLM), GET_STATFNAME(STAT_TexturesSummaryLLM)) \ - macro(RenderTargets, "RenderTargets", GET_STATFNAME(STAT_RenderTargetsLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(RHIMisc, "RHIMisc", GET_STATFNAME(STAT_RHIMiscLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(AsyncLoading, "AsyncLoading", GET_STATFNAME(STAT_AsyncLoadingLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(UObject, "UObject", GET_STATFNAME(STAT_UObjectLLM), GET_STATFNAME(STAT_UObjectSummaryLLM)) \ - macro(Animation, "Animation", GET_STATFNAME(STAT_AnimationLLM), GET_STATFNAME(STAT_AnimationSummaryLLM)) \ - macro(StaticMesh, "StaticMesh", GET_STATFNAME(STAT_StaticMeshLLM), GET_STATFNAME(STAT_StaticMeshSummaryLLM)) \ - macro(Materials, "Materials", GET_STATFNAME(STAT_MaterialsLLM), GET_STATFNAME(STAT_MaterialsSummaryLLM)) \ - macro(Particles, "Particles", GET_STATFNAME(STAT_ParticlesLLM), GET_STATFNAME(STAT_ParticlesSummaryLLM)) \ - macro(GC, "GC", GET_STATFNAME(STAT_GCLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(UI, "UI", GET_STATFNAME(STAT_UILLM), GET_STATFNAME(STAT_UISummaryLLM)) \ - macro(PhysX, "PhysX", GET_STATFNAME(STAT_PhysXLLM), GET_STATFNAME(STAT_PhysXSummaryLLM)) \ - macro(EnginePreInitMemory, "EnginePreInit", GET_STATFNAME(STAT_EnginePreInitLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(EngineInitMemory, "EngineInit", GET_STATFNAME(STAT_EngineInitLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(RenderingThreadMemory, "RenderingThread", GET_STATFNAME(STAT_RenderingThreadLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(LoadMapMisc, "LoadMapMisc", GET_STATFNAME(STAT_LoadMapMiscLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(StreamingManager, "StreamingManager", GET_STATFNAME(STAT_StreamingManagerLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(GraphicsPlatform, "Graphics", GET_STATFNAME(STAT_GraphicsPlatformLLM), NAME_None) \ - macro(FileSystem, "FileSystem", GET_STATFNAME(STAT_FileSystemLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(Localization, "Localization", GET_STATFNAME(STAT_LocalizationLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(VertexBuffer, "VertexBuffer", GET_STATFNAME(STAT_VertexBufferLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(IndexBuffer, "IndexBuffer", GET_STATFNAME(STAT_IndexBufferLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(UniformBuffer, "UniformBuffer", GET_STATFNAME(STAT_UniformBufferLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(AssetRegistry, "AssetRegistry", GET_STATFNAME(STAT_AssetRegistryLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(ConfigSystem, "ConfigSystem", GET_STATFNAME(STAT_ConfigSystemLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(InitUObject, "InitUObject", GET_STATFNAME(STAT_InitUObjectLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(VideoRecording, "VideoRecording", GET_STATFNAME(STAT_VideoRecordingLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) \ - macro(CsvProfiler, "CsvProfiler", GET_STATFNAME(STAT_CsvProfilerLLM), GET_STATFNAME(STAT_EngineSummaryLLM)) + macro(Untagged, "Untagged", NAME_None, NAME_None, -1)\ + macro(Paused, "Paused", NAME_None, NAME_None, -1)\ + macro(Total, "Total", GET_STATFNAME(STAT_TotalLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM), -1)\ + macro(Untracked, "Untracked", GET_STATFNAME(STAT_UntrackedLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM), -1)\ + macro(PlatformTotal, "Total", GET_STATFNAME(STAT_PlatformTotalLLM), NAME_None, -1)\ + macro(TrackedTotal, "TrackedTotal", GET_STATFNAME(STAT_TrackedTotalLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM), -1)\ + macro(UntaggedTotal, "Untagged", GET_STATFNAME(STAT_UntaggedTotalLLM), NAME_None, -1)\ + macro(WorkingSetSize, "WorkingSetSize", GET_STATFNAME(STAT_WorkingSetSizeLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM), -1)\ + macro(PagefileUsed, "PagefileUsed", GET_STATFNAME(STAT_PagefileUsedLLM), GET_STATFNAME(STAT_TrackedTotalSummaryLLM), -1)\ + macro(PlatformTrackedTotal, "TrackedTotal", GET_STATFNAME(STAT_PlatformTrackedTotalLLM), NAME_None, -1)\ + macro(PlatformUntaggedTotal, "Untagged", GET_STATFNAME(STAT_PlatformUntaggedTotalLLM), NAME_None, -1)\ + macro(PlatformUntracked, "Untracked", GET_STATFNAME(STAT_PlatformUntrackedLLM), NAME_None, -1)\ + macro(PlatformOverhead, "LLMOverhead", GET_STATFNAME(STAT_PlatformOverheadLLM), NAME_None, -1)\ + macro(FMalloc, "FMalloc", GET_STATFNAME(STAT_FMallocLLM), NAME_None, -1)\ + macro(FMallocUnused, "FMallocUnused", GET_STATFNAME(STAT_FMallocUnusedLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(ThreadStack, "ThreadStack", GET_STATFNAME(STAT_ThreadStackLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(ThreadStackPlatform, "ThreadStack", GET_STATFNAME(STAT_ThreadStackPlatformLLM), NAME_None, -1)\ + macro(ProgramSizePlatform, "ProgramSize", GET_STATFNAME(STAT_ProgramSizePlatformLLM), NAME_None, -1)\ + macro(ProgramSize, "ProgramSize", GET_STATFNAME(STAT_ProgramSizeLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(BackupOOMMemoryPoolPlatform, "OOMBackupPool", GET_STATFNAME(STAT_OOMBackupPoolPlatformLLM), NAME_None, -1)\ + macro(BackupOOMMemoryPool, "OOMBackupPool", GET_STATFNAME(STAT_OOMBackupPoolLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(GenericPlatformMallocCrash, "GenericPlatformMallocCrash", GET_STATFNAME(STAT_GenericPlatformMallocCrashLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(GenericPlatformMallocCrashPlatform, "GenericPlatformMallocCrash", GET_STATFNAME(STAT_GenericPlatformMallocCrashPlatformLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(EngineMisc, "EngineMisc", GET_STATFNAME(STAT_EngineMiscLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(TaskGraphTasksMisc, "TaskGraphMiscTasks", GET_STATFNAME(STAT_TaskGraphTasksMiscLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(Audio, "Audio", GET_STATFNAME(STAT_AudioLLM), GET_STATFNAME(STAT_AudioSummaryLLM), -1)\ + macro(AudioMixer, "AudioMixer", GET_STATFNAME(STAT_AudioMixerLLM), GET_STATFNAME(STAT_AudioSummaryLLM), -1)\ + macro(AudioPrecache, "AudioPrecache", GET_STATFNAME(STAT_AudioPrecacheLLM), GET_STATFNAME(STAT_AudioSummaryLLM), -1)\ + macro(AudioDecompress, "AudioDecompress", GET_STATFNAME(STAT_AudioDecompressLLM), GET_STATFNAME(STAT_AudioSummaryLLM), -1)\ + macro(AudioRealtimePrecache, "AudioRealtimePrecache", GET_STATFNAME(STAT_AudioRealtimePrecacheLLM), GET_STATFNAME(STAT_AudioSummaryLLM), -1)\ + macro(AudioFullDecompress, "AudioFullDecompress", GET_STATFNAME(STAT_AudioFullDecompressLLM), GET_STATFNAME(STAT_AudioSummaryLLM), -1)\ + macro(FName, "FName", GET_STATFNAME(STAT_FNameLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(Networking, "Networking", GET_STATFNAME(STAT_NetworkingLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(Meshes, "Meshes", GET_STATFNAME(STAT_MeshesLLM), GET_STATFNAME(STAT_MeshesSummaryLLM), -1)\ + macro(Stats, "Stats", GET_STATFNAME(STAT_StatsLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(Shaders, "Shaders", GET_STATFNAME(STAT_ShadersLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(PSO, "PSO", GET_STATFNAME(STAT_PSOLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(Textures, "Textures", GET_STATFNAME(STAT_TexturesLLM), GET_STATFNAME(STAT_TexturesSummaryLLM), -1)\ + macro(RenderTargets, "RenderTargets", GET_STATFNAME(STAT_RenderTargetsLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(RHIMisc, "RHIMisc", GET_STATFNAME(STAT_RHIMiscLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(AsyncLoading, "AsyncLoading", GET_STATFNAME(STAT_AsyncLoadingLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(UObject, "UObject", GET_STATFNAME(STAT_UObjectLLM), GET_STATFNAME(STAT_UObjectSummaryLLM), -1)\ + macro(Animation, "Animation", GET_STATFNAME(STAT_AnimationLLM), GET_STATFNAME(STAT_AnimationSummaryLLM), -1)\ + macro(StaticMesh, "StaticMesh", GET_STATFNAME(STAT_StaticMeshLLM), GET_STATFNAME(STAT_StaticMeshSummaryLLM), -1)\ + macro(Materials, "Materials", GET_STATFNAME(STAT_MaterialsLLM), GET_STATFNAME(STAT_MaterialsSummaryLLM), -1)\ + macro(Particles, "Particles", GET_STATFNAME(STAT_ParticlesLLM), GET_STATFNAME(STAT_ParticlesSummaryLLM), -1)\ + macro(GC, "GC", GET_STATFNAME(STAT_GCLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(UI, "UI", GET_STATFNAME(STAT_UILLM), GET_STATFNAME(STAT_UISummaryLLM), -1)\ + macro(PhysX, "PhysX", GET_STATFNAME(STAT_PhysXLLM), GET_STATFNAME(STAT_PhysXSummaryLLM), -1)\ + macro(EnginePreInitMemory, "EnginePreInit", GET_STATFNAME(STAT_EnginePreInitLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(EngineInitMemory, "EngineInit", GET_STATFNAME(STAT_EngineInitLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(RenderingThreadMemory, "RenderingThread", GET_STATFNAME(STAT_RenderingThreadLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(LoadMapMisc, "LoadMapMisc", GET_STATFNAME(STAT_LoadMapMiscLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(StreamingManager, "StreamingManager", GET_STATFNAME(STAT_StreamingManagerLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(GraphicsPlatform, "Graphics", GET_STATFNAME(STAT_GraphicsPlatformLLM), NAME_None, -1)\ + macro(FileSystem, "FileSystem", GET_STATFNAME(STAT_FileSystemLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(Localization, "Localization", GET_STATFNAME(STAT_LocalizationLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(VertexBuffer, "VertexBuffer", GET_STATFNAME(STAT_VertexBufferLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(IndexBuffer, "IndexBuffer", GET_STATFNAME(STAT_IndexBufferLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(UniformBuffer, "UniformBuffer", GET_STATFNAME(STAT_UniformBufferLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(AssetRegistry, "AssetRegistry", GET_STATFNAME(STAT_AssetRegistryLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(ConfigSystem, "ConfigSystem", GET_STATFNAME(STAT_ConfigSystemLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(InitUObject, "InitUObject", GET_STATFNAME(STAT_InitUObjectLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(VideoRecording, "VideoRecording", GET_STATFNAME(STAT_VideoRecordingLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1)\ + macro(CsvProfiler, "CsvProfiler", GET_STATFNAME(STAT_CsvProfilerLLM), GET_STATFNAME(STAT_EngineSummaryLLM), -1) /* * Enum values to be passed in to LLM_SCOPE() macro */ enum class ELLMTag : LLM_TAG_TYPE { -#define LLM_ENUM(Enum,Str,Stat,Group) Enum, +#define LLM_ENUM(Enum,Str,Stat,Group,Parent) Enum, LLM_ENUM_GENERIC_TAGS(LLM_ENUM) #undef LLM_ENUM @@ -430,7 +430,7 @@ public: // get the top active tag for the given tracker int64 GetActiveTag(ELLMTracker Tracker); - void RegisterPlatformTag(int32 Tag, const TCHAR* Name, FName StatName, FName SummaryStatName); + void RegisterPlatformTag(int32 Tag, const TCHAR* Name, FName StatName, FName SummaryStatName, int32 ParentTag = -1); // look up the tag associated with the given name bool FindTagByName( const TCHAR* Name, uint64& OutTag ) const; @@ -471,6 +471,8 @@ private: FLLMTracker* Trackers[(int32)ELLMTracker::Max]; + int32 ParentTags[(int32)ELLMTag::PlatformTagEnd]; + static FLowLevelMemTracker* TrackerInstance; public: // really internal but needs to be visible for LLM_IF_ENABLED macro @@ -530,4 +532,3 @@ protected: #define LLM_SCOPED_PAUSE_TRACKING_WITH_STAT_AND_AMOUNT(...) #define LLM_PUSH_STATS_FOR_ASSET_TAGS() #endif // #if ENABLE_LOW_LEVEL_MEM_TRACKER - diff --git a/Engine/Source/Runtime/Core/Public/HAL/MallocBinned2.h b/Engine/Source/Runtime/Core/Public/HAL/MallocBinned2.h index 3713abeadc6e..90ca19f46abb 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/MallocBinned2.h +++ b/Engine/Source/Runtime/Core/Public/HAL/MallocBinned2.h @@ -650,7 +650,7 @@ public: } virtual bool ValidateHeap() override; - virtual void Trim() override; + virtual void Trim(bool bTrimThreadCaches) override; virtual void SetupTLSCachesOnCurrentThread() override; virtual void ClearAndDisableTLSCachesOnCurrentThread() override; virtual const TCHAR* GetDescriptiveName() override; diff --git a/Engine/Source/Runtime/Core/Public/HAL/MallocBinned3.h b/Engine/Source/Runtime/Core/Public/HAL/MallocBinned3.h index cac0be679ee1..e5237bac72a5 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/MallocBinned3.h +++ b/Engine/Source/Runtime/Core/Public/HAL/MallocBinned3.h @@ -677,7 +677,7 @@ public: } virtual bool ValidateHeap() override; - virtual void Trim() override; + virtual void Trim(bool bTrimThreadCaches) override; virtual void SetupTLSCachesOnCurrentThread() override; virtual void ClearAndDisableTLSCachesOnCurrentThread() override; virtual const TCHAR* GetDescriptiveName() override; diff --git a/Engine/Source/Runtime/Core/Public/HAL/MallocPoisonProxy.h b/Engine/Source/Runtime/Core/Public/HAL/MallocPoisonProxy.h index 8bd72d10ee77..5d437193ba80 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/MallocPoisonProxy.h +++ b/Engine/Source/Runtime/Core/Public/HAL/MallocPoisonProxy.h @@ -132,9 +132,9 @@ public: return UsedMalloc->GetDescriptiveName(); } - virtual void Trim() override + virtual void Trim(bool bTrimThreadCaches) override { - UsedMalloc->Trim(); + UsedMalloc->Trim(bTrimThreadCaches); } virtual void SetupTLSCachesOnCurrentThread() override diff --git a/Engine/Source/Runtime/Core/Public/HAL/MallocReplayProxy.h b/Engine/Source/Runtime/Core/Public/HAL/MallocReplayProxy.h index e0de253f3ca6..007b559e1cc0 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/MallocReplayProxy.h +++ b/Engine/Source/Runtime/Core/Public/HAL/MallocReplayProxy.h @@ -115,7 +115,7 @@ public: virtual const TCHAR* GetDescriptiveName() override; - virtual void Trim() override; + virtual void Trim(bool bTrimThreadCaches) override; virtual void SetupTLSCachesOnCurrentThread() override; diff --git a/Engine/Source/Runtime/Core/Public/HAL/MallocThreadSafeProxy.h b/Engine/Source/Runtime/Core/Public/HAL/MallocThreadSafeProxy.h index 7bac33f2a204..49a189ad25fa 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/MallocThreadSafeProxy.h +++ b/Engine/Source/Runtime/Core/Public/HAL/MallocThreadSafeProxy.h @@ -116,10 +116,10 @@ public: return UsedMalloc->GetDescriptiveName(); } - virtual void Trim() override + virtual void Trim(bool bTrimThreadCaches) override { FScopeLock ScopeLock(&SynchronizationObject); check(UsedMalloc); - UsedMalloc->Trim(); + UsedMalloc->Trim(bTrimThreadCaches); } }; diff --git a/Engine/Source/Runtime/Core/Public/HAL/MemoryBase.h b/Engine/Source/Runtime/Core/Public/HAL/MemoryBase.h index ba50aa7ad6db..fbefd666fccb 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/MemoryBase.h +++ b/Engine/Source/Runtime/Core/Public/HAL/MemoryBase.h @@ -117,7 +117,7 @@ public: /** * Releases as much memory as possible. Must be called from the main thread. */ - virtual void Trim() + virtual void Trim(bool bTrimThreadCaches) { } diff --git a/Engine/Source/Runtime/Core/Public/HAL/UnrealMemory.h b/Engine/Source/Runtime/Core/Public/HAL/UnrealMemory.h index a58112bab8d6..f7bd281d2889 100644 --- a/Engine/Source/Runtime/Core/Public/HAL/UnrealMemory.h +++ b/Engine/Source/Runtime/Core/Public/HAL/UnrealMemory.h @@ -178,7 +178,7 @@ struct CORE_API FMemory /** * Releases as much memory as possible. Must be called from the main thread. */ - static void Trim(); + static void Trim(bool bTrimThreadCaches = true); /** * Set up TLS caches on the current thread. These are the threads that we can trim. diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h new file mode 100644 index 000000000000..69fbc0879738 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec.h @@ -0,0 +1,75 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Animation/AnimTypes.h" +#include "AnimCurveCompressionCodec.generated.h" + +class UAnimCurveCompressionCodec; +class UAnimSequence; +struct FBlendedCurve; + +#if WITH_EDITORONLY_DATA +/** Holds the result from animation curve compression */ +struct FAnimCurveCompressionResult +{ + /** The animation curves as raw compressed bytes */ + TArray CompressedBytes; + + /** The codec used by the compressed bytes */ + UAnimCurveCompressionCodec* Codec; + + /** Default constructor */ + FAnimCurveCompressionResult() : CompressedBytes(), Codec(nullptr) {} +}; +#endif + +/* + * Base class for all curve compression codecs. + */ +UCLASS(abstract, hidecategories = Object, EditInlineNew) +class ENGINE_API UAnimCurveCompressionCodec : public UObject +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** A GUID that is unique to this codec instance. After creation, it never changes. */ + FGuid InstanceGuid; +#endif + + ////////////////////////////////////////////////////////////////////////// + +#if WITH_EDITORONLY_DATA + // UObject overrides + virtual void PostInitProperties() override; + virtual void PostDuplicate(bool bDuplicateForPIE) override; + virtual void Serialize(FArchive& Ar) override; + + /** Returns whether or not we can use this codec to compress. */ + virtual bool IsCodecValid() const { return true; } + + /** Compresses the curve data from an animation sequence. */ + virtual bool Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) PURE_VIRTUAL(UAnimCurveCompressionCodec::Compress, return false;); + + /* + * Called to generate a unique DDC key for this codec instance. + * A suitable key should be generated from: the InstanceGuid, a codec version, and all relevant properties that drive the behavior. + */ + virtual void PopulateDDCKey(FArchive& Ar); +#endif + + /* + * Decompresses all the active blended curves. + * Note: Codecs should _NOT_ rely on any member properties during decompression. Decompression + * behavior should entirely be driven by code and the compressed data. + */ + virtual void DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const PURE_VIRTUAL(UAnimCurveCompressionCodec::DecompressCurves, ); + + /* + * Decompress a single curve. + * Note: Codecs should _NOT_ rely on any member properties during decompression. Decompression + * behavior should entirely be driven by code and the compressed data. + */ + virtual float DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const PURE_VIRTUAL(UAnimCurveCompressionCodec::DecompressCurve, return 0.0f;); +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h new file mode 100644 index 000000000000..293996abca50 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_CompressedRichCurve.h @@ -0,0 +1,42 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** +* Stores the raw rich curves as FCompressedRichCurve internally with optional key reduction and key time quantization. +*/ + +#include "CoreMinimal.h" +#include "Animation/AnimCurveCompressionCodec.h" +#include "AnimCurveCompressionCodec_CompressedRichCurve.generated.h" + +UCLASS(meta = (DisplayName = "Compressed Rich Curves")) +class ENGINE_API UAnimCurveCompressionCodec_CompressedRichCurve : public UAnimCurveCompressionCodec +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** Max error allowed when compressing the rich curves */ + UPROPERTY(Category = Compression, EditAnywhere, meta = (ClampMin = "0")) + float MaxCurveError; + + /** Whether to use the animation sequence sample rate or an explicit value */ + UPROPERTY(Category = Compression, EditAnywhere) + bool UseAnimSequenceSampleRate; + + /** Sample rate to use when measuring the curve error */ + UPROPERTY(Category = Compression, EditAnywhere, meta = (ClampMin = "0", EditCondition = "!UseAnimSequenceSampleRate")) + float ErrorSampleRate; +#endif + + ////////////////////////////////////////////////////////////////////////// + +#if WITH_EDITORONLY_DATA + // UAnimCurveCompressionCodec overrides + virtual bool Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) override; + virtual void PopulateDDCKey(FArchive& Ar) override; +#endif + + virtual void DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const override; + virtual float DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const override; +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h new file mode 100644 index 000000000000..cb636bfdb5a7 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionCodec_UniformlySampled.h @@ -0,0 +1,38 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +/** +* Stores the raw rich curves as FCompressedRichCurve internally with optional key reduction and key time quantization. +*/ + +#include "CoreMinimal.h" +#include "Animation/AnimCurveCompressionCodec.h" +#include "AnimCurveCompressionCodec_UniformlySampled.generated.h" + +UCLASS(meta = (DisplayName = "Uniformly Sampled")) +class ENGINE_API UAnimCurveCompressionCodec_UniformlySampled : public UAnimCurveCompressionCodec +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** Whether to use the animation sequence sample rate or an explicit value */ + UPROPERTY(Category = Compression, EditAnywhere) + bool UseAnimSequenceSampleRate; + + /** Sample rate to use when uniformly sampling */ + UPROPERTY(Category = Compression, EditAnywhere, meta = (ClampMin = "0", EditCondition = "!UseAnimSequenceSampleRate")) + float SampleRate; +#endif + + ////////////////////////////////////////////////////////////////////////// + +#if WITH_EDITORONLY_DATA + // UAnimCurveCompressionCodec overrides + virtual bool Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) override; + virtual void PopulateDDCKey(FArchive& Ar) override; +#endif + + virtual void DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const override; + virtual float DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const override; +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h new file mode 100644 index 000000000000..d2fa7704fbf0 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimCurveCompressionSettings.h @@ -0,0 +1,45 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AnimCurveCompressionSettings.generated.h" + +class UAnimCurveCompressionCodec; +class UAnimSequence; + +/* + * This object is used to wrap a curve compression codec. It allows a clean integration in the editor by avoiding the need + * to create asset types and factory wrappers for every codec. + */ +UCLASS(hidecategories = Object) +class ENGINE_API UAnimCurveCompressionSettings : public UObject +{ + GENERATED_UCLASS_BODY() + +#if WITH_EDITORONLY_DATA + /** An animation curve compression codec. */ + UPROPERTY(Category = Compression, Instanced, EditAnywhere, NoClear) + UAnimCurveCompressionCodec* Codec; +#endif + + ////////////////////////////////////////////////////////////////////////// + +#if WITH_EDITORONLY_DATA + // UObject overrides + virtual void PostInitProperties() override; + + /** Returns whether or not we can use these settings to compress. */ + bool AreSettingsValid() const; + + /* + * Compresses the animation curves inside the supplied sequence. + * Note that this will modify the animation sequence by populating the compressed bytes + * and the codec used but it is left unchanged if compression fails. + */ + bool Compress(UAnimSequence& AnimSeq) const; + + /** Generates a DDC key that takes into account the current settings and selected codec. */ + FString MakeDDCKey() const; +#endif +}; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimMontage.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimMontage.h index e422cbdc178d..7e599f2f7f51 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimMontage.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimMontage.h @@ -280,7 +280,7 @@ private: }; USTRUCT() -struct FAnimMontageInstance +struct ENGINE_API FAnimMontageInstance { GENERATED_USTRUCT_BODY() @@ -384,7 +384,7 @@ public: * If Follower gets ticked after Leader, then synchronization will be exact and support more complex cases (i.e. timeline jumps). * This can be enforced by setting up tick pre-requisites if desired. */ - ENGINE_API void MontageSync_Follow(struct FAnimMontageInstance* NewLeaderMontageInstance); + void MontageSync_Follow(struct FAnimMontageInstance* NewLeaderMontageInstance); /** Stop leading, release all followers. */ void MontageSync_StopLeading(); /** Stop following our leader */ @@ -428,7 +428,7 @@ public: //~ Begin montage instance Interfaces void Play(float InPlayRate = 1.f); - ENGINE_API void Stop(const FAlphaBlend& InBlendOut, bool bInterrupt=true); + void Stop(const FAlphaBlend& InBlendOut, bool bInterrupt=true); void Pause(); void Initialize(class UAnimMontage * InMontage); @@ -482,10 +482,10 @@ public: * second is normal tick. This tick has to happen later when all node ticks * to accumulate and update curve data/notifies/branching points */ - ENGINE_API void UpdateWeight(float DeltaTime); + void UpdateWeight(float DeltaTime); #if WITH_EDITOR - ENGINE_API void EditorOnly_PreAdvance(); + void EditorOnly_PreAdvance(); #endif /** Simulate is same as Advance, but without calling any events or touching any of the instance data. So it performs a simulation of advancing the timeline. */ @@ -494,7 +494,7 @@ public: FName GetCurrentSection() const; FName GetNextSection() const; - ENGINE_API int32 GetNextSectionID(int32 const & CurrentSectionID) const; + int32 GetNextSectionID(int32 const & CurrentSectionID) const; FName GetSectionNameFromID(int32 const & SectionID) const; // reference has to be managed manually @@ -502,7 +502,7 @@ public: /** Delegate function handlers */ - ENGINE_API void HandleEvents(float PreviousTrackPos, float CurrentTrackPos, const FBranchingPointMarker* BranchingPointMarker); + void HandleEvents(float PreviousTrackPos, float CurrentTrackPos, const FBranchingPointMarker* BranchingPointMarker); private: /** Called by blueprint functions that modify the montages current position. */ @@ -519,11 +519,11 @@ private: public: /** static functions that are used by matinee functionality */ - ENGINE_API static UAnimMontage* SetMatineeAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping); - ENGINE_API static UAnimMontage* PreviewMatineeSetAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping, bool bFireNotifies, float DeltaTime); + static UAnimMontage* SetMatineeAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping); + static UAnimMontage* PreviewMatineeSetAnimPositionInner(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, float InPosition, bool bLooping, bool bFireNotifies, float DeltaTime); /** static functions that are used by sequencer montage support*/ - ENGINE_API static UAnimMontage* SetSequencerMontagePosition(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, int32& InOutInstanceId, UAnimSequenceBase* InAnimSequence, float InPosition, float Weight, bool bLooping, bool bPlaying); - ENGINE_API static UAnimMontage* PreviewSequencerMontagePosition(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, int32& InOutInstanceId, UAnimSequenceBase* InAnimSequence, float InPosition, float Weight, bool bLooping, bool bFireNotifies, bool bPlaying); + static UAnimMontage* SetSequencerMontagePosition(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, int32& InOutInstanceId, UAnimSequenceBase* InAnimSequence, float InPosition, float Weight, bool bLooping, bool bPlaying); + static UAnimMontage* PreviewSequencerMontagePosition(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, int32& InOutInstanceId, UAnimSequenceBase* InAnimSequence, float InPosition, float Weight, bool bLooping, bool bFireNotifies, bool bPlaying); private: static UAnimMontage* InitializeMatineeControl(FName SlotName, USkeletalMeshComponent* SkeletalMeshComponent, UAnimSequenceBase* InAnimSequence, bool bLooping); }; diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h index 56b7b6f30792..7444d6045d96 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequence.h @@ -448,6 +448,12 @@ protected: */ TArray CompressedTrackToSkeletonMapTable; + /** + * Much like track indices above, we need to be able to remap curve names. The FName inside FSmartName + * is serialized and the UID is generated at runtime. Stored in the DDC. + */ + TArray CompressedCurveNames; + /** * Raw uncompressed keyframe data. */ @@ -491,8 +497,15 @@ public: */ UPROPERTY(Category = Compression, EditAnywhere) bool bAllowFrameStripping; + + /** The curve compression settings used to compress curves in this sequence. */ + UPROPERTY(Category=Compression, EditAnywhere) + class UAnimCurveCompressionSettings* CurveCompressionSettings; #endif // WITH_EDITORONLY_DATA + /** The codec used by the compressed data as determined by the compression settings. */ + class UAnimCurveCompressionCodec* CurveCompressionCodec; + /** The compression format that was used to compress translation tracks. */ TEnumAsByte TranslationCompressionFormat; @@ -548,8 +561,8 @@ public: class AnimEncoding* ScaleCodec; - // Built during compression, could be baked additive or original curve data - FRawCurveTracks CompressedCurveData; + /* Compressed curve data stream used by AnimCurveCompressionCodec */ + TArray CompressedCurveByteStream; // The size of the raw data used to create the compressed data int32 CompressedRawDataSize; @@ -666,6 +679,7 @@ public: #endif // WITH_EDITOR virtual void BeginDestroy() override; virtual void GetAssetRegistryTags(TArray& OutTags) const override; + static void AddReferencedObjects(UObject* This, FReferenceCollector& Collector); //~ End UObject Interface //~ Begin UAnimationAsset Interface @@ -683,8 +697,11 @@ public: virtual bool HasRootMotion() const override { return bEnableRootMotion; } virtual void RefreshCacheData() override; virtual EAdditiveAnimationType GetAdditiveAnimType() const override { return AdditiveAnimType; } - virtual void EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData=false) const; - virtual const FRawCurveTracks& GetCurveData() const; + + virtual void EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData = false) const override; + virtual float EvaluateCurveData(SmartName::UID_Type CurveUID, float CurrentTime, bool bForceUseRawData = false) const override; + virtual bool HasCurveData(SmartName::UID_Type CurveUID, bool bForceUseRawData) const override; + #if WITH_EDITOR virtual void MarkRawDataAsModified(bool bForceNewRawDatGuid = true) override { @@ -738,6 +755,8 @@ public: const TArray& GetAdditiveBaseAnimationData() const { return TemporaryAdditiveBaseAnimationData; } void UpdateCompressedTrackMapFromRaw() { CompressedTrackToSkeletonMapTable = TrackToSkeletonMapTable; } void UpdateCompressedNumFramesFromRaw() { CompressedNumFrames = NumFrames; } + void UpdateCompressedCurveNames(); + void UpdateCompressedCurveName(SmartName::UID_Type CurveUID, const struct FSmartName& NewCurveName); // Adds a new track (if no track of the supplied name is found) to the raw animation data, optionally setting it to TrackData. int32 AddNewRawTrack(FName TrackName, FRawAnimSequenceTrack* TrackData = nullptr); @@ -745,6 +764,7 @@ public: const TArray& GetRawTrackToSkeletonMapTable() const { return TrackToSkeletonMapTable; } const TArray& GetCompressedTrackToSkeletonMapTable() const { return CompressedTrackToSkeletonMapTable; } + const TArray& GetCompressedCurveNames() const { return CompressedCurveNames; } FRawAnimSequenceTrack& GetRawAnimationTrack(int32 TrackIndex) { return RawAnimationData[TrackIndex]; } const FRawAnimSequenceTrack& GetRawAnimationTrack(int32 TrackIndex) const { return RawAnimationData[TrackIndex]; } @@ -874,6 +894,7 @@ public: void RequestAnimCompression(FRequestAnimCompressionParams Params); void RequestSyncAnimRecompression(bool bOutput = false) { RequestAnimCompression(FRequestAnimCompressionParams(false, false, bOutput)); } bool IsCompressedDataValid() const; + bool IsCurveCompressedDataValid() const; // Write the compressed data to the supplied FArchive void SerializeCompressedData(FArchive& Ar, bool bDDCData); diff --git a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequenceBase.h b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequenceBase.h index 5f62ad385146..ea64555ee8c5 100644 --- a/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequenceBase.h +++ b/Engine/Source/Runtime/Engine/Classes/Animation/AnimSequenceBase.h @@ -101,9 +101,11 @@ class ENGINE_API UAnimSequenceBase : public UAnimationAsset virtual void GetAnimNotifiesFromDeltaPositions(const float& PreviousPosition, const float & CurrentPosition, TArray& OutActiveNotifies) const; /** Evaluate curve data to Instance at the time of CurrentTime **/ - virtual void EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData=false) const; + virtual void EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData = false) const; + virtual float EvaluateCurveData(SmartName::UID_Type CurveUID, float CurrentTime, bool bForceUseRawData = false) const; virtual const FRawCurveTracks& GetCurveData() const { return RawCurveData; } + virtual bool HasCurveData(SmartName::UID_Type CurveUID, bool bForceUseRawData = false) const; /** Return Number of Frames **/ virtual int32 GetNumberOfFrames() const; diff --git a/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h b/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h index 19d2fb5d171f..a48163ab3f09 100644 --- a/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h +++ b/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h @@ -38,6 +38,37 @@ enum ERichCurveTangentWeightMode RCTWM_WeightedBoth UMETA(DisplayName="Both") }; +/** Enumerates curve compression options. */ +UENUM() +enum ERichCurveCompressionFormat +{ + /** No keys are present */ + RCCF_Empty UMETA(DisplayName = "Empty"), + + /** All keys use constant interpolation */ + RCCF_Constant UMETA(DisplayName = "Constant"), + + /** All keys use linear interpolation */ + RCCF_Linear UMETA(DisplayName = "Linear"), + + /** All keys use cubic interpolation */ + RCCF_Cubic UMETA(DisplayName = "Cubic"), + + /** Keys use mixed interpolation modes */ + RCCF_Mixed UMETA(DisplayName = "Mixed"), +}; + +/** Enumerates key time compression options. */ +UENUM() +enum ERichCurveKeyTimeCompressionFormat +{ + /** Key time is quantized to 16 bits */ + RCKTCF_uint16 UMETA(DisplayName = "uint16"), + + /** Key time uses full precision */ + RCKTCF_float32 UMETA(DisplayName = "float32"), +}; + /** One key in a rich, editable float curve */ USTRUCT() struct ENGINE_API FRichCurveKey @@ -225,6 +256,12 @@ public: /** Returns a pair for the specified key */ virtual TPair GetKeyTimeValuePair(FKeyHandle KeyHandle) const final override; + /** Returns whether the curve is constant or not */ + bool IsConstant(float Tolerance = SMALL_NUMBER) const; + + /** Returns whether the curve is empty or not */ + bool IsEmpty() const { return Keys.Num() == 0; } + /** Set the interp mode of the specified key */ virtual void SetKeyInterpMode(FKeyHandle KeyHandle, ERichCurveInterpMode NewInterpMode) final override; @@ -272,6 +309,9 @@ public: virtual void RemoveRedundantKeys(float Tolerance) final override; virtual void RemoveRedundantKeys(float Tolerance, float FirstKeyTime, float LastKeyTime) final override; + /** Compresses a rich curve for efficient runtime storage and evaluation */ + void CompressCurve(struct FCompressedRichCurve& OutCurve, float ErrorThreshold = 0.0001f, float SampleRate = 120.0f) const; + private: void RemoveRedundantKeysInternal(float Tolerance, int32 InStartKeepKey, int32 InEndKeepKey); virtual int32 GetKeyIndex(float KeyTime, float KeyTimeTolerance) const override final; @@ -289,6 +329,85 @@ public: TArray Keys; }; +/** + * A runtime optimized representation of a FRichCurve. It consumes less memory and evaluates faster. + */ +USTRUCT() +struct ENGINE_API FCompressedRichCurve +{ + GENERATED_USTRUCT_BODY() + + /** Compression format used by CompressedKeys */ + TEnumAsByte CompressionFormat; + + /** Compression format used to pack the key time */ + TEnumAsByte KeyTimeCompressionFormat; + + /** Pre-infinity extrapolation state */ + TEnumAsByte PreInfinityExtrap; + + /** Post-infinity extrapolation state */ + TEnumAsByte PostInfinityExtrap; + + union TConstantValueNumKeys + { + float ConstantValue; + int32 NumKeys; + + TConstantValueNumKeys() : NumKeys(0) {} + }; + + /** + * If the compression format is constant, this is the value returned + * Inline here to reduce the likelihood of accessing the compressed keys data for the common case of constant/zero/empty curves + * When a curve is linear/cubic/mixed, the constant float value isn't used and instead we use the number of keys + */ + TConstantValueNumKeys ConstantValueNumKeys; + + /** Compressed keys, used only outside of the editor */ + TArray CompressedKeys; + + FCompressedRichCurve() + : CompressionFormat(RCCF_Empty) + , KeyTimeCompressionFormat(RCKTCF_float32) + , PreInfinityExtrap(RCCE_None) + , PostInfinityExtrap(RCCE_None) + , ConstantValueNumKeys() + , CompressedKeys() + {} + + /** Evaluate this rich curve at the specified time */ + float Eval(float InTime, float InDefaultValue = 0.0f) const; + + /** Evaluate this rich curve at the specified time */ + static float StaticEval(ERichCurveCompressionFormat CompressionFormat, ERichCurveKeyTimeCompressionFormat KeyTimeCompressionFormat, ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue = 0.0f); + + /** ICPPStructOps interface */ + bool Serialize(FArchive& Ar); + bool operator==(const FCompressedRichCurve& Other) const; + bool operator!=(const FCompressedRichCurve& Other) const { return !(*this == Other); } + + friend FArchive& operator<<(FArchive& Ar, FCompressedRichCurve& Curve) + { + Curve.Serialize(Ar); + return Ar; + } +}; + +/* + * Override serialization for compressed rich curves to handle the union + */ +template<> +struct TStructOpsTypeTraits + : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithSerializer = true, + WithCopy = false, + WithIdenticalViaEquality = true, + }; +}; /** * Info about a curve to be edited. diff --git a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.h b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.h index 47ea86330550..adb0c29ac743 100644 --- a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.h +++ b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.h @@ -1835,7 +1835,7 @@ class ENGINE_API UKismetMathLibrary : public UBlueprintFunctionLibrary /** Breaks a 4D vector apart into X, Y, Z, W. */ UFUNCTION(BlueprintPure, meta = (NativeBreakFunc), Category = "Math|Vector4") - static void BreakVector4(FVector4 InVec, float& X, float& Y, float& Z, float& W); + static void BreakVector4(const FVector4& InVec, float& X, float& Y, float& Z, float& W); /** Convert a Vector4 to a Vector (dropping the W element) */ UFUNCTION(BlueprintPure, meta = (DisplayName = "To Vector (Vector4)", CompactNodeTitle = "->", ScriptMethod = "Vector", Keywords = "cast convert", BlueprintAutocast), Category = "Math|Conversions") diff --git a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.inl b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.inl index 1c70bbc1d4a4..af2a292af14f 100644 --- a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.inl +++ b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetMathLibrary.inl @@ -1833,7 +1833,7 @@ FVector4 UKismetMathLibrary::MakeVector4(float X, float Y, float Z, float W) } KISMET_MATH_FORCEINLINE -void UKismetMathLibrary::BreakVector4(FVector4 InVec, float& X, float& Y, float& Z, float& W) +void UKismetMathLibrary::BreakVector4(const FVector4& InVec, float& X, float& Y, float& Z, float& W) { X = InVec.X; Y = InVec.Y; diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp index 6e1112307f3e..e07749e78619 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.cpp @@ -6,6 +6,7 @@ #include "AnimationUtils.h" #include "AnimEncoding.h" #include "Animation/AnimCompress.h" +#include "Animation/AnimCurveCompressionSettings.h" #include "AnimationCompression.h" #include "UObject/Package.h" @@ -70,6 +71,7 @@ FString FDerivedDataAnimationCompression::GetPluginSpecificCacheKeySuffix() cons // * Baked Additive Flag // * Additive ref pose GUID or hardcoded string if not available // * Compression Settings + // * Curve compression settings uint8 AdditiveSettings = bCanBakeAdditive ? (OriginalAnimSequence->RefPoseType << 4) + OriginalAnimSequence->AdditiveAnimType : 0; @@ -78,7 +80,7 @@ FString FDerivedDataAnimationCompression::GetPluginSpecificCacheKeySuffix() cons const int32 StripFrame = bPerformStripping ? 1 : 0; - FString Ret = FString::Printf(TEXT("%i_%i_%i_%i_%s%s%s_%c%c%i_%s_%s"), + FString Ret = FString::Printf(TEXT("%i_%i_%i_%i_%s%s%s_%c%c%i_%s_%s_%s"), (int32)UE_ANIMCOMPRESSION_DERIVEDDATA_VER, (int32)CURRENT_ANIMATION_ENCODING_PACKAGE_VERSION, OriginalAnimSequence->CompressCommandletVersion, @@ -90,7 +92,8 @@ FString FDerivedDataAnimationCompression::GetPluginSpecificCacheKeySuffix() cons RefType, OriginalAnimSequence->RefFrameIndex, (bCanBakeAdditive && AdditiveBase) ? *AdditiveBase->GetRawDataGuid().ToString() : TEXT("NoAdditiveBase"), - *OriginalAnimSequence->CompressionScheme->MakeDDCKey() + *OriginalAnimSequence->CompressionScheme->MakeDDCKey(), + *OriginalAnimSequence->CurveCompressionSettings->MakeDDCKey() ); return Ret; @@ -155,13 +158,9 @@ bool FDerivedDataAnimationCompression::Build( TArray& OutData ) } AnimToOperateOn->UpdateCompressedTrackMapFromRaw(); - AnimToOperateOn->CompressedCurveData = AnimToOperateOn->RawCurveData; //Curves don't actually get compressed, but could have additives baked in + AnimToOperateOn->UpdateCompressedCurveNames(); - const float MaxCurveError = AnimToOperateOn->CompressionScheme->MaxCurveError; - for (FFloatCurve& Curve : AnimToOperateOn->CompressedCurveData.FloatCurves) - { - Curve.FloatCurve.RemoveRedundantKeys(MaxCurveError); - } + bool bCurveCompressionSuccess = FAnimationUtils::CompressAnimCurves(*AnimToOperateOn); #if DO_CHECK FString CompressionName = AnimToOperateOn->CompressionScheme->GetFullName(); @@ -172,7 +171,7 @@ bool FDerivedDataAnimationCompression::Build( TArray& OutData ) AnimToOperateOn->UpdateCompressedNumFramesFromRaw(); //Do this before compression so compress code can read the correct value FAnimationUtils::CompressAnimSequence(AnimToOperateOn, *CompressContext.Get()); - bCompressionSuccessful = AnimToOperateOn->IsCompressedDataValid(); + bCompressionSuccessful = AnimToOperateOn->IsCompressedDataValid() && bCurveCompressionSuccess; ensureMsgf(bCompressionSuccessful, TEXT("Anim Compression failed for Sequence '%s' with compression scheme '%s': compressed data empty\n\tAnimIndex: %i\n\tMaxAnim:%i\n\tAllowAltCompressor:%s\n\tOutput:%s"), *AnimToOperateOn->GetFullName(), @@ -191,6 +190,7 @@ bool FDerivedDataAnimationCompression::Build( TArray& OutData ) { CA_SUPPRESS(6011); // See https://connect.microsoft.com/VisualStudio/feedback/details/3007725 OriginalAnimSequence->CompressionScheme = static_cast(StaticDuplicateObject(AnimToOperateOn->CompressionScheme, OriginalAnimSequence)); + OriginalAnimSequence->CurveCompressionSettings = AnimToOperateOn->CurveCompressionSettings; } if (bCompressionSuccessful) diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h index 747a013706ed..276aacdd1702 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCompressionDerivedData.h @@ -47,7 +47,7 @@ public: // This is a version string that mimics the old versioning scheme. If you // want to bump this version, generate a new guid using VS->Tools->Create GUID and // return it here. Ex. - return TEXT("EFCFC622A8794B758D53CDE253471CBD"); + return TEXT("D58C7C3E48274C04BFC4D7823AF46F5E"); } virtual FString GetPluginSpecificCacheKeySuffix() const override; diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec.cpp new file mode 100644 index 000000000000..1548cc7b869a --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec.cpp @@ -0,0 +1,42 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Animation/AnimCurveCompressionCodec.h" + +UAnimCurveCompressionCodec::UAnimCurveCompressionCodec(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_EDITORONLY_DATA +void UAnimCurveCompressionCodec::PostInitProperties() +{ + Super::PostInitProperties(); + + if (!IsTemplate()) + { + InstanceGuid = FGuid::NewGuid(); + } +} + +void UAnimCurveCompressionCodec::PostDuplicate(bool bDuplicateForPIE) +{ + Super::PostDuplicate(bDuplicateForPIE); + + InstanceGuid = FGuid::NewGuid(); +} + +void UAnimCurveCompressionCodec::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (!Ar.IsCooking()) + { + Ar << InstanceGuid; + } +} + +void UAnimCurveCompressionCodec::PopulateDDCKey(FArchive& Ar) +{ + Ar << InstanceGuid; +} +#endif diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp new file mode 100644 index 000000000000..d5de3d8d1368 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_CompressedRichCurve.cpp @@ -0,0 +1,137 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Animation/AnimCurveCompressionCodec_CompressedRichCurve.h" +#include "Animation/AnimSequence.h" +#include "Serialization/MemoryWriter.h" + +UAnimCurveCompressionCodec_CompressedRichCurve::UAnimCurveCompressionCodec_CompressedRichCurve(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITORONLY_DATA + MaxCurveError = 0.0f; + UseAnimSequenceSampleRate = true; + ErrorSampleRate = 60.0f; +#endif +} + +// This mirrors in part the FCompressedRichCurve +struct FCurveDesc +{ + TEnumAsByte CompressionFormat; + TEnumAsByte KeyTimeCompressionFormat; + TEnumAsByte PreInfinityExtrap; + TEnumAsByte PostInfinityExtrap; + FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys; + int32 KeyDataOffset; +}; + +#if WITH_EDITORONLY_DATA +bool UAnimCurveCompressionCodec_CompressedRichCurve::Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) +{ + int32 NumCurves = AnimSeq.RawCurveData.FloatCurves.Num(); + + TArray Curves; + Curves.Reserve(NumCurves); + Curves.AddUninitialized(NumCurves); + + int32 KeyDataOffset = 0; + KeyDataOffset += sizeof(FCurveDesc) * NumCurves; + + const FAnimKeyHelper Helper(AnimSeq.SequenceLength, AnimSeq.GetRawNumberOfFrames()); + const float SampleRate = UseAnimSequenceSampleRate ? Helper.KeysPerSecond() : ErrorSampleRate; + + TArray KeyData; + + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = AnimSeq.RawCurveData.FloatCurves[CurveIndex]; + + FRichCurve RawCurve = Curve.FloatCurve; // Copy + RawCurve.RemoveRedundantKeys(MaxCurveError); + + FCompressedRichCurve CompressedCurve; + RawCurve.CompressCurve(CompressedCurve, MaxCurveError, SampleRate); + + FCurveDesc& CurveDesc = Curves[CurveIndex]; + CurveDesc.CompressionFormat = CompressedCurve.CompressionFormat; + CurveDesc.KeyTimeCompressionFormat = CompressedCurve.KeyTimeCompressionFormat; + CurveDesc.PreInfinityExtrap = CompressedCurve.PreInfinityExtrap; + CurveDesc.PostInfinityExtrap = CompressedCurve.PostInfinityExtrap; + CurveDesc.ConstantValueNumKeys = CompressedCurve.ConstantValueNumKeys; + CurveDesc.KeyDataOffset = KeyDataOffset; + + KeyDataOffset += CompressedCurve.CompressedKeys.Num(); + KeyData.Append(CompressedCurve.CompressedKeys); + } + + TArray TempBytes; + TempBytes.Reserve(KeyDataOffset); + + // Serialize the compression settings into a temporary array. The archive + // is flagged as persistent so that machines of different endianness produce + // identical binary results. + FMemoryWriter Ar(TempBytes, /*bIsPersistent=*/ true); + + Ar.Serialize(Curves.GetData(), sizeof(FCurveDesc) * NumCurves); + Ar.Serialize(KeyData.GetData(), KeyData.Num()); + + OutResult.CompressedBytes = TempBytes; + OutResult.Codec = this; + + return true; +} + +void UAnimCurveCompressionCodec_CompressedRichCurve::PopulateDDCKey(FArchive& Ar) +{ + Super::PopulateDDCKey(Ar); + + int32 CodecVersion = 0; + + Ar << CodecVersion; + Ar << MaxCurveError; + Ar << UseAnimSequenceSampleRate; + Ar << ErrorSampleRate; +} +#endif + +void UAnimCurveCompressionCodec_CompressedRichCurve::DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const +{ + const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); + const FCurveDesc* CurveDescriptions = (const FCurveDesc*)(Buffer); + + const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const int32 NumCurves = CompressedCurveNames.Num(); + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FSmartName& CurveName = CompressedCurveNames[CurveIndex]; + if (Curves.IsEnabled(CurveName.UID)) + { + const FCurveDesc& Curve = CurveDescriptions[CurveIndex]; + const uint8* CompressedKeys = Buffer + Curve.KeyDataOffset; + const float Value = FCompressedRichCurve::StaticEval(Curve.CompressionFormat, Curve.KeyTimeCompressionFormat, Curve.PreInfinityExtrap, Curve.PostInfinityExtrap, Curve.ConstantValueNumKeys, CompressedKeys, CurrentTime); + Curves.Set(CurveName.UID, Value); + } + } +} + +float UAnimCurveCompressionCodec_CompressedRichCurve::DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const +{ + const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); + const FCurveDesc* CurveDescriptions = (const FCurveDesc*)(Buffer); + + const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const int32 NumCurves = CompressedCurveNames.Num(); + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FSmartName& CurveName = CompressedCurveNames[CurveIndex]; + if (CurveName.UID == CurveUID) + { + const FCurveDesc& Curve = CurveDescriptions[CurveIndex]; + const uint8* CompressedKeys = Buffer + Curve.KeyDataOffset; + const float Value = FCompressedRichCurve::StaticEval(Curve.CompressionFormat, Curve.KeyTimeCompressionFormat, Curve.PreInfinityExtrap, Curve.PostInfinityExtrap, Curve.ConstantValueNumKeys, CompressedKeys, CurrentTime); + return Value; + } + } + + return 0.0f; +} diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp new file mode 100644 index 000000000000..efbe63ff0e63 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionCodec_UniformlySampled.cpp @@ -0,0 +1,293 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Animation/AnimCurveCompressionCodec_UniformlySampled.h" +#include "Animation/AnimSequence.h" +#include "Serialization/MemoryWriter.h" + +UAnimCurveCompressionCodec_UniformlySampled::UAnimCurveCompressionCodec_UniformlySampled(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITORONLY_DATA + UseAnimSequenceSampleRate = true; + SampleRate = 30.0f; +#endif +} + +#if WITH_EDITORONLY_DATA +bool UAnimCurveCompressionCodec_UniformlySampled::Compress(const UAnimSequence& AnimSeq, FAnimCurveCompressionResult& OutResult) +{ + const int32 NumCurves = AnimSeq.RawCurveData.FloatCurves.Num(); + const float Duration = AnimSeq.SequenceLength; + + int32 NumSamples; + float SampleRate_; + if (UseAnimSequenceSampleRate) + { + const FAnimKeyHelper Helper(AnimSeq.SequenceLength, AnimSeq.GetRawNumberOfFrames()); + SampleRate_ = Helper.KeysPerSecond(); + NumSamples = FMath::RoundToInt(Duration * SampleRate_) + 1; + } + else + { + // If our duration isn't an exact multiple of the sample rate, we'll round + // and end up with a sample rate slightly corrected to make sure we spread + // the resulting error over the whole duration + NumSamples = FMath::RoundToInt(Duration * SampleRate) + 1; + SampleRate_ = (NumSamples - 1) / Duration; + } + + int32 NumConstantCurves = 0; + for (const FFloatCurve& Curve : AnimSeq.RawCurveData.FloatCurves) + { + if (Curve.FloatCurve.IsConstant()) + { + NumConstantCurves++; + } + } + + const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + + // 1 bit per curve, round up + const int32 ConstantBitsetSize = sizeof(uint32) * ((NumCurves + 31) / 32); + + int32 BufferSize = 0; + BufferSize += sizeof(int32); // NumConstantCurves + BufferSize += sizeof(int32); // NumSamples + BufferSize += sizeof(float); // SampleRate + BufferSize += ConstantBitsetSize; // Constant curve bitset + BufferSize += sizeof(float) * NumConstantCurves; // Constant curve samples + BufferSize += sizeof(float) * NumAnimatedCurves * NumSamples; // Animated curve samples + + TArray Buffer; + Buffer.Reserve(BufferSize); + Buffer.AddUninitialized(BufferSize); + + int32 BufferOffset = 0; + int32* NumConstantCurvesPtr = (int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + int32* NumSamplesPtr = (int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + float* SampleRatePtr = (float*)&Buffer[BufferOffset]; + BufferOffset += sizeof(float); + + *NumConstantCurvesPtr = NumConstantCurves; + *NumSamplesPtr = NumSamples; + *SampleRatePtr = SampleRate_; + + if (NumCurves > 0 && NumSamples > 0) + { + uint32* ConstantCurvesBitsetPtr = (uint32*)&Buffer[BufferOffset]; + BufferOffset += ConstantBitsetSize; + FMemory::Memzero(ConstantCurvesBitsetPtr, ConstantBitsetSize); + + float* ConstantSamplesPtr = NumConstantCurves > 0 ? (float*)&Buffer[BufferOffset] : nullptr; + BufferOffset += sizeof(float) * NumConstantCurves; + + float* AnimatedSamplesPtr = NumAnimatedCurves > 0 ? (float*)&Buffer[BufferOffset] : nullptr; + BufferOffset += sizeof(float) * NumAnimatedCurves * NumSamples; + + for (int32 CurveIndex = 0, ConstantCurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = AnimSeq.RawCurveData.FloatCurves[CurveIndex]; + if (Curve.FloatCurve.IsConstant()) + { + if (Curve.FloatCurve.IsEmpty()) + { + ConstantSamplesPtr[ConstantCurveIndex] = Curve.FloatCurve.DefaultValue; + } + else + { + ConstantSamplesPtr[ConstantCurveIndex] = Curve.FloatCurve.Keys[0].Value; + } + + // Bitset uses little-endian bit ordering + ConstantCurvesBitsetPtr[CurveIndex / 32] |= 1 << (CurveIndex % 32); + ConstantCurveIndex++; + } + } + + // Write out samples sorted by time first in order to have everything contiguous in memory + // for improved cache locality + // Curve 0 Key 0, Curve 0 Key 1, Curve 0 Key N, Curve 1 Key 0, Curve 1 Key 1, Curve 1 Key N, Curve M Key 0, ... + const float InvSampleRate = 1.0f / SampleRate_; + for (int32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex) + { + const float SampleTime = FMath::Clamp(SampleIndex * InvSampleRate, 0.0f, Duration); + float* AnimatedSamples = AnimatedSamplesPtr + (SampleIndex * NumAnimatedCurves); + + for (int32 CurveIndex = 0, AnimatedCurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = AnimSeq.RawCurveData.FloatCurves[CurveIndex]; + if (Curve.FloatCurve.IsConstant()) + { + // Skip constant curves, their data has already been written + continue; + } + + const float SampleValue = Curve.FloatCurve.Eval(SampleTime); + + AnimatedSamples[AnimatedCurveIndex] = SampleValue; + AnimatedCurveIndex++; + } + } + } + + check(BufferOffset == BufferSize); + + OutResult.CompressedBytes = Buffer; + OutResult.Codec = this; + + return true; +} + +void UAnimCurveCompressionCodec_UniformlySampled::PopulateDDCKey(FArchive& Ar) +{ + Super::PopulateDDCKey(Ar); + + int32 CodecVersion = 0; + + Ar << CodecVersion; + Ar << UseAnimSequenceSampleRate; + Ar << SampleRate; +} +#endif + +void UAnimCurveCompressionCodec_UniformlySampled::DecompressCurves(const UAnimSequence& AnimSeq, FBlendedCurve& Curves, float CurrentTime) const +{ + const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const int32 NumCurves = CompressedCurveNames.Num(); + + if (NumCurves == 0) + { + return; + } + + const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); + + int32 BufferOffset = 0; + const int32 NumConstantCurves = *(const int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + const int32 NumSamples = *(const int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + if (NumSamples == 0) + { + return; + } + + const float SampleRate_ = *(const float*)&Buffer[BufferOffset]; + BufferOffset += sizeof(float); + + const uint32* ConstantCurvesBitsetPtr = (const uint32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(uint32) * NumCurves; + + const int32 ConstantBitsetSize = sizeof(uint32) * ((NumCurves + 31) / 32); + const float* ConstantSamplesPtr = (const float*)&Buffer[BufferOffset]; + BufferOffset += ConstantBitsetSize; + + const float* AnimatedSamplesPtr = (const float*)&Buffer[BufferOffset]; + //const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + //BufferOffset += sizeof(float) * NumAnimatedCurves * NumSamples; + + const float SamplePoint = CurrentTime * SampleRate_; + const int32 SampleIndex0 = FMath::Clamp(FMath::FloorToInt(SamplePoint), 0, NumSamples - 1); + const int32 SampleIndex1 = FMath::Min(SampleIndex0 + 1, NumSamples - 1); + const float InterpolationAlpha = SamplePoint - float(SampleIndex0); + const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + + const float* AnimatedSamples0 = AnimatedSamplesPtr + (SampleIndex0 * NumAnimatedCurves); + const float* AnimatedSamples1 = AnimatedSamplesPtr + (SampleIndex1 * NumAnimatedCurves); + + for (int32 CurveIndex = 0, ConstantCurveIndex = 0, AnimatedCurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FSmartName& CurveName = CompressedCurveNames[CurveIndex]; + const bool bIsConstant = (ConstantCurvesBitsetPtr[CurveIndex / 32] & (1 << (CurveIndex % 32))) != 0; + if (Curves.IsEnabled(CurveName.UID)) + { + float Sample; + if (bIsConstant) + { + Sample = ConstantSamplesPtr[ConstantCurveIndex]; + } + else + { + const float Sample0 = AnimatedSamples0[AnimatedCurveIndex]; + const float Sample1 = AnimatedSamples1[AnimatedCurveIndex]; + Sample = FMath::Lerp(Sample0, Sample1, InterpolationAlpha); + } + + Curves.Set(CurveName.UID, Sample); + } + + (bIsConstant ? ConstantCurveIndex : AnimatedCurveIndex)++; + } +} + +float UAnimCurveCompressionCodec_UniformlySampled::DecompressCurve(const UAnimSequence& AnimSeq, SmartName::UID_Type CurveUID, float CurrentTime) const +{ + const TArray& CompressedCurveNames = AnimSeq.GetCompressedCurveNames(); + const int32 NumCurves = CompressedCurveNames.Num(); + + if (NumCurves == 0) + { + return 0.0f; + } + + const uint8* Buffer = AnimSeq.CompressedCurveByteStream.GetData(); + + int32 BufferOffset = 0; + const int32 NumConstantCurves = *(const int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + const int32 NumSamples = *(const int32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(int32); + + const float SampleRate_ = *(const float*)&Buffer[BufferOffset]; + BufferOffset += sizeof(float); + + const uint32* ConstantCurvesBitsetPtr = (const uint32*)&Buffer[BufferOffset]; + BufferOffset += sizeof(uint32) * NumCurves; + + const int32 ConstantBitsetSize = sizeof(uint32) * ((NumCurves + 31) / 32); + const float* ConstantSamplesPtr = (const float*)&Buffer[BufferOffset]; + BufferOffset += ConstantBitsetSize; + + const float* AnimatedSamplesPtr = (const float*)&Buffer[BufferOffset]; + //const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + //BufferOffset += sizeof(float) * NumAnimatedCurves * NumSamples; + + for (int32 CurveIndex = 0, ConstantCurveIndex = 0, AnimatedCurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FSmartName& CurveName = CompressedCurveNames[CurveIndex]; + const bool bIsConstant = (ConstantCurvesBitsetPtr[CurveIndex / 32] & (1 << (CurveIndex % 32))) != 0; + if (CurveName.UID == CurveUID) + { + float Sample; + if (bIsConstant) + { + Sample = ConstantSamplesPtr[ConstantCurveIndex]; + } + else + { + const float SamplePoint = CurrentTime * SampleRate_; + const int32 SampleIndex0 = FMath::Clamp(FMath::FloorToInt(SamplePoint), 0, NumSamples - 1); + const int32 SampleIndex1 = FMath::Min(SampleIndex0 + 1, NumSamples - 1); + const float InterpolationAlpha = SamplePoint - float(SampleIndex0); + const int32 NumAnimatedCurves = NumCurves - NumConstantCurves; + + const float Sample0 = AnimatedSamplesPtr[(SampleIndex0 * NumAnimatedCurves) + CurveIndex]; + const float Sample1 = AnimatedSamplesPtr[(SampleIndex1 * NumAnimatedCurves) + CurveIndex]; + Sample = FMath::Lerp(Sample0, Sample1, InterpolationAlpha); + } + + return Sample; + } + + (bIsConstant ? ConstantCurveIndex : AnimatedCurveIndex)++; + } + + return 0.0f; +} diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp new file mode 100644 index 000000000000..f1986ba890b6 --- /dev/null +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveCompressionSettings.cpp @@ -0,0 +1,74 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Animation/AnimCurveCompressionSettings.h" +#include "Animation/AnimCurveCompressionCodec_CompressedRichCurve.h" +#include "Animation/AnimSequence.h" +#include "Serialization/MemoryWriter.h" + +UAnimCurveCompressionSettings::UAnimCurveCompressionSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_EDITORONLY_DATA +void UAnimCurveCompressionSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + if (!IsTemplate()) + { + // Ensure we are never null + Codec = NewObject(this, NAME_None, RF_Public); + } +} + +bool UAnimCurveCompressionSettings::AreSettingsValid() const +{ + return Codec != nullptr && Codec->IsCodecValid(); +} + +bool UAnimCurveCompressionSettings::Compress(UAnimSequence& AnimSeq) const +{ + if (Codec == nullptr || !AreSettingsValid()) + { + return false; + } + + FAnimCurveCompressionResult CompressionResult; + bool Success = Codec->Compress(AnimSeq, CompressionResult); + if (Success) + { + AnimSeq.CompressedCurveByteStream = CompressionResult.CompressedBytes; + AnimSeq.CurveCompressionCodec = CompressionResult.Codec; + } + + return Success; +} + +FString UAnimCurveCompressionSettings::MakeDDCKey() const +{ + if (Codec == nullptr) + { + return TEXT(""); + } + + TArray TempBytes; + TempBytes.Reserve(64); + + // Serialize the compression settings into a temporary array. The archive + // is flagged as persistent so that machines of different endianness produce + // identical binary results. + FMemoryWriter Ar(TempBytes, /*bIsPersistent=*/ true); + + Codec->PopulateDDCKey(Ar); + + FString Key; + Key.Reserve(TempBytes.Num() + 1); + for (int32 ByteIndex = 0; ByteIndex < TempBytes.Num(); ++ByteIndex) + { + ByteToHex(TempBytes[ByteIndex], Key); + } + + return Key; +} +#endif diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveTypes.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveTypes.cpp index bbf8028a6e9a..dc0c4dbf7ace 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveTypes.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimCurveTypes.cpp @@ -3,7 +3,7 @@ #include "Animation/AnimCurveTypes.h" #include "UObject/FrameworkObjectVersion.h" -DECLARE_CYCLE_STAT(TEXT("AnimSeq EvalCurveData"), STAT_AnimSeq_EvalCurveData, STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("EvalRawCurveData"), STAT_EvalRawCurveData, STATGROUP_Anim); ///////////////////////////////////////////////////// // FFloatCurve @@ -293,7 +293,7 @@ void FTransformCurve::Resize(float NewLength, bool bInsert/* whether insert or r void FRawCurveTracks::EvaluateCurveData( FBlendedCurve& Curves, float CurrentTime ) const { - SCOPE_CYCLE_COUNTER(STAT_AnimSeq_EvalCurveData); + SCOPE_CYCLE_COUNTER(STAT_EvalRawCurveData); if (Curves.NumValidCurveCount > 0) { // evaluate the curve data at the CurrentTime and add to Instance @@ -302,7 +302,8 @@ void FRawCurveTracks::EvaluateCurveData( FBlendedCurve& Curves, float CurrentTim const FFloatCurve& Curve = *CurveIter; if (Curves.IsEnabled(Curve.Name.UID)) { - Curves.Set(Curve.Name.UID, Curve.Evaluate(CurrentTime)); + float Value = Curve.Evaluate(CurrentTime); + Curves.Set(Curve.Name.UID, Value); } } } diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimNode_SubInstance.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimNode_SubInstance.cpp index 0d178c6e51d3..f0e361faf26c 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimNode_SubInstance.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimNode_SubInstance.cpp @@ -34,12 +34,7 @@ void FAnimNode_SubInstance::Update_AnyThread(const FAnimationUpdateContext& Cont { FAnimInstanceProxy& Proxy = InstanceToRun->GetProxyOnAnyThread(); - // Only update if we've not had a single-threaded update already - if(InstanceToRun->bNeedsUpdate) - { - Proxy.UpdateAnimation(); - } - + // First copy properties check(InstanceProperties.Num() == SubInstanceProperties.Num()); for(int32 PropIdx = 0; PropIdx < InstanceProperties.Num(); ++PropIdx) { @@ -58,6 +53,12 @@ void FAnimNode_SubInstance::Update_AnyThread(const FAnimationUpdateContext& Cont CallerProperty->CopyCompleteValue(DestPtr, SrcPtr); } } + + // Only update if we've not had a single-threaded update already + if(InstanceToRun->bNeedsUpdate) + { + Proxy.UpdateAnimation(); + } } } diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp index 1e832ca5f144..3d2c93082f92 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp @@ -21,6 +21,8 @@ #include "Animation/AnimNotifies/AnimNotify.h" #include "Animation/Rig.h" #include "Animation/AnimationSettings.h" +#include "Animation/AnimCurveCompressionCodec.h" +#include "Animation/AnimCurveCompressionSettings.h" #include "EditorFramework/AssetImportData.h" #include "Logging/TokenizedMessage.h" #include "Logging/MessageLog.h" @@ -41,6 +43,7 @@ CSV_DEFINE_CATEGORY(Animation, (!UE_BUILD_SHIPPING)); DECLARE_CYCLE_STAT(TEXT("AnimSeq GetBonePose"), STAT_AnimSeq_GetBonePose, STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("AnimSeq EvalCurveData"), STAT_AnimSeq_EvalCurveData, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("Build Anim Track Pairs"), STAT_BuildAnimTrackPairs, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("Extract Pose From Anim Data"), STAT_ExtractPoseFromAnimData, STATGROUP_Anim); @@ -277,9 +280,24 @@ void UAnimSequence::GetAssetRegistryTags(TArray& OutTags) con Super::GetAssetRegistryTags(OutTags); } +void UAnimSequence::AddReferencedObjects(UObject* This, FReferenceCollector& Collector) +{ + Super::AddReferencedObjects(This, Collector); + + UAnimSequence* AnimSeq = CastChecked(This); + Collector.AddReferencedObject(AnimSeq->CurveCompressionCodec); +} + int32 UAnimSequence::GetUncompressedRawSize() const { - return ((sizeof(FVector) + sizeof(FQuat) + sizeof(FVector)) * RawAnimationData.Num() * NumFrames); + int32 BoneRawSize = ((sizeof(FVector) + sizeof(FQuat) + sizeof(FVector)) * RawAnimationData.Num() * NumFrames); + int32 CurveRawSize = 0; + for (const FFloatCurve& Curve : RawCurveData.FloatCurves) + { + CurveRawSize += sizeof(FFloatCurve); + CurveRawSize += sizeof(FRichCurveKey) * Curve.FloatCurve.Keys.Num(); + } + return BoneRawSize + CurveRawSize; } int32 UAnimSequence::GetApproxRawSize() const @@ -293,13 +311,19 @@ int32 UAnimSequence::GetApproxRawSize() const sizeof( FQuat ) * RawTrack.RotKeys.Num() + sizeof( FVector ) * RawTrack.ScaleKeys.Num(); } + for (const FFloatCurve& Curve : RawCurveData.FloatCurves) + { + Total += sizeof(FFloatCurve); + Total += sizeof(FRichCurveKey) * Curve.FloatCurve.Keys.Num(); + } return Total; } int32 UAnimSequence::GetApproxCompressedSize() const { - const int32 Total = sizeof(int32)*CompressedTrackOffsets.Num() + CompressedByteStream.Num() + CompressedScaleOffsets.GetMemorySize() + sizeof(FCompressedSegment)*CompressedSegments.Num(); - return Total; + int32 BoneTotal = sizeof(int32)*CompressedTrackOffsets.Num() + CompressedByteStream.Num() + CompressedScaleOffsets.GetMemorySize() + sizeof(FCompressedSegment)*CompressedSegments.Num(); + int32 CurveTotal = CompressedCurveByteStream.Num(); + return BoneTotal + CurveTotal; } /** @@ -654,7 +678,10 @@ void UAnimSequence::PostLoad() if (USkeleton* CurrentSkeleton = GetSkeleton()) { - VerifyCurveNames(*CurrentSkeleton, USkeleton::AnimCurveMappingName, CompressedCurveData.FloatCurves); + for (FSmartName& CurveName : CompressedCurveNames) + { + CurrentSkeleton->VerifySmartName(USkeleton::AnimCurveMappingName, CurveName); + } #if WITH_EDITOR VerifyCurveNames(*CurrentSkeleton, USkeleton::AnimTrackCurveMappingName, RawCurveData.TransformCurves); @@ -666,18 +693,6 @@ void UAnimSequence::PostLoad() #endif } -#if WITH_EDITOR - - // Compressed curve flags are not authoritative (they come from the DDC). Keep them up to date with - // actual anim flags - for (FFloatCurve& Curve : RawCurveData.FloatCurves) - { - if (FAnimCurveBase* CompressedCurve = CompressedCurveData.GetCurveData(Curve.Name.UID)) - { - CompressedCurve->SetCurveTypeFlags(Curve.GetCurveTypeFlags()); - } - } -#endif // WITH_EDITOR AddAnimLoadingDebugEntry(TEXT("PostLoadEnd")); } @@ -845,11 +860,17 @@ void UAnimSequence::PostEditChangeProperty(FPropertyChangedEvent& PropertyChange } } + // @Todo fix me: This is temporary fix to make sure they always have compressed data if (RawAnimationData.Num() > 0 && (!IsCompressedDataValid() || bAdditiveSettingsChanged)) { PostProcessSequence(); } + + if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAnimSequence, CurveCompressionSettings)) + { + RequestSyncAnimRecompression(false); + } } void UAnimSequence::PostDuplicate(bool bDuplicateForPIE) @@ -1461,6 +1482,7 @@ void UAnimSequence::BuildPoseFromRawData(const TArray& In void UAnimSequence::GetBonePose(FCompactPose& OutPose, FBlendedCurve& OutCurve, const FAnimExtractContext& ExtractionContext, bool bForceUseRawData) const { SCOPE_CYCLE_COUNTER(STAT_AnimSeq_GetBonePose); + CSV_SCOPED_TIMING_STAT(Animation, AnimSeq_GetBonePose); const FBoneContainer& RequiredBones = OutPose.GetBoneContainer(); const bool bUseRawDataForPoseExtraction = bForceUseRawData || UseRawDataForPoseExtraction(RequiredBones); @@ -1736,6 +1758,31 @@ void UAnimSequence::GetBonePose(FCompactPose& OutPose, FBlendedCurve& OutCurve, } #if WITH_EDITORONLY_DATA +void UAnimSequence::UpdateCompressedCurveNames() +{ + // Copy our curve names to the compressed array + const int32 NumCurves = RawCurveData.FloatCurves.Num(); + CompressedCurveNames.Reset(NumCurves); + CompressedCurveNames.AddUninitialized(NumCurves); + for (int32 CurveIndex = 0; CurveIndex < NumCurves; ++CurveIndex) + { + const FFloatCurve& Curve = RawCurveData.FloatCurves[CurveIndex]; + CompressedCurveNames[CurveIndex] = Curve.Name; + } +} + +void UAnimSequence::UpdateCompressedCurveName(SmartName::UID_Type CurveUID, const struct FSmartName& NewCurveName) +{ + for (FSmartName& CurveName : CompressedCurveNames) + { + if (CurveName.UID == CurveUID) + { + CurveName = NewCurveName; + break; + } + } +} + int32 UAnimSequence::AddNewRawTrack(FName TrackName, FRawAnimSequenceTrack* TrackData) { const int32 SkeletonIndex = GetSkeleton() ? GetSkeleton()->GetReferenceSkeleton().FindBoneIndex(TrackName) : INDEX_NONE; @@ -2428,6 +2475,11 @@ void UAnimSequence::RequestAnimCompression(FRequestAnimCompressionParams Params) CompressionScheme = FAnimationUtils::GetDefaultAnimationCompressionAlgorithm(); } + if (CurveCompressionSettings == nullptr || !CurveCompressionSettings->AreSettingsValid()) + { + CurveCompressionSettings = FAnimationUtils::GetDefaultAnimationCurveCompressionSettings(); + } + if (!RawDataGuid.IsValid()) { RawDataGuid = GenerateGuidFromRawData(); @@ -2526,7 +2578,7 @@ void UAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData) Ar << CompressedSegments; Ar << CompressedTrackToSkeletonMapTable; - Ar << CompressedCurveData; + Ar << CompressedCurveNames; Ar << CompressedRawDataSize; Ar << CompressedNumFrames; @@ -2552,6 +2604,31 @@ void UAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData) // and then use the codecs to byte swap check(RotationCodec != NULL); ((AnimEncoding*)RotationCodec)->ByteSwapIn(*this, MemoryReader); + +#if WITH_EDITOR + if (bDDCData) + { + FString CurveCodecPath; + Ar << CurveCodecPath; + + CurveCompressionCodec = LoadObject(nullptr, *CurveCodecPath); + } + else +#else + check(!bDDCData); +#endif + { + UAnimCurveCompressionCodec* CurveCodec = nullptr; + Ar << CurveCodec; + CurveCompressionCodec = CurveCodec; + } + + int32 NumCurveBytes; + Ar << NumCurveBytes; + + CompressedCurveByteStream.Empty(NumCurveBytes); + CompressedCurveByteStream.AddUninitialized(NumCurveBytes); + Ar.Serialize(CompressedCurveByteStream.GetData(), NumCurveBytes); } else if (Ar.IsSaving() || Ar.IsCountingMemory()) { @@ -2578,6 +2655,24 @@ void UAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData) // Count compressed data. Ar.CountBytes(SerializedData.Num(), SerializedData.Num()); + +#if WITH_EDITOR + if (bDDCData) + { + FString CurveCodecPath = CurveCompressionCodec->GetPathName(); + Ar << CurveCodecPath; + } + else +#else + check(!bDDCData); +#endif + { + Ar << CurveCompressionCodec; + } + + int32 NumCurveBytes = CompressedCurveByteStream.Num(); + Ar << NumCurveBytes; + Ar.Serialize(CompressedCurveByteStream.GetData(), NumCurveBytes); } #if WITH_EDITOR @@ -2590,8 +2685,14 @@ void UAnimSequence::SerializeCompressedData(FArchive& Ar, bool bDDCData) { if(USkeleton* CurrentSkeleton = GetSkeleton()) { - VerifyCurveNames(*CurrentSkeleton, USkeleton::AnimCurveMappingName, CompressedCurveData.FloatCurves); - bUseRawDataOnly = !IsCompressedDataValid(); + // Refresh the compressed curve names since the IDs might have changed since + for (FSmartName& CurveName : CompressedCurveNames) + { + CurrentSkeleton->VerifySmartName(USkeleton::AnimCurveMappingName, CurveName); + } + + bUseRawDataOnly = !IsCompressedDataValid() || !IsCurveCompressedDataValid(); + ensureMsgf(!bUseRawDataOnly, TEXT("Anim Compression failed for Sequence '%s' Guid:%s CompressedDebugData:\n\tOriginal Anim:%s\n\tAdditiveSetting:%i\n\tCompression Scheme:%s\n\tRawDataGuid:%s"), *GetFullName(), *RawDataGuid.ToString(), @@ -3023,12 +3124,14 @@ void UAnimSequence::RecycleAnimSequence() CompressedSegments.Empty(0); SourceRawAnimationData.Empty(0); RawCurveData.Empty(); - CompressedCurveData.Empty(); + CompressedCurveByteStream.Empty(0); + CompressedCurveNames.Empty(0); AuthoredSyncMarkers.Empty(); UniqueMarkerNames.Empty(); Notifies.Empty(); AnimNotifyTracks.Empty(); CompressionScheme = nullptr; + CurveCompressionCodec = nullptr; TranslationCompressionFormat = RotationCompressionFormat = ScaleCompressionFormat = ACF_None; #endif // WITH_EDITORONLY_DATA } @@ -4993,28 +5096,56 @@ void UAnimSequence::RefreshCacheData() void UAnimSequence::EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData) const { - if (bUseRawDataOnly || bForceUseRawData) + SCOPE_CYCLE_COUNTER(STAT_AnimSeq_EvalCurveData); + + if (OutCurve.NumValidCurveCount == 0) { - Super::EvaluateCurveData(OutCurve, CurrentTime); + return; + } + + if (bUseRawDataOnly || bForceUseRawData || !IsCurveCompressedDataValid()) + { + Super::EvaluateCurveData(OutCurve, CurrentTime, bForceUseRawData); } else { - CompressedCurveData.EvaluateCurveData(OutCurve, CurrentTime); + CSV_SCOPED_TIMING_STAT(Animation, EvaluateCurveData); + CurveCompressionCodec->DecompressCurves(*this, OutCurve, CurrentTime); } } -const FRawCurveTracks& UAnimSequence::GetCurveData() const +float UAnimSequence::EvaluateCurveData(SmartName::UID_Type CurveUID, float CurrentTime, bool bForceUseRawData) const { - if (bUseRawDataOnly) + SCOPE_CYCLE_COUNTER(STAT_AnimSeq_EvalCurveData); + + if (bUseRawDataOnly || bForceUseRawData || !IsCurveCompressedDataValid()) { - return Super::GetCurveData(); + return Super::EvaluateCurveData(CurveUID, CurrentTime, bForceUseRawData); } else { - return CompressedCurveData; + return CurveCompressionCodec->DecompressCurve(*this, CurveUID, CurrentTime); } } +bool UAnimSequence::HasCurveData(SmartName::UID_Type CurveUID, bool bForceUseRawData) const +{ + if (bUseRawDataOnly || bForceUseRawData || !IsCurveCompressedDataValid()) + { + return Super::HasCurveData(CurveUID, bForceUseRawData); + } + + for (const FSmartName& CurveName : CompressedCurveNames) + { + if (CurveName.UID == CurveUID) + { + return true; + } + } + + return false; +} + void UAnimSequence::RefreshSyncMarkerDataFromAuthored() { #if WITH_EDITOR @@ -5048,7 +5179,7 @@ void UAnimSequence::AdvanceMarkerPhaseAsLeader(bool bLooping, float MoveDelta, c { check(MoveDelta != 0.f); const bool bPlayingForwards = MoveDelta > 0.f; - float CurrentMoveDelta = MoveDelta * RateScale; + float CurrentMoveDelta = MoveDelta; bool bOffsetInitialized = false; float MarkerTimeOffset = 0.f; @@ -5740,13 +5871,13 @@ void UAnimSequence::OnRawDataChanged() CompressedTrackOffsets.Empty(); CompressedScaleOffsets.Empty(); CompressedByteStream.Empty(); + CompressedSegments.Empty(); bUseRawDataOnly = true; RequestSyncAnimRecompression(false); //MDW - Once we have async anim ddc requests we should do this too //RequestDependentAnimRecompression(); } - #endif bool UAnimSequence::IsCompressedDataValid() const @@ -5755,6 +5886,29 @@ bool UAnimSequence::IsCompressedDataValid() const (TranslationCompressionFormat == ACF_Identity && RotationCompressionFormat == ACF_Identity && ScaleCompressionFormat == ACF_Identity); } +bool UAnimSequence::IsCurveCompressedDataValid() const +{ + if (CurveCompressionCodec == nullptr) + { + // No codec + return false; + } + + if (CompressedCurveByteStream.Num() == 0 && RawCurveData.FloatCurves.Num() != 0) + { + // No compressed data but we have raw data + if (!IsValidAdditive()) + { + return false; + } + + // Additive sequences can have raw curves that all end up being 0.0 (e.g. they 100% match the base sequence curves) + // in which case there will be no compressed curve data. + } + + return true; +} + /*----------------------------------------------------------------------------- AnimNotify& subclasses -----------------------------------------------------------------------------*/ diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequenceBase.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequenceBase.cpp index f8edee061771..0fcc8fce0a5f 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequenceBase.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequenceBase.cpp @@ -12,6 +12,7 @@ #include "UObject/FortniteMainBranchObjectVersion.h" DEFINE_LOG_CATEGORY(LogAnimMarkerSync); +CSV_DECLARE_CATEGORY_EXTERN(Animation); #define LOCTEXT_NAMESPACE "AnimSequenceBase" ///////////////////////////////////////////////////// @@ -720,9 +721,21 @@ void UAnimSequenceBase::RefreshParentAssetData() /** Add curve data to Instance at the time of CurrentTime **/ void UAnimSequenceBase::EvaluateCurveData(FBlendedCurve& OutCurve, float CurrentTime, bool bForceUseRawData) const { + CSV_SCOPED_TIMING_STAT(Animation, EvaluateCurveData); RawCurveData.EvaluateCurveData(OutCurve, CurrentTime); } +float UAnimSequenceBase::EvaluateCurveData(SmartName::UID_Type CurveUID, float CurrentTime, bool bForceUseRawData) const +{ + const FFloatCurve* Curve = (const FFloatCurve*)RawCurveData.GetCurveData(CurveUID, ERawCurveTrackTypes::RCT_Float); + return Curve != nullptr ? Curve->Evaluate(CurrentTime) : 0.0f; +} + +bool UAnimSequenceBase::HasCurveData(SmartName::UID_Type CurveUID, bool bForceUseRawData) const +{ + return RawCurveData.GetCurveData(CurveUID) != nullptr; +} + void UAnimSequenceBase::Serialize(FArchive& Ar) { Ar.UsingCustomVersion(FFrameworkObjectVersion::GUID); diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp index 63d8f0e0a8a3..b74d7e93f57f 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimationUtils.cpp @@ -16,6 +16,7 @@ #include "Animation/AnimCompress_Automatic.h" #include "Animation/AnimSet.h" #include "Animation/AnimationSettings.h" +#include "Animation/AnimCurveCompressionSettings.h" #include "AnimationCompression.h" #include "Engine/SkeletalMeshSocket.h" #include "AnimEncoding.h" @@ -2233,3 +2234,79 @@ void FAnimationUtils::TallyErrorsFromPerturbation( TrackError.MaxErrorInScaleDueToScale = MaxErrorS_DueToR; } } + +#if WITH_EDITOR +static UAnimCurveCompressionSettings* DefaultCurveCompressionSettings = nullptr; + +UAnimCurveCompressionSettings* FAnimationUtils::GetDefaultAnimationCurveCompressionSettings() +{ + if (DefaultCurveCompressionSettings == nullptr) + { + FConfigSection* AnimDefaultObjectSettingsSection = GConfig->GetSectionPrivate(TEXT("Animation.DefaultObjectSettings"), false, true, GEngineIni); + const FConfigValue* Value = AnimDefaultObjectSettingsSection != nullptr ? AnimDefaultObjectSettingsSection->Find(TEXT("CurveCompressionSettings")) : nullptr; + + if (Value != nullptr) + { + const FString& CurveCompressionSettingsName = Value->GetValue(); + DefaultCurveCompressionSettings = LoadObject(nullptr, *CurveCompressionSettingsName); + } + + if (DefaultCurveCompressionSettings == nullptr) + { + UE_LOG(LogAnimationCompression, Fatal, TEXT("Couldn't find default curve compression settings under '[Animation.DefaultObjectSettings]'")); + } + + // Force load the default settings and all its dependencies just in case it hasn't happened yet + bool bLoadDependencies = false; + if (DefaultCurveCompressionSettings->HasAnyFlags(RF_NeedLoad)) + { + DefaultCurveCompressionSettings->GetLinker()->Preload(DefaultCurveCompressionSettings); + bLoadDependencies = true; + } + + if (DefaultCurveCompressionSettings->HasAnyFlags(RF_NeedPostLoad)) + { + DefaultCurveCompressionSettings->ConditionalPostLoad(); + bLoadDependencies = true; + } + + if (bLoadDependencies) + { + TArray ObjectReferences; + FReferenceFinder(ObjectReferences, nullptr, false, true, false, true).FindReferences(DefaultCurveCompressionSettings); + + for (UObject* Dependency : ObjectReferences) + { + if (Dependency->HasAnyFlags(RF_NeedLoad)) + { + Dependency->GetLinker()->Preload(Dependency); + } + + if (Dependency->HasAnyFlags(RF_NeedPostLoad)) + { + Dependency->ConditionalPostLoad(); + } + } + } + + DefaultCurveCompressionSettings->AddToRoot(); + } + + return DefaultCurveCompressionSettings; +} + +bool FAnimationUtils::CompressAnimCurves(UAnimSequence& AnimSeq) +{ + // Clear any previous data we might have even if we end up failing to compress + AnimSeq.CompressedCurveByteStream.Empty(); + AnimSeq.CurveCompressionCodec = nullptr; + + if (AnimSeq.CurveCompressionSettings == nullptr || !AnimSeq.CurveCompressionSettings->AreSettingsValid()) + { + return false; + } + + check(AnimSeq.CurveCompressionSettings->AreSettingsValid()); + return AnimSeq.CurveCompressionSettings->Compress(AnimSeq); +} +#endif diff --git a/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp b/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp index 2459b81bdd56..7e734cafe7d7 100644 --- a/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp @@ -1574,6 +1574,7 @@ void USkinnedMeshComponent::RefreshSlaveComponents() MeshCompPtr->UpdateChildTransforms(EUpdateTransformFlags::OnlyUpdateIfUsingSocket); MeshCompPtr->MarkRenderDynamicDataDirty(); + MeshCompPtr->MarkRenderTransformDirty(); } } } diff --git a/Engine/Source/Runtime/Engine/Private/Curves/RichCurve.cpp b/Engine/Source/Runtime/Engine/Private/Curves/RichCurve.cpp index 2c1379f55d3e..0cccf3d99adc 100644 --- a/Engine/Source/Runtime/Engine/Private/Curves/RichCurve.cpp +++ b/Engine/Source/Runtime/Engine/Private/Curves/RichCurve.cpp @@ -1,7 +1,7 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "Curves/RichCurve.h" - +#include "Templates/Function.h" DECLARE_CYCLE_STAT(TEXT("RichCurve Eval"), STAT_RichCurve_Eval, STATGROUP_Engine); @@ -353,6 +353,25 @@ float FRichCurve::GetKeyValue(FKeyHandle KeyHandle) const return GetKey(KeyHandle).Value; } +bool FRichCurve::IsConstant(float Tolerance) const +{ + if (Keys.Num() <= 1) + { + return true; + } + + const FRichCurveKey& RefKey = Keys[0]; + for (const FRichCurveKey& Key : Keys) + { + if (!FMath::IsNearlyEqual(Key.Value, RefKey.Value, Tolerance)) + { + return false; + } + } + + return true; +} + TPair FRichCurve::GetKeyTimeValuePair(FKeyHandle KeyHandle) const { if (!IsKeyHandleValid(KeyHandle)) @@ -785,7 +804,7 @@ void FRichCurve::RemoveRedundantKeys(float Tolerance, float FirstKeyTime, float } /** Util to find float value on bezier defined by 4 control points */ -static float BezierInterp(float P0, float P1, float P2, float P3, float Alpha) +FORCEINLINE_DEBUGGABLE static float BezierInterp(float P0, float P1, float P2, float P3, float Alpha) { const float P01 = FMath::Lerp(P0, P1, Alpha); const float P12 = FMath::Lerp(P1, P2, Alpha); @@ -1084,3 +1103,801 @@ bool FRichCurve::operator==(const FRichCurve& Curve) const return true; } + +static ERichCurveCompressionFormat FindRichCurveCompressionFormat(const FRichCurve& Curve) +{ + if (Curve.Keys.Num() == 0) + { + return RCCF_Empty; + } + + if (Curve.IsConstant()) + { + return RCCF_Constant; + } + + const FRichCurveKey& RefKey = Curve.Keys[0]; + for (const FRichCurveKey& Key : Curve.Keys) + { + if (Key.InterpMode != RefKey.InterpMode) + { + return RCCF_Mixed; + } + } + + switch (RefKey.InterpMode) + { + case RCIM_Constant: + case RCIM_None: + default: + return RCCF_Constant; + case RCIM_Linear: + return RCCF_Linear; + case RCIM_Cubic: + return RCCF_Cubic; + } +} + +static ERichCurveKeyTimeCompressionFormat FindRichCurveKeyFormat(const FRichCurve& Curve, float ErrorThreshold, float SampleRate, ERichCurveCompressionFormat CompressionFormat) +{ + const int32 NumKeys = Curve.Keys.Num(); + if (NumKeys == 0 || CompressionFormat == RCCF_Constant || CompressionFormat == RCCF_Empty || ErrorThreshold <= 0.0f || SampleRate <= 0.0f) + { + return RCKTCF_float32; + } + + auto EvalForTwoKeys = [](const FRichCurveKey& Key1, float KeyTime1, const FRichCurveKey& Key2, float KeyTime2, float InTime) + { + const float Diff = KeyTime2 - KeyTime1; + + if (Diff > 0.f && Key1.InterpMode != RCIM_Constant) + { + const float Alpha = (InTime - KeyTime1) / Diff; + const float P0 = Key1.Value; + const float P3 = Key2.Value; + + if (Key1.InterpMode == RCIM_Linear) + { + return FMath::Lerp(P0, P3, Alpha); + } + else + { + const float OneThird = 1.0f / 3.0f; + const float P1 = P0 + (Key1.LeaveTangent * Diff * OneThird); + const float P2 = P3 - (Key2.ArriveTangent * Diff * OneThird); + + return BezierInterp(P0, P1, P2, P3, Alpha); + } + } + else + { + return Key1.Value; + } + }; + + auto DecayTime = [](const FRichCurveKey& Key, float MinTime, float DeltaTime, float InvDeltaTime, float QuantizationScale, float InvQuantizationScale) + { + // 0.0f -> 0, 1.0f -> 255 for 8 bits + const float NormalizedTime = FMath::Clamp((Key.Time - MinTime) * InvDeltaTime, 0.0f, 1.0f); + const float QuantizedTime = FMath::RoundHalfFromZero(NormalizedTime * QuantizationScale); + const float LossyNormalizedTime = QuantizedTime * InvQuantizationScale; + return (LossyNormalizedTime * DeltaTime) + MinTime; + }; + + const float MinTime = Curve.Keys[0].Time; + const float MaxTime = Curve.Keys.Last().Time; + const float DeltaTime = MaxTime - MinTime; + const float InvDeltaTime = 1.0f / DeltaTime; + const float SampleRateIncrement = 1.0f / SampleRate; + + // This is only acceptable if the maximum error is within a reasonable value + bool bFitsOn16Bits = true; + + int32 CurrentLossyKey = 0; + int32 CurrentRefKey = 0; + float CurrentTime = MinTime; + while (CurrentTime <= MaxTime && bFitsOn16Bits) + { + if (CurrentTime > Curve.Keys[CurrentRefKey + 1].Time) + { + CurrentRefKey++; + + if (CurrentRefKey >= NumKeys) + { + break; + } + } + + float LossyTime1_16; + float LossyTime2_16 = DecayTime(Curve.Keys[CurrentLossyKey + 1], MinTime, DeltaTime, InvDeltaTime, 65535.0f, 1.0f / 65535.0f); + if (CurrentTime > LossyTime2_16) + { + CurrentLossyKey++; + + if (CurrentLossyKey >= NumKeys) + { + break; + } + + LossyTime1_16 = LossyTime2_16; + LossyTime2_16 = DecayTime(Curve.Keys[CurrentLossyKey + 1], MinTime, DeltaTime, InvDeltaTime, 65535.0f, 1.0f / 65535.0f); + } + else + { + LossyTime1_16 = DecayTime(Curve.Keys[CurrentLossyKey], MinTime, DeltaTime, InvDeltaTime, 65535.0f, 1.0f / 65535.0f); + } + + const float Result_16 = EvalForTwoKeys(Curve.Keys[CurrentLossyKey], LossyTime1_16, Curve.Keys[CurrentLossyKey + 1], LossyTime2_16, CurrentTime); + const float Result_Ref = ::EvalForTwoKeys(Curve.Keys[CurrentRefKey], Curve.Keys[CurrentRefKey + 1], CurrentTime); + + const float Error_16 = FMath::Abs(Result_Ref - Result_16); + + bFitsOn16Bits &= Error_16 <= ErrorThreshold; + + CurrentTime += SampleRateIncrement; + } + + // In order to normalize time values, we need to store the MinTime and the DeltaTime with full precision + // This means we need 8 bytes of overhead + // If the number of keys is too small, the overhead is larger or equal to the space we save and isn't + // worth it. + + // For 8 bits, the formula is: 8 + (N * sizeof(uint8)) + // With 8 bits, 2 keys need 8 bytes with full precision and 10 bytes packed. No savings, not worth using. + // With 8 bits, 3 keys need 12 bytes with full precision and 11 bytes packed. We save 1 byte, worth using. + // For 16 bits, the formula is: 8 + (N * sizeof(uint16)) + // With 16 bits, 6 keys need 20 bytes with full precision and 20 bytes packed. No savings, not worth using. + // With 16 bits, 7 keys need 24 bytes with full precision and 22 bytes packed. We save 2 bytes, worth using. + // Alignment and padding must also be taken into account + + // Note: Support for storing key time on 8 bits was attempted but it was rarely selected and wasn't worth the complexity + + int32 SizeInterpMode = 0; + if (CompressionFormat == RCCF_Mixed) + { + SizeInterpMode += NumKeys * sizeof(uint8); + } + + const int32 SizeUInt16 = Align(Align(SizeInterpMode, sizeof(uint16)) + (NumKeys * sizeof(uint16)), sizeof(float)) + (2 * sizeof(float)); + const int32 SizeFloat32 = Align(SizeInterpMode, sizeof(float)) + NumKeys * sizeof(float); + + if (bFitsOn16Bits && SizeUInt16 < SizeFloat32) + { + return RCKTCF_uint16; + } + else + { + return RCKTCF_float32; + } +} + +void FRichCurve::CompressCurve(FCompressedRichCurve& OutCurve, float ErrorThreshold, float SampleRate) const +{ + ERichCurveCompressionFormat CompressionFormat = FindRichCurveCompressionFormat(*this); + OutCurve.CompressionFormat = CompressionFormat; + + ERichCurveKeyTimeCompressionFormat KeyFormat = FindRichCurveKeyFormat(*this, ErrorThreshold, SampleRate, CompressionFormat); + OutCurve.KeyTimeCompressionFormat = KeyFormat; + + OutCurve.PreInfinityExtrap = PreInfinityExtrap; + OutCurve.PostInfinityExtrap = PostInfinityExtrap; + + if (CompressionFormat == RCCF_Empty) + { + OutCurve.ConstantValueNumKeys.ConstantValue = DefaultValue; + OutCurve.CompressedKeys.Empty(); + } + else if (CompressionFormat == RCCF_Constant) + { + OutCurve.ConstantValueNumKeys.ConstantValue = Keys[0].Value; + OutCurve.CompressedKeys.Empty(); + } + else + { + int32 PackedDataSize = 0; + + // If we are mixed, we need to store the interp mode for every key, this data comes first following the header + // Next comes the quantized time values followed by the normalization range + // And the values/tangents follow last + + if (CompressionFormat == RCCF_Mixed) + { + PackedDataSize += Keys.Num() * sizeof(uint8); + } + + if (KeyFormat == RCKTCF_uint16) + { + PackedDataSize = Align(PackedDataSize, sizeof(uint16)); + PackedDataSize += Keys.Num() * sizeof(uint16); + PackedDataSize = Align(PackedDataSize, sizeof(float)); + PackedDataSize += 2 * sizeof(float); + } + else + { + check(KeyFormat == RCKTCF_float32); + PackedDataSize = Align(PackedDataSize, sizeof(float)); + PackedDataSize += Keys.Num() * sizeof(float); + } + + PackedDataSize += Keys.Num() * sizeof(float); // Key values + + // Key tangents + if (CompressionFormat == RCCF_Cubic) + { + PackedDataSize += Keys.Num() * 2 * sizeof(float); + } + else if (CompressionFormat == RCCF_Mixed) + { + for (const FRichCurveKey& Key : Keys) + { + if (Key.InterpMode == RCIM_Cubic) + { + PackedDataSize += 2 * sizeof(float); + } + } + } + + OutCurve.CompressedKeys.Empty(PackedDataSize); + OutCurve.CompressedKeys.AddUninitialized(PackedDataSize); + + int32 WriteOffset = 0; + uint8* BasePtr = OutCurve.CompressedKeys.GetData(); + + OutCurve.ConstantValueNumKeys.NumKeys = Keys.Num(); + + // Key interp modes + if (CompressionFormat == RCCF_Mixed) + { + uint8* InterpModes = BasePtr + WriteOffset; + WriteOffset += Keys.Num() * sizeof(uint8); + + for (const FRichCurveKey& Key : Keys) + { + if (Key.InterpMode == RCIM_Linear) + { + *InterpModes++ = (uint8)RCCF_Linear; + } + else if (Key.InterpMode == RCIM_Cubic) + { + *InterpModes++ = (uint8)RCCF_Cubic; + } + else + { + *InterpModes++ = (uint8)RCCF_Constant; + } + } + } + + // Key times + if (KeyFormat == RCKTCF_uint16) + { + const float MinTime = Keys[0].Time; + const float MaxTime = Keys.Last().Time; + const float DeltaTime = MaxTime - MinTime; + const float InvDeltaTime = 1.0f / DeltaTime; + + const int32 KeySize = sizeof(uint16); + + if (KeyFormat == RCKTCF_uint16) + { + WriteOffset = Align(WriteOffset, sizeof(uint16)); + } + + uint8* KeyTimes8 = BasePtr + WriteOffset; + uint16* KeyTimes16 = reinterpret_cast(KeyTimes8); + WriteOffset += Keys.Num() * KeySize; + + for (const FRichCurveKey& Key : Keys) + { + const float NormalizedTime = FMath::Clamp((Key.Time - MinTime) * InvDeltaTime, 0.0f, 1.0f); + const uint16 QuantizedTime = (uint16)FMath::RoundHalfFromZero(NormalizedTime * 65535.0f); + *KeyTimes16++ = QuantizedTime; + } + + WriteOffset = Align(WriteOffset, sizeof(float)); + float* RangeData = reinterpret_cast(BasePtr + WriteOffset); + WriteOffset += 2 * sizeof(float); + + RangeData[0] = MinTime; + RangeData[1] = DeltaTime; + } + else + { + WriteOffset = Align(WriteOffset, sizeof(float)); + float* KeyTimes = reinterpret_cast(BasePtr + WriteOffset); + WriteOffset += Keys.Num() * sizeof(float); + + for (const FRichCurveKey& Key : Keys) + { + *KeyTimes++ = Key.Time; + } + } + + // Key values and tangents + float* KeyData = reinterpret_cast(BasePtr + WriteOffset); + for (const FRichCurveKey& Key : Keys) + { + *KeyData++ = Key.Value; + + if (Key.InterpMode == RCIM_Cubic) + { + check(CompressionFormat == RCCF_Cubic || CompressionFormat == RCCF_Mixed); + *KeyData++ = Key.ArriveTangent; + *KeyData++ = Key.LeaveTangent; + } + } + + check(((uint8*)KeyData - BasePtr) == PackedDataSize); + } +} + +struct Quantized16BitKeyTimeAdapter +{ + static constexpr float QuantizationScale = 1.0f / 65535.0f; + static constexpr int32 KeySize = sizeof(uint16); + static constexpr int32 RangeDataSize = 2 * sizeof(float); + + using KeyTimeType = uint16; + + const uint16* KeyTimes; + float MinTime; + float DeltaTime; + int32 KeyDataOffset; + + Quantized16BitKeyTimeAdapter(const uint8* BasePtr, int32 KeyTimesOffset, int32 NumKeys) + { + const int32 RangeDataOffset = Align(KeyTimesOffset + (NumKeys * sizeof(uint16)), sizeof(float)); + KeyDataOffset = RangeDataOffset + RangeDataSize; + const float* RangeData = reinterpret_cast(BasePtr + RangeDataOffset); + + KeyTimes = reinterpret_cast(BasePtr + KeyTimesOffset); + MinTime = RangeData[0]; + DeltaTime = RangeData[1]; + } + + float GetTime(int32 KeyIndex) const + { + const float KeyNormalizedTime = KeyTimes[KeyIndex] * QuantizationScale; + return (KeyNormalizedTime * DeltaTime) + MinTime; + }; +}; + +struct Float32BitKeyTimeAdapter +{ + static constexpr int32 KeySize = sizeof(float); + static constexpr int32 RangeDataSize = 0; + + using KeyTimeType = float; + + const float* KeyTimes; + int32 KeyDataOffset; + + Float32BitKeyTimeAdapter(const uint8* BasePtr, int32 KeyTimesOffset, int32 NumKeys) + { + KeyTimes = reinterpret_cast(BasePtr + KeyTimesOffset); + KeyDataOffset = Align(KeyTimesOffset + (NumKeys * sizeof(float)), sizeof(float)); + } + + constexpr float GetTime(int32 KeyIndex) const + { + return KeyTimes[KeyIndex]; + }; +}; + +using KeyDataHandle = int32; + +template +struct UniformKeyDataAdapter +{ + const float* KeyData; + + template + + constexpr UniformKeyDataAdapter(const uint8* BasePtr, const KeyTimeAdapterType& KeyTimeAdapter) + : KeyData(reinterpret_cast(BasePtr + KeyTimeAdapter.KeyDataOffset)) + {} + + constexpr KeyDataHandle GetKeyDataHandle(int32 KeyIndexToQuery) const + { + return Format == RCCF_Cubic ? (KeyIndexToQuery * 3) : KeyIndexToQuery; + }; + + constexpr float GetKeyValue(KeyDataHandle Handle) const + { + return KeyData[Handle]; + } + + constexpr float GetKeyArriveTangent(KeyDataHandle Handle) const + { + return KeyData[Handle + 1]; + } + + constexpr float GetKeyLeaveTangent(KeyDataHandle Handle) const + { + return KeyData[Handle + 2]; + } + + constexpr ERichCurveCompressionFormat GetKeyInterpMode(int32 KeyIndex) const + { + return Format; + } +}; + +struct MixedKeyDataAdapter +{ + const uint8* InterpModes; + const float* KeyData; + + template + MixedKeyDataAdapter(const uint8* BasePtr, int32 InterpModesOffset, const KeyTimeAdapterType& KeyTimeAdapter) + { + InterpModes = BasePtr + InterpModesOffset; + KeyData = reinterpret_cast(BasePtr + KeyTimeAdapter.KeyDataOffset); + } + + KeyDataHandle GetKeyDataHandle(int32 KeyIndexToQuery) const + { + int32 Offset = 0; + for (int32 KeyIndex = 0; KeyIndex < KeyIndexToQuery; ++KeyIndex) + { + Offset += InterpModes[KeyIndex] == RCCF_Cubic ? 3 : 1; + } + + return Offset; + }; + + constexpr float GetKeyValue(KeyDataHandle Handle) const + { + return KeyData[Handle]; + } + + constexpr float GetKeyArriveTangent(KeyDataHandle Handle) const + { + return KeyData[Handle + 1]; + } + + constexpr float GetKeyLeaveTangent(KeyDataHandle Handle) const + { + return KeyData[Handle + 2]; + } + + constexpr ERichCurveCompressionFormat GetKeyInterpMode(int32 KeyIndex) const + { + return (ERichCurveCompressionFormat)InterpModes[KeyIndex]; + } +}; + +static void CycleTime(float MinTime, float MaxTime, float& InTime, int& CycleCount) +{ + float InitTime = InTime; + float Duration = MaxTime - MinTime; + + if (InTime > MaxTime) + { + CycleCount = FMath::FloorToInt((MaxTime-InTime)/Duration); + InTime = InTime + Duration*CycleCount; + } + else if (InTime < MinTime) + { + CycleCount = FMath::FloorToInt((InTime-MinTime)/Duration); + InTime = InTime - Duration*CycleCount; + } + + if (InTime == MaxTime && InitTime < MinTime) + { + InTime = MinTime; + } + + if (InTime == MinTime && InitTime > MaxTime) + { + InTime = MaxTime; + } + + CycleCount = FMath::Abs(CycleCount); +} + +template +static float RemapTimeValue(float InTime, const KeyTimeAdapterType& KeyTimeAdapter, const KeyDataAdapterType& KeyDataAdapter, int32 NumKeys, ERichCurveExtrapolation InfinityExtrap, int32 KeyIndex0, int32 KeyIndex1, float& CycleValueOffset) +{ + // For Pre-infinity, key0 and key1 are the actual key 0 and key 1 + // For Post-infinity, key0 and key1 are the last and second to last key + const float MinTime = KeyTimeAdapter.GetTime(0); + const float MaxTime = KeyTimeAdapter.GetTime(NumKeys - 1); + + int CycleCount = 0; + CycleTime(MinTime, MaxTime, InTime, CycleCount); + + if (InfinityExtrap == RCCE_CycleWithOffset) + { + const KeyDataHandle ValueHandle0 = KeyDataAdapter.GetKeyDataHandle(KeyIndex0); + const float KeyValue0 = KeyDataAdapter.GetKeyValue(ValueHandle0); + const KeyDataHandle ValueHandle1 = KeyDataAdapter.GetKeyDataHandle(KeyIndex1); + const float KeyValue1 = KeyDataAdapter.GetKeyValue(ValueHandle1); + + const float DV = KeyValue0 - KeyValue1; + CycleValueOffset = DV * CycleCount; + } + else if (InfinityExtrap == RCCE_Oscillate) + { + if (CycleCount % 2 == 1) + { + InTime = MinTime + (MaxTime - InTime); + } + } + + return InTime; +} + +template +static float InterpEvalExtrapolate(float InTime, const KeyTimeAdapterType& KeyTimeAdapter, const KeyDataAdapterType& KeyDataAdapter, ERichCurveExtrapolation InfinityExtrap, int32 KeyIndex0, int32 KeyIndex1, float KeyTime0) +{ + // For Pre-infinity, key0 and key1 are the actual key 0 and key 1 + // For Post-infinity, key0 and key1 are the last and second to last key + const KeyDataHandle ValueHandle0 = KeyDataAdapter.GetKeyDataHandle(KeyIndex0); + const float KeyValue0 = KeyDataAdapter.GetKeyValue(ValueHandle0); + + if (InfinityExtrap == RCCE_Linear) + { + const float KeyTime1 = KeyTimeAdapter.GetTime(KeyIndex1); + const float DT = KeyTime1 - KeyTime0; + + if (FMath::IsNearlyZero(DT)) + { + return KeyValue0; + } + else + { + const KeyDataHandle ValueHandle1 = KeyDataAdapter.GetKeyDataHandle(KeyIndex1); + const float KeyValue1 = KeyDataAdapter.GetKeyValue(ValueHandle1); + const float DV = KeyValue1 - KeyValue0; + const float Slope = DV / DT; + + return Slope * (InTime - KeyTime0) + KeyValue0; + } + } + else + { + // Otherwise if constant or in a cycle or oscillate, always use the first key value + return KeyValue0; + } +} + +// Each template permutation is only called from one place, force inline it to avoid issues +template +FORCEINLINE_DEBUGGABLE static float InterpEval(float InTime, const KeyTimeAdapterType& KeyTimeAdapter, const KeyDataAdapterType& KeyDataAdapter, int32 NumKeys, ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap) +{ + float CycleValueOffset = 0.0; + + const float FirstKeyTime = KeyTimeAdapter.GetTime(0); + if (InTime <= FirstKeyTime) + { + if (PreInfinityExtrap != RCCE_Linear && PreInfinityExtrap != RCCE_Constant) + { + InTime = RemapTimeValue(InTime, KeyTimeAdapter, KeyDataAdapter, NumKeys, PreInfinityExtrap, 0, NumKeys - 1, CycleValueOffset); + } + else + { + return InterpEvalExtrapolate(InTime, KeyTimeAdapter, KeyDataAdapter, PreInfinityExtrap, 0, 1, FirstKeyTime); + } + } + + const float LastKeyTime = KeyTimeAdapter.GetTime(NumKeys - 1); + if (InTime >= LastKeyTime) + { + if (PostInfinityExtrap != RCCE_Linear && PostInfinityExtrap != RCCE_Constant) + { + InTime = RemapTimeValue(InTime, KeyTimeAdapter, KeyDataAdapter, NumKeys, PostInfinityExtrap, NumKeys - 1, 0, CycleValueOffset); + } + else + { + return InterpEvalExtrapolate(InTime, KeyTimeAdapter, KeyDataAdapter, PostInfinityExtrap, NumKeys - 1, NumKeys - 2, LastKeyTime); + } + } + + // perform a lower bound to get the second of the interpolation nodes + int32 First = 1; + int32 Last = NumKeys - 1; + int32 Count = Last - First; + + while (Count > 0) + { + const int32 Step = Count / 2; + const int32 Middle = First + Step; + + // TODO: Can we do the search with integers? In order to do so, we need to Floorf(..) the key times + const float KeyTime = KeyTimeAdapter.GetTime(Middle); + if (InTime >= KeyTime) + { + First = Middle + 1; + Count -= Step + 1; + } + else + { + Count = Step; + } + } + + const float KeyTime0 = KeyTimeAdapter.GetTime(First - 1); + const float KeyTime1 = KeyTimeAdapter.GetTime(First); + const float Diff = KeyTime1 - KeyTime0; + + const KeyDataHandle KeyValueHandle0 = KeyDataAdapter.GetKeyDataHandle(First - 1); + const float KeyValue0 = KeyDataAdapter.GetKeyValue(KeyValueHandle0); + + // const value here allows the code to be stripped statically if the data is uniform + // which it is most of the time + const ERichCurveCompressionFormat KeyInterpMode0 = KeyDataAdapter.GetKeyInterpMode(First - 1); + float InterpolatedValue; + if (Diff > 0.0f && KeyInterpMode0 != RCCF_Constant) + { + const KeyDataHandle KeyValueHandle1 = KeyDataAdapter.GetKeyDataHandle(First); + const float KeyValue1 = KeyDataAdapter.GetKeyValue(KeyValueHandle1); + + const float Alpha = (InTime - KeyTime0) / Diff; + const float P0 = KeyValue0; + const float P3 = KeyValue1; + + if (KeyInterpMode0 == RCCF_Linear) + { + InterpolatedValue = FMath::Lerp(P0, P3, Alpha); + } + else + { + const float OneThird = 1.0f / 3.0f; + const float ScaledDiff = Diff * OneThird; + const float KeyLeaveTangent0 = KeyDataAdapter.GetKeyLeaveTangent(KeyValueHandle0); + const float KeyArriveTangent1 = KeyDataAdapter.GetKeyArriveTangent(KeyValueHandle1); + const float P1 = P0 + (KeyLeaveTangent0 * ScaledDiff); + const float P2 = P3 - (KeyArriveTangent1 * ScaledDiff); + + InterpolatedValue = BezierInterp(P0, P1, P2, P3, Alpha); + } + } + else + { + InterpolatedValue = KeyValue0; + } + + return InterpolatedValue + CycleValueOffset; +} + +static TFunction InterpEvalMap[5][2] +{ + // RCCF_Empty + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + return ConstantValueNumKeys.ConstantValue == MAX_flt ? InDefaultValue : ConstantValueNumKeys.ConstantValue; + }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + return ConstantValueNumKeys.ConstantValue == MAX_flt ? InDefaultValue : ConstantValueNumKeys.ConstantValue; + }, + }, + // RCCF_Constant + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) { return ConstantValueNumKeys.ConstantValue; }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) { return ConstantValueNumKeys.ConstantValue; }, + }, + // RCCF_Linear + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 KeyTimesOffset = 0; + Quantized16BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + UniformKeyDataAdapter KeyDataAdapter(CompressedKeys, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 KeyTimesOffset = 0; + Float32BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + UniformKeyDataAdapter KeyDataAdapter(CompressedKeys, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + }, + // RCCF_Cubic + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 KeyTimesOffset = 0; + Quantized16BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + UniformKeyDataAdapter KeyDataAdapter(CompressedKeys, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 KeyTimesOffset = 0; + Float32BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + UniformKeyDataAdapter KeyDataAdapter(CompressedKeys, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + }, + // RCCF_Mixed + { + // RCKTCF_uint16 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 InterpModesOffset = 0; + const int32 KeyTimesOffset = InterpModesOffset + Align(ConstantValueNumKeys.NumKeys * sizeof(uint8), sizeof(uint16)); + Quantized16BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + MixedKeyDataAdapter KeyDataAdapter(CompressedKeys, InterpModesOffset, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + // RCKTCF_float32 + [](ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) + { + const int32 InterpModesOffset = 0; + const int32 KeyTimesOffset = InterpModesOffset + Align(ConstantValueNumKeys.NumKeys * sizeof(uint8), sizeof(float)); + Float32BitKeyTimeAdapter KeyTimeAdapter(CompressedKeys, KeyTimesOffset, ConstantValueNumKeys.NumKeys); + MixedKeyDataAdapter KeyDataAdapter(CompressedKeys, InterpModesOffset, KeyTimeAdapter); + return InterpEval(InTime, KeyTimeAdapter, KeyDataAdapter, ConstantValueNumKeys.NumKeys, PreInfinityExtrap, PostInfinityExtrap); + }, + }, +}; + +float FCompressedRichCurve::Eval(float InTime, float InDefaultValue) const +{ + SCOPE_CYCLE_COUNTER(STAT_RichCurve_Eval); + + // Dynamic dispatch into a template optimized code path + const float Value = InterpEvalMap[CompressionFormat][KeyTimeCompressionFormat](PreInfinityExtrap, PostInfinityExtrap, ConstantValueNumKeys, CompressedKeys.GetData(), InTime, InDefaultValue); + return Value; +} + +float FCompressedRichCurve::StaticEval(ERichCurveCompressionFormat CompressionFormat, ERichCurveKeyTimeCompressionFormat KeyTimeCompressionFormat, ERichCurveExtrapolation PreInfinityExtrap, ERichCurveExtrapolation PostInfinityExtrap, FCompressedRichCurve::TConstantValueNumKeys ConstantValueNumKeys, const uint8* CompressedKeys, float InTime, float InDefaultValue) +{ + SCOPE_CYCLE_COUNTER(STAT_RichCurve_Eval); + + // Dynamic dispatch into a template optimized code path + const float Value = InterpEvalMap[CompressionFormat][KeyTimeCompressionFormat](PreInfinityExtrap, PostInfinityExtrap, ConstantValueNumKeys, CompressedKeys, InTime, InDefaultValue); + return Value; +} + +bool FCompressedRichCurve::Serialize(FArchive& Ar) +{ + Ar << CompressionFormat; + Ar << KeyTimeCompressionFormat; + Ar << PreInfinityExtrap; + Ar << PostInfinityExtrap; + + int32 NumKeysOrConstant = ConstantValueNumKeys.NumKeys; + Ar << NumKeysOrConstant; + ConstantValueNumKeys.NumKeys = NumKeysOrConstant; + + if (Ar.IsLoading()) + { + int32 NumBytes; + Ar << NumBytes; + + CompressedKeys.Empty(NumBytes); + CompressedKeys.AddUninitialized(NumBytes); + Ar.Serialize(CompressedKeys.GetData(), NumBytes); + } + else + { + int32 NumBytes = CompressedKeys.Num(); + Ar << NumBytes; + Ar.Serialize(CompressedKeys.GetData(), NumBytes); + } + + return true; +} + +bool FCompressedRichCurve::operator==(const FCompressedRichCurve& Other) const +{ + return CompressionFormat == Other.CompressionFormat + && KeyTimeCompressionFormat == Other.KeyTimeCompressionFormat + && PreInfinityExtrap == Other.PreInfinityExtrap + && PostInfinityExtrap == Other.PostInfinityExtrap + && ConstantValueNumKeys.NumKeys == Other.ConstantValueNumKeys.NumKeys + && CompressedKeys == Other.CompressedKeys; +} diff --git a/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp b/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp index a6c40db66cf6..9303ccf87908 100644 --- a/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp +++ b/Engine/Source/Runtime/Engine/Private/SkeletalMesh.cpp @@ -3079,6 +3079,7 @@ void USkeletalMesh::GetMappableNodeData(TArray& OutNames, TArrayCopyConstraintParamsFrom(&OutConstraintsetup->DefaultInstance); ConInst->ConstraintIndex = ConstraintIdx; // Set the ConstraintIndex property in the ConstraintInstance. #if WITH_EDITOR - if(GetWorld()->IsGameWorld()) + UWorld* World = GetWorld(); + if(World && World->IsGameWorld()) { //In the editor we may be currently editing the physics asset, so make sure to use the default profile OutConstraintsetup->ApplyConstraintProfile(NAME_None, *ConInst, /*bDefaultIfNotFound=*/true); diff --git a/Engine/Source/Runtime/Engine/Private/SkeletalRenderGPUSkin.cpp b/Engine/Source/Runtime/Engine/Private/SkeletalRenderGPUSkin.cpp index c4d204b13411..7226c85539db 100644 --- a/Engine/Source/Runtime/Engine/Private/SkeletalRenderGPUSkin.cpp +++ b/Engine/Source/Runtime/Engine/Private/SkeletalRenderGPUSkin.cpp @@ -155,7 +155,7 @@ void FSkeletalMeshObjectGPUSkin::InitResources(USkinnedMeshComponent* InMeshComp FSkeletalMeshObjectLOD& SkelLOD = LODs[LODIndex]; // Skip LODs that have their render data stripped - if (SkelLOD.SkelMeshRenderData->LODRenderData[LODIndex].GetNumVertices() > 0) + if (SkelLOD.SkelMeshRenderData && SkelLOD.SkelMeshRenderData->LODRenderData.IsValidIndex(LODIndex) && SkelLOD.SkelMeshRenderData->LODRenderData[LODIndex].GetNumVertices() > 0) { const FSkelMeshObjectLODInfo& MeshLODInfo = LODInfo[LODIndex]; @@ -177,7 +177,7 @@ void FSkeletalMeshObjectGPUSkin::ReleaseResources() FSkeletalMeshObjectLOD& SkelLOD = LODs[LODIndex]; // Skip LODs that have their render data stripped - if (SkelLOD.SkelMeshRenderData->LODRenderData[LODIndex].GetNumVertices() > 0) + if (SkelLOD.SkelMeshRenderData && SkelLOD.SkelMeshRenderData->LODRenderData.IsValidIndex(LODIndex) && SkelLOD.SkelMeshRenderData->LODRenderData[LODIndex].GetNumVertices() > 0) { SkelLOD.ReleaseResources(); } diff --git a/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp b/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp index 90c46561f73f..85256e0b9bc3 100644 --- a/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp +++ b/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp @@ -5341,7 +5341,7 @@ bool UEngine::HandleListAnimsCommand(const TCHAR* Cmd, FOutputDevice& Ar) NumKeys = AnimSeq->GetRawNumberOfFrames(); SequenceLength = AnimSeq->GetPlayLength(); RateScale = AnimSeq->RateScale; - NumCurves = AnimSeq->CompressedCurveData.FloatCurves.Num(); + NumCurves = AnimSeq->RawCurveData.FloatCurves.Num(); TranslationFormat = FAnimationUtils::GetAnimationCompressionFormatString(AnimSeq->TranslationCompressionFormat); RotationFormat = FAnimationUtils::GetAnimationCompressionFormatString(AnimSeq->RotationCompressionFormat); diff --git a/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h b/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h index df634d5f5f6e..6b378aa0def6 100644 --- a/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h +++ b/Engine/Source/Runtime/Engine/Public/Animation/AnimTypes.h @@ -638,6 +638,12 @@ struct FAnimKeyHelper return (NumKeys > 1) ? Length / (float)(NumKeys - 1) : MINIMUM_ANIMATION_LENGTH; } + // Returns the FPS for the given length and number of keys + float KeysPerSecond() const + { + return NumKeys > 0 ? (float(NumKeys - 1) / Length) : 0.0f; + } + int32 LastKey() const { return (NumKeys > 1) ? NumKeys - 1 : 0; diff --git a/Engine/Source/Runtime/Engine/Public/AnimationUtils.h b/Engine/Source/Runtime/Engine/Public/AnimationUtils.h index c526f43ce01a..fdd2a39978c5 100644 --- a/Engine/Source/Runtime/Engine/Public/AnimationUtils.h +++ b/Engine/Source/Runtime/Engine/Public/AnimationUtils.h @@ -276,4 +276,16 @@ public: */ ENGINE_API static UAnimCompress* GetDefaultAnimationCompressionAlgorithm(); +#if WITH_EDITOR + /** Returns the default animation curve compression settings, can never by null. */ + ENGINE_API static UAnimCurveCompressionSettings* GetDefaultAnimationCurveCompressionSettings(); + + /** + * Compresses the animation curves within a sequence with the chosen settings. + * Note: This modifies the sequence. + * + * @return Returns true on success, false it we fail to compress + */ + ENGINE_API static bool CompressAnimCurves(UAnimSequence& AnimSeq); +#endif }; diff --git a/Engine/Source/Runtime/MediaUtils/Private/AppMediaTimeSource.cpp b/Engine/Source/Runtime/MediaUtils/Private/AppMediaTimeSource.cpp index 67023165a58c..aa1a2d49bef9 100644 --- a/Engine/Source/Runtime/MediaUtils/Private/AppMediaTimeSource.cpp +++ b/Engine/Source/Runtime/MediaUtils/Private/AppMediaTimeSource.cpp @@ -6,6 +6,8 @@ #include "Misc/App.h" #include "Misc/Timespan.h" +/** Declares a log category for this module. */ +DECLARE_LOG_CATEGORY_CLASS(LogMediaTimeSource, Log, All); /* IMediaTimeSource interface *****************************************************************************/ @@ -15,7 +17,7 @@ FTimespan FAppMediaTimeSource::GetTimecode() const double CurrentTime = FApp::GetCurrentTime(); const FTimespan Timecode = FTimespan::FromSeconds(CurrentTime); - UE_LOG(LogMediaUtils, VeryVerbose, TEXT("AppMediaTimeSource: Time %.10f, Delta %.10f, Timecode %s"), CurrentTime, FApp::GetDeltaTime(), *Timecode.ToString(TEXT("%h:%m:%s.%t"))); + UE_LOG(LogMediaTimeSource, VeryVerbose, TEXT("AppMediaTimeSource: Time %.10f, Delta %.10f, Timecode %s"), CurrentTime, FApp::GetDeltaTime(), *Timecode.ToString(TEXT("%h:%m:%s.%t"))); return Timecode; } diff --git a/Engine/Source/Runtime/NavigationSystem/Private/NavAreas/NavArea.cpp b/Engine/Source/Runtime/NavigationSystem/Private/NavAreas/NavArea.cpp index 6fdd6616fb37..3959635227c9 100644 --- a/Engine/Source/Runtime/NavigationSystem/Private/NavAreas/NavArea.cpp +++ b/Engine/Source/Runtime/NavigationSystem/Private/NavAreas/NavArea.cpp @@ -44,7 +44,8 @@ void UNavArea::PostInitProperties() void UNavArea::RegisterArea() { - if (HasAnyFlags(RF_ClassDefaultObject) + if (HasAnyFlags(RF_ClassDefaultObject) && + !HasAnyFlags(RF_NeedInitialization) // Don't register BP Area that has still not finished loaded their properties, it was also try again to register later via UNavArea::PostLoad() #if WITH_HOT_RELOAD && !GIsHotReload #endif // WITH_HOT_RELOAD diff --git a/Engine/Source/Runtime/RHI/Public/RHIShaderPlatformDefinitions.inl b/Engine/Source/Runtime/RHI/Public/RHIShaderPlatformDefinitions.inl index b19975a48134..b11de9fe8db6 100644 --- a/Engine/Source/Runtime/RHI/Public/RHIShaderPlatformDefinitions.inl +++ b/Engine/Source/Runtime/RHI/Public/RHIShaderPlatformDefinitions.inl @@ -1,4 +1,4 @@ -// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. /*============================================================================= RHIShaderPlatformDefinitions.h: Localizable Friendly Names for Shader Platforms diff --git a/Engine/Source/Runtime/UMG/Private/Components/WidgetComponent.cpp b/Engine/Source/Runtime/UMG/Private/Components/WidgetComponent.cpp index 96ec20455b59..3fd6efd5a833 100644 --- a/Engine/Source/Runtime/UMG/Private/Components/WidgetComponent.cpp +++ b/Engine/Source/Runtime/UMG/Private/Components/WidgetComponent.cpp @@ -649,7 +649,10 @@ void UWidgetComponent::UpdateMaterialInstance() UMaterialInterface* BaseMaterial = GetMaterial(0); MaterialInstance = UMaterialInstanceDynamic::Create(BaseMaterial, this); - + if (MaterialInstance) + { + MaterialInstance->AddToCluster(this); + } UpdateMaterialInstanceParameters(); } diff --git a/Engine/Source/ThirdParty/SPL/SPL.Build.cs b/Engine/Source/ThirdParty/SPL/SPL.Build.cs deleted file mode 100644 index 2dcdb4274afa..000000000000 --- a/Engine/Source/ThirdParty/SPL/SPL.Build.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -using UnrealBuildTool; - -public class SPL : ModuleRules -{ - public SPL(ReadOnlyTargetRules Target) : base(Target) - { - Type = ModuleType.External; - - bOutputPubliclyDistributable = true; - - string SPLDirectory = Target.UEThirdPartySourceDirectory + "NotForLicensees/SPL/"; - string SPLLibPath = SPLDirectory; - PublicIncludePaths.Add(SPLDirectory + "Public/Include"); - PublicIncludePaths.Add(SPLDirectory + "Public/json"); - - if (Target.Platform == UnrealTargetPlatform.Win64) - { - SPLLibPath = SPLLibPath + "lib/win64/vs" + Target.WindowsPlatform.GetVisualStudioCompilerVersionName() + "/"; - PublicLibraryPaths.Add(SPLLibPath ); - - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - PublicAdditionalLibraries.Add("SPLd.lib"); - } - else - { - PublicAdditionalLibraries.Add("SPL.lib"); - } - } - else if (Target.Platform == UnrealTargetPlatform.Mac) - { - string LibPath = SPLDirectory + "lib/Mac/"; - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - PublicAdditionalLibraries.Add(LibPath + "libspl_debug.a"); - } - else - { - PublicAdditionalLibraries.Add(LibPath + "libspl.a"); - } - - } - } -} - diff --git a/Engine/Source/ThirdParty/SPL/SPL.tps b/Engine/Source/ThirdParty/SPL/SPL.tps deleted file mode 100644 index 780670f5b662..000000000000 --- a/Engine/Source/ThirdParty/SPL/SPL.tps +++ /dev/null @@ -1,16 +0,0 @@ - - - - /Engine/Source/ThirdParty/NotForLicensees/SPL/ - - - - - - - -Redirect: ../Simplygon/Simplygon.tps -Notes: This was added by the Simplygon partners as part of the Simplygon integration. - - - \ No newline at end of file diff --git a/Engine/Source/ThirdParty/SSF/SSF.Build.cs b/Engine/Source/ThirdParty/SSF/SSF.Build.cs deleted file mode 100644 index a3b2b286a434..000000000000 --- a/Engine/Source/ThirdParty/SSF/SSF.Build.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -using UnrealBuildTool; - -public class SSF : ModuleRules -{ - public SSF(ReadOnlyTargetRules Target) : base(Target) - { - Type = ModuleType.External; - - bOutputPubliclyDistributable = true; - - string SSFDirectory = Target.UEThirdPartySourceDirectory + "NotForLicensees/SSF/"; - string SSFLibPath = SSFDirectory; - PublicIncludePaths.Add(SSFDirectory + "Public"); - - if (Target.Platform == UnrealTargetPlatform.Win64) - { - SSFLibPath = SSFLibPath + "lib/win64/vs" + Target.WindowsPlatform.GetVisualStudioCompilerVersionName() + "/"; - PublicLibraryPaths.Add(SSFLibPath); - - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - PublicAdditionalLibraries.Add("SSFd.lib"); - } - else - { - PublicAdditionalLibraries.Add("SSF.lib"); - } - } - else if (Target.Platform == UnrealTargetPlatform.Mac) - { - string LibPath = SSFDirectory + "lib/Mac/"; - if (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) - { - PublicAdditionalLibraries.Add(LibPath + "libssf_debug.a"); - } - else - { - PublicAdditionalLibraries.Add(LibPath + "libssf.a"); - } - - } - } -} - diff --git a/Engine/Source/ThirdParty/SSF/SSF.tps b/Engine/Source/ThirdParty/SSF/SSF.tps deleted file mode 100644 index e1bba6c5c233..000000000000 --- a/Engine/Source/ThirdParty/SSF/SSF.tps +++ /dev/null @@ -1,7 +0,0 @@ - - - -Redirect: ../NotForLicensees/Simplygon/Simplygon.tps -Notes: This was added by the Simplygon partners as part of the Simplygon integration. - - diff --git a/Engine/Source/ThirdParty/Simplygon/Simplygon.Build.cs b/Engine/Source/ThirdParty/Simplygon/Simplygon.Build.cs deleted file mode 100644 index a3621522fc29..000000000000 --- a/Engine/Source/ThirdParty/Simplygon/Simplygon.Build.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.IO; - -public class Simplygon : ModuleRules -{ - public Simplygon(ReadOnlyTargetRules Target) : base(Target) - { - Type = ModuleType.External; - - bOutputPubliclyDistributable = true; - - PublicDefinitions.Add("SGDEPRECATED_OFF=1"); - - //@third party code BEGIN SIMPLYGON - //Change the path to make it easier to update Simplygon - string SimplygonPath = Target.UEThirdPartySourceDirectory + "NotForLicensees/Simplygon/Simplygon-latest/"; - //@third party code END SIMPLYGON - PublicIncludePaths.Add(SimplygonPath + "Inc"); - - // Simplygon depends on D3DX9. - if ((Target.Platform == UnrealTargetPlatform.Win64) || - (Target.Platform == UnrealTargetPlatform.Win32)) - { - if (Target.Platform == UnrealTargetPlatform.Win64) - { - PublicLibraryPaths.Add( Target.UEThirdPartySourceDirectory + "Windows/DirectX/Lib/x64" ); - } - else - { - PublicLibraryPaths.Add( Target.UEThirdPartySourceDirectory + "Windows/DirectX/Lib/x86" ); - } - PublicAdditionalLibraries.Add( - (Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT) ? "d3dx9d.lib" : "d3dx9.lib" - ); - - // Simplygon requires GetProcessMemoryInfo exported by psapi.dll. http://msdn.microsoft.com/en-us/library/windows/desktop/ms683219(v=vs.85).aspx - PublicAdditionalLibraries.Add("psapi.lib"); - - PublicDelayLoadDLLs.Add("d3dcompiler_47.dll"); - string EngineDir = Path.GetFullPath(Target.RelativeEnginePath); - if (Target.Platform == UnrealTargetPlatform.Win32) - { - RuntimeDependencies.Add(EngineDir + "Binaries/ThirdParty/Windows/DirectX/x86/d3dcompiler_47.dll"); - } - else if (Target.Platform == UnrealTargetPlatform.Win64) - { - RuntimeDependencies.Add(EngineDir + "Binaries/ThirdParty/Windows/DirectX/x64/d3dcompiler_47.dll"); - } - - } - } -} - diff --git a/Engine/Source/ThirdParty/Simplygon/Simplygon.tps b/Engine/Source/ThirdParty/Simplygon/Simplygon.tps deleted file mode 100644 index 5af4f816b19e..000000000000 --- a/Engine/Source/ThirdParty/Simplygon/Simplygon.tps +++ /dev/null @@ -1,7 +0,0 @@ - - - -Redirect: ../NotForLicensees/Simplygon/Simplygon.tps -Notes: We don't distribute the Simplygon SDK, but our TPS info for internal usage is given at the above link. - - diff --git a/Engine/Source/ThirdParty/libOpus/opus-1.1/include/opus_types.h b/Engine/Source/ThirdParty/libOpus/opus-1.1/include/opus_types.h index 71808266655a..38259fccf69b 100644 --- a/Engine/Source/ThirdParty/libOpus/opus-1.1/include/opus_types.h +++ b/Engine/Source/ThirdParty/libOpus/opus-1.1/include/opus_types.h @@ -138,6 +138,13 @@ typedef int opus_int32; typedef unsigned int opus_uint32; +#elif defined(__NX__) + +typedef signed short int opus_int16; +typedef unsigned short int opus_uint16; +typedef signed int opus_int32; +typedef unsigned int opus_uint32; + #else /* Give up, take a reasonable guess */