/******************************************************************************  
 | 
 * Spine Runtimes License Agreement  
 | 
 * Last updated July 28, 2023. Replaces all prior versions.  
 | 
 *  
 | 
 * Copyright (c) 2013-2023, Esoteric Software LLC  
 | 
 *  
 | 
 * Integration of the Spine Runtimes into software or otherwise creating  
 | 
 * derivative works of the Spine Runtimes is permitted under the terms and  
 | 
 * conditions of Section 2 of the Spine Editor License Agreement:  
 | 
 * http://esotericsoftware.com/spine-editor-license  
 | 
 *  
 | 
 * Otherwise, it is permitted to integrate the Spine Runtimes into software or  
 | 
 * otherwise create derivative works of the Spine Runtimes (collectively,  
 | 
 * "Products"), provided that each user of the Products must obtain their own  
 | 
 * Spine Editor license and redistribution of the Products in any form must  
 | 
 * include this license and copyright notice.  
 | 
 *  
 | 
 * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY  
 | 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED  
 | 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE  
 | 
 * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY  
 | 
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  
 | 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,  
 | 
 * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND  
 | 
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  
 | 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE  
 | 
 * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
 | 
 *****************************************************************************/  
 | 
  
 | 
using Spine;  
 | 
using Spine.Unity;  
 | 
using System.Collections;  
 | 
using System.Collections.Generic;  
 | 
using UnityEngine;  
 | 
  
 | 
namespace Spine.Unity.Examples {  
 | 
  
 | 
    //[CreateAssetMenu(menuName = "Spine/SkeletonData Modifiers/Animation Match", order = 200)]  
 | 
    public class AnimationMatchModifierAsset : SkeletonDataModifierAsset {  
 | 
  
 | 
        public bool matchAllAnimations = true;  
 | 
  
 | 
        public override void Apply (SkeletonData skeletonData) {  
 | 
            if (matchAllAnimations)  
 | 
                AnimationTools.MatchAnimationTimelines(skeletonData.Animations, skeletonData);  
 | 
        }  
 | 
  
 | 
        public static class AnimationTools {  
 | 
  
 | 
            #region Filler Timelines  
 | 
            /// <summary>  
 | 
            /// Matches the animation timelines across the given set of animations.  
 | 
            /// This allows unkeyed properties to assume setup pose when animations are naively mixed using Animation.Apply.  
 | 
            /// </summary>  
 | 
            /// <param name="animations">An enumerable collection animations whose timelines will be matched.</param>  
 | 
            /// <param name="skeletonData">The SkeletonData where the animations belong.</param>  
 | 
            public static void MatchAnimationTimelines (IEnumerable<Spine.Animation> animations, SkeletonData skeletonData) {  
 | 
                if (animations == null) return;  
 | 
                if (skeletonData == null) throw new System.ArgumentNullException("skeletonData", "Timelines can't be matched without a SkeletonData source.");  
 | 
  
 | 
                // Build a reference collection of timelines to match  
 | 
                // and a collection of dummy timelines that can be used to fill-in missing items.  
 | 
                Dictionary<string, Timeline> timelineDictionary = new Dictionary<string, Spine.Timeline>();  
 | 
                foreach (Animation animation in animations) {  
 | 
                    foreach (Timeline timeline in animation.Timelines) {  
 | 
                        if (timeline is EventTimeline) continue;  
 | 
  
 | 
                        foreach (string propertyId in timeline.PropertyIds) {  
 | 
                            if (!timelineDictionary.ContainsKey(propertyId)) {  
 | 
                                timelineDictionary.Add(propertyId, GetFillerTimeline(timeline, skeletonData));  
 | 
                            }  
 | 
                        }  
 | 
                    }  
 | 
                }  
 | 
                List<string> idsToMatch = new List<string>(timelineDictionary.Keys);  
 | 
  
 | 
                // For each animation in the list, check for and add missing timelines.  
 | 
                HashSet<string> currentAnimationIDs = new HashSet<string>();  
 | 
                foreach (Animation animation in animations) {  
 | 
                    currentAnimationIDs.Clear();  
 | 
                    foreach (Timeline timeline in animation.Timelines) {  
 | 
                        if (timeline is EventTimeline) continue;  
 | 
                        foreach (string propertyId in timeline.PropertyIds) {  
 | 
                            currentAnimationIDs.Add(propertyId);  
 | 
                        }  
 | 
                    }  
 | 
  
 | 
                    ExposedList<Timeline> animationTimelines = animation.Timelines;  
 | 
                    foreach (string propertyId in idsToMatch) {  
 | 
                        if (!currentAnimationIDs.Contains(propertyId))  
 | 
                            animationTimelines.Add(timelineDictionary[propertyId]);  
 | 
                    }  
 | 
                }  
 | 
  
 | 
                // These are locals, but sometimes Unity's GC does weird stuff. So let's clean up.  
 | 
                timelineDictionary.Clear();  
 | 
                timelineDictionary = null;  
 | 
                idsToMatch.Clear();  
 | 
                idsToMatch = null;  
 | 
                currentAnimationIDs.Clear();  
 | 
                currentAnimationIDs = null;  
 | 
            }  
 | 
  
