| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835 | #if UNITY_EDITORusing System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Text.RegularExpressions;using GPUECSAnimationBaker.Engine.AnimatorSystem;using GpuEcsAnimationBaker.Engine.Data;using Unity.Collections;using Unity.Mathematics;using UnityEditor;using UnityEngine;namespace GPUECSAnimationBaker.Engine.Baker{    public static class GpuEcsAnimationBakerServices    {        private static readonly int AnimatedBoneMatrices = Shader.PropertyToID("_AnimatedBoneMatrices");        private static readonly int EnableAnimation = Shader.PropertyToID("_EnableAnimation");        public static bool ValidateAnimationBakerData(GpuEcsAnimationBakerData bakerData, GameObject sourceModel, out string errors)        {            StringBuilder sbErrors = new StringBuilder();            if (bakerData.animations.Length == 0)                sbErrors.AppendLine("At least one animation must be baked.");            foreach (AnimationData animation in bakerData.animations)            {                if (string.IsNullOrWhiteSpace(animation.animationID))                    sbErrors.AppendLine("Animation ID is mandatory");                if (!Regex.IsMatch(animation.animationID, @"^[a-zA-Z_][a-zA-Z0-9_]+$"))                    sbErrors.AppendLine("Animation ID must be only letters, numbers or underscore, must not start with number");                if (string.IsNullOrWhiteSpace(animation.animatorStateName))                    sbErrors.AppendLine("Animation State Name is mandatory");                if (animation.animationType == AnimationTypes.SingleClip)                {                    if (animation.singleClipData.animationClip == null)                        sbErrors.AppendLine("Animation Clip is mandatory");                }                else if (animation.animationType == AnimationTypes.DualClipBlend)                {                    if (string.IsNullOrWhiteSpace(animation.dualClipBlendData.blendParameterName))                        sbErrors.AppendLine("Blend parameter name is mandatory");                    if (animation.dualClipBlendData.clip1.animationClip == null)                        sbErrors.AppendLine("Animation Clip 1 is mandatory");                    if (animation.dualClipBlendData.clip1.animationClip == null)                        sbErrors.AppendLine("Animation Clip 2 is mandatory");                    if (animation.dualClipBlendData.nbrOfInBetweenSamples < 2)                        sbErrors.AppendLine("Nbr of in between samples must be at least 2");                    if (animation.dualClipBlendData.nbrOfInBetweenSamples > 100)                        sbErrors.AppendLine("Nbr of in between samples is maximum 100");                }                foreach (AnimatorParameter parameterValue in animation.additionalAnimatorParameterValues)                {                    if (string.IsNullOrWhiteSpace(parameterValue.parameterName))                        sbErrors.AppendLine("Additional animator parameter name is mandatory");                }            }            bool foundDouble = false;            for (int i = 0; i < bakerData.animations.Length; i++)            {                for (int j = i + 1; j < bakerData.animations.Length; j++)                {                    if (bakerData.animations[i].animationID == bakerData.animations[j].animationID)                    {                        foundDouble = true; break;                    }                }                if(foundDouble) break;            }            if(foundDouble) sbErrors.AppendLine("Animation IDs must be unique");            foreach (AttachmentAnchor attachmentAnchor in bakerData.attachmentAnchors)            {                if (string.IsNullOrWhiteSpace(attachmentAnchor.attachmentAnchorID))                    sbErrors.AppendLine("Attachment Anchor ID is mandatory");                if(attachmentAnchor.attachmentAnchorTransform == null)                    sbErrors.AppendLine("Attachment Anchor reference transform is mandatory");                else if(!hasParent(attachmentAnchor.attachmentAnchorTransform, sourceModel.transform))                    sbErrors.AppendLine($"Attachment Anchor reference transform must be nested inside {sourceModel.name}");            }                        if(bakerData.boneUsage.numberOfBonesPerVertex < 1)                 sbErrors.AppendLine("Nbr of bones per vertex must be at least 1");            foreach (BoneUsagePerLoD boneUsagePerLOD in bakerData.boneUsage.boneUsagesPerLoD)            {                if(boneUsagePerLOD.maxNumberOfBonesPerVertex < 1)                     sbErrors.AppendLine("Nbr of bones per vertex must be at least 1");            }                        errors = sbErrors.ToString();            return sbErrors.Length == 0;        }        private static bool hasParent(Transform child, Transform parent)        {            if (child.parent == null) return false;            if (child.parent.gameObject == parent.gameObject) return true;            else return hasParent(child.parent, parent);        }        public static GameObject GenerateAnimationObject(string assetPath,             GpuEcsAnimationBakerData bakerData, string animatorName, string generatedAssetsFolder,             string nameSuffixAsset = "_GpuEcsAnimator",            string nameSuffixAnimationIDsEnum = "_AnimationIDs",            string nameSuffixAnimationInitializerBehaviour = "_AnimationInitializerBehaviour",            string nameSuffixAnimationEventIDsEnum = "_AnimationEventIDs",            string nameSuffixAnimationAnchorIDsEnum = "_AttachmentAnchorIDs",            string nameSuffixAttachmentInitializerBehaviour = "_AttachmentInitializerBehaviour",            string meshPartSuffix = "Mesh",            string animationMatricesTexturePartSuffix = "AnimationMatricesTexture",             string materialPartSuffix = "Material"            )        {            GameObject sourceModel = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);            GameObject refModel = PrefabUtility.LoadPrefabContents(assetPath);            Debug.Log($"Generating Animation object for {assetPath}");            GameObject animationObject = GenerateAnimationObjectFromModel(refModel, sourceModel, bakerData, animatorName, generatedAssetsFolder,                nameSuffixAsset, nameSuffixAnimationIDsEnum, nameSuffixAnimationInitializerBehaviour, nameSuffixAnimationEventIDsEnum,                nameSuffixAnimationAnchorIDsEnum, nameSuffixAttachmentInitializerBehaviour, meshPartSuffix, animationMatricesTexturePartSuffix, materialPartSuffix);            PrefabUtility.UnloadPrefabContents(refModel);                        return animationObject;        }                public static GameObject GenerateAnimationObjectFromModel(GameObject refModel, GameObject sourceModel,             GpuEcsAnimationBakerData bakerData, string animatorName, string generatedAssetsFolder,            string nameSuffixAsset = "_GpuEcsAnimator",            string nameSuffixAnimationIDsEnum = "_AnimationIDs",            string nameSuffixAnimationInitializerBehaviour = "_AnimationInitializerBehaviour",            string nameSuffixAnimationEventIDsEnum = "_AnimationEventIDs",            string nameSuffixAnimationAnchorIDsEnum = "_AttachmentAnchorIDs",            string nameSuffixAttachmentInitializerBehaviour = "_AttachmentInitializerBehaviour",            string meshPartSuffix = "Mesh",            string animationMatricesTexturePartSuffix = "AnimationMatricesTexture",             string materialPartSuffix = "Material"            )         {            if(!ValidateAnimationBakerData(bakerData, sourceModel, out string errors))            {                Debug.LogError(errors);                return null;            }            string targetAssetPath =                Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAsset}.prefab");            string targetAnimationEnumAssetPath =                Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAnimationIDsEnum}.cs");            string targetAnimationInitializerBehaviourAssetPath =                Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAnimationInitializerBehaviour}.cs");            string targetAnimationEventEnumAssetPath =                Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAnimationEventIDsEnum}.cs");            string targetAttachmentAnchorEnumAssetPath =                Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAnimationAnchorIDsEnum}.cs");            string targetAttachmentInitializerBehaviourAssetPath =                Path.Combine(generatedAssetsFolder, $"{animatorName}{nameSuffixAttachmentInitializerBehaviour}.cs");                        refModel.transform.position = Vector3.zero;            refModel.transform.rotation = quaternion.identity;            Animator refModelAnimator = refModel.GetComponent<Animator>();            refModelAnimator.cullingMode = AnimatorCullingMode.AlwaysAnimate;            LODGroup refModelLoDGroup = refModel.GetComponent<LODGroup>();            LOD[] refModelLoDs = refModelLoDGroup == null ? null : refModelLoDGroup.GetLODs();            List<AnimationMatricesTexture> animationMatricesTexturesCache = new List<AnimationMatricesTexture>();            List<EcsGpuMaterial> ecsGpuMaterialsCache = new List<EcsGpuMaterial>();            GameObject test = AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);            if (test == null)            {                GameObject empty = new GameObject(refModel.name);                PrefabUtility.SaveAsPrefabAsset(empty, targetAssetPath);                GameObject.DestroyImmediate(empty, allowDestroyingAssets:true);            }            GameObject target = PrefabUtility.LoadPrefabContents(targetAssetPath);            target.transform.localScale = refModel.transform.localScale;            GameObject[] toDestroy = new GameObject[target.transform.childCount];            for (int childIndex = 0; childIndex < target.transform.childCount; childIndex++)                toDestroy[childIndex] = target.transform.GetChild(childIndex).gameObject;            foreach(GameObject go in toDestroy) Object.DestroyImmediate(go, allowDestroyingAssets: true);            RemoveComponent<MeshRenderer>(target);            RemoveComponent<MeshFilter>(target);            RemoveComponent<LODGroup>(target);            RemoveComponent<GpuEcsAnimatorBehaviour>(target);            RemoveComponent<GpuEcsAnimatedMeshBehaviour>(target);                        GpuEcsAnimatorBehaviour gpuEcsAnimator = AddGpuEcsAnimationBehaviour(target, bakerData, out string[] eventNames);            gpuEcsAnimator.transformUsageFlags = bakerData.transformUsageFlagsParent;            Dictionary<GameObject, GameObject> sourceToTargetMapping = new Dictionary<GameObject, GameObject>();            ProcessAnimationObjectRecursive(refModel, target, sourceToTargetMapping, animatorName,                meshPartSuffix, animationMatricesTexturePartSuffix, materialPartSuffix,                refModelAnimator, refModelLoDs, animationMatricesTexturesCache, ecsGpuMaterialsCache,                bakerData, gpuEcsAnimator.animations, gpuEcsAnimator.totalNbrOfFrames, generatedAssetsFolder, gpuEcsAnimator);            if (refModelLoDs != null)            {                LODGroup targetLoDGroup = target.AddComponent<LODGroup>();                LOD[] targetLoDs = new LOD[refModelLoDs.Length];                for (int lodIndex = 0; lodIndex < refModelLoDs.Length; lodIndex++)                {                    targetLoDs[lodIndex] = refModelLoDs[lodIndex];                    Renderer[] refModelRenderers = refModelLoDs[lodIndex].renderers;                    for (int rendererIndex = 0; rendererIndex < refModelRenderers.Length; rendererIndex++)                    {                        targetLoDs[lodIndex].renderers[rendererIndex] =                            sourceToTargetMapping[refModelRenderers[rendererIndex].gameObject].GetComponent<MeshRenderer>();                    }                }                targetLoDGroup.SetLODs(targetLoDs);            }                        if(bakerData.generateAnimationIdsEnum)                GenerateAnimationsEnumCode(bakerData, targetAnimationEnumAssetPath, targetAnimationInitializerBehaviourAssetPath);                        if(bakerData.generateAnimationEventIdsEnum)                GenerateAnimationEventsEnumCode(bakerData, targetAnimationEventEnumAssetPath, eventNames );            gpuEcsAnimator.nbrOfAttachmentAnchors = bakerData.attachmentAnchors.Length;            gpuEcsAnimator.attachmentAnchorData = BakeAttachmentAnchorTransforms(generatedAssetsFolder, sourceModel, animatorName,                 refModelAnimator, bakerData, gpuEcsAnimator.animations, gpuEcsAnimator.totalNbrOfFrames,gpuEcsAnimator);                        if(bakerData.generateAttachmentAnchorIdsEnum)                GenerateAttachmentAnchorsEnumCode(bakerData, targetAttachmentAnchorEnumAssetPath, targetAttachmentInitializerBehaviourAssetPath);                        PrefabUtility.SaveAsPrefabAsset(target, targetAssetPath);            PrefabUtility.UnloadPrefabContents(target);                        return AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);        }        private const string enumFileTemplate = "namespace GPUECSAnimationBaker.Engine.AnimatorSystem\n"                                                + "{\n"                                                + "    public enum @ENUMNAME@\n"                                                + "    {\n"                                                + "@IDLIST@\n"                                                + "    }\n"                                                + "}";        private const string behaviourFileTemplate = "namespace GPUECSAnimationBaker.Engine.AnimatorSystem\n"                                                + "{\n"                                                + "    public class @CLASSNAME_@ENUMNAME@_Behaviour : @CLASSNAMEBehaviour<@ENUMNAME@> { } \n"                                                + "}";                private static void GenerateAnimationsEnumCode(GpuEcsAnimationBakerData bakerData, string targetEnumAssetPath, string targetEnumBehaviourAssetPath)        {            GenerateEnumCodeFiles<AnimationData>($"AnimationIds{bakerData.animationIdsEnumName}", "GpuEcsAnimatorInitializer",                bakerData.animations, (animationData) => animationData.animationID, targetEnumAssetPath, targetEnumBehaviourAssetPath);        }        private static void GenerateAnimationEventsEnumCode(GpuEcsAnimationBakerData bakerData, string targetEnumAssetPath, string[] eventNames)        {            GenerateEnumCodeFiles<string>($"AnimationEventIds{bakerData.animationEventIdsEnumName}", null,                eventNames, (name) => name, targetEnumAssetPath, null);        }                private static void GenerateAttachmentAnchorsEnumCode(GpuEcsAnimationBakerData bakerData, string targetEnumAssetPath, string targetEnumBehaviourAssetPath)        {            GenerateEnumCodeFiles<AttachmentAnchor>($"AnchorIds{bakerData.attachmentAnchorIdsEnumName}", "GpuEcsAttachmentInitializer",                 bakerData.attachmentAnchors, (attachmentAnchor) => attachmentAnchor.attachmentAnchorID, targetEnumAssetPath, targetEnumBehaviourAssetPath);        }                private static void GenerateEnumCodeFiles<T>(string enumName, string className, T[] list, System.Func<T, string> listIdGetter,             string targetEnumAssetPath, string targetEnumBehaviourAssetPath)        {            StringBuilder idList = new StringBuilder();            for (int index = 0; index < list.Length; index++)            {                idList.Append($"        {listIdGetter(list[index])} = {index.ToString()}");                if (index < list.Length - 1)                {                    idList.AppendLine(",");                }            }                        string enumCodeText = enumFileTemplate                .Replace("@ENUMNAME@", enumName)                .Replace("@IDLIST@", idList.ToString());            string enumCodePath = Path.Combine(Application.dataPath, "../" + targetEnumAssetPath);             File.WriteAllText(enumCodePath, enumCodeText);            if (targetEnumBehaviourAssetPath != null)            {                string enumBehaviourText = behaviourFileTemplate                    .Replace("@CLASSNAME", className)                    .Replace("@ENUMNAME@", enumName);                string enumBehaviourPath = Path.Combine(Application.dataPath, "../" + targetEnumBehaviourAssetPath);                File.WriteAllText(enumBehaviourPath, enumBehaviourText);            }            AssetDatabase.Refresh();        }        private static GpuEcsAnimatorBehaviour AddGpuEcsAnimationBehaviour(GameObject target,            GpuEcsAnimationBakerData bakerData, out string[] eventNames)        {            GpuEcsAnimatorBehaviour gpuEcsAnimator = target.AddComponent<GpuEcsAnimatorBehaviour>();            gpuEcsAnimator.animations = new GpuEcsAnimationData[bakerData.animations.Length];            // gpuEcsAnimator.BackAnimationDatas= new AnimationData[bakerData.animations.Length];            List<GpuEcsAnimationEventOccurence> occurences = new List<GpuEcsAnimationEventOccurence>();            List<string> foundEvents = new List<string>();            int currentFrameIndex = 0;            int currentEventOccurenceId = 0;            int currentFoundEventId = 0;            for (int animationIndex = 0; animationIndex < bakerData.animations.Length; animationIndex++)            {                AnimationData animationData = bakerData.animations[animationIndex];                int nbrOfFramesPerSample = 0;                int nbrOfInBetweenSamples = 0;                float blendTimeCorrection = 1;                int startEventOccurenceId = currentEventOccurenceId;                int nbrOfEventOccurenceIds = 0;                if (animationData.animationType == AnimationTypes.SingleClip)                {                    SingleClipData singleClipData = animationData.singleClipData;                    nbrOfFramesPerSample = (int)(singleClipData.animationClip.length * GlobalConstants.SampleFrameRate) + 1;                    nbrOfInBetweenSamples = 1;                    blendTimeCorrection = 1;                    AddAnimationEvents(bakerData, animationData, occurences, ref currentEventOccurenceId, ref nbrOfEventOccurenceIds, foundEvents,                         ref currentFoundEventId, singleClipData.animationClip);                }                else if (animationData.animationType == AnimationTypes.DualClipBlend)                {                    DualClipBlendData dualClipBlendData = animationData.dualClipBlendData;                    int clip1NbrOfFrames = (int)(dualClipBlendData.clip1.animationClip.length * GlobalConstants.SampleFrameRate) + 1;                    int clip2NbrOfFrames = (int)(dualClipBlendData.clip2.animationClip.length * GlobalConstants.SampleFrameRate) + 1;                    nbrOfFramesPerSample = math.max(clip1NbrOfFrames, clip2NbrOfFrames);                    blendTimeCorrection = dualClipBlendData.clip1.animationClip.length / dualClipBlendData.clip2.animationClip.length;                    nbrOfInBetweenSamples = dualClipBlendData.nbrOfInBetweenSamples;                    AddAnimationEvents(bakerData, animationData, occurences, ref currentEventOccurenceId, ref nbrOfEventOccurenceIds, foundEvents,                         ref currentFoundEventId, dualClipBlendData.clip1.animationClip);                    AddAnimationEvents(bakerData, animationData, occurences, ref currentEventOccurenceId, ref nbrOfEventOccurenceIds, foundEvents,                         ref currentFoundEventId, dualClipBlendData.clip2.animationClip);                }                bool loop = animationData.loop;                                gpuEcsAnimator.animations[animationIndex] = new GpuEcsAnimationData()                {                    startFrameIndex = currentFrameIndex,                    nbrOfFramesPerSample = nbrOfFramesPerSample,                    nbrOfInBetweenSamples = nbrOfInBetweenSamples,                    blendTimeCorrection = blendTimeCorrection,                    startEventOccurenceId = startEventOccurenceId,                    nbrOfEventOccurenceIds = nbrOfEventOccurenceIds,                    loop = loop,                    animationID = animationData.animationID,                    stateName = animationData.animatorStateName                };                currentFrameIndex += nbrOfFramesPerSample * nbrOfInBetweenSamples;            }            gpuEcsAnimator.totalNbrOfFrames = currentFrameIndex;            gpuEcsAnimator.animationEventOccurences = occurences.ToArray();            eventNames = bakerData.usePredefinedAnimationEventIds ? bakerData.predefinedAnimationEventIds : foundEvents.ToArray();            return gpuEcsAnimator;        }        private static void AddAnimationEvents(GpuEcsAnimationBakerData bakerData, AnimationData animationData,             List<GpuEcsAnimationEventOccurence> occurences,             ref int currentEventOccurenceId, ref int nbrOfEventOccurenceIds,             List<string> foundEvents, ref int currentFoundEventId, AnimationClip animationClip)        {            AnimationEvent[] animationEvents = AnimationUtility.GetAnimationEvents(animationClip);            foreach (AnimationEvent animationEvent in animationEvents)            {                int eventId = -1;                if (bakerData.usePredefinedAnimationEventIds)                {                    eventId = bakerData.predefinedAnimationEventIds.ToList().FindIndex((n) => n.Equals(animationEvent.stringParameter));                    if(eventId == -1) Debug.LogWarning(                        $"Found event {animationEvent.stringParameter} that is not in the predefined event Ids list, so it will be ignored.");                }                else                {                    string name = $"{animationData.animationID}_{animationEvent.stringParameter}";                    eventId = foundEvents.FindIndex((n) => n.Equals(name));                    if (eventId == -1)                    {                        eventId = currentFoundEventId;                        foundEvents.Add(name);                        currentFoundEventId++;                    }                }                if (eventId != -1)                {                    occurences.Add(new GpuEcsAnimationEventOccurence()                    {                        eventId = eventId,                        eventNormalizedTime = animationEvent.time / animationClip.length,                    });                    currentEventOccurenceId++;                    nbrOfEventOccurenceIds++;                }            }        }                private static void ProcessAnimationObjectRecursive(GameObject sourceNode, GameObject targetNode,             Dictionary<GameObject, GameObject> sourceToTargetMapping, string animatorName, string meshPartSuffix,            string animationMatricesTexturePartSuffix, string materialPartSuffix,            Animator refModelAnimator, LOD[] refModelLoDs,             List<AnimationMatricesTexture> animationMatricesTexturesCache, List<EcsGpuMaterial> ecsGpuMaterialsCache,             GpuEcsAnimationBakerData bakerData, GpuEcsAnimationData[] animations, int totalNbrOfFrames,            string generatedAssetsFolder, GpuEcsAnimatorBehaviour gpuEcsAnimator)        {            sourceToTargetMapping.Add(sourceNode, targetNode);            SkinnedMeshRenderer existingSkinnedMeshRenderer = sourceNode.GetComponent<SkinnedMeshRenderer>();            if (existingSkinnedMeshRenderer != null)            {                Texture2D animationMatricesTexture = BakeAndSaveAnimationMatricesTexture(                    animationMatricesTexturesCache, generatedAssetsFolder, animatorName, animationMatricesTexturePartSuffix,                    refModelAnimator, bakerData, existingSkinnedMeshRenderer, animations, totalNbrOfFrames);                Mesh newMesh = BakeAndSaveBoneWeightsIntoMesh(refModelLoDs, generatedAssetsFolder, animatorName,                    meshPartSuffix, bakerData, existingSkinnedMeshRenderer);                MeshFilter meshFilter = targetNode.AddComponent<MeshFilter>();                MeshRenderer meshRenderer = targetNode.AddComponent<MeshRenderer>();                Material newMaterial = CreateAndSaveEcsGpuMaterial(ecsGpuMaterialsCache, generatedAssetsFolder,                    animatorName, materialPartSuffix, existingSkinnedMeshRenderer);                newMaterial.SetTexture(AnimatedBoneMatrices, animationMatricesTexture);                meshRenderer.sharedMaterial = newMaterial;                meshFilter.sharedMesh = newMesh;                GpuEcsAnimatedMeshBehaviour gpuEcsAnimatedMesh = targetNode.AddComponent<GpuEcsAnimatedMeshBehaviour>();                gpuEcsAnimatedMesh.animator = gpuEcsAnimator;                gpuEcsAnimatedMesh.transformUsageFlags = bakerData.transformUsageFlagsChildren;            }            for (int childIndex = 0; childIndex < sourceNode.transform.childCount; childIndex++)            {                GameObject sourceChild = sourceNode.transform.GetChild(childIndex).gameObject;                if (sourceChild.gameObject.activeSelf && !CheckEmptyRecursive(sourceChild))                {                    GameObject targetChild = new GameObject(sourceChild.name);                    targetChild.transform.parent = targetNode.transform;                    targetChild.transform.localPosition = sourceChild.transform.localPosition;                    targetChild.transform.localRotation = sourceChild.transform.localRotation;                    targetChild.transform.localScale = sourceChild.transform.localScale;                    ProcessAnimationObjectRecursive(sourceChild, targetChild, sourceToTargetMapping,                        animatorName, meshPartSuffix, animationMatricesTexturePartSuffix, materialPartSuffix,                        refModelAnimator, refModelLoDs, animationMatricesTexturesCache, ecsGpuMaterialsCache,                        bakerData, animations, totalNbrOfFrames, generatedAssetsFolder, gpuEcsAnimator);                }            }        }        private static void RemoveComponent<T>(GameObject gameObject)  where T : Component         {            T component = gameObject.GetComponent<T>();            if(component != null) Object.DestroyImmediate(component, allowDestroyingAssets:true);        }                private static bool CheckEmptyRecursive(GameObject node)        {            Component[] components = node.GetComponents<Component>();            bool empty = !components.Any(c => (c is SkinnedMeshRenderer));            if (empty)            {                for (int childIndex = 0; childIndex < node.transform.childCount; childIndex++)                {                    empty = CheckEmptyRecursive(node.transform.GetChild(childIndex).gameObject);                    if (!empty) break;                }            }            return empty;        }                private static Mesh BakeAndSaveBoneWeightsIntoMesh(LOD[] refModelLoDs,            string generatedAssetsFolder, string animatorName, string meshPartSuffix, GpuEcsAnimationBakerData bakerData,            SkinnedMeshRenderer skinnedMeshRenderer)        {            int maxNumberOfBonesPerVertex = GetMaxNumberOfBonesPerVertex(refModelLoDs, bakerData, skinnedMeshRenderer);            Mesh newMesh = BakeBoneWeightsIntoMesh(skinnedMeshRenderer.sharedMesh, maxNumberOfBonesPerVertex);            SavePartAsAsset(newMesh, generatedAssetsFolder, animatorName, skinnedMeshRenderer, meshPartSuffix, "mesh");            return newMesh;        }        private static Texture2D BakeAndSaveAnimationMatricesTexture(List<AnimationMatricesTexture> cache,            string generatedAssetsFolder, string animatorName, string animationMatricesTexturePartSuffix,            Animator refModelAnimator, GpuEcsAnimationBakerData bakerData,            SkinnedMeshRenderer skinnedMeshRenderer, GpuEcsAnimationData[] animations, int totalNbrOfFrames)        {            if (!CheckAnimationMatricesTextureInCache(cache, skinnedMeshRenderer, out Texture2D animationMatricesTexture))            {                animationMatricesTexture = BakeAnimationMatricesTexture(skinnedMeshRenderer, refModelAnimator, bakerData,                    animations, totalNbrOfFrames);                SavePartAsAsset(animationMatricesTexture, generatedAssetsFolder, animatorName, skinnedMeshRenderer,                    animationMatricesTexturePartSuffix, "asset");                                    cache.Add(new AnimationMatricesTexture()                {                    texture = animationMatricesTexture,                    skinnedMeshRenderer = skinnedMeshRenderer                });            }            return animationMatricesTexture;        }        private static Material CreateAndSaveEcsGpuMaterial(List<EcsGpuMaterial> cache,            string generatedAssetsFolder, string animatorName, string materialPartSuffix,            SkinnedMeshRenderer skinnedMeshRenderer)        {            if (!CheckEcsGpuMaterialInCache(cache, skinnedMeshRenderer, out Material ecsGpuMaterial))            {                ecsGpuMaterial = Object.Instantiate<Material>(skinnedMeshRenderer.sharedMaterial);                ecsGpuMaterial.shader = Shader.Find("Shader Graphs/GPUECSAnimator_URP_SimpleShader");                ecsGpuMaterial.SetFloat(EnableAnimation, 0);                ecsGpuMaterial.enableInstancing = true;                SavePartAsAsset(ecsGpuMaterial, generatedAssetsFolder, animatorName, skinnedMeshRenderer,                    materialPartSuffix, "mat");                cache.Add(new EcsGpuMaterial()                {                    material = ecsGpuMaterial,                    skinnedMeshRenderer = skinnedMeshRenderer                });            }            return ecsGpuMaterial;        }        private static string GenerateAssetPath(string generatedAssetsFolder, string animatorName,            string assetName, string assetFileExtension)        {            return Path.Combine(generatedAssetsFolder, $"{animatorName}_{assetName}.{assetFileExtension}");        }                private static void SavePartAsAsset(Object asset, string generatedAssetsFolder, string animatorName,            SkinnedMeshRenderer skinnedMeshRenderer, string partName, string assetFileExtension)        {            string assetName = $"{partName}_{skinnedMeshRenderer.name}";            string assetPath = GenerateAssetPath(generatedAssetsFolder, animatorName, assetName, assetFileExtension);            AssetDatabase.CreateAsset(asset, assetPath);        }                private static int GetMaxNumberOfBonesPerVertex(LOD[] refModelLoDs, GpuEcsAnimationBakerData bakerData,            SkinnedMeshRenderer skinnedMeshRenderer)        {            int maxNumberOfBonesPerVertex = bakerData.boneUsage.numberOfBonesPerVertex;            if (refModelLoDs != null)            {                for (int lodIndex = 0; lodIndex < refModelLoDs.Length; lodIndex++)                {                    if (refModelLoDs[lodIndex].renderers.Any(r => r == (Renderer)skinnedMeshRenderer))                    {                        BoneUsagePerLoD boneUsagePerLoD = bakerData.boneUsage.boneUsagesPerLoD                            .SingleOrDefault(b => b.lodIndex == lodIndex);                        if (boneUsagePerLoD != null)                            maxNumberOfBonesPerVertex = boneUsagePerLoD.maxNumberOfBonesPerVertex;                        break;                    }                }            }            return maxNumberOfBonesPerVertex;        }        private static bool CheckAnimationMatricesTextureInCache(List<AnimationMatricesTexture> cache,            SkinnedMeshRenderer skinnedMeshRenderer, out Texture2D foundTexture)        {            bool found = false;            foundTexture = null;             foreach (AnimationMatricesTexture cachedTexture in cache)            {                if (CheckSkinnedMeshRendererHasSameBones(cachedTexture.skinnedMeshRenderer,                        skinnedMeshRenderer))                {                    found = true;                    foundTexture = cachedTexture.texture;                    break;                }            }            return found;        }        private static bool CheckEcsGpuMaterialInCache(List<EcsGpuMaterial> cache,            SkinnedMeshRenderer skinnedMeshRenderer, out Material foundMaterial)        {            bool found = false;            foundMaterial = null;            foreach (EcsGpuMaterial cachedEcsGpuMaterial in cache)            {                if (cachedEcsGpuMaterial.skinnedMeshRenderer.sharedMaterial == skinnedMeshRenderer.sharedMaterial                    && CheckSkinnedMeshRendererHasSameBones(cachedEcsGpuMaterial.skinnedMeshRenderer,                        skinnedMeshRenderer))                {                    found = true;                    foundMaterial = cachedEcsGpuMaterial.material;                    break;                }            }            return found;        }                private static bool CheckSkinnedMeshRendererHasSameBones(SkinnedMeshRenderer skinnedMeshRendererA,            SkinnedMeshRenderer skinnedMeshRendererB)        {            bool hasSameBones = false;            if (skinnedMeshRendererA.bones.Length == skinnedMeshRendererB.bones.Length)            {                hasSameBones = true;                for (int i = 0; i < skinnedMeshRendererA.bones.Length; i++)                {                    if (!CheckMatrixEquality(skinnedMeshRendererA.bones[i].localToWorldMatrix,                            skinnedMeshRendererB.bones[i].localToWorldMatrix))                    {                        hasSameBones = false; break;                    }                    if (!CheckMatrixEquality(skinnedMeshRendererA.sharedMesh.bindposes[i],                            skinnedMeshRendererB.sharedMesh.bindposes[i]))                    {                        hasSameBones = false; break;                    }                }            }            return hasSameBones;        }        private static bool CheckMatrixEquality(Matrix4x4 m1, Matrix4x4 m2)        {            return m1.m00 == m2.m00 && m1.m01 == m2.m01 && m1.m02 == m2.m02 && m1.m03 == m2.m03                   && m1.m10 == m2.m10 && m1.m11 == m2.m11 && m1.m12 == m2.m12 && m1.m13 == m2.m13                   && m1.m20 == m2.m20 && m1.m21 == m2.m21 && m1.m22 == m2.m22 && m1.m23 == m2.m23                   && m1.m30 == m2.m30 && m1.m31 == m2.m31 && m1.m32 == m2.m32 && m1.m33 == m2.m33;        }        private static void IterateOverAllFramesThroughAnimator(            Animator refModelAnimator, GpuEcsAnimationBakerData bakerData, GpuEcsAnimationData[] animations,            System.Action<int> actionPerFrame)        {            refModelAnimator.speed = 0;            int currentFrameIndex = 0;            for (int animationIndex = 0; animationIndex < bakerData.animations.Length; animationIndex++)            {                AnimationData animationData = bakerData.animations[animationIndex];                GpuEcsAnimationData gpuEcsAnimationData = animations[animationIndex];                foreach (AnimatorParameter parameterValue in animationData.additionalAnimatorParameterValues)                {                    if(parameterValue.parameterType == AnimatorParameterTypes.Bool)                        refModelAnimator.SetBool(parameterValue.parameterName, parameterValue.boolValue);                    else if(parameterValue.parameterType == AnimatorParameterTypes.Float)                        refModelAnimator.SetFloat(parameterValue.parameterName, parameterValue.floatValue);                    else if(parameterValue.parameterType == AnimatorParameterTypes.Integer)                        refModelAnimator.SetInteger(parameterValue.parameterName, parameterValue.intValue);                }                                for (int sampleIndex = 0; sampleIndex < gpuEcsAnimationData.nbrOfInBetweenSamples; sampleIndex++)                {                    if (animationData.animationType == AnimationTypes.DualClipBlend)                    {                        DualClipBlendData dualClipBlendData = animationData.dualClipBlendData;                        float sampleRatio = (float)sampleIndex / (float)(gpuEcsAnimationData.nbrOfInBetweenSamples - 1);                        float blendValue = dualClipBlendData.clip1.parameterValue +                            (dualClipBlendData.clip2.parameterValue - dualClipBlendData.clip1.parameterValue) * sampleRatio;                        refModelAnimator.SetFloat(dualClipBlendData.blendParameterName, blendValue);                    }                    for (int frameIndex = 0; frameIndex < gpuEcsAnimationData.nbrOfFramesPerSample; frameIndex++)                    {                        float progressRatio = (float)frameIndex / (float)(gpuEcsAnimationData.nbrOfFramesPerSample - 1);                        refModelAnimator.Play(animationData.animatorStateName, -1, progressRatio);                        foreach(AnimatorState animatorState in animationData.additionalAnimatorStatesPerLayer)                            refModelAnimator.Play(animatorState.stateName, animatorState.layer, progressRatio);                        refModelAnimator.Update(0);                        actionPerFrame(currentFrameIndex);                                                currentFrameIndex++;                    }                }            }        }        private static GpuEcsAttachmentAnchorData BakeAttachmentAnchorTransforms(string generatedAssetsFolder, GameObject sourceModel,            string animatorName, Animator refModelAnimator, GpuEcsAnimationBakerData bakerData, GpuEcsAnimationData[] animations,            int totalNbrOfFrames,GpuEcsAnimatorBehaviour gpuEcsAnimator)        {            int nbrOfAttachments = bakerData.attachmentAnchors.Length;            if (nbrOfAttachments == 0) return null;            else            {                GpuEcsAttachmentAnchorData gpuEcsAttachmentAnchorData = ScriptableObject.CreateInstance<GpuEcsAttachmentAnchorData>();                gpuEcsAttachmentAnchorData.anchorTransforms = new float4x4[totalNbrOfFrames * nbrOfAttachments];                gpuEcsAnimator.anchorNames.Clear();                for (int attachmentAnchorIndex = 0; attachmentAnchorIndex < nbrOfAttachments; attachmentAnchorIndex++)                {                    AttachmentAnchor attachmentAnchor = bakerData.attachmentAnchors[attachmentAnchorIndex];                    gpuEcsAnimator.anchorNames.Add(attachmentAnchor.attachmentAnchorID);                    Transform referenceAnchorTransform = attachmentAnchor.attachmentAnchorTransform;                    Stack<int> siblingIndexStack = new Stack<int>();                    GetSiblingIndexStack(referenceAnchorTransform, sourceModel.transform, siblingIndexStack);                    Transform anchorTransform = refModelAnimator.transform;                    while (siblingIndexStack.Count > 0)                        anchorTransform = anchorTransform.GetChild(siblingIndexStack.Pop());                    Debug.Assert(anchorTransform.name.Equals(referenceAnchorTransform.name));                    int baseIndex = attachmentAnchorIndex * totalNbrOfFrames;                    IterateOverAllFramesThroughAnimator(refModelAnimator, bakerData, animations,                        (currentFrameIndex) =>                        {                            gpuEcsAttachmentAnchorData.anchorTransforms[baseIndex + currentFrameIndex] = anchorTransform.localToWorldMatrix;                        });                }                string assetPath = GenerateAssetPath(generatedAssetsFolder, animatorName, "AttachmentAnchors", "asset");                AssetDatabase.CreateAsset(gpuEcsAttachmentAnchorData, assetPath);                return gpuEcsAttachmentAnchorData;            }        }        private static void GetSiblingIndexStack(Transform current, Transform root, Stack<int> currentStack)        {            if(current.gameObject == root.gameObject) return;            currentStack.Push(current.GetSiblingIndex());            GetSiblingIndexStack(current.parent, root, currentStack);        }                private static Texture2D BakeAnimationMatricesTexture(SkinnedMeshRenderer skinnedMeshRenderer,             Animator refModelAnimator, GpuEcsAnimationBakerData bakerData, GpuEcsAnimationData[] animations,            int totalNbrOfFrames)        {            int boneCount = skinnedMeshRenderer.bones.Length;            int animationMatricesTextureWidth = boneCount * 3;            NativeArray<half4> animatedBoneMatricesTextureData = new NativeArray<half4>(                totalNbrOfFrames * animationMatricesTextureWidth, Allocator.Temp);            float4x4 invSkinnedMeshRendererLocalToWorld =  math.inverse(skinnedMeshRenderer.transform.localToWorldMatrix);            IterateOverAllFramesThroughAnimator(refModelAnimator, bakerData, animations,                (currentFrameIndex) =>                {                    for (int boneIndex = 0; boneIndex < boneCount; boneIndex++)                    {                        Transform boneTransform = skinnedMeshRenderer.bones[boneIndex];                        if (boneTransform != null)                        {                            float4x4 matrix = math.mul(                                math.mul(invSkinnedMeshRendererLocalToWorld, boneTransform.localToWorldMatrix),                                skinnedMeshRenderer.sharedMesh.bindposes[boneIndex]                            );                            Debug.Assert(Mathf.Approximately(matrix[0][3], 0f)                                && Mathf.Approximately(matrix[1][3], 0f)                                && Mathf.Approximately(matrix[2][3], 0f)                                && Mathf.Approximately(matrix[3][3], 1f), "matrix row 4 must be 0,0,0,1");                            int matrixIndex = (currentFrameIndex * animationMatricesTextureWidth) + (boneIndex * 3);                            for (int i = 0; i < 3; i++)                                animatedBoneMatricesTextureData[matrixIndex + i] = new half4(                                    (half)matrix[0][i], (half)matrix[1][i], (half)matrix[2][i], (half)matrix[3][i]);                        }                    }                });            Texture2D animationMatricesTexture = new Texture2D(animationMatricesTextureWidth, totalNbrOfFrames,                 TextureFormat.RGBAHalf, false);            animationMatricesTexture.SetPixelData(animatedBoneMatricesTextureData, 0);            animationMatricesTexture.Apply();            animatedBoneMatricesTextureData.Dispose();            return animationMatricesTexture;        }                private static Mesh BakeBoneWeightsIntoMesh(Mesh sourceMesh, int maxNumberOfBonesPerVertex)        {            Mesh mesh = GameObject.Instantiate(sourceMesh);                        int vertexCount = mesh.vertices.Length;            NativeArray<Vector4> uvs1 = new NativeArray<Vector4>(vertexCount, Allocator.Temp);            NativeArray<Vector4> uvs2 = new NativeArray<Vector4>(vertexCount, Allocator.Temp);            NativeArray<Vector4> uvs3 = new NativeArray<Vector4>(vertexCount, Allocator.Temp);            NativeArray<byte> bonesPerVertex = mesh.GetBonesPerVertex();            NativeArray<BoneWeight1> boneWeightsSource = mesh.GetAllBoneWeights();            int startBoneWeightIndex = 0;            for (int vertIndex = 0; vertIndex < vertexCount; vertIndex++)            {                float totalWeight = 0f;                float totalWeightCapped = 0f;                int numberOfBonesForThisVertex = bonesPerVertex[vertIndex];                int boneWeightIndexTemp = startBoneWeightIndex;                for (int i = 0; i < numberOfBonesForThisVertex; i++)                {                    BoneWeight1 currentBoneWeight = boneWeightsSource[boneWeightIndexTemp];                    totalWeight += currentBoneWeight.weight;                    if (i < maxNumberOfBonesPerVertex) totalWeightCapped += currentBoneWeight.weight;                    if (i > 0) Debug.Assert(boneWeightsSource[boneWeightIndexTemp - 1].weight >= currentBoneWeight.weight);                    boneWeightIndexTemp++;                }                Debug.Assert(Mathf.Approximately(1f, totalWeight));                float weightMultiplier = totalWeight / totalWeightCapped;                int nbrOfBonesToBake = math.min(maxNumberOfBonesPerVertex, numberOfBonesForThisVertex);                totalWeight = 0f;                boneWeightIndexTemp = startBoneWeightIndex;                float4 uv1 = float4.zero;                float4 uv2 = float4.zero;                float4 uv3 = float4.zero;                for (int i = 0; i < nbrOfBonesToBake; i++)                {                    BoneWeight1 currentBoneWeight = boneWeightsSource[boneWeightIndexTemp];                    float adjustedWeight = currentBoneWeight.weight * weightMultiplier;                     totalWeight += adjustedWeight;                    boneWeightIndexTemp++;                    if      (i == 0) uv1 = new float4(currentBoneWeight.boneIndex, adjustedWeight, uv1.z, uv1.w);                    else if (i == 1) uv1 = new float4(uv1.x, uv1. y, currentBoneWeight.boneIndex, adjustedWeight);                    else if (i == 2) uv2 = new float4(currentBoneWeight.boneIndex, adjustedWeight, uv2.z, uv2.w);                    else if (i == 3) uv2 = new float4(uv2.x, uv2. y, currentBoneWeight.boneIndex, adjustedWeight);                    else if (i == 4) uv3 = new float4(currentBoneWeight.boneIndex, adjustedWeight, uv3.z, uv3.w);                    else if (i == 5) uv3 = new float4(uv3.x, uv3. y, currentBoneWeight.boneIndex, adjustedWeight);                }                Debug.Assert(Mathf.Approximately(1f, totalWeight));                uvs1[vertIndex] = uv1;                uvs2[vertIndex] = uv2;                uvs3[vertIndex] = uv3;                startBoneWeightIndex += numberOfBonesForThisVertex;            }                        mesh.SetUVs(1, uvs1);            mesh.SetUVs(2, uvs2);            mesh.SetUVs(3, uvs3);                        uvs1.Dispose();            uvs2.Dispose();            uvs3.Dispose();            bonesPerVertex.Dispose();            boneWeightsSource.Dispose();            return mesh;        }    }}#endif
 |