From 97756d69217d3f3d1776f57ffc545da7aec8bcec Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Mon, 17 Oct 2016 11:27:36 +0200 Subject: [PATCH 1/8] Added version placeholder for building JAR. --- .../src/com/esotericsoftware/spine/SkeletonViewer.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java index d333fe661..13a3e785a 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -397,6 +397,15 @@ public class SkeletonViewer extends ApplicationAdapter { table.add(toasts); } + { + Table table = new Table(); + table.setFillParent(true); + table.setTouchable(Touchable.disabled); + stage.addActor(table); + table.pad(10).top().right(); + table.add(new Label("", skin, "default", Color.LIGHT_GRAY)); // Version. + } + // Events. window.addListener(new InputListener() { From 92236380c09546800496d36cc7492d7c31c27e9f Mon Sep 17 00:00:00 2001 From: John Date: Mon, 17 Oct 2016 18:50:57 +0800 Subject: [PATCH 2/8] [unity] Fix backwards json check. --- spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs index ee46f4ab9..db41f7d4b 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs @@ -1162,7 +1162,7 @@ namespace Spine.Unity.Editor { var skeletonInfo = (Dictionary)root["skeleton"]; object jv; skeletonInfo.TryGetValue("spine", out jv); - string jsonVersion = (jv == null) ? (string)jv : null; + string jsonVersion = jv as string; if (!string.IsNullOrEmpty(jsonVersion)) { string[] jsonVersionSplit = jsonVersion.Split('.'); bool match = false; From 6629a7d332b3ea9bb9712a5e9fefe285b7bbf04d Mon Sep 17 00:00:00 2001 From: badlogic Date: Mon, 17 Oct 2016 14:20:36 +0200 Subject: [PATCH 3/8] [lua] Fixed type in SkeletonJson.lua --- spine-lua/SkeletonJson.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spine-lua/SkeletonJson.lua b/spine-lua/SkeletonJson.lua index 697fec741..16c2cf10c 100644 --- a/spine-lua/SkeletonJson.lua +++ b/spine-lua/SkeletonJson.lua @@ -563,7 +563,7 @@ function SkeletonJson.new (attachmentLoader) end local frameIndex = 0 for i,valueMap in ipairs(values) do - timeline:setFrame(frameIndex, valueMap.time, getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1) getValue(valueMap, "shearMix", 1)) + timeline:setFrame(frameIndex, valueMap.time, getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1), getValue(valueMap, "shearMix", 1)) readCurve(valueMap, timeline, frameIndex) frameIndex = frameIndex + 1 end From 3bcf3992277465f0241432f6bfaba9fb193e70f8 Mon Sep 17 00:00:00 2001 From: pharan Date: Wed, 19 Oct 2016 09:04:10 +0800 Subject: [PATCH 4/8] [unity] Added some multi-editing support. --- .../spine-unity/Asset Types/AtlasAsset.cs | 6 +- .../Editor/SkeletonAnimationInspector.cs | 99 ++++++--- .../Editor/SkeletonAnimatorInspector.cs | 20 +- .../Editor/SkeletonRendererInspector.cs | 195 +++++++++++++----- .../Editor/SpineAttributeDrawers.cs | 6 +- .../Editor/SpineInspectorUtility.cs | 62 +++++- .../Editor/SkeletonGraphicInspector.cs | 1 + .../Editor/SkeletonUtilityInspector.cs | 27 +-- 8 files changed, 296 insertions(+), 120 deletions(-) diff --git a/spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs b/spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs index 07daab329..e66986163 100644 --- a/spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs +++ b/spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs @@ -38,14 +38,14 @@ namespace Spine.Unity { public class AtlasAsset : ScriptableObject { public TextAsset atlasFile; public Material[] materials; - private Atlas atlas; + protected Atlas atlas; - public void Reset () { + public virtual void Reset () { atlas = null; } /// The atlas or null if it could not be loaded. - public Atlas GetAtlas () { + public virtual Atlas GetAtlas () { if (atlasFile == null) { Debug.LogError("Atlas file not set for atlas asset: " + name, this); Reset(); diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs index 6c5faac14..f79c67ed6 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs @@ -35,9 +35,11 @@ using Spine; namespace Spine.Unity.Editor { [CustomEditor(typeof(SkeletonAnimation))] + [CanEditMultipleObjects] public class SkeletonAnimationInspector : SkeletonRendererInspector { protected SerializedProperty animationName, loop, timeScale, autoReset; protected bool wasAnimationNameChanged; + protected bool requireRepaint; protected override void OnEnable () { base.OnEnable(); @@ -46,64 +48,93 @@ namespace Spine.Unity.Editor { timeScale = serializedObject.FindProperty("timeScale"); } - protected override void DrawInspectorGUI () { - base.DrawInspectorGUI(); + protected override void DrawInspectorGUI (bool multi) { + base.DrawInspectorGUI(multi); + if (!TargetIsValid) return; + bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject); - SkeletonAnimation component = (SkeletonAnimation)target; - if (!component.valid) + // Try to reflect the animation name on the scene object. + { + if (multi) + foreach (var o in targets) + TrySetAnimation(o); + else + TrySetAnimation(target); + } + + EditorGUILayout.Space(); + + if (multi && !sameData) + EditorGUILayout.DelayedTextField(animationName); + else { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(animationName); + wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update. + } + + EditorGUILayout.PropertyField(loop); + + EditorGUILayout.PropertyField(timeScale); + if (multi) { + foreach (var o in targets) { + var component = o as SkeletonAnimation; + component.timeScale = Mathf.Max(component.timeScale, 0); + } + } else { + var component = (SkeletonAnimation)target; + component.timeScale = Mathf.Max(component.timeScale, 0); + } + + if (!isInspectingPrefab) { + if (requireRepaint) { + SceneView.RepaintAll(); + requireRepaint = false; + } + + DrawSkeletonUtilityButton(multi); + } + } + + protected void TrySetAnimation (Object o) { + var skeletonAnimation = o as SkeletonAnimation; + if (skeletonAnimation == null) return; + if (!skeletonAnimation.valid) return; if (!isInspectingPrefab) { if (wasAnimationNameChanged) { if (!Application.isPlaying) { - if (component.state != null) component.state.ClearTrack(0); - component.skeleton.SetToSetupPose(); + if (skeletonAnimation.state != null) skeletonAnimation.state.ClearTrack(0); + skeletonAnimation.skeleton.SetToSetupPose(); } - Spine.Animation animationToUse = component.skeleton.Data.FindAnimation(animationName.stringValue); + Spine.Animation animationToUse = skeletonAnimation.skeleton.Data.FindAnimation(animationName.stringValue); if (!Application.isPlaying) { - if (animationToUse != null) animationToUse.Apply(component.skeleton, 0f, 0f, false, null); - component.Update(); - component.LateUpdate(); - SceneView.RepaintAll(); + if (animationToUse != null) animationToUse.Apply(skeletonAnimation.skeleton, 0f, 0f, false, null); + skeletonAnimation.Update(); + skeletonAnimation.LateUpdate(); + requireRepaint = true; } else { if (animationToUse != null) - component.state.SetAnimation(0, animationToUse, loop.boolValue); + skeletonAnimation.state.SetAnimation(0, animationToUse, loop.boolValue); else - component.state.ClearTrack(0); + skeletonAnimation.state.ClearTrack(0); } wasAnimationNameChanged = false; } // Reflect animationName serialized property in the inspector even if SetAnimation API was used. - if (Application.isPlaying) { - TrackEntry current = component.state.GetCurrent(0); + bool multi = animationName.serializedObject.isEditingMultipleObjects; + if (!multi && Application.isPlaying) { + TrackEntry current = skeletonAnimation.state.GetCurrent(0); if (current != null) { - if (component.AnimationName != animationName.stringValue) + if (skeletonAnimation.AnimationName != animationName.stringValue) animationName.stringValue = current.Animation.Name; } } } - - EditorGUILayout.Space(); - EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(animationName); - wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update. - - EditorGUILayout.PropertyField(loop); - EditorGUILayout.PropertyField(timeScale); - component.timeScale = Mathf.Max(component.timeScale, 0); - - EditorGUILayout.Space(); - - if (!isInspectingPrefab) { - if (component.GetComponent() == null) { - if (GUILayout.Button(new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility), GUILayout.Height(30))) - component.gameObject.AddComponent(); - } - } } } } diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs index 369249e10..8204cd800 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs @@ -31,10 +31,10 @@ // Contributed by: Mitch Thompson using UnityEditor; -using UnityEngine; namespace Spine.Unity.Editor { [CustomEditor(typeof(SkeletonAnimator))] + [CanEditMultipleObjects] public class SkeletonAnimatorInspector : SkeletonRendererInspector { protected SerializedProperty layerMixModes; protected override void OnEnable () { @@ -42,22 +42,14 @@ namespace Spine.Unity.Editor { layerMixModes = serializedObject.FindProperty("layerMixModes"); } - protected override void DrawInspectorGUI () { - base.DrawInspectorGUI(); + protected override void DrawInspectorGUI (bool multi) { + base.DrawInspectorGUI(multi); EditorGUILayout.PropertyField(layerMixModes, true); - var component = (SkeletonAnimator)target; - if (!component.valid) - return; - EditorGUILayout.Space(); + if (!TargetIsValid) return; - if (!isInspectingPrefab) { - if (component.GetComponent() == null) { - if (GUILayout.Button(new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility), GUILayout.Height(30))) { - component.gameObject.AddComponent(); - } - } - } + if (!isInspectingPrefab) + DrawSkeletonUtilityButton(multi); } } } diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs index 37d81745f..f106870d3 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs @@ -31,17 +31,34 @@ #define NO_PREFAB_MESH using UnityEditor; +using System.Collections.Generic; using UnityEngine; namespace Spine.Unity.Editor { [CustomEditor(typeof(SkeletonRenderer))] + [CanEditMultipleObjects] public class SkeletonRendererInspector : UnityEditor.Editor { protected static bool advancedFoldout; protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, frontFacing, zSpacing, pmaVertexColors; protected SpineInspectorUtility.SerializedSortingProperties sortingProperties; protected bool isInspectingPrefab; - protected MeshFilter meshFilter; + + protected bool TargetIsValid { + get { + if (serializedObject.isEditingMultipleObjects) { + foreach (var o in targets) { + var component = (SkeletonRenderer)o; + if (!component.valid) + return false; + } + return true; + } else { + var component = (SkeletonRenderer)target; + return component.valid; + } + } + } protected virtual void OnEnable () { isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab); @@ -60,8 +77,8 @@ namespace Spine.Unity.Editor { frontFacing = serializedObject.FindProperty("frontFacing"); zSpacing = serializedObject.FindProperty("zSpacing"); - var renderer = ((SkeletonRenderer)target).GetComponent(); - sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(renderer); + SerializedObject rso = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject); + sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(rso); } public static void ReapplySeparatorSlotNames (SkeletonRenderer skeletonRenderer) { @@ -76,62 +93,105 @@ namespace Spine.Unity.Editor { var slot = skeleton.FindSlot(separatorSlotNames[i]); if (slot != null) { separatorSlots.Add(slot); - //Debug.Log(slot + " added as separator."); } else { Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonRenderer.skeletonDataAsset.skeletonJSON.name); } } - - //Debug.Log("Reapplied Separator Slot Names. Count is now: " + separatorSlots.Count); } - protected virtual void DrawInspectorGUI () { - // JOHN: todo: support multiediting. - SkeletonRenderer component = (SkeletonRenderer)target; - - using (new EditorGUILayout.HorizontalScope()) { - EditorGUILayout.PropertyField(skeletonDataAsset); - const string ReloadButtonLabel = "Reload"; - float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20; - if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) { - if (component.skeletonDataAsset != null) { - foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { - if (aa != null) - aa.Reset(); + protected virtual void DrawInspectorGUI (bool multi) { + bool valid = TargetIsValid; + if (multi) { + using (new EditorGUILayout.HorizontalScope()) { + EditorGUILayout.PropertyField(skeletonDataAsset); + const string ReloadButtonLabel = "Reload"; + float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20; + if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) { + foreach (var c in targets) { + var component = c as SkeletonRenderer; + if (component.skeletonDataAsset != null) { + foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { + if (aa != null) + aa.Reset(); + } + component.skeletonDataAsset.Reset(); + } + component.Initialize(true); } - component.skeletonDataAsset.Reset(); } + } + + foreach (var c in targets) { + var component = c as SkeletonRenderer; + if (!component.valid) { + component.Initialize(true); + component.LateUpdate(); + if (!component.valid) + continue; + } + + #if NO_PREFAB_MESH + if (isInspectingPrefab) { + MeshFilter meshFilter = component.GetComponent(); + if (meshFilter != null) + meshFilter.sharedMesh = null; + } + #endif + } + + if (valid) + EditorGUILayout.PropertyField(initialSkinName); + } else { + var component = (SkeletonRenderer)target; + + using (new EditorGUILayout.HorizontalScope()) { + EditorGUILayout.PropertyField(skeletonDataAsset); + if (valid) { + const string ReloadButtonLabel = "Reload"; + float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20; + if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) { + if (component.skeletonDataAsset != null) { + foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { + if (aa != null) + aa.Reset(); + } + component.skeletonDataAsset.Reset(); + } + component.Initialize(true); + } + } + } + + if (!component.valid) { component.Initialize(true); + component.LateUpdate(); + if (!component.valid) { + EditorGUILayout.HelpBox("Skeleton Data Asset required", MessageType.Warning); + return; + } } - } - if (!component.valid) { - component.Initialize(true); - component.LateUpdate(); - if (!component.valid) - return; - } - - #if NO_PREFAB_MESH - if (meshFilter == null) - meshFilter = component.GetComponent(); - - if (isInspectingPrefab) - meshFilter.sharedMesh = null; - #endif - - // Initial skin name. - { - string[] skins = new string[component.skeleton.Data.Skins.Count]; - int skinIndex = 0; - for (int i = 0; i < skins.Length; i++) { - string skinNameString = component.skeleton.Data.Skins.Items[i].Name; - skins[i] = skinNameString; - if (skinNameString == initialSkinName.stringValue) - skinIndex = i; + #if NO_PREFAB_MESH + if (isInspectingPrefab) { + MeshFilter meshFilter = component.GetComponent(); + if (meshFilter != null) + meshFilter.sharedMesh = null; + } + #endif + + // Initial skin name. + if (valid) { + string[] skins = new string[component.skeleton.Data.Skins.Count]; + int skinIndex = 0; + for (int i = 0; i < skins.Length; i++) { + string skinNameString = component.skeleton.Data.Skins.Items[i].Name; + skins[i] = skinNameString; + if (skinNameString == initialSkinName.stringValue) + skinIndex = i; + } + skinIndex = EditorGUILayout.Popup("Initial Skin", skinIndex, skins); + initialSkinName.stringValue = skins[skinIndex]; } - skinIndex = EditorGUILayout.Popup("Initial Skin", skinIndex, skins); - initialSkinName.stringValue = skins[skinIndex]; } EditorGUILayout.Space(); @@ -139,6 +199,8 @@ namespace Spine.Unity.Editor { // Sorting Layers SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true); + if (!valid) return; + // More Render Options... using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { EditorGUI.indentLevel++; @@ -183,14 +245,47 @@ namespace Spine.Unity.Editor { } } + public void DrawSkeletonUtilityButton (bool multi) { + var buttonContent = new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility); + if (multi) { + // Support multi-edit SkeletonUtility button. + // EditorGUILayout.Space(); + // bool addSkeletonUtility = GUILayout.Button(buttonContent, GUILayout.Height(30)); + // foreach (var t in targets) { + // var component = t as SkeletonAnimation; + // if (addSkeletonUtility && component.GetComponent() == null) + // component.gameObject.AddComponent(); + // } + } else { + EditorGUILayout.Space(); + var component = (SkeletonAnimation)target; + if (component.GetComponent() == null) { + if (GUILayout.Button(buttonContent, GUILayout.Height(30))) + component.gameObject.AddComponent(); + } + } + } + override public void OnInspectorGUI () { //serializedObject.Update(); - DrawInspectorGUI(); + bool multi = serializedObject.isEditingMultipleObjects; + DrawInspectorGUI(multi); if (serializedObject.ApplyModifiedProperties() || (UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed") ) { - if (!Application.isPlaying) - ((SkeletonRenderer)target).Initialize(true); + if (!Application.isPlaying) { + if (multi) { + foreach (var o in targets) { + var sr = o as SkeletonRenderer; + sr.Initialize(true); + } + } else { + ((SkeletonRenderer)target).Initialize(true); + } + + } + + } } diff --git a/spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs b/spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs index d773176e4..219488a91 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs @@ -53,8 +53,11 @@ namespace Spine.Unity.Editor { internal const string NoneLabel = ""; protected T TargetAttribute { get { return (T)attribute; } } + protected SerializedProperty SerializedProperty { get; private set; } public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) { + SerializedProperty = property; + if (property.propertyType != SerializedPropertyType.String) { EditorGUI.LabelField(position, "ERROR:", "May only apply to type string"); return; @@ -87,7 +90,7 @@ namespace Spine.Unity.Editor { position = EditorGUI.PrefixLabel(position, label); - var propertyStringValue = property.stringValue; + var propertyStringValue = (property.hasMultipleDifferentValues) ? SpineInspectorUtility.EmDash : property.stringValue; if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel : propertyStringValue, EditorStyles.popup)) Selector(property); @@ -302,7 +305,6 @@ namespace Spine.Unity.Editor { [CustomPropertyDrawer(typeof(SpineBone))] public class SpineBoneDrawer : SpineTreeItemDrawerBase { - protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineBone targetAttribute, SkeletonData data) { menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name)); menu.AddSeparator(""); diff --git a/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs b/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs index 6839d7a37..d96e5bd1c 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs @@ -30,6 +30,7 @@ using UnityEngine; using UnityEditor; +using System.Collections.Generic; using System.Reflection; namespace Spine.Unity.Editor { @@ -43,6 +44,10 @@ namespace Spine.Unity.Editor { return n == 1 ? "" : "s"; } + public static string EmDash { + get { return "\u2014"; } + } + public static void PropertyFieldWideLabel (SerializedProperty property, GUIContent label = null, float minimumLabelWidth = 150) { using (new EditorGUILayout.HorizontalScope()) { GUILayout.Label(label ?? new GUIContent(property.displayName, property.tooltip), GUILayout.MinWidth(minimumLabelWidth)); @@ -70,25 +75,72 @@ namespace Spine.Unity.Editor { public SerializedProperty sortingLayerID; public SerializedProperty sortingOrder; - public SerializedSortingProperties (Renderer r) { - renderer = new SerializedObject(r); + public SerializedSortingProperties (Renderer r) : this(new SerializedObject(r)) {} + public SerializedSortingProperties (Object[] renderers) : this(new SerializedObject(renderers)) {} + public SerializedSortingProperties (SerializedObject rendererSerializedObject) { + renderer = rendererSerializedObject; sortingLayerID = renderer.FindProperty("m_SortingLayerID"); sortingOrder = renderer.FindProperty("m_SortingOrder"); } public void ApplyModifiedProperties () { renderer.ApplyModifiedProperties(); + this.SetDirty(); } + + internal void SetDirty () { + if (renderer.isEditingMultipleObjects) + foreach (var o in renderer.targetObjects) + EditorUtility.SetDirty(o); + else + EditorUtility.SetDirty(renderer.targetObject); + } + } + + public static SerializedObject GetRenderersSerializedObject (SerializedObject serializedObject) { + if (serializedObject.isEditingMultipleObjects) { + var renderers = new List(); + foreach (var o in serializedObject.targetObjects) { + var component = o as Component; + if (component != null) { + var renderer = component.GetComponent(); + if (renderer != null) + renderers.Add(renderer); + } + } + return new SerializedObject(renderers.ToArray()); + } else { + var component = serializedObject.targetObject as Component; + if (component != null) { + var renderer = component.GetComponent(); + if (renderer != null) + return new SerializedObject(renderer); + } + } + + return null; + } + + public static bool TargetsUseSameData (SerializedObject so) { + bool multi = so.isEditingMultipleObjects; + if (multi) { + int n = so.targetObjects.Length; + var first = so.targetObjects[0] as SkeletonRenderer; + for (int i = 1; i < n; i++) { + var sr = so.targetObjects[i] as SkeletonRenderer; + if (sr != null && sr.skeletonDataAsset != first.skeletonDataAsset) + return false; + } + } + return true; } public static void SortingPropertyFields (SerializedSortingProperties prop, bool applyModifiedProperties) { if (applyModifiedProperties) { EditorGUI.BeginChangeCheck(); SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder); - if(EditorGUI.EndChangeCheck()) { + if(EditorGUI.EndChangeCheck()) prop.ApplyModifiedProperties(); - EditorUtility.SetDirty(prop.renderer.targetObject); - } } else { SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder); } diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs index c75a3e09e..2c096fa00 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs @@ -40,6 +40,7 @@ namespace Spine.Unity.Editor { [InitializeOnLoad] [CustomEditor(typeof(SkeletonGraphic))] + [CanEditMultipleObjects] public class SkeletonGraphicInspector : UnityEditor.Editor { SerializedProperty material_, color_; SerializedProperty skeletonDataAsset_, initialSkinName_; diff --git a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs index daacc7fed..3b32920a8 100644 --- a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs +++ b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs @@ -114,6 +114,8 @@ namespace Spine.Unity.Editor { skeleton = skeletonRenderer.skeleton; } + if (!skeletonRenderer.valid) return; + UpdateAttachments(); isPrefab |= PrefabUtility.GetPrefabType(this.target) == PrefabType.Prefab; } @@ -142,7 +144,6 @@ namespace Spine.Unity.Editor { void UpdateAttachments () { attachmentTable = new Dictionary>(); - Skin skin = skeleton.Skin ?? skeletonRenderer.skeletonDataAsset.GetSkeletonData(true).DefaultSkin; for (int i = skeleton.Slots.Count-1; i >= 0; i--) { List attachments = new List(); @@ -176,20 +177,22 @@ namespace Spine.Unity.Editor { return; } + if (!skeletonRenderer.valid) { + GUILayout.Label(new GUIContent("Spine Component invalid. Check Skeleton Data Asset.", SpineEditorUtilities.Icons.warning)); + return; + } + skeletonUtility.boneRoot = (Transform)EditorGUILayout.ObjectField("Bone Root", skeletonUtility.boneRoot, typeof(Transform), true); - GUILayout.BeginHorizontal(); - EditorGUI.BeginDisabledGroup(skeletonUtility.boneRoot != null); - { - if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24))) - SpawnHierarchyContextMenu(); + using (new GUILayout.HorizontalScope()) { + using (new EditorGUI.DisabledGroupScope(skeletonUtility.boneRoot != null)) { + if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24))) + SpawnHierarchyContextMenu(); + } + + // if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24))) + // skeletonUtility.SpawnSubRenderers(true); } - EditorGUI.EndDisabledGroup(); - - // if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24))) - // skeletonUtility.SpawnSubRenderers(true); - - GUILayout.EndHorizontal(); EditorGUI.BeginChangeCheck(); skeleton.FlipX = EditorGUILayout.ToggleLeft("Flip X", skeleton.FlipX); From 69889d8a07d98ff910cde208c058c766a75edd1a Mon Sep 17 00:00:00 2001 From: badlogic Date: Wed, 19 Oct 2016 12:00:59 +0200 Subject: [PATCH 5/8] [c] Fixes compilation in VS201x --- spine-c/src/spine/Skeleton.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spine-c/src/spine/Skeleton.c b/spine-c/src/spine/Skeleton.c index a193d2bf7..4af6db685 100644 --- a/spine-c/src/spine/Skeleton.c +++ b/spine-c/src/spine/Skeleton.c @@ -188,7 +188,8 @@ static void _sortPathConstraintAttachmentBones(_spSkeleton* const internal, spAt int i = 0; while (i < pathBonesCount) { int boneCount = pathBones[i++]; - for (int n = i + boneCount; i < n; i++) + int n; + for (n = i + boneCount; i < n; i++) _sortBone(internal, bones[pathBones[i]]); } } From f92e770bb1ce847b1d4aae29ff8e4baa9250c8c0 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Tue, 18 Oct 2016 21:26:08 +0200 Subject: [PATCH 6/8] noRotationOrReflection simplification. --- .../src/com/esotericsoftware/spine/Bone.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java index 3b3d451a1..98e398107 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -153,28 +153,25 @@ public class Bone implements Updatable { break; } case noRotationOrReflection: { - float psx = (float)Math.sqrt(pa * pa + pc * pc), psy, prx; - if (psx > 0.0001f) { - psy = Math.abs((pa * pd - pb * pc) / psx); + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) { + s = Math.abs(pa * pd - pb * pc) / s; + pb = pc * s; + pd = pa * s; prx = atan2(pc, pa) * radDeg; } else { - psx = 0; - psy = (float)Math.sqrt(pb * pb + pd * pd); + pa = 0; + pc = 0; prx = 90 - atan2(pd, pb) * radDeg; } - float cos = cosDeg(prx), sin = sinDeg(prx); - pa = cos * psx; - pb = -sin * psy; - pc = sin * psx; - pd = cos * psy; float rx = rotation + shearX - prx; float ry = rotation + shearY - prx + 90; float la = cosDeg(rx) * scaleX; float lb = cosDeg(ry) * scaleY; float lc = sinDeg(rx) * scaleX; float ld = sinDeg(ry) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; c = pc * la + pd * lc; d = pc * lb + pd * ld; break; From 5cd29e55df45000344dc56bb213fef9b01ae34b9 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 19 Oct 2016 22:36:04 +0200 Subject: [PATCH 7/8] AnimationState, mixing from multiple animations. #621 --- .../com/esotericsoftware/spine/Animation.java | 6 +- .../spine/AnimationState.java | 112 +++++++----------- 2 files changed, 46 insertions(+), 72 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index 465cfb21b..8bc95c640 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -136,8 +136,8 @@ public class Animation { static public interface Timeline { /** Sets the value(s) for the specified time. * @param events May be null to not collect fired events. - * @param setupPose If true, the timeline is mixed with the setup pose, else it is mixed with the current pose. Passing true - * when alpha is 1 is slightly more efficient. + * @param setupPose True when the timeline is mixed with the setup pose, false when it is mixed with the current pose. + * Passing true when alpha is 1 is slightly more efficient. * @param mixingOut True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose. * Irrelevant when alpha is 1. */ public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, @@ -916,7 +916,7 @@ public class Animation { if (time < frames[0]) return; // Time is before first frame. // BOZO - Finish timelines handling setupPose and mixingOut from here down. - + IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex); if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index f3806240c..b7486c6af 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -52,7 +52,7 @@ public class AnimationState { static private final Animation emptyAnimation = new Animation("", new Array(0), 0); private AnimationStateData data; - private final Array tracks = new Array(); + final Array tracks = new Array(); private final Array events = new Array(); final Array listeners = new Array(); private final EventQueue queue = new EventQueue(); @@ -60,9 +60,6 @@ public class AnimationState { boolean animationsChanged; private float timeScale = 1; - StringBuilder last = new StringBuilder(); - StringBuilder log = new StringBuilder(); - final Pool trackEntryPool = new Pool() { protected Object newObject () { return new TrackEntry(); @@ -112,9 +109,9 @@ public class AnimationState { } continue; } - updateMixingFrom(current, delta); + updateMixingFrom(current, delta, true); } else { - updateMixingFrom(current, delta); + updateMixingFrom(current, delta, true); // Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. if (current.trackLast >= current.trackEnd && current.mixingFrom == null) { tracks.set(i, null); @@ -130,11 +127,11 @@ public class AnimationState { queue.drain(); } - private void updateMixingFrom (TrackEntry entry, float delta) { + private void updateMixingFrom (TrackEntry entry, float delta, boolean canEnd) { TrackEntry from = entry.mixingFrom; if (from == null) return; - if (entry.mixTime >= entry.mixDuration && entry.mixTime > 0) { + if (canEnd && entry.mixTime >= entry.mixDuration && entry.mixTime > 0) { queue.end(from); TrackEntry newFrom = from.mixingFrom; entry.mixingFrom = newFrom; @@ -150,7 +147,7 @@ public class AnimationState { from.trackTime += mixingFromDelta; entry.mixTime += mixingFromDelta; - updateMixingFrom(from, delta); + updateMixingFrom(from, delta, canEnd && from.alpha == 1); } /** Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the @@ -161,7 +158,7 @@ public class AnimationState { Array events = this.events; - for (int i = 0; i < tracks.size; i++) { + for (int i = 0, n = tracks.size; i < n; i++) { TrackEntry current = tracks.get(i); if (current == null || current.delay > 0) continue; @@ -171,24 +168,24 @@ public class AnimationState { // Apply current entry. float animationLast = current.animationLast, animationTime = current.getAnimationTime(); - Array timelines = current.animation.timelines; - log("apply current: " + current + ", mix: " + mix + " * " + current.alpha); + int timelineCount = current.animation.timelines.size; + Object[] timelines = current.animation.timelines.items; if (mix == 1) { - for (int ii = 0, n = timelines.size; ii < n; ii++) - timelines.get(ii).apply(skeleton, animationLast, animationTime, events, 1, false, false); + for (int ii = 0; ii < timelineCount; ii++) + ((Timeline)timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, true, false); } else { boolean firstFrame = current.timelinesRotation.size == 0; - if (firstFrame) current.timelinesRotation.setSize(timelines.size << 1); + if (firstFrame) current.timelinesRotation.setSize(timelineCount << 1); float[] timelinesRotation = current.timelinesRotation.items; + boolean[] timelinesFirst = current.timelinesFirst.items; - for (int ii = 0, n = timelines.size; ii < n; ii++) { - Timeline timeline = timelines.get(ii); + for (int ii = 0; ii < timelineCount; ii++) { + Timeline timeline = (Timeline)timelines[ii]; if (timeline instanceof RotateTimeline) { - applyRotateTimeline((RotateTimeline)timeline, skeleton, animationLast, animationTime, events, mix, - timelinesFirst[ii], false, timelinesRotation, ii << 1, firstFrame); - } else { + applyRotateTimeline(timeline, skeleton, animationTime, mix, timelinesFirst[ii], timelinesRotation, ii << 1, + firstFrame); + } else timeline.apply(skeleton, animationLast, animationTime, events, mix, timelinesFirst[ii], false); - } } } queueEvents(current, animationTime); @@ -197,38 +194,26 @@ public class AnimationState { } queue.drain(); - - if (!log.toString().equals(last.toString())) { - System.out.println(log); - last.setLength(0); - last.append(log); - } - log.setLength(0); - } - - void log (String m) { - log.append(m); - log.append('\n'); } private float applyMixingFrom (TrackEntry entry, Skeleton skeleton, float alpha) { + TrackEntry from = entry.mixingFrom; + if (from.mixingFrom != null) applyMixingFrom(from, skeleton, alpha); + float mix; if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes. mix = 1; else { - mix = alpha * entry.mixTime / entry.mixDuration; + mix = entry.mixTime / entry.mixDuration; if (mix > 1) mix = 1; + mix *= alpha; } - TrackEntry from = entry.mixingFrom; - if (from.mixingFrom != null) applyMixingFrom(from, skeleton, alpha); - Array events = mix < from.eventThreshold ? this.events : null; boolean attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - float animationLast = from.animationLast, animationTime = from.getAnimationTime(); - Array timelines = from.animation.timelines; - int timelineCount = timelines.size; + int timelineCount = from.animation.timelines.size; + Object[] timelines = from.animation.timelines.items; boolean[] timelinesFirst = from.timelinesFirst.items, timelinesLast = from.timelinesLast.items; float alphaFull = from.alpha, alphaMix = alphaFull * (1 - mix); @@ -236,23 +221,19 @@ public class AnimationState { if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1); float[] timelinesRotation = from.timelinesRotation.items; - log("applyMixingFrom: " + entry.mixingFrom + " -> " + entry + ", mix: " + entry.mixTime / entry.mixDuration); - if (timelineCount == 0) log("apply from: " + from + " " + alphaFull + " * " + entry.alpha); - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelines.get(i); + Timeline timeline = (Timeline)timelines[i]; boolean setupPose = timelinesFirst[i]; + // If there's a higher timeline for the property, use full alpha to avoid a dip during the mix. float a = timelinesLast[i] ? alphaMix : alphaFull; - log("apply from: " + from + " " + a + " * " + entry.alpha); - if (timeline instanceof RotateTimeline) { - applyRotateTimeline((RotateTimeline)timeline, skeleton, animationLast, animationTime, events, a, setupPose, setupPose, - timelinesRotation, i << 1, firstFrame); - } else { + if (timeline instanceof RotateTimeline) + applyRotateTimeline(timeline, skeleton, animationTime, a, setupPose, timelinesRotation, i << 1, firstFrame); + else { if (!setupPose) { if (!attachments && timeline instanceof AttachmentTimeline) continue; if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; } - timeline.apply(skeleton, animationLast, animationTime, events, a, setupPose, setupPose); + timeline.apply(skeleton, animationLast, animationTime, events, a, setupPose, true); } } @@ -263,18 +244,18 @@ public class AnimationState { return mix; } - /** @param events May be null. */ - private void applyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float lastTime, float time, Array events, - float alpha, boolean setupPose, boolean mixingOut, float[] timelinesRotation, int i, boolean firstFrame) { + private void applyRotateTimeline (Timeline timeline, Skeleton skeleton, float time, float alpha, boolean setupPose, + float[] timelinesRotation, int i, boolean firstFrame) { if (alpha == 1) { - timeline.apply(skeleton, lastTime, time, events, 1, setupPose, setupPose); + timeline.apply(skeleton, 0, time, null, 1, setupPose, false); return; } - float[] frames = timeline.frames; + RotateTimeline rotateTimeline = (RotateTimeline)timeline; + float[] frames = rotateTimeline.frames; if (time < frames[0]) return; // Time is before first frame. - Bone bone = skeleton.bones.get(timeline.boneIndex); + Bone bone = skeleton.bones.get(rotateTimeline.boneIndex); float r2; if (time >= frames[frames.length - ENTRIES]) // Time is after last frame. @@ -284,7 +265,7 @@ public class AnimationState { int frame = Animation.binarySearch(frames, time, ENTRIES); float prevRotation = frames[frame + PREV_ROTATION]; float frameTime = frames[frame]; - float percent = timeline.getCurvePercent((frame >> 1) - 1, + float percent = rotateTimeline.getCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); r2 = frames[frame + ROTATION] - prevRotation; @@ -293,7 +274,7 @@ public class AnimationState { r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; } - // Mix between two rotations using the direction of the shortest route on the first frame while detecting crosses. + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. float r1 = setupPose ? bone.data.rotation : bone.rotation; float total, diff = r2 - r1; if (diff == 0) { @@ -319,7 +300,7 @@ public class AnimationState { if (Math.abs(lastTotal) > 180) lastTotal += 360 * Math.signum(lastTotal); dir = current; } - total = diff + lastTotal - lastTotal % 360; // Keep loops part of lastTotal. + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. if (dir != current) total += 360 * Math.signum(lastTotal); timelinesRotation[i] = total; } @@ -405,19 +386,12 @@ public class AnimationState { if (from != null) { queue.interrupt(from); current.mixingFrom = from; - // entry.mixTime = Math.max(0, entry.mixDuration - current.trackTime); - // log("setCurrent mixTime: " + entry.mixDuration + " - " + current.trackTime + " = " + entry.mixTime); current.mixTime = 0; - from.timelinesRotation.clear(); // BOZO - Needed? Recursive? + from.timelinesRotation.clear(); -// float alpha = 1; - float duration = from.animationEnd - from.animationStart; - if (duration > 0) from.alpha *= (from.getAnimationTime() - from.animationStart) / duration; -// do { -// from.alpha *= alpha; -// from = from.mixingFrom; -// } while (from != null); + // If not completely mixed in, set alpha so mixing out happens from current mix to zero. + if (from.mixingFrom != null) from.alpha *= Math.min(from.mixTime / from.mixDuration, 1); } queue.start(current); From e084f7c745c9b526bebe2b09cb76aec960e7ffb0 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 19 Oct 2016 22:43:41 +0200 Subject: [PATCH 8/8] Removed crossfade "dip" fix. It worked by applying the mixing from timeline with full alpha, then mix the higher timeline. It wasn't good since it would blow away any pose from before the mixing from timeline. --- .../spine/AnimationState.java | 39 +++---------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index b7486c6af..12cec0d18 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -212,10 +212,10 @@ public class AnimationState { Array events = mix < from.eventThreshold ? this.events : null; boolean attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; float animationLast = from.animationLast, animationTime = from.getAnimationTime(); + alpha = from.alpha * (1 - mix); int timelineCount = from.animation.timelines.size; Object[] timelines = from.animation.timelines.items; - boolean[] timelinesFirst = from.timelinesFirst.items, timelinesLast = from.timelinesLast.items; - float alphaFull = from.alpha, alphaMix = alphaFull * (1 - mix); + boolean[] timelinesFirst = from.timelinesFirst.items; boolean firstFrame = from.timelinesRotation.size == 0; if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1); @@ -224,16 +224,14 @@ public class AnimationState { for (int i = 0; i < timelineCount; i++) { Timeline timeline = (Timeline)timelines[i]; boolean setupPose = timelinesFirst[i]; - // If there's a higher timeline for the property, use full alpha to avoid a dip during the mix. - float a = timelinesLast[i] ? alphaMix : alphaFull; if (timeline instanceof RotateTimeline) - applyRotateTimeline(timeline, skeleton, animationTime, a, setupPose, timelinesRotation, i << 1, firstFrame); + applyRotateTimeline(timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame); else { if (!setupPose) { if (!attachments && timeline instanceof AttachmentTimeline) continue; if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; } - timeline.apply(skeleton, animationLast, animationTime, events, a, setupPose, true); + timeline.apply(skeleton, animationLast, animationTime, events, alpha, setupPose, true); } } @@ -566,32 +564,6 @@ public class AnimationState { TrackEntry entry = tracks.get(i); if (entry != null) checkTimelinesFirst(entry); } - - // Compute timelinesLast from highest to lowest track entries that have mixingFrom. - propertyIDs.clear(); - int lowestMixingFrom = n; - for (i = 0; i < n; i++) { // Find lowest with a mixingFrom entry. - TrackEntry entry = tracks.get(i); - if (entry == null) continue; - if (entry.mixingFrom != null) { - lowestMixingFrom = i; - break; - } - } - for (i = n - 1; i >= lowestMixingFrom; i--) { - TrackEntry entry = tracks.get(i); - if (entry == null) continue; - - Array timelines = entry.animation.timelines; - for (int ii = 0, nn = timelines.size; ii < nn; ii++) - propertyIDs.add(timelines.get(ii).getPropertyId()); - - entry = entry.mixingFrom; - while (entry != null) { - checkTimelinesUsage(entry, entry.timelinesLast); - entry = entry.mixingFrom; - } - } } /** From last to first mixingFrom entries, sets timelinesFirst to true on last, calls checkTimelineUsage on rest. */ @@ -701,7 +673,7 @@ public class AnimationState { float animationStart, animationEnd, animationLast, nextAnimationLast; float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale; float alpha, mixTime, mixDuration, mixAlpha; - final BooleanArray timelinesFirst = new BooleanArray(), timelinesLast = new BooleanArray(); + final BooleanArray timelinesFirst = new BooleanArray(); final FloatArray timelinesRotation = new FloatArray(); public void reset () { @@ -710,7 +682,6 @@ public class AnimationState { animation = null; listener = null; timelinesFirst.clear(); - timelinesLast.clear(); timelinesRotation.clear(); }