 | 
            static Timeline GetFillerTimeline (Timeline timeline, SkeletonData skeletonData) {  
 | 
                if (timeline is RotateTimeline)  
 | 
                    return GetFillerTimeline((RotateTimeline)timeline, skeletonData);  
 | 
                if (timeline is TranslateTimeline)  
 | 
                    return GetFillerTimeline((TranslateTimeline)timeline, skeletonData);  
 | 
                if (timeline is ScaleTimeline)  
 | 
                    return GetFillerTimeline((ScaleTimeline)timeline, skeletonData);  
 | 
                if (timeline is ShearTimeline)  
 | 
                    return GetFillerTimeline((ShearTimeline)timeline, skeletonData);  
 | 
                if (timeline is AttachmentTimeline)  
 | 
                    return GetFillerTimeline((AttachmentTimeline)timeline, skeletonData);  
 | 
                if (timeline is RGBATimeline)  
 | 
                    return GetFillerTimeline((RGBATimeline)timeline, skeletonData);  
 | 
                if (timeline is RGBA2Timeline)  
 | 
                    return GetFillerTimeline((RGBA2Timeline)timeline, skeletonData);  
 | 
                if (timeline is DeformTimeline)  
 | 
                    return GetFillerTimeline((DeformTimeline)timeline, skeletonData);  
 | 
                if (timeline is DrawOrderTimeline)  
 | 
                    return GetFillerTimeline((DrawOrderTimeline)timeline, skeletonData);  
 | 
                if (timeline is IkConstraintTimeline)  
 | 
                    return GetFillerTimeline((IkConstraintTimeline)timeline, skeletonData);  
 | 
                if (timeline is TransformConstraintTimeline)  
 | 
                    return GetFillerTimeline((TransformConstraintTimeline)timeline, skeletonData);  
 | 
                if (timeline is PathConstraintPositionTimeline)  
 | 
                    return GetFillerTimeline((PathConstraintPositionTimeline)timeline, skeletonData);  
 | 
                if (timeline is PathConstraintSpacingTimeline)  
 | 
                    return GetFillerTimeline((PathConstraintSpacingTimeline)timeline, skeletonData);  
 | 
                if (timeline is PathConstraintMixTimeline)  
 | 
                    return GetFillerTimeline((PathConstraintMixTimeline)timeline, skeletonData);  
 | 
                return null;  
 | 
            }  
 | 
  
