GpuEcsAnimationBakeServices.cs 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. #if UNITY_EDITOR
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using GPUECSAnimationBaker.Engine.AnimatorSystem;
  8. using GpuEcsAnimationBaker.Engine.Data;
  9. using Unity.Collections;
  10. using Unity.Mathematics;
  11. using UnityEditor;
  12. using UnityEngine;
  13. namespace GPUECSAnimationBaker.Engine.Baker
  14. {
  15. public static class GpuEcsAnimationBakerServices
  16. {
  17. private static readonly int AnimatedBoneMatrices = Shader.PropertyToID("_AnimatedBoneMatrices");
  18. private static readonly int EnableAnimation = Shader.PropertyToID("_EnableAnimation");
  19. public static bool ValidateAnimationBakerData(GpuEcsAnimationBakerData bakerData, GameObject sourceModel, out string errors)
  20. {
  21. StringBuilder sbErrors = new StringBuilder();
  22. if (bakerData.animations.Length == 0)
  23. sbErrors.AppendLine("At least one animation must be baked.");
  24. foreach (AnimationData animation in bakerData.animations)
  25. {
  26. if (string.IsNullOrWhiteSpace(animation.animationID))
  27. sbErrors.AppendLine("Animation ID is mandatory");
  28. if (!Regex.IsMatch(animation.animationID, @"^[a-zA-Z_][a-zA-Z0-9_]+$"))
  29. sbErrors.AppendLine("Animation ID must be only letters, numbers or underscore, must not start with number");
  30. if (string.IsNullOrWhiteSpace(animation.animatorStateName))
  31. sbErrors.AppendLine("Animation State Name is mandatory");
  32. if (animation.animationType == AnimationTypes.SingleClip)
  33. {
  34. if (animation.singleClipData.animationClip == null)
  35. sbErrors.AppendLine("Animation Clip is mandatory");
  36. }
  37. else if (animation.animationType == AnimationTypes.DualClipBlend)
  38. {
  39. if (string.IsNullOrWhiteSpace(animation.dualClipBlendData.blendParameterName))
  40. sbErrors.AppendLine("Blend parameter name is mandatory");
  41. if (animation.dualClipBlendData.clip1.animationClip == null)
  42. sbErrors.AppendLine("Animation Clip 1 is mandatory");
  43. if (animation.dualClipBlendData.clip1.animationClip == null)
  44. sbErrors.AppendLine("Animation Clip 2 is mandatory");
  45. if (animation.dualClipBlendData.nbrOfInBetweenSamples < 2)
  46. sbErrors.AppendLine("Nbr of in between samples must be at least 2");
  47. if (animation.dualClipBlendData.nbrOfInBetweenSamples > 100)
  48. sbErrors.AppendLine("Nbr of in between samples is maximum 100");
  49. }
  50. foreach (AnimatorParameter parameterValue in animation.additionalAnimatorParameterValues)
  51. {
  52. if (string.IsNullOrWhiteSpace(parameterValue.parameterName))
  53. sbErrors.AppendLine("Additional animator parameter name is mandatory");
  54. }
  55. }
  56. bool foundDouble = false;
  57. for (int i = 0; i < bakerData.animations.Length; i++)
  58. {
  59. for (int j = i + 1; j < bakerData.animations.Length; j++)
  60. {
  61. if (bakerData.animations[i].animationID == bakerData.animations[j].animationID)
  62. {
  63. foundDouble = true; break;
  64. }
  65. }
  66. if(foundDouble) break;
  67. }
  68. if(foundDouble) sbErrors.AppendLine("Animation IDs must be unique");
  69. foreach (AttachmentAnchor attachmentAnchor in bakerData.attachmentAnchors)
  70. {
  71. if (string.IsNullOrWhiteSpace(attachmentAnchor.attachmentAnchorID))
  72. sbErrors.AppendLine("Attachment Anchor ID is mandatory");
  73. if(attachmentAnchor.attachmentAnchorTransform == null)
  74. sbErrors.AppendLine("Attachment Anchor reference transform is mandatory");
  75. else if(!hasParent(attachmentAnchor.attachmentAnchorTransform, sourceModel.transform))
  76. sbErrors.AppendLine($"Attachment Anchor reference transform must be nested inside {sourceModel.name}");
  77. }
  78. if(bakerData.boneUsage.numberOfBonesPerVertex < 1)
  79. sbErrors.AppendLine("Nbr of bones per vertex must be at least 1");
  80. foreach (BoneUsagePerLoD boneUsagePerLOD in bakerData.boneUsage.boneUsagesPerLoD)
  81. {
  82. if(boneUsagePerLOD.maxNumberOfBonesPerVertex < 1)
  83. sbErrors.AppendLine("Nbr of bones per vertex must be at least 1");
  84. }
  85. errors = sbErrors.ToString();
  86. return sbErrors.Length == 0;
  87. }
  88. private static bool hasParent(Transform child, Transform parent)
  89. {
  90. if (child.parent == null) return false;
  91. if (child.parent.gameObject == parent.gameObject) return true;
  92. else return hasParent(child.parent, parent);
  93. }
  94. public static GameObject GenerateAnimationObject(string assetPath,
  95. GpuEcsAnimationBakerData bakerData, string animatorName, string generatedAssetsFolder,
  96. string nameSuffixAsset = "_GpuEcsAnimator",
  97. string nameSuffixAnimationIDsEnum = "_AnimationIDs",
  98. string nameSuffixAnimationInitializerBehaviour = "_AnimationInitializerBehaviour",
  99. string nameSuffixAnimationEventIDsEnum = "_AnimationEventIDs",
  100. string nameSuffixAnimationAnchorIDsEnum = "_AttachmentAnchorIDs",
  101. string nameSuffixAttachmentInitializerBehaviour = "_AttachmentInitializerBehaviour",
  102. string meshPartSuffix = "Mesh",
  103. string animationMatricesTexturePartSuffix = "AnimationMatricesTexture",
  104. string materialPartSuffix = "Material"
  105. )
  106. {
  107. GameObject sourceModel = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
  108. GameObject refModel = PrefabUtility.LoadPrefabContents(assetPath);
  109. Debug.Log($"Generating Animation object for {assetPath}");
  110. GameObject animationObject = GenerateAnimationObjectFromModel(refModel, sourceModel, bakerData, animatorName, generatedAssetsFolder,
  111. nameSuffixAsset, nameSuffixAnimationIDsEnum, nameSuffixAnimationInitializerBehaviour, nameSuffixAnimationEventIDsEnum,
  112. nameSuffixAnimationAnchorIDsEnum, nameSuffixAttachmentInitializerBehaviour, meshPartSuffix, animationMatricesTexturePartSuffix, materialPartSuffix);
  113. PrefabUtility.UnloadPrefabContents(refModel);
  114. return animationObject;
  115. }
  116. public static GameObject GenerateAnimationObjectFromModel(GameObject refModel, GameObject sourceModel,
  117. GpuEcsAnimationBakerData bakerData, string animatorName, string generatedAssetsFolder,
  118. string nameSuffixAsset = "_GpuEcsAnimator",
  119. string nameSuffixAnimationIDsEnum = "_AnimationIDs",
  120. string nameSuffixAnimationInitializerBehaviour = "_AnimationInitializerBehaviour",
  121. string nameSuffixAnimationEventIDsEnum = "_AnimationEventIDs",
  122. string nameSuffixAnimationAnchorIDsEnum = "_AttachmentAnchorIDs",
  123. string nameSuffixAttachmentInitializerBehaviour = "_AttachmentInitializerBehaviour",
  124. string meshPartSuffix = "Mesh",
  125. string animationMatricesTexturePartSuffix = "AnimationMatricesTexture",
  126. string materialPartSuffix = "Material"
  127. )
  128. {
  129. if(!ValidateAnimationBakerData(bakerData, sourceModel, out string errors))
  130. {
  131. Debug.LogError(errors);
  132. return null;
  133. }
  134. string targetAssetPath =
  135. Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAsset}.prefab");
  136. string targetAnimationEnumAssetPath =
  137. Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAnimationIDsEnum}.cs");
  138. string targetAnimationInitializerBehaviourAssetPath =
  139. Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAnimationInitializerBehaviour}.cs");
  140. string targetAnimationEventEnumAssetPath =
  141. Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAnimationEventIDsEnum}.cs");
  142. string targetAttachmentAnchorEnumAssetPath =
  143. Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAnimationAnchorIDsEnum}.cs");
  144. string targetAttachmentInitializerBehaviourAssetPath =
  145. Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAttachmentInitializerBehaviour}.cs");
  146. refModel.transform.position = Vector3.zero;
  147. refModel.transform.rotation = quaternion.identity;
  148. Animator refModelAnimator = refModel.GetComponent<Animator>();
  149. refModelAnimator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
  150. LODGroup refModelLoDGroup = refModel.GetComponent<LODGroup>();
  151. LOD[] refModelLoDs = refModelLoDGroup == null ? null : refModelLoDGroup.GetLODs();
  152. List<AnimationMatricesTexture> animationMatricesTexturesCache = new List<AnimationMatricesTexture>();
  153. List<EcsGpuMaterial> ecsGpuMaterialsCache = new List<EcsGpuMaterial>();
  154. GameObject test = AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);
  155. if (test == null)
  156. {
  157. GameObject empty = new GameObject(refModel.name);
  158. PrefabUtility.SaveAsPrefabAsset(empty, targetAssetPath);
  159. GameObject.DestroyImmediate(empty, allowDestroyingAssets:true);
  160. }
  161. GameObject target = PrefabUtility.LoadPrefabContents(targetAssetPath);
  162. target.transform.localScale = refModel.transform.localScale;
  163. GameObject[] toDestroy = new GameObject[target.transform.childCount];
  164. for (int childIndex = 0; childIndex < target.transform.childCount; childIndex++)
  165. toDestroy[childIndex] = target.transform.GetChild(childIndex).gameObject;
  166. foreach(GameObject go in toDestroy) Object.DestroyImmediate(go, allowDestroyingAssets: true);
  167. RemoveComponent<MeshRenderer>(target);
  168. RemoveComponent<MeshFilter>(target);
  169. RemoveComponent<LODGroup>(target);
  170. RemoveComponent<GpuEcsAnimatorBehaviour>(target);
  171. RemoveComponent<GpuEcsAnimatedMeshBehaviour>(target);
  172. GpuEcsAnimatorBehaviour gpuEcsAnimator = AddGpuEcsAnimationBehaviour(target, bakerData, out string[] eventNames);
  173. gpuEcsAnimator.transformUsageFlags = bakerData.transformUsageFlagsParent;
  174. Dictionary<GameObject, GameObject> sourceToTargetMapping = new Dictionary<GameObject, GameObject>();
  175. ProcessAnimationObjectRecursive(refModel, target, sourceToTargetMapping, animatorName,
  176. meshPartSuffix, animationMatricesTexturePartSuffix, materialPartSuffix,
  177. refModelAnimator, refModelLoDs, animationMatricesTexturesCache, ecsGpuMaterialsCache,
  178. bakerData, gpuEcsAnimator.animations, gpuEcsAnimator.totalNbrOfFrames, generatedAssetsFolder, gpuEcsAnimator);
  179. if (refModelLoDs != null)
  180. {
  181. LODGroup targetLoDGroup = target.AddComponent<LODGroup>();
  182. LOD[] targetLoDs = new LOD[refModelLoDs.Length];
  183. for (int lodIndex = 0; lodIndex < refModelLoDs.Length; lodIndex++)
  184. {
  185. targetLoDs[lodIndex] = refModelLoDs[lodIndex];
  186. Renderer[] refModelRenderers = refModelLoDs[lodIndex].renderers;
  187. for (int rendererIndex = 0; rendererIndex < refModelRenderers.Length; rendererIndex++)
  188. {
  189. targetLoDs[lodIndex].renderers[rendererIndex] =
  190. sourceToTargetMapping[refModelRenderers[rendererIndex].gameObject].GetComponent<MeshRenderer>();
  191. }
  192. }
  193. targetLoDGroup.SetLODs(targetLoDs);
  194. }
  195. if(bakerData.generateAnimationIdsEnum)
  196. GenerateAnimationsEnumCode(bakerData, targetAnimationEnumAssetPath, targetAnimationInitializerBehaviourAssetPath);
  197. if(bakerData.generateAnimationEventIdsEnum)
  198. GenerateAnimationEventsEnumCode(bakerData, targetAnimationEventEnumAssetPath, eventNames );
  199. gpuEcsAnimator.nbrOfAttachmentAnchors = bakerData.attachmentAnchors.Length;
  200. gpuEcsAnimator.attachmentAnchorData = BakeAttachmentAnchorTransforms(generatedAssetsFolder, sourceModel, animatorName,
  201. refModelAnimator, bakerData, gpuEcsAnimator.animations, gpuEcsAnimator.totalNbrOfFrames,gpuEcsAnimator);
  202. if(bakerData.generateAttachmentAnchorIdsEnum)
  203. GenerateAttachmentAnchorsEnumCode(bakerData, targetAttachmentAnchorEnumAssetPath, targetAttachmentInitializerBehaviourAssetPath);
  204. PrefabUtility.SaveAsPrefabAsset(target, targetAssetPath);
  205. PrefabUtility.UnloadPrefabContents(target);
  206. return AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);
  207. }
  208. private const string enumFileTemplate = "namespace GPUECSAnimationBaker.Engine.AnimatorSystem\n"
  209. + "{\n"
  210. + " public enum @ENUMNAME@\n"
  211. + " {\n"
  212. + "@IDLIST@\n"
  213. + " }\n"
  214. + "}";
  215. private const string behaviourFileTemplate = "namespace GPUECSAnimationBaker.Engine.AnimatorSystem\n"
  216. + "{\n"
  217. + " public class @CLASSNAME_@ENUMNAME@_Behaviour : @CLASSNAMEBehaviour<@ENUMNAME@> { } \n"
  218. + "}";
  219. private static void GenerateAnimationsEnumCode(GpuEcsAnimationBakerData bakerData, string targetEnumAssetPath, string targetEnumBehaviourAssetPath)
  220. {
  221. GenerateEnumCodeFiles<AnimationData>($"AnimationIds{bakerData.animationIdsEnumName}", "GpuEcsAnimatorInitializer",
  222. bakerData.animations, (animationData) => animationData.animationID, targetEnumAssetPath, targetEnumBehaviourAssetPath);
  223. }
  224. private static void GenerateAnimationEventsEnumCode(GpuEcsAnimationBakerData bakerData, string targetEnumAssetPath, string[] eventNames)
  225. {
  226. GenerateEnumCodeFiles<string>($"AnimationEventIds{bakerData.animationEventIdsEnumName}", null,
  227. eventNames, (name) => name, targetEnumAssetPath, null);
  228. }
  229. private static void GenerateAttachmentAnchorsEnumCode(GpuEcsAnimationBakerData bakerData, string targetEnumAssetPath, string targetEnumBehaviourAssetPath)
  230. {
  231. GenerateEnumCodeFiles<AttachmentAnchor>($"AnchorIds{bakerData.attachmentAnchorIdsEnumName}", "GpuEcsAttachmentInitializer",
  232. bakerData.attachmentAnchors, (attachmentAnchor) => attachmentAnchor.attachmentAnchorID, targetEnumAssetPath, targetEnumBehaviourAssetPath);
  233. }
  234. private static void GenerateEnumCodeFiles<T>(string enumName, string className, T[] list, System.Func<T, string> listIdGetter,
  235. string targetEnumAssetPath, string targetEnumBehaviourAssetPath)
  236. {
  237. StringBuilder idList = new StringBuilder();
  238. for (int index = 0; index < list.Length; index++)
  239. {
  240. idList.Append($" {listIdGetter(list[index])} = {index.ToString()}");
  241. if (index < list.Length - 1)
  242. {
  243. idList.AppendLine(",");
  244. }
  245. }
  246. string enumCodeText = enumFileTemplate
  247. .Replace("@ENUMNAME@", enumName)
  248. .Replace("@IDLIST@", idList.ToString());
  249. string enumCodePath = Path.Combine(Application.dataPath, "../" + targetEnumAssetPath);
  250. File.WriteAllText(enumCodePath, enumCodeText);
  251. if (targetEnumBehaviourAssetPath != null)
  252. {
  253. string enumBehaviourText = behaviourFileTemplate
  254. .Replace("@CLASSNAME", className)
  255. .Replace("@ENUMNAME@", enumName);
  256. string enumBehaviourPath = Path.Combine(Application.dataPath, "../" + targetEnumBehaviourAssetPath);
  257. File.WriteAllText(enumBehaviourPath, enumBehaviourText);
  258. }
  259. AssetDatabase.Refresh();
  260. }
  261. private static GpuEcsAnimatorBehaviour AddGpuEcsAnimationBehaviour(GameObject target,
  262. GpuEcsAnimationBakerData bakerData, out string[] eventNames)
  263. {
  264. GpuEcsAnimatorBehaviour gpuEcsAnimator = target.AddComponent<GpuEcsAnimatorBehaviour>();
  265. gpuEcsAnimator.animations = new GpuEcsAnimationData[bakerData.animations.Length];
  266. // gpuEcsAnimator.BackAnimationDatas= new AnimationData[bakerData.animations.Length];
  267. List<GpuEcsAnimationEventOccurence> occurences = new List<GpuEcsAnimationEventOccurence>();
  268. List<string> foundEvents = new List<string>();
  269. int currentFrameIndex = 0;
  270. int currentEventOccurenceId = 0;
  271. int currentFoundEventId = 0;
  272. for (int animationIndex = 0; animationIndex < bakerData.animations.Length; animationIndex++)
  273. {
  274. AnimationData animationData = bakerData.animations[animationIndex];
  275. int nbrOfFramesPerSample = 0;
  276. int nbrOfInBetweenSamples = 0;
  277. float blendTimeCorrection = 1;
  278. int startEventOccurenceId = currentEventOccurenceId;
  279. int nbrOfEventOccurenceIds = 0;
  280. if (animationData.animationType == AnimationTypes.SingleClip)
  281. {
  282. SingleClipData singleClipData = animationData.singleClipData;
  283. nbrOfFramesPerSample = (int)(singleClipData.animationClip.length * GlobalConstants.SampleFrameRate) + 1;
  284. nbrOfInBetweenSamples = 1;
  285. blendTimeCorrection = 1;
  286. AddAnimationEvents(bakerData, animationData, occurences, ref currentEventOccurenceId, ref nbrOfEventOccurenceIds, foundEvents,
  287. ref currentFoundEventId, singleClipData.animationClip);
  288. }
  289. else if (animationData.animationType == AnimationTypes.DualClipBlend)
  290. {
  291. DualClipBlendData dualClipBlendData = animationData.dualClipBlendData;
  292. int clip1NbrOfFrames = (int)(dualClipBlendData.clip1.animationClip.length * GlobalConstants.SampleFrameRate) + 1;
  293. int clip2NbrOfFrames = (int)(dualClipBlendData.clip2.animationClip.length * GlobalConstants.SampleFrameRate) + 1;
  294. nbrOfFramesPerSample = math.max(clip1NbrOfFrames, clip2NbrOfFrames);
  295. blendTimeCorrection = dualClipBlendData.clip1.animationClip.length / dualClipBlendData.clip2.animationClip.length;
  296. nbrOfInBetweenSamples = dualClipBlendData.nbrOfInBetweenSamples;
  297. AddAnimationEvents(bakerData, animationData, occurences, ref currentEventOccurenceId, ref nbrOfEventOccurenceIds, foundEvents,
  298. ref currentFoundEventId, dualClipBlendData.clip1.animationClip);
  299. AddAnimationEvents(bakerData, animationData, occurences, ref currentEventOccurenceId, ref nbrOfEventOccurenceIds, foundEvents,
  300. ref currentFoundEventId, dualClipBlendData.clip2.animationClip);
  301. }
  302. bool loop = animationData.loop;
  303. gpuEcsAnimator.animations[animationIndex] = new GpuEcsAnimationData()
  304. {
  305. startFrameIndex = currentFrameIndex,
  306. nbrOfFramesPerSample = nbrOfFramesPerSample,
  307. nbrOfInBetweenSamples = nbrOfInBetweenSamples,
  308. blendTimeCorrection = blendTimeCorrection,
  309. startEventOccurenceId = startEventOccurenceId,
  310. nbrOfEventOccurenceIds = nbrOfEventOccurenceIds,
  311. loop = loop,
  312. animationID = animationData.animationID,
  313. stateName = animationData.animatorStateName
  314. };
  315. currentFrameIndex += nbrOfFramesPerSample * nbrOfInBetweenSamples;
  316. }
  317. gpuEcsAnimator.totalNbrOfFrames = currentFrameIndex;
  318. gpuEcsAnimator.animationEventOccurences = occurences.ToArray();
  319. eventNames = bakerData.usePredefinedAnimationEventIds ? bakerData.predefinedAnimationEventIds : foundEvents.ToArray();
  320. return gpuEcsAnimator;
  321. }
  322. private static void AddAnimationEvents(GpuEcsAnimationBakerData bakerData, AnimationData animationData,
  323. List<GpuEcsAnimationEventOccurence> occurences,
  324. ref int currentEventOccurenceId, ref int nbrOfEventOccurenceIds,
  325. List<string> foundEvents, ref int currentFoundEventId, AnimationClip animationClip)
  326. {
  327. AnimationEvent[] animationEvents = AnimationUtility.GetAnimationEvents(animationClip);
  328. foreach (AnimationEvent animationEvent in animationEvents)
  329. {
  330. int eventId = -1;
  331. if (bakerData.usePredefinedAnimationEventIds)
  332. {
  333. eventId = bakerData.predefinedAnimationEventIds.ToList().FindIndex((n) => n.Equals(animationEvent.stringParameter));
  334. if(eventId == -1) Debug.LogWarning(
  335. $"Found event {animationEvent.stringParameter} that is not in the predefined event Ids list, so it will be ignored.");
  336. }
  337. else
  338. {
  339. string name = $"{animationData.animationID}_{animationEvent.stringParameter}";
  340. eventId = foundEvents.FindIndex((n) => n.Equals(name));
  341. if (eventId == -1)
  342. {
  343. eventId = currentFoundEventId;
  344. foundEvents.Add(name);
  345. currentFoundEventId++;
  346. }
  347. }
  348. if (eventId != -1)
  349. {
  350. occurences.Add(new GpuEcsAnimationEventOccurence()
  351. {
  352. eventId = eventId,
  353. eventNormalizedTime = animationEvent.time / animationClip.length,
  354. });
  355. currentEventOccurenceId++;
  356. nbrOfEventOccurenceIds++;
  357. }
  358. }
  359. }
  360. private static void ProcessAnimationObjectRecursive(GameObject sourceNode, GameObject targetNode,
  361. Dictionary<GameObject, GameObject> sourceToTargetMapping, string animatorName, string meshPartSuffix,
  362. string animationMatricesTexturePartSuffix, string materialPartSuffix,
  363. Animator refModelAnimator, LOD[] refModelLoDs,
  364. List<AnimationMatricesTexture> animationMatricesTexturesCache, List<EcsGpuMaterial> ecsGpuMaterialsCache,
  365. GpuEcsAnimationBakerData bakerData, GpuEcsAnimationData[] animations, int totalNbrOfFrames,
  366. string generatedAssetsFolder, GpuEcsAnimatorBehaviour gpuEcsAnimator)
  367. {
  368. sourceToTargetMapping.Add(sourceNode, targetNode);
  369. SkinnedMeshRenderer existingSkinnedMeshRenderer = sourceNode.GetComponent<SkinnedMeshRenderer>();
  370. if (existingSkinnedMeshRenderer != null)
  371. {
  372. Texture2D animationMatricesTexture = BakeAndSaveAnimationMatricesTexture(
  373. animationMatricesTexturesCache, generatedAssetsFolder, animatorName, animationMatricesTexturePartSuffix,
  374. refModelAnimator, bakerData, existingSkinnedMeshRenderer, animations, totalNbrOfFrames);
  375. Mesh newMesh = BakeAndSaveBoneWeightsIntoMesh(refModelLoDs, generatedAssetsFolder, animatorName,
  376. meshPartSuffix, bakerData, existingSkinnedMeshRenderer);
  377. MeshFilter meshFilter = targetNode.AddComponent<MeshFilter>();
  378. MeshRenderer meshRenderer = targetNode.AddComponent<MeshRenderer>();
  379. Material newMaterial = CreateAndSaveEcsGpuMaterial(ecsGpuMaterialsCache, generatedAssetsFolder,
  380. animatorName, materialPartSuffix, existingSkinnedMeshRenderer);
  381. newMaterial.SetTexture(AnimatedBoneMatrices, animationMatricesTexture);
  382. meshRenderer.sharedMaterial = newMaterial;
  383. meshFilter.sharedMesh = newMesh;
  384. GpuEcsAnimatedMeshBehaviour gpuEcsAnimatedMesh = targetNode.AddComponent<GpuEcsAnimatedMeshBehaviour>();
  385. gpuEcsAnimatedMesh.animator = gpuEcsAnimator;
  386. gpuEcsAnimatedMesh.transformUsageFlags = bakerData.transformUsageFlagsChildren;
  387. }
  388. for (int childIndex = 0; childIndex < sourceNode.transform.childCount; childIndex++)
  389. {
  390. GameObject sourceChild = sourceNode.transform.GetChild(childIndex).gameObject;
  391. if (sourceChild.gameObject.activeSelf && !CheckEmptyRecursive(sourceChild))
  392. {
  393. GameObject targetChild = new GameObject(sourceChild.name);
  394. targetChild.transform.parent = targetNode.transform;
  395. targetChild.transform.localPosition = sourceChild.transform.localPosition;
  396. targetChild.transform.localRotation = sourceChild.transform.localRotation;
  397. targetChild.transform.localScale = sourceChild.transform.localScale;
  398. ProcessAnimationObjectRecursive(sourceChild, targetChild, sourceToTargetMapping,
  399. animatorName, meshPartSuffix, animationMatricesTexturePartSuffix, materialPartSuffix,
  400. refModelAnimator, refModelLoDs, animationMatricesTexturesCache, ecsGpuMaterialsCache,
  401. bakerData, animations, totalNbrOfFrames, generatedAssetsFolder, gpuEcsAnimator);
  402. }
  403. }
  404. }
  405. private static void RemoveComponent<T>(GameObject gameObject) where T : Component
  406. {
  407. T component = gameObject.GetComponent<T>();
  408. if(component != null) Object.DestroyImmediate(component, allowDestroyingAssets:true);
  409. }
  410. private static bool CheckEmptyRecursive(GameObject node)
  411. {
  412. Component[] components = node.GetComponents<Component>();
  413. bool empty = !components.Any(c => (c is SkinnedMeshRenderer));
  414. if (empty)
  415. {
  416. for (int childIndex = 0; childIndex < node.transform.childCount; childIndex++)
  417. {
  418. empty = CheckEmptyRecursive(node.transform.GetChild(childIndex).gameObject);
  419. if (!empty) break;
  420. }
  421. }
  422. return empty;
  423. }
  424. private static Mesh BakeAndSaveBoneWeightsIntoMesh(LOD[] refModelLoDs,
  425. string generatedAssetsFolder, string animatorName, string meshPartSuffix, GpuEcsAnimationBakerData bakerData,
  426. SkinnedMeshRenderer skinnedMeshRenderer)
  427. {
  428. int maxNumberOfBonesPerVertex = GetMaxNumberOfBonesPerVertex(refModelLoDs, bakerData, skinnedMeshRenderer);
  429. Mesh newMesh = BakeBoneWeightsIntoMesh(skinnedMeshRenderer.sharedMesh, maxNumberOfBonesPerVertex);
  430. SavePartAsAsset(newMesh, generatedAssetsFolder, animatorName, skinnedMeshRenderer, meshPartSuffix, "mesh");
  431. return newMesh;
  432. }
  433. private static Texture2D BakeAndSaveAnimationMatricesTexture(List<AnimationMatricesTexture> cache,
  434. string generatedAssetsFolder, string animatorName, string animationMatricesTexturePartSuffix,
  435. Animator refModelAnimator, GpuEcsAnimationBakerData bakerData,
  436. SkinnedMeshRenderer skinnedMeshRenderer, GpuEcsAnimationData[] animations, int totalNbrOfFrames)
  437. {
  438. if (!CheckAnimationMatricesTextureInCache(cache, skinnedMeshRenderer, out Texture2D animationMatricesTexture))
  439. {
  440. animationMatricesTexture = BakeAnimationMatricesTexture(skinnedMeshRenderer, refModelAnimator, bakerData,
  441. animations, totalNbrOfFrames);
  442. SavePartAsAsset(animationMatricesTexture, generatedAssetsFolder, animatorName, skinnedMeshRenderer,
  443. animationMatricesTexturePartSuffix, "asset");
  444. cache.Add(new AnimationMatricesTexture()
  445. {
  446. texture = animationMatricesTexture,
  447. skinnedMeshRenderer = skinnedMeshRenderer
  448. });
  449. }
  450. return animationMatricesTexture;
  451. }
  452. private static Material CreateAndSaveEcsGpuMaterial(List<EcsGpuMaterial> cache,
  453. string generatedAssetsFolder, string animatorName, string materialPartSuffix,
  454. SkinnedMeshRenderer skinnedMeshRenderer)
  455. {
  456. if (!CheckEcsGpuMaterialInCache(cache, skinnedMeshRenderer, out Material ecsGpuMaterial))
  457. {
  458. ecsGpuMaterial = Object.Instantiate<Material>(skinnedMeshRenderer.sharedMaterial);
  459. ecsGpuMaterial.shader = Shader.Find("Shader Graphs/GPUECSAnimator_URP_SimpleShader");
  460. ecsGpuMaterial.SetFloat(EnableAnimation, 0);
  461. ecsGpuMaterial.enableInstancing = true;
  462. SavePartAsAsset(ecsGpuMaterial, generatedAssetsFolder, animatorName, skinnedMeshRenderer,
  463. materialPartSuffix, "mat");
  464. cache.Add(new EcsGpuMaterial()
  465. {
  466. material = ecsGpuMaterial,
  467. skinnedMeshRenderer = skinnedMeshRenderer
  468. });
  469. }
  470. return ecsGpuMaterial;
  471. }
  472. private static string GenerateAssetPath(string generatedAssetsFolder, string animatorName,
  473. string assetName, string assetFileExtension)
  474. {
  475. return Path.Combine(generatedAssetsFolder, $"{animatorName}_{assetName}.{assetFileExtension}");
  476. }
  477. private static void SavePartAsAsset(Object asset, string generatedAssetsFolder, string animatorName,
  478. SkinnedMeshRenderer skinnedMeshRenderer, string partName, string assetFileExtension)
  479. {
  480. string assetName = $"{partName}_{skinnedMeshRenderer.name}";
  481. string assetPath = GenerateAssetPath(generatedAssetsFolder, animatorName, assetName, assetFileExtension);
  482. AssetDatabase.CreateAsset(asset, assetPath);
  483. }
  484. private static int GetMaxNumberOfBonesPerVertex(LOD[] refModelLoDs, GpuEcsAnimationBakerData bakerData,
  485. SkinnedMeshRenderer skinnedMeshRenderer)
  486. {
  487. int maxNumberOfBonesPerVertex = bakerData.boneUsage.numberOfBonesPerVertex;
  488. if (refModelLoDs != null)
  489. {
  490. for (int lodIndex = 0; lodIndex < refModelLoDs.Length; lodIndex++)
  491. {
  492. if (refModelLoDs[lodIndex].renderers.Any(r => r == (Renderer)skinnedMeshRenderer))
  493. {
  494. BoneUsagePerLoD boneUsagePerLoD = bakerData.boneUsage.boneUsagesPerLoD
  495. .SingleOrDefault(b => b.lodIndex == lodIndex);
  496. if (boneUsagePerLoD != null)
  497. maxNumberOfBonesPerVertex = boneUsagePerLoD.maxNumberOfBonesPerVertex;
  498. break;
  499. }
  500. }
  501. }
  502. return maxNumberOfBonesPerVertex;
  503. }
  504. private static bool CheckAnimationMatricesTextureInCache(List<AnimationMatricesTexture> cache,
  505. SkinnedMeshRenderer skinnedMeshRenderer, out Texture2D foundTexture)
  506. {
  507. bool found = false;
  508. foundTexture = null;
  509. foreach (AnimationMatricesTexture cachedTexture in cache)
  510. {
  511. if (CheckSkinnedMeshRendererHasSameBones(cachedTexture.skinnedMeshRenderer,
  512. skinnedMeshRenderer))
  513. {
  514. found = true;
  515. foundTexture = cachedTexture.texture;
  516. break;
  517. }
  518. }
  519. return found;
  520. }
  521. private static bool CheckEcsGpuMaterialInCache(List<EcsGpuMaterial> cache,
  522. SkinnedMeshRenderer skinnedMeshRenderer, out Material foundMaterial)
  523. {
  524. bool found = false;
  525. foundMaterial = null;
  526. foreach (EcsGpuMaterial cachedEcsGpuMaterial in cache)
  527. {
  528. if (cachedEcsGpuMaterial.skinnedMeshRenderer.sharedMaterial == skinnedMeshRenderer.sharedMaterial
  529. && CheckSkinnedMeshRendererHasSameBones(cachedEcsGpuMaterial.skinnedMeshRenderer,
  530. skinnedMeshRenderer))
  531. {
  532. found = true;
  533. foundMaterial = cachedEcsGpuMaterial.material;
  534. break;
  535. }
  536. }
  537. return found;
  538. }
  539. private static bool CheckSkinnedMeshRendererHasSameBones(SkinnedMeshRenderer skinnedMeshRendererA,
  540. SkinnedMeshRenderer skinnedMeshRendererB)
  541. {
  542. bool hasSameBones = false;
  543. if (skinnedMeshRendererA.bones.Length == skinnedMeshRendererB.bones.Length)
  544. {
  545. hasSameBones = true;
  546. for (int i = 0; i < skinnedMeshRendererA.bones.Length; i++)
  547. {
  548. if (!CheckMatrixEquality(skinnedMeshRendererA.bones[i].localToWorldMatrix,
  549. skinnedMeshRendererB.bones[i].localToWorldMatrix))
  550. {
  551. hasSameBones = false; break;
  552. }
  553. if (!CheckMatrixEquality(skinnedMeshRendererA.sharedMesh.bindposes[i],
  554. skinnedMeshRendererB.sharedMesh.bindposes[i]))
  555. {
  556. hasSameBones = false; break;
  557. }
  558. }
  559. }
  560. return hasSameBones;
  561. }
  562. private static bool CheckMatrixEquality(Matrix4x4 m1, Matrix4x4 m2)
  563. {
  564. return m1.m00 == m2.m00 && m1.m01 == m2.m01 && m1.m02 == m2.m02 && m1.m03 == m2.m03
  565. && m1.m10 == m2.m10 && m1.m11 == m2.m11 && m1.m12 == m2.m12 && m1.m13 == m2.m13
  566. && m1.m20 == m2.m20 && m1.m21 == m2.m21 && m1.m22 == m2.m22 && m1.m23 == m2.m23
  567. && m1.m30 == m2.m30 && m1.m31 == m2.m31 && m1.m32 == m2.m32 && m1.m33 == m2.m33;
  568. }
  569. private static void IterateOverAllFramesThroughAnimator(
  570. Animator refModelAnimator, GpuEcsAnimationBakerData bakerData, GpuEcsAnimationData[] animations,
  571. System.Action<int> actionPerFrame)
  572. {
  573. refModelAnimator.speed = 0;
  574. int currentFrameIndex = 0;
  575. for (int animationIndex = 0; animationIndex < bakerData.animations.Length; animationIndex++)
  576. {
  577. AnimationData animationData = bakerData.animations[animationIndex];
  578. GpuEcsAnimationData gpuEcsAnimationData = animations[animationIndex];
  579. foreach (AnimatorParameter parameterValue in animationData.additionalAnimatorParameterValues)
  580. {
  581. if(parameterValue.parameterType == AnimatorParameterTypes.Bool)
  582. refModelAnimator.SetBool(parameterValue.parameterName, parameterValue.boolValue);
  583. else if(parameterValue.parameterType == AnimatorParameterTypes.Float)
  584. refModelAnimator.SetFloat(parameterValue.parameterName, parameterValue.floatValue);
  585. else if(parameterValue.parameterType == AnimatorParameterTypes.Integer)
  586. refModelAnimator.SetInteger(parameterValue.parameterName, parameterValue.intValue);
  587. }
  588. for (int sampleIndex = 0; sampleIndex < gpuEcsAnimationData.nbrOfInBetweenSamples; sampleIndex++)
  589. {
  590. if (animationData.animationType == AnimationTypes.DualClipBlend)
  591. {
  592. DualClipBlendData dualClipBlendData = animationData.dualClipBlendData;
  593. float sampleRatio = (float)sampleIndex / (float)(gpuEcsAnimationData.nbrOfInBetweenSamples - 1);
  594. float blendValue = dualClipBlendData.clip1.parameterValue +
  595. (dualClipBlendData.clip2.parameterValue - dualClipBlendData.clip1.parameterValue) * sampleRatio;
  596. refModelAnimator.SetFloat(dualClipBlendData.blendParameterName, blendValue);
  597. }
  598. for (int frameIndex = 0; frameIndex < gpuEcsAnimationData.nbrOfFramesPerSample; frameIndex++)
  599. {
  600. float progressRatio = (float)frameIndex / (float)(gpuEcsAnimationData.nbrOfFramesPerSample - 1);
  601. refModelAnimator.Play(animationData.animatorStateName, -1, progressRatio);
  602. foreach(AnimatorState animatorState in animationData.additionalAnimatorStatesPerLayer)
  603. refModelAnimator.Play(animatorState.stateName, animatorState.layer, progressRatio);
  604. refModelAnimator.Update(0);
  605. actionPerFrame(currentFrameIndex);
  606. currentFrameIndex++;
  607. }
  608. }
  609. }
  610. }
  611. private static GpuEcsAttachmentAnchorData BakeAttachmentAnchorTransforms(string generatedAssetsFolder, GameObject sourceModel,
  612. string animatorName, Animator refModelAnimator, GpuEcsAnimationBakerData bakerData, GpuEcsAnimationData[] animations,
  613. int totalNbrOfFrames,GpuEcsAnimatorBehaviour gpuEcsAnimator)
  614. {
  615. int nbrOfAttachments = bakerData.attachmentAnchors.Length;
  616. if (nbrOfAttachments == 0) return null;
  617. else
  618. {
  619. GpuEcsAttachmentAnchorData gpuEcsAttachmentAnchorData = ScriptableObject.CreateInstance<GpuEcsAttachmentAnchorData>();
  620. gpuEcsAttachmentAnchorData.anchorTransforms = new float4x4[totalNbrOfFrames * nbrOfAttachments];
  621. gpuEcsAnimator.anchorNames.Clear();
  622. for (int attachmentAnchorIndex = 0; attachmentAnchorIndex < nbrOfAttachments; attachmentAnchorIndex++)
  623. {
  624. AttachmentAnchor attachmentAnchor = bakerData.attachmentAnchors[attachmentAnchorIndex];
  625. gpuEcsAnimator.anchorNames.Add(attachmentAnchor.attachmentAnchorID);
  626. Transform referenceAnchorTransform = attachmentAnchor.attachmentAnchorTransform;
  627. Stack<int> siblingIndexStack = new Stack<int>();
  628. GetSiblingIndexStack(referenceAnchorTransform, sourceModel.transform, siblingIndexStack);
  629. Transform anchorTransform = refModelAnimator.transform;
  630. while (siblingIndexStack.Count > 0)
  631. anchorTransform = anchorTransform.GetChild(siblingIndexStack.Pop());
  632. Debug.Assert(anchorTransform.name.Equals(referenceAnchorTransform.name));
  633. int baseIndex = attachmentAnchorIndex * totalNbrOfFrames;
  634. IterateOverAllFramesThroughAnimator(refModelAnimator, bakerData, animations,
  635. (currentFrameIndex) =>
  636. {
  637. gpuEcsAttachmentAnchorData.anchorTransforms[baseIndex + currentFrameIndex] = anchorTransform.localToWorldMatrix;
  638. });
  639. }
  640. string assetPath = GenerateAssetPath(generatedAssetsFolder, animatorName, "AttachmentAnchors", "asset");
  641. AssetDatabase.CreateAsset(gpuEcsAttachmentAnchorData, assetPath);
  642. return gpuEcsAttachmentAnchorData;
  643. }
  644. }
  645. private static void GetSiblingIndexStack(Transform current, Transform root, Stack<int> currentStack)
  646. {
  647. if(current.gameObject == root.gameObject) return;
  648. currentStack.Push(current.GetSiblingIndex());
  649. GetSiblingIndexStack(current.parent, root, currentStack);
  650. }
  651. private static Texture2D BakeAnimationMatricesTexture(SkinnedMeshRenderer skinnedMeshRenderer,
  652. Animator refModelAnimator, GpuEcsAnimationBakerData bakerData, GpuEcsAnimationData[] animations,
  653. int totalNbrOfFrames)
  654. {
  655. int boneCount = skinnedMeshRenderer.bones.Length;
  656. int animationMatricesTextureWidth = boneCount * 3;
  657. NativeArray<half4> animatedBoneMatricesTextureData = new NativeArray<half4>(
  658. totalNbrOfFrames * animationMatricesTextureWidth, Allocator.Temp);
  659. float4x4 invSkinnedMeshRendererLocalToWorld = math.inverse(skinnedMeshRenderer.transform.localToWorldMatrix);
  660. IterateOverAllFramesThroughAnimator(refModelAnimator, bakerData, animations,
  661. (currentFrameIndex) =>
  662. {
  663. for (int boneIndex = 0; boneIndex < boneCount; boneIndex++)
  664. {
  665. Transform boneTransform = skinnedMeshRenderer.bones[boneIndex];
  666. if (boneTransform != null)
  667. {
  668. float4x4 matrix = math.mul(
  669. math.mul(invSkinnedMeshRendererLocalToWorld, boneTransform.localToWorldMatrix),
  670. skinnedMeshRenderer.sharedMesh.bindposes[boneIndex]
  671. );
  672. Debug.Assert(Mathf.Approximately(matrix[0][3], 0f)
  673. && Mathf.Approximately(matrix[1][3], 0f)
  674. && Mathf.Approximately(matrix[2][3], 0f)
  675. && Mathf.Approximately(matrix[3][3], 1f), "matrix row 4 must be 0,0,0,1");
  676. int matrixIndex = (currentFrameIndex * animationMatricesTextureWidth) + (boneIndex * 3);
  677. for (int i = 0; i < 3; i++)
  678. animatedBoneMatricesTextureData[matrixIndex + i] = new half4(
  679. (half)matrix[0][i], (half)matrix[1][i], (half)matrix[2][i], (half)matrix[3][i]);
  680. }
  681. }
  682. });
  683. Texture2D animationMatricesTexture = new Texture2D(animationMatricesTextureWidth, totalNbrOfFrames,
  684. TextureFormat.RGBAHalf, false);
  685. animationMatricesTexture.SetPixelData(animatedBoneMatricesTextureData, 0);
  686. animationMatricesTexture.Apply();
  687. animatedBoneMatricesTextureData.Dispose();
  688. return animationMatricesTexture;
  689. }
  690. private static Mesh BakeBoneWeightsIntoMesh(Mesh sourceMesh, int maxNumberOfBonesPerVertex)
  691. {
  692. Mesh mesh = GameObject.Instantiate(sourceMesh);
  693. int vertexCount = mesh.vertices.Length;
  694. NativeArray<Vector4> uvs1 = new NativeArray<Vector4>(vertexCount, Allocator.Temp);
  695. NativeArray<Vector4> uvs2 = new NativeArray<Vector4>(vertexCount, Allocator.Temp);
  696. NativeArray<Vector4> uvs3 = new NativeArray<Vector4>(vertexCount, Allocator.Temp);
  697. NativeArray<byte> bonesPerVertex = mesh.GetBonesPerVertex();
  698. NativeArray<BoneWeight1> boneWeightsSource = mesh.GetAllBoneWeights();
  699. int startBoneWeightIndex = 0;
  700. for (int vertIndex = 0; vertIndex < vertexCount; vertIndex++)
  701. {
  702. float totalWeight = 0f;
  703. float totalWeightCapped = 0f;
  704. int numberOfBonesForThisVertex = bonesPerVertex[vertIndex];
  705. int boneWeightIndexTemp = startBoneWeightIndex;
  706. for (int i = 0; i < numberOfBonesForThisVertex; i++)
  707. {
  708. BoneWeight1 currentBoneWeight = boneWeightsSource[boneWeightIndexTemp];
  709. totalWeight += currentBoneWeight.weight;
  710. if (i < maxNumberOfBonesPerVertex) totalWeightCapped += currentBoneWeight.weight;
  711. if (i > 0) Debug.Assert(boneWeightsSource[boneWeightIndexTemp - 1].weight >= currentBoneWeight.weight);
  712. boneWeightIndexTemp++;
  713. }
  714. Debug.Assert(Mathf.Approximately(1f, totalWeight));
  715. float weightMultiplier = totalWeight / totalWeightCapped;
  716. int nbrOfBonesToBake = math.min(maxNumberOfBonesPerVertex, numberOfBonesForThisVertex);
  717. totalWeight = 0f;
  718. boneWeightIndexTemp = startBoneWeightIndex;
  719. float4 uv1 = float4.zero;
  720. float4 uv2 = float4.zero;
  721. float4 uv3 = float4.zero;
  722. for (int i = 0; i < nbrOfBonesToBake; i++)
  723. {
  724. BoneWeight1 currentBoneWeight = boneWeightsSource[boneWeightIndexTemp];
  725. float adjustedWeight = currentBoneWeight.weight * weightMultiplier;
  726. totalWeight += adjustedWeight;
  727. boneWeightIndexTemp++;
  728. if (i == 0) uv1 = new float4(currentBoneWeight.boneIndex, adjustedWeight, uv1.z, uv1.w);
  729. else if (i == 1) uv1 = new float4(uv1.x, uv1. y, currentBoneWeight.boneIndex, adjustedWeight);
  730. else if (i == 2) uv2 = new float4(currentBoneWeight.boneIndex, adjustedWeight, uv2.z, uv2.w);
  731. else if (i == 3) uv2 = new float4(uv2.x, uv2. y, currentBoneWeight.boneIndex, adjustedWeight);
  732. else if (i == 4) uv3 = new float4(currentBoneWeight.boneIndex, adjustedWeight, uv3.z, uv3.w);
  733. else if (i == 5) uv3 = new float4(uv3.x, uv3. y, currentBoneWeight.boneIndex, adjustedWeight);
  734. }
  735. Debug.Assert(Mathf.Approximately(1f, totalWeight));
  736. uvs1[vertIndex] = uv1;
  737. uvs2[vertIndex] = uv2;
  738. uvs3[vertIndex] = uv3;
  739. startBoneWeightIndex += numberOfBonesForThisVertex;
  740. }
  741. mesh.SetUVs(1, uvs1);
  742. mesh.SetUVs(2, uvs2);
  743. mesh.SetUVs(3, uvs3);
  744. uvs1.Dispose();
  745. uvs2.Dispose();
  746. uvs3.Dispose();
  747. bonesPerVertex.Dispose();
  748. boneWeightsSource.Dispose();
  749. return mesh;
  750. }
  751. }
  752. }
  753. #endif