 | 
            static RotateTimeline GetFillerTimeline (RotateTimeline timeline, SkeletonData skeletonData) {  
 | 
                RotateTimeline t = new RotateTimeline(1, 0, timeline.BoneIndex);  
 | 
                t.SetFrame(0, 0, 0);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static TranslateTimeline GetFillerTimeline (TranslateTimeline timeline, SkeletonData skeletonData) {  
 | 
                TranslateTimeline t = new TranslateTimeline(1, 0, timeline.BoneIndex);  
 | 
                t.SetFrame(0, 0, 0, 0);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static ScaleTimeline GetFillerTimeline (ScaleTimeline timeline, SkeletonData skeletonData) {  
 | 
                ScaleTimeline t = new ScaleTimeline(1, 0, timeline.BoneIndex);  
 | 
                t.SetFrame(0, 0, 0, 0);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static ShearTimeline GetFillerTimeline (ShearTimeline timeline, SkeletonData skeletonData) {  
 | 
                ShearTimeline t = new ShearTimeline(1, 0, timeline.BoneIndex);  
 | 
                t.SetFrame(0, 0, 0, 0);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static AttachmentTimeline GetFillerTimeline (AttachmentTimeline timeline, SkeletonData skeletonData) {  
 | 
                AttachmentTimeline t = new AttachmentTimeline(1, timeline.SlotIndex);  
 | 
                SlotData slotData = skeletonData.Slots.Items[t.SlotIndex];  
 | 
                t.SetFrame(0, 0, slotData.AttachmentName);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static RGBATimeline GetFillerTimeline (RGBATimeline timeline, SkeletonData skeletonData) {  
 | 
                RGBATimeline t = new RGBATimeline(1, 0, timeline.SlotIndex);  
 | 
                SlotData slotData = skeletonData.Slots.Items[t.SlotIndex];  
 | 
                t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static RGBA2Timeline GetFillerTimeline (RGBA2Timeline timeline, SkeletonData skeletonData) {  
 | 
                RGBA2Timeline t = new RGBA2Timeline(1, 0, timeline.SlotIndex);  
 | 
                SlotData slotData = skeletonData.Slots.Items[t.SlotIndex];  
 | 
                t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A, slotData.R2, slotData.G2, slotData.B2);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static DeformTimeline GetFillerTimeline (DeformTimeline timeline, SkeletonData skeletonData) {  
 | 
                DeformTimeline t = new DeformTimeline(1, 0, timeline.SlotIndex, timeline.Attachment);  
 | 
                if (t.Attachment.IsWeighted()) {  
 | 
                    t.SetFrame(0, 0, new float[t.Attachment.Vertices.Length]);  
 | 
                } else {  
 | 
                    t.SetFrame(0, 0, t.Attachment.Vertices.Clone() as float[]);  
 | 
                }  
 | 
  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static DrawOrderTimeline GetFillerTimeline (DrawOrderTimeline timeline, SkeletonData skeletonData) {  
 | 
                DrawOrderTimeline t = new DrawOrderTimeline(1);  
 | 
                t.SetFrame(0, 0, null); // null means use setup pose in DrawOrderTimeline.Apply.  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static IkConstraintTimeline GetFillerTimeline (IkConstraintTimeline timeline, SkeletonData skeletonData) {  
 | 
                IkConstraintTimeline t = new IkConstraintTimeline(1, 0, timeline.IkConstraintIndex);  
 | 
                IkConstraintData ikConstraintData = skeletonData.IkConstraints.Items[timeline.IkConstraintIndex];  
 | 
                t.SetFrame(0, 0, ikConstraintData.Mix, ikConstraintData.Softness, ikConstraintData.BendDirection, ikConstraintData.Compress, ikConstraintData.Stretch);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static TransformConstraintTimeline GetFillerTimeline (TransformConstraintTimeline timeline, SkeletonData skeletonData) {  
 | 
                TransformConstraintTimeline t = new TransformConstraintTimeline(1, 0, timeline.TransformConstraintIndex);  
 | 
                TransformConstraintData data = skeletonData.TransformConstraints.Items[timeline.TransformConstraintIndex];  
 | 
                t.SetFrame(0, 0, data.MixRotate, data.MixX, data.MixY, data.MixScaleX, data.MixScaleY, data.MixShearY);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static PathConstraintPositionTimeline GetFillerTimeline (PathConstraintPositionTimeline timeline, SkeletonData skeletonData) {  
 | 
                PathConstraintPositionTimeline t = new PathConstraintPositionTimeline(1, 0, timeline.PathConstraintIndex);  
 | 
                PathConstraintData data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex];  
 | 
                t.SetFrame(0, 0, data.Position);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static PathConstraintSpacingTimeline GetFillerTimeline (PathConstraintSpacingTimeline timeline, SkeletonData skeletonData) {  
 | 
                PathConstraintSpacingTimeline t = new PathConstraintSpacingTimeline(1, 0, timeline.PathConstraintIndex);  
 | 
                PathConstraintData data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex];  
 | 
                t.SetFrame(0, 0, data.Spacing);  
 | 
                return t;  
 | 
            }  
 | 
  
 | 
            static PathConstraintMixTimeline GetFillerTimeline (PathConstraintMixTimeline timeline, SkeletonData skeletonData) {  
 | 
                PathConstraintMixTimeline t = new PathConstraintMixTimeline(1, 0, timeline.PathConstraintIndex);  
 | 
                PathConstraintData data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex];  
 | 
                t.SetFrame(0, 0, data.RotateMix, data.MixX, data.MixY);  
 | 
                return t;  
 | 
            }  
 | 
            #endregion  
 | 
        }  
 | 
  
 | 
    }  
 | 
  
 | 
}  
 |