From ca97abb2a176591dea3473683e3fb02daf32cae4 Mon Sep 17 00:00:00 2001 From: Fenrisul Date: Mon, 2 Feb 2015 05:49:36 -0800 Subject: [PATCH] Added SkeletonBaker system --- .../spine-unity/Editor/SkeletonBaker.cs | 1350 +++++++++++++++++ .../Editor/SkeletonDataAssetInspector.cs | 199 ++- .../Editor/SpineEditorUtilities.cs | 35 +- 3 files changed, 1527 insertions(+), 57 deletions(-) create mode 100644 spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs new file mode 100644 index 000000000..6edf63a86 --- /dev/null +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs @@ -0,0 +1,1350 @@ +/****************************************************************************** + * Spine Runtimes Software License + * Version 2.1 + * + * Copyright (c) 2013, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable and + * non-transferable license to install, execute and perform the Spine Runtimes + * Software (the "Software") solely for internal use. Without the written + * permission of Esoteric Software (typically granted by licensing Spine), you + * may not (a) modify, translate, adapt or otherwise create derivative works, + * improvements of the Software or develop new applications using the Software + * or (b) remove, delete, alter or obscure any trademarks or any copyright, + * trademark, patent or other intellectual property or proprietary rights + * notices on or in the Software, including any copy thereof. Redistributions + * in binary or source form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +/***************************************************************************** + * SkeletonBaker added by Mitch Thompson + * Full irrevocable rights and permissions granted to Esoteric Software +*****************************************************************************/ +using UnityEngine; +using UnityEditor; +using UnityEditorInternal; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Spine; + + +/// +/// [SUPPORTS] +/// Linear, Constant, and Bezier Curves* +/// Inverse Kinematics* +/// Inherit Rotation +/// Translate Timeline +/// Rotate Timeline +/// Scale Timeline** +/// Event Timeline*** +/// Attachment Timeline +/// +/// RegionAttachment +/// MeshAttachment +/// SkinnedMeshAttachment +/// +/// [LIMITATIONS] +/// *Inverse Kinematics & Bezier Curves are baked into the animation at 60fps and are not realtime. Use bakeIncrement constant to adjust key density if desired. +/// **Non-uniform Scale Keys (ie: if ScaleX and ScaleY are not equal to eachother, it will not be accurate to Spine source) +/// ***Events may only fire 1 type of data per event in Unity safely so priority to String data if present in Spine key, otherwise a Float is sent whether the Spine key was Int or Float with priority given to Int. +/// +/// [DOES NOT SUPPORT] +/// FlipX or FlipY (Maybe one day) +/// FFD (Unity does not provide access to BlendShapes with code) +/// Color Keys (Maybe one day when Unity supports full FBX standard and provides access with code) +/// InheritScale (Never. Unity and Spine do scaling very differently) + +/// +public static class SkeletonBaker { + /// + /// Interval between key sampling for Bezier curves, IK controlled bones, and Inherit Rotation effected bones. + /// + const float bakeIncrement = 1 / 60f; + + public static void BakeToPrefab (SkeletonDataAsset skeletonDataAsset, List skins, string outputPath = "", bool bakeAnimations = true, bool bakeIK = true, SendMessageOptions eventOptions = SendMessageOptions.DontRequireReceiver) { + if (skeletonDataAsset == null || skeletonDataAsset.GetSkeletonData(true) == null) { + Debug.LogError("Could not export Spine Skeleton because SkeletonDataAsset is null or invalid!"); + return; + } + + if (outputPath == "") { + outputPath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(skeletonDataAsset)) + "/Baked"; + System.IO.Directory.CreateDirectory(outputPath); + } + + var skeletonData = skeletonDataAsset.GetSkeletonData(true); + bool hasAnimations = bakeAnimations && skeletonData.Animations.Count > 0; + UnityEditorInternal.AnimatorController controller = null; + + if (hasAnimations) { + string controllerPath = outputPath + "/" + skeletonDataAsset.skeletonJSON.name + " Controller.controller"; + bool newAnimContainer = false; + + var runtimeController = AssetDatabase.LoadAssetAtPath(controllerPath, typeof(RuntimeAnimatorController)); + + if (runtimeController != null) { + controller = (UnityEditorInternal.AnimatorController)runtimeController; + } else { + controller = UnityEditorInternal.AnimatorController.CreateAnimatorControllerAtPath(controllerPath); + newAnimContainer = true; + } + + Dictionary existingClipTable = new Dictionary(); + List unusedClipNames = new List(); + Object[] animObjs = AssetDatabase.LoadAllAssetsAtPath(controllerPath); + + foreach (Object o in animObjs) { + if (o is AnimationClip) { + var clip = (AnimationClip)o; + existingClipTable.Add(clip.name, clip); + unusedClipNames.Add(clip.name); + } + } + + Dictionary> slotLookup = new Dictionary>(); + + int skinCount = skins.Count; + + for (int s = 0; s < skeletonData.Slots.Count; s++) { + List attachmentNames = new List(); + for (int i = 0; i < skinCount; i++) { + var skin = skins[i]; + List temp = new List(); + skin.FindNamesForSlot(s, temp); + foreach (string str in temp) { + if (!attachmentNames.Contains(str)) + attachmentNames.Add(str); + } + } + slotLookup.Add(s, attachmentNames); + } + + foreach (var anim in skeletonData.Animations) { + + AnimationClip clip = null; + if (existingClipTable.ContainsKey(anim.Name)) { + clip = existingClipTable[anim.Name]; + } + + clip = ExtractAnimation(anim.Name, skeletonData, slotLookup, bakeIK, eventOptions, clip); + + if (unusedClipNames.Contains(clip.name)) { + unusedClipNames.Remove(clip.name); + } else { + AssetDatabase.AddObjectToAsset(clip, controller); + AnimatorController.AddAnimationClipToController(controller, clip); + } + } + + if (newAnimContainer) { + + } else { + + foreach (string str in unusedClipNames) { + AnimationClip.DestroyImmediate(existingClipTable[str], true); + } + + EditorUtility.SetDirty(controller); + AssetDatabase.SaveAssets(); + AssetDatabase.ImportAsset(controllerPath, ImportAssetOptions.ForceUpdate); + AssetDatabase.Refresh(); + } + } + + foreach (var skin in skins) { + bool newPrefab = false; + + string prefabPath = outputPath + "/" + skeletonDataAsset.skeletonJSON.name + " (" + skin.Name + ").prefab"; + + + Object prefab = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject)); + if (prefab == null) { + prefab = PrefabUtility.CreateEmptyPrefab(prefabPath); + newPrefab = true; + } + + Dictionary meshTable = new Dictionary(); + List unusedMeshNames = new List(); + Object[] assets = AssetDatabase.LoadAllAssetsAtPath(prefabPath); + foreach (var obj in assets) { + if (obj is Mesh) { + meshTable.Add(obj.name, (Mesh)obj); + unusedMeshNames.Add(obj.name); + } + } + + GameObject prefabRoot = new GameObject("root"); + + Dictionary slotTable = new Dictionary(); + Dictionary boneTable = new Dictionary(); + List boneList = new List(); + + //create bones + for (int i = 0; i < skeletonData.Bones.Count; i++) { + var boneData = skeletonData.Bones[i]; + Transform boneTransform = new GameObject(boneData.Name).transform; + boneTransform.parent = prefabRoot.transform; + boneTable.Add(boneTransform.name, boneTransform); + boneList.Add(boneTransform); + } + + for (int i = 0; i < skeletonData.Bones.Count; i++) { + + var boneData = skeletonData.Bones[i]; + Transform boneTransform = boneTable[boneData.Name]; + Transform parentTransform = null; + if (i > 0) + parentTransform = boneTable[boneData.Parent.Name]; + else + parentTransform = boneTransform.parent; + + boneTransform.parent = parentTransform; + boneTransform.localPosition = new Vector3(boneData.X, boneData.Y, 0); + if (boneData.InheritRotation) + boneTransform.localRotation = Quaternion.Euler(0, 0, boneData.Rotation); + else + boneTransform.rotation = Quaternion.Euler(0, 0, boneData.Rotation); + + if (boneData.InheritScale) + boneTransform.localScale = new Vector3(boneData.ScaleX, boneData.ScaleY, 1); + } + + //create slots and attachments + for (int i = 0; i < skeletonData.Slots.Count; i++) { + var slotData = skeletonData.Slots[i]; + Transform slotTransform = new GameObject(slotData.Name).transform; + slotTransform.parent = prefabRoot.transform; + slotTable.Add(slotData.Name, slotTransform); + + List attachments = new List(); + List attachmentNames = new List(); + + skin.FindAttachmentsForSlot(i, attachments); + skin.FindNamesForSlot(i, attachmentNames); + + if (skin != skeletonData.DefaultSkin) { + skeletonData.DefaultSkin.FindAttachmentsForSlot(i, attachments); + skeletonData.DefaultSkin.FindNamesForSlot(i, attachmentNames); + } + + for (int a = 0; a < attachments.Count; a++) { + var attachment = attachments[a]; + var attachmentName = attachmentNames[a]; + var attachmentMeshName = "[" + slotData.Name + "] " + attachmentName; + Vector3 offset = Vector3.zero; + float rotation = 0; + Mesh mesh = null; + Material material = null; + + if (meshTable.ContainsKey(attachmentMeshName)) + mesh = meshTable[attachmentMeshName]; + if (attachment is RegionAttachment) { + var regionAttachment = (RegionAttachment)attachment; + offset.x = regionAttachment.X; + offset.y = regionAttachment.Y; + rotation = regionAttachment.Rotation; + mesh = ExtractRegionAttachment(attachmentMeshName, regionAttachment, mesh); + material = ExtractMaterial((RegionAttachment)attachment); + unusedMeshNames.Remove(attachmentMeshName); + if (newPrefab || meshTable.ContainsKey(attachmentMeshName) == false) + AssetDatabase.AddObjectToAsset(mesh, prefab); + } else if (attachment is MeshAttachment) { + var meshAttachment = (MeshAttachment)attachment; + offset.x = 0; + offset.y = 0; + rotation = 0; + mesh = ExtractMeshAttachment(attachmentMeshName, meshAttachment, mesh); + material = ExtractMaterial((MeshAttachment)attachment); + unusedMeshNames.Remove(attachmentMeshName); + if (newPrefab || meshTable.ContainsKey(attachmentMeshName) == false) + AssetDatabase.AddObjectToAsset(mesh, prefab); + } else if (attachment is SkinnedMeshAttachment) { + var meshAttachment = (SkinnedMeshAttachment)attachment; + offset.x = 0; + offset.y = 0; + rotation = 0; + mesh = ExtractSkinnedMeshAttachment(attachmentMeshName, meshAttachment, i, skeletonData, boneList, mesh); + material = ExtractMaterial((SkinnedMeshAttachment)attachment); + unusedMeshNames.Remove(attachmentMeshName); + if (newPrefab || meshTable.ContainsKey(attachmentMeshName) == false) + AssetDatabase.AddObjectToAsset(mesh, prefab); + } else + continue; //disregard unsupported types for now + + Transform attachmentTransform = new GameObject(attachmentName).transform; + + attachmentTransform.parent = slotTransform; + attachmentTransform.localPosition = offset; + attachmentTransform.localRotation = Quaternion.Euler(0, 0, rotation); + + if (attachment is SkinnedMeshAttachment) { + attachmentTransform.position = Vector3.zero; + attachmentTransform.rotation = Quaternion.identity; + var skinnedMeshRenderer = attachmentTransform.gameObject.AddComponent(); + skinnedMeshRenderer.rootBone = boneList[0]; + skinnedMeshRenderer.bones = boneList.ToArray(); + skinnedMeshRenderer.sharedMesh = mesh; + + } else { + attachmentTransform.gameObject.AddComponent().sharedMesh = mesh; + attachmentTransform.gameObject.AddComponent(); + } + + attachmentTransform.renderer.sharedMaterial = material; + attachmentTransform.renderer.sortingOrder = i; + + if (attachmentName != slotData.AttachmentName) + attachmentTransform.gameObject.SetActive(false); + } + + } + + foreach (var slotData in skeletonData.Slots) { + Transform slotTransform = slotTable[slotData.Name]; + slotTransform.parent = boneTable[slotData.BoneData.Name]; + slotTransform.localPosition = Vector3.zero; + slotTransform.localRotation = Quaternion.identity; + slotTransform.localScale = Vector3.one; + } + + if (hasAnimations) { + var animator = prefabRoot.AddComponent(); + animator.applyRootMotion = false; + animator.runtimeAnimatorController = (RuntimeAnimatorController)controller; + EditorGUIUtility.PingObject(controller); + } + + + if (newPrefab) { + PrefabUtility.ReplacePrefab(prefabRoot, prefab, ReplacePrefabOptions.ConnectToPrefab); + } else { + + foreach (string str in unusedMeshNames) { + Mesh.DestroyImmediate(meshTable[str], true); + } + + PrefabUtility.ReplacePrefab(prefabRoot, prefab, ReplacePrefabOptions.ReplaceNameBased); + } + + EditorGUIUtility.PingObject(prefab); + + AssetDatabase.Refresh(); + AssetDatabase.SaveAssets(); + + GameObject.DestroyImmediate(prefabRoot); + } + + } + + static Bone extractionBone; + static Slot extractionSlot; + + static Bone GetExtractionBone () { + if (extractionBone != null) + return extractionBone; + + SkeletonData skelData = new SkeletonData(); + BoneData data = new BoneData("temp", null); + data.ScaleX = 1; + data.ScaleY = 1; + data.Length = 100; + + skelData.Bones.Add(data); + + Skeleton skeleton = new Skeleton(skelData); + + Bone bone = new Bone(data, skeleton, null); + bone.UpdateWorldTransform(); + + extractionBone = bone; + + return extractionBone; + } + + static Slot GetExtractionSlot () { + if (extractionSlot != null) + return extractionSlot; + + Bone bone = GetExtractionBone(); + + SlotData data = new SlotData("temp", bone.Data); + Slot slot = new Slot(data, bone); + extractionSlot = slot; + return extractionSlot; + } + + static Material ExtractMaterial (Attachment attachment) { + if (attachment == null || attachment is BoundingBoxAttachment) + return null; + + if (attachment is RegionAttachment) { + var att = (RegionAttachment)attachment; + return (Material)((AtlasRegion)att.RendererObject).page.rendererObject; + } else if (attachment is MeshAttachment) { + var att = (MeshAttachment)attachment; + return (Material)((AtlasRegion)att.RendererObject).page.rendererObject; + } else if (attachment is SkinnedMeshAttachment) { + var att = (SkinnedMeshAttachment)attachment; + return (Material)((AtlasRegion)att.RendererObject).page.rendererObject; + } else { + return null; + } + } + + static Mesh ExtractRegionAttachment (string name, RegionAttachment attachment, Mesh mesh = null) { + var bone = GetExtractionBone(); + + bone.X = -attachment.X; + bone.Y = -attachment.Y; + bone.UpdateWorldTransform(); + + Vector2[] uvs = ExtractUV(attachment.UVs); + float[] floatVerts = new float[8]; + attachment.ComputeWorldVertices(bone, floatVerts); + Vector3[] verts = ExtractVerts(floatVerts); + + //unrotate verts now that they're centered + for (int i = 0; i < verts.Length; i++) { + verts[i] = Quaternion.Euler(0, 0, -attachment.Rotation) * verts[i]; + } + + int[] triangles = new int[6] { 1, 3, 0, 2, 3, 1 }; + Color color = new Color(attachment.R, attachment.G, attachment.B, attachment.A); + + if (mesh == null) + mesh = new Mesh(); + + mesh.triangles = new int[0]; + + mesh.vertices = verts; + mesh.uv = uvs; + mesh.triangles = triangles; + mesh.colors = new Color[] { color, color, color, color }; + mesh.RecalculateBounds(); + mesh.RecalculateNormals(); + mesh.name = name; + + return mesh; + } + + static Mesh ExtractMeshAttachment (string name, MeshAttachment attachment, Mesh mesh = null) { + var slot = GetExtractionSlot(); + + slot.Bone.X = 0; + slot.Bone.Y = 0; + slot.Bone.UpdateWorldTransform(); + + Vector2[] uvs = ExtractUV(attachment.UVs); + float[] floatVerts = new float[attachment.Vertices.Length]; + attachment.ComputeWorldVertices(slot, floatVerts); + Vector3[] verts = ExtractVerts(floatVerts); + + int[] triangles = attachment.Triangles; + Color color = new Color(attachment.R, attachment.G, attachment.B, attachment.A); + + if (mesh == null) + mesh = new Mesh(); + + mesh.triangles = new int[0]; + + mesh.vertices = verts; + mesh.uv = uvs; + mesh.triangles = triangles; + Color[] colors = new Color[verts.Length]; + for (int i = 0; i < verts.Length; i++) + colors[i] = color; + + mesh.colors = colors; + mesh.RecalculateBounds(); + mesh.RecalculateNormals(); + mesh.name = name; + + return mesh; + } + + public class BoneWeightContainer { + public struct Pair { + public Transform bone; + public float weight; + + public Pair (Transform bone, float weight) { + this.bone = bone; + this.weight = weight; + } + } + + public List bones; + public List weights; + public List pairs; + + + public BoneWeightContainer () { + this.bones = new List(); + this.weights = new List(); + this.pairs = new List(); + } + + public void Add (Transform transform, float weight) { + bones.Add(transform); + weights.Add(weight); + + pairs.Add(new Pair(transform, weight)); + } + } + + static Mesh ExtractSkinnedMeshAttachment (string name, SkinnedMeshAttachment attachment, int slotIndex, SkeletonData skeletonData, List boneList, Mesh mesh = null) { + + Skeleton skeleton = new Skeleton(skeletonData); + skeleton.UpdateWorldTransform(); + + float[] floatVerts = new float[attachment.UVs.Length]; + attachment.ComputeWorldVertices(skeleton.Slots[slotIndex], floatVerts); + + Vector2[] uvs = ExtractUV(attachment.UVs); + Vector3[] verts = ExtractVerts(floatVerts); + + int[] triangles = attachment.Triangles; + Color color = new Color(attachment.R, attachment.G, attachment.B, attachment.A); + + if (mesh == null) + mesh = new Mesh(); + + mesh.triangles = new int[0]; + + mesh.vertices = verts; + mesh.uv = uvs; + mesh.triangles = triangles; + Color[] colors = new Color[verts.Length]; + for (int i = 0; i < verts.Length; i++) + colors[i] = color; + + mesh.colors = colors; + mesh.name = name; + mesh.RecalculateNormals(); + mesh.RecalculateBounds(); + + //Handle weights and binding + Dictionary weightTable = new Dictionary(); + System.Text.StringBuilder warningBuilder = new System.Text.StringBuilder(); + + int[] bones = attachment.Bones; + float[] weights = attachment.Weights; + for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) { + + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3) { + Transform boneTransform = boneList[bones[v]]; + int vIndex = w / 2; + + float weight = weights[b + 2]; + + BoneWeightContainer container; + if (weightTable.ContainsKey(vIndex)) + container = weightTable[vIndex]; + else { + container = new BoneWeightContainer(); + weightTable.Add(vIndex, container); + } + + + container.Add(boneTransform, weight); + } + } + + BoneWeight[] boneWeights = new BoneWeight[weightTable.Count]; + + for (int i = 0; i < weightTable.Count; i++) { + BoneWeight bw = new BoneWeight(); + var container = weightTable[i]; + + var pairs = container.pairs.OrderByDescending(pair => pair.weight).ToList(); + + for (int b = 0; b < pairs.Count; b++) { + if (b > 3) { + if (warningBuilder.Length == 0) + warningBuilder.Insert(0, "[SkinnedMeshAttachment " + name + "]\r\nUnity only supports 4 weight influences per vertex! The 4 strongest influences will be used.\r\n"); + + warningBuilder.AppendFormat("{0} ignored on vertex {1}!\r\n", pairs[b].bone.name, i); + continue; + } + + int boneIndex = boneList.IndexOf(pairs[b].bone); + float weight = pairs[b].weight; + + switch (b) { + case 0: + bw.boneIndex0 = boneIndex; + bw.weight0 = weight; + break; + case 1: + bw.boneIndex1 = boneIndex; + bw.weight1 = weight; + break; + case 2: + bw.boneIndex2 = boneIndex; + bw.weight2 = weight; + break; + case 3: + bw.boneIndex3 = boneIndex; + bw.weight3 = weight; + break; + } + } + + boneWeights[i] = bw; + } + + Matrix4x4[] bindPoses = new Matrix4x4[boneList.Count]; + for (int i = 0; i < boneList.Count; i++) { + bindPoses[i] = boneList[i].worldToLocalMatrix; + } + + mesh.boneWeights = boneWeights; + mesh.bindposes = bindPoses; + + string warningString = warningBuilder.ToString(); + if (warningString.Length > 0) + Debug.LogWarning(warningString); + + + return mesh; + } + + static Vector2[] ExtractUV (float[] floats) { + Vector2[] arr = new Vector2[floats.Length / 2]; + + for (int i = 0; i < floats.Length; i += 2) { + arr[i / 2] = new Vector2(floats[i], floats[i + 1]); + } + + return arr; + } + + static Vector3[] ExtractVerts (float[] floats) { + Vector3[] arr = new Vector3[floats.Length / 2]; + + for (int i = 0; i < floats.Length; i += 2) { + arr[i / 2] = new Vector3(floats[i], floats[i + 1], 0);// *scale; + } + + return arr; + } + + static void SetAnimationSettings (AnimationClip clip, AnimationClipSettings settings) { + MethodInfo methodInfo = typeof(AnimationUtility).GetMethod("SetAnimationClipSettings", BindingFlags.Static | BindingFlags.NonPublic); + methodInfo.Invoke(null, new object[] { clip, settings }); + } + + static AnimationClip ExtractAnimation (string name, SkeletonData skeletonData, Dictionary> slotLookup, bool bakeIK, SendMessageOptions eventOptions, AnimationClip clip = null) { + var animation = skeletonData.FindAnimation(name); + + var timelines = animation.Timelines; + + if (clip == null) { + clip = new AnimationClip(); + } else { + clip.ClearCurves(); + AnimationUtility.SetAnimationEvents(clip, new AnimationEvent[0]); + } + + AnimationUtility.SetAnimationType(clip, ModelImporterAnimationType.Generic); + + clip.name = name; + + Skeleton skeleton = new Skeleton(skeletonData); + + List ignoreRotateTimelineIndexes = new List(); + + if (bakeIK) { + foreach (IkConstraint i in skeleton.IkConstraints) { + foreach (Bone b in i.Bones) { + int index = skeleton.FindBoneIndex(b.Data.Name); + ignoreRotateTimelineIndexes.Add(index); + BakeBone(b, animation, clip); + } + } + } + + foreach (Bone b in skeleton.Bones) { + if (b.Data.InheritRotation == false) { + int index = skeleton.FindBoneIndex(b.Data.Name); + + if (ignoreRotateTimelineIndexes.Contains(index) == false) { + ignoreRotateTimelineIndexes.Add(index); + BakeBone(b, animation, clip); + } + } + } + + foreach (Timeline t in timelines) { + skeleton.SetToSetupPose(); + + if (t is ScaleTimeline) { + ParseScaleTimeline(skeleton, (ScaleTimeline)t, clip); + } else if (t is TranslateTimeline) { + ParseTranslateTimeline(skeleton, (TranslateTimeline)t, clip); + } else if (t is RotateTimeline) { + //bypass any rotation keys if they're going to get baked anyway to prevent localEulerAngles vs Baked collision + if (ignoreRotateTimelineIndexes.Contains(((RotateTimeline)t).BoneIndex) == false) + ParseRotateTimeline(skeleton, (RotateTimeline)t, clip); + } else if (t is AttachmentTimeline) { + ParseAttachmentTimeline(skeleton, (AttachmentTimeline)t, slotLookup, clip); + } else if (t is EventTimeline) { + ParseEventTimeline(skeleton, (EventTimeline)t, clip, eventOptions); + } + + } + + var settings = AnimationUtility.GetAnimationClipSettings(clip); + settings.loopTime = true; + settings.stopTime = Mathf.Max(clip.length, 0.001f); + + SetAnimationSettings(clip, settings); + + clip.EnsureQuaternionContinuity(); + clip.EnsureQuaternionContinuity(); + clip.EnsureQuaternionContinuity(); + + return clip; + } + static int BinarySearch (float[] values, float target) { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + static void ParseEventTimeline (Skeleton skeleton, EventTimeline timeline, AnimationClip clip, SendMessageOptions eventOptions) { + + float[] frames = timeline.Frames; + var events = timeline.Events; + + List animEvents = new List(); + for (int i = 0; i < frames.Length; i++) { + var ev = events[i]; + + AnimationEvent ae = new AnimationEvent(); + ae.time = frames[i]; + ae.functionName = ev.Data.Name; + ae.messageOptions = eventOptions; + + if (ev.String != "") + ae.stringParameter = ev.String; + else { + if (ev.Int == 0 && ev.Float == 0) { + //do nothing, raw function + } else { + if (ev.Int != 0) + ae.floatParameter = (float)ev.Int; + else + ae.floatParameter = ev.Float; + } + + } + + animEvents.Add(ae); + } + + AnimationUtility.SetAnimationEvents(clip, animEvents.ToArray()); + } + + static void ParseAttachmentTimeline (Skeleton skeleton, AttachmentTimeline timeline, Dictionary> slotLookup, AnimationClip clip) { + var attachmentNames = slotLookup[timeline.SlotIndex]; + + string bonePath = GetPath(skeleton.Slots[timeline.SlotIndex].Bone.Data); + string slotPath = bonePath + "/" + skeleton.Slots[timeline.SlotIndex].Data.Name; + + Dictionary curveTable = new Dictionary(); + + foreach (string str in attachmentNames) { + curveTable.Add(str, new AnimationCurve()); + } + + float[] frames = timeline.Frames; + + if (frames[0] != 0) { + string startingName = skeleton.Slots[timeline.SlotIndex].Data.AttachmentName; + foreach (var pair in curveTable) { + if (startingName == "" || startingName == null) { + pair.Value.AddKey(new Keyframe(0, 0, float.PositiveInfinity, float.PositiveInfinity)); + } else { + if (pair.Key == startingName) { + pair.Value.AddKey(new Keyframe(0, 1, float.PositiveInfinity, float.PositiveInfinity)); + } else { + pair.Value.AddKey(new Keyframe(0, 0, float.PositiveInfinity, float.PositiveInfinity)); + } + } + } + } + + float currentTime = timeline.Frames[0]; + float endTime = frames[frames.Length - 1]; + int f = 0; + while (currentTime < endTime) { + float time = frames[f]; + + int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : BinarySearch(frames, time)) - 1; + + string name = timeline.AttachmentNames[frameIndex]; + foreach (var pair in curveTable) { + if (name == "") { + pair.Value.AddKey(new Keyframe(time, 0, float.PositiveInfinity, float.PositiveInfinity)); + } else { + if (pair.Key == name) { + pair.Value.AddKey(new Keyframe(time, 1, float.PositiveInfinity, float.PositiveInfinity)); + } else { + pair.Value.AddKey(new Keyframe(time, 0, float.PositiveInfinity, float.PositiveInfinity)); + } + } + } + + currentTime = time; + f += 1; + } + + foreach (var pair in curveTable) { + string path = slotPath + "/" + pair.Key; + string prop = "m_IsActive"; + + clip.SetCurve(path, typeof(GameObject), prop, pair.Value); + } + } + + static float GetUninheritedRotation (Bone b) { + + Bone parent = b.Parent; + float angle = b.RotationIK; + + while (parent != null) { + angle -= parent.RotationIK; + parent = parent.Parent; + } + + return angle; + } + static void BakeBone (Bone bone, Spine.Animation animation, AnimationClip clip) { + Skeleton skeleton = bone.Skeleton; + bool inheritRotation = bone.Data.InheritRotation; + + skeleton.SetToSetupPose(); + animation.Apply(skeleton, 0, 0, true, null); + skeleton.UpdateWorldTransform(); + float duration = animation.Duration; + + AnimationCurve curve = new AnimationCurve(); + + List keys = new List(); + + float rotation = bone.RotationIK; + if (!inheritRotation) + rotation = GetUninheritedRotation(bone); + + keys.Add(new Keyframe(0, rotation, 0, 0)); + + int listIndex = 1; + + float r = rotation; + + int steps = Mathf.CeilToInt(duration / bakeIncrement); + + float currentTime = 0; + float lastTime = 0; + float angle = rotation; + + for (int i = 1; i <= steps; i++) { + currentTime += bakeIncrement; + if (i == steps) + currentTime = duration; + + animation.Apply(skeleton, lastTime, currentTime, true, null); + skeleton.UpdateWorldTransform(); + + int pIndex = listIndex - 1; + + Keyframe pk = keys[pIndex]; + + pk = keys[pIndex]; + + if (inheritRotation) + rotation = bone.RotationIK; + else { + rotation = GetUninheritedRotation(bone); + } + + angle += Mathf.DeltaAngle(angle, rotation); + + r = angle; + + float rOut = (r - pk.value) / (currentTime - pk.time); + + pk.outTangent = rOut; + + keys.Add(new Keyframe(currentTime, r, rOut, 0)); + + keys[pIndex] = pk; + + listIndex++; + lastTime = currentTime; + } + + curve = EnsureCurveKeyCount(new AnimationCurve(keys.ToArray())); + + string path = GetPath(bone.Data); + string propertyName = "localEulerAnglesBaked"; + + EditorCurveBinding xBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".x"); + AnimationUtility.SetEditorCurve(clip, xBind, new AnimationCurve()); + EditorCurveBinding yBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".y"); + AnimationUtility.SetEditorCurve(clip, yBind, new AnimationCurve()); + EditorCurveBinding zBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".z"); + AnimationUtility.SetEditorCurve(clip, zBind, curve); + } + + static void ParseTranslateTimeline (Skeleton skeleton, TranslateTimeline timeline, AnimationClip clip) { + var boneData = skeleton.Data.Bones[timeline.BoneIndex]; + var bone = skeleton.Bones[timeline.BoneIndex]; + + AnimationCurve xCurve = new AnimationCurve(); + AnimationCurve yCurve = new AnimationCurve(); + AnimationCurve zCurve = new AnimationCurve(); + + float endTime = timeline.Frames[(timeline.FrameCount * 3) - 3]; + + float currentTime = timeline.Frames[0]; + + List xKeys = new List(); + List yKeys = new List(); + + xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] + boneData.X, 0, 0)); + yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] + boneData.Y, 0, 0)); + + int listIndex = 1; + int frameIndex = 1; + int f = 3; + float[] frames = timeline.Frames; + skeleton.SetToSetupPose(); + float lastTime = 0; + while (currentTime < endTime) { + int pIndex = listIndex - 1; + + + + float curveType = timeline.GetCurveType(frameIndex - 1); + + if (curveType == 0) { + //linear + Keyframe px = xKeys[pIndex]; + Keyframe py = yKeys[pIndex]; + + float time = frames[f]; + float x = frames[f + 1] + boneData.X; + float y = frames[f + 2] + boneData.Y; + + float xOut = (x - px.value) / (time - px.time); + float yOut = (y - py.value) / (time - py.time); + + px.outTangent = xOut; + py.outTangent = yOut; + + xKeys.Add(new Keyframe(time, x, xOut, 0)); + yKeys.Add(new Keyframe(time, y, yOut, 0)); + + xKeys[pIndex] = px; + yKeys[pIndex] = py; + + currentTime = time; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + + lastTime = time; + listIndex++; + } else if (curveType == 1) { + //stepped + Keyframe px = xKeys[pIndex]; + Keyframe py = yKeys[pIndex]; + + float time = frames[f]; + float x = frames[f + 1] + boneData.X; + float y = frames[f + 2] + boneData.Y; + + float xOut = float.PositiveInfinity; + float yOut = float.PositiveInfinity; + + px.outTangent = xOut; + py.outTangent = yOut; + + xKeys.Add(new Keyframe(time, x, xOut, 0)); + yKeys.Add(new Keyframe(time, y, yOut, 0)); + + xKeys[pIndex] = px; + yKeys[pIndex] = py; + + currentTime = time; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + + lastTime = time; + listIndex++; + } else if (curveType == 2) { + + //bezier + Keyframe px = xKeys[pIndex]; + Keyframe py = yKeys[pIndex]; + + float time = frames[f]; + + int steps = Mathf.FloorToInt((time - px.time) / bakeIncrement); + + for (int i = 1; i <= steps; i++) { + currentTime += bakeIncrement; + if (i == steps) + currentTime = time; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + + px = xKeys[listIndex - 1]; + py = yKeys[listIndex - 1]; + + float xOut = (bone.X - px.value) / (currentTime - px.time); + float yOut = (bone.Y - py.value) / (currentTime - py.time); + + px.outTangent = xOut; + py.outTangent = yOut; + + xKeys.Add(new Keyframe(currentTime, bone.X, xOut, 0)); + yKeys.Add(new Keyframe(currentTime, bone.Y, yOut, 0)); + + xKeys[listIndex - 1] = px; + yKeys[listIndex - 1] = py; + + listIndex++; + lastTime = currentTime; + } + } + + frameIndex++; + f += 3; + } + + xCurve = EnsureCurveKeyCount(new AnimationCurve(xKeys.ToArray())); + yCurve = EnsureCurveKeyCount(new AnimationCurve(yKeys.ToArray())); + + + + string path = GetPath(boneData); + string propertyName = "localPosition"; + + clip.SetCurve(path, typeof(Transform), propertyName + ".x", xCurve); + clip.SetCurve(path, typeof(Transform), propertyName + ".y", yCurve); + clip.SetCurve(path, typeof(Transform), propertyName + ".z", zCurve); + } + + static AnimationCurve EnsureCurveKeyCount (AnimationCurve curve) { + if (curve.length == 1) + curve.AddKey(curve.keys[0].time + 0.25f, curve.keys[0].value); + + return curve; + } + + static void ParseScaleTimeline (Skeleton skeleton, ScaleTimeline timeline, AnimationClip clip) { + var boneData = skeleton.Data.Bones[timeline.BoneIndex]; + var bone = skeleton.Bones[timeline.BoneIndex]; + + AnimationCurve xCurve = new AnimationCurve(); + AnimationCurve yCurve = new AnimationCurve(); + AnimationCurve zCurve = new AnimationCurve(); + + float endTime = timeline.Frames[(timeline.FrameCount * 3) - 3]; + + float currentTime = timeline.Frames[0]; + + List xKeys = new List(); + List yKeys = new List(); + + xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] * boneData.ScaleX, 0, 0)); + yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] * boneData.ScaleY, 0, 0)); + + int listIndex = 1; + int frameIndex = 1; + int f = 3; + float[] frames = timeline.Frames; + skeleton.SetToSetupPose(); + float lastTime = 0; + while (currentTime < endTime) { + int pIndex = listIndex - 1; + float curveType = timeline.GetCurveType(frameIndex - 1); + + if (curveType == 0) { + //linear + Keyframe px = xKeys[pIndex]; + Keyframe py = yKeys[pIndex]; + + float time = frames[f]; + float x = frames[f + 1] * boneData.ScaleX; + float y = frames[f + 2] * boneData.ScaleY; + + float xOut = (x - px.value) / (time - px.time); + float yOut = (y - py.value) / (time - py.time); + + px.outTangent = xOut; + py.outTangent = yOut; + + xKeys.Add(new Keyframe(time, x, xOut, 0)); + yKeys.Add(new Keyframe(time, y, yOut, 0)); + + xKeys[pIndex] = px; + yKeys[pIndex] = py; + + currentTime = time; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + + lastTime = time; + listIndex++; + } else if (curveType == 1) { + //stepped + Keyframe px = xKeys[pIndex]; + Keyframe py = yKeys[pIndex]; + + float time = frames[f]; + float x = frames[f + 1] * boneData.ScaleX; + float y = frames[f + 2] * boneData.ScaleY; + + float xOut = float.PositiveInfinity; + float yOut = float.PositiveInfinity; + + px.outTangent = xOut; + py.outTangent = yOut; + + xKeys.Add(new Keyframe(time, x, xOut, 0)); + yKeys.Add(new Keyframe(time, y, yOut, 0)); + + xKeys[pIndex] = px; + yKeys[pIndex] = py; + + currentTime = time; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + + lastTime = time; + listIndex++; + } else if (curveType == 2) { + //bezier + Keyframe px = xKeys[pIndex]; + Keyframe py = yKeys[pIndex]; + + float time = frames[f]; + + int steps = Mathf.FloorToInt((time - px.time) / bakeIncrement); + + for (int i = 1; i <= steps; i++) { + currentTime += bakeIncrement; + if (i == steps) + currentTime = time; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + + px = xKeys[listIndex - 1]; + py = yKeys[listIndex - 1]; + + float xOut = (bone.ScaleX - px.value) / (currentTime - px.time); + float yOut = (bone.ScaleY - py.value) / (currentTime - py.time); + + px.outTangent = xOut; + py.outTangent = yOut; + + xKeys.Add(new Keyframe(currentTime, bone.ScaleX, xOut, 0)); + yKeys.Add(new Keyframe(currentTime, bone.ScaleY, yOut, 0)); + + xKeys[listIndex - 1] = px; + yKeys[listIndex - 1] = py; + + listIndex++; + lastTime = currentTime; + } + } + + frameIndex++; + f += 3; + } + + xCurve = EnsureCurveKeyCount(new AnimationCurve(xKeys.ToArray())); + yCurve = EnsureCurveKeyCount(new AnimationCurve(yKeys.ToArray())); + + string path = GetPath(boneData); + string propertyName = "localScale"; + + clip.SetCurve(path, typeof(Transform), propertyName + ".x", xCurve); + clip.SetCurve(path, typeof(Transform), propertyName + ".y", yCurve); + clip.SetCurve(path, typeof(Transform), propertyName + ".z", zCurve); + } + + static void ParseRotateTimeline (Skeleton skeleton, RotateTimeline timeline, AnimationClip clip) { + var boneData = skeleton.Data.Bones[timeline.BoneIndex]; + var bone = skeleton.Bones[timeline.BoneIndex]; + + AnimationCurve curve = new AnimationCurve(); + + float endTime = timeline.Frames[(timeline.FrameCount * 2) - 2]; + + float currentTime = timeline.Frames[0]; + + List keys = new List(); + + float rotation = timeline.Frames[1] + boneData.Rotation; + + keys.Add(new Keyframe(timeline.Frames[0], rotation, 0, 0)); + + int listIndex = 1; + int frameIndex = 1; + int f = 2; + float[] frames = timeline.Frames; + skeleton.SetToSetupPose(); + float lastTime = 0; + float angle = rotation; + while (currentTime < endTime) { + int pIndex = listIndex - 1; + float curveType = timeline.GetCurveType(frameIndex - 1); + + if (curveType == 0) { + //linear + Keyframe pk = keys[pIndex]; + + float time = frames[f]; + + rotation = frames[f + 1] + boneData.Rotation; + angle += Mathf.DeltaAngle(angle, rotation); + float r = angle; + + float rOut = (r - pk.value) / (time - pk.time); + + pk.outTangent = rOut; + + keys.Add(new Keyframe(time, r, rOut, 0)); + + keys[pIndex] = pk; + + currentTime = time; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + + lastTime = time; + listIndex++; + } else if (curveType == 1) { + //stepped + + Keyframe pk = keys[pIndex]; + + float time = frames[f]; + + rotation = frames[f + 1] + boneData.Rotation; + angle += Mathf.DeltaAngle(angle, rotation); + float r = angle; + + float rOut = float.PositiveInfinity; + + pk.outTangent = rOut; + + keys.Add(new Keyframe(time, r, rOut, 0)); + + keys[pIndex] = pk; + + currentTime = time; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + + lastTime = time; + listIndex++; + } else if (curveType == 2) { + //bezier + Keyframe pk = keys[pIndex]; + + float time = frames[f]; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + skeleton.UpdateWorldTransform(); + + rotation = frames[f + 1] + boneData.Rotation; + angle += Mathf.DeltaAngle(angle, rotation); + float r = angle; + + int steps = Mathf.FloorToInt((time - pk.time) / bakeIncrement); + + for (int i = 1; i <= steps; i++) { + currentTime += bakeIncrement; + if (i == steps) + currentTime = time; + + timeline.Apply(skeleton, lastTime, currentTime, null, 1); + skeleton.UpdateWorldTransform(); + pk = keys[listIndex - 1]; + + rotation = bone.Rotation; + angle += Mathf.DeltaAngle(angle, rotation); + r = angle; + + float rOut = (r - pk.value) / (currentTime - pk.time); + + pk.outTangent = rOut; + + keys.Add(new Keyframe(currentTime, r, rOut, 0)); + + keys[listIndex - 1] = pk; + + listIndex++; + lastTime = currentTime; + } + } + + frameIndex++; + f += 2; + } + + curve = EnsureCurveKeyCount(new AnimationCurve(keys.ToArray())); + + string path = GetPath(boneData); + string propertyName = "localEulerAnglesBaked"; + + EditorCurveBinding xBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".x"); + AnimationUtility.SetEditorCurve(clip, xBind, new AnimationCurve()); + EditorCurveBinding yBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".y"); + AnimationUtility.SetEditorCurve(clip, yBind, new AnimationCurve()); + EditorCurveBinding zBind = EditorCurveBinding.FloatCurve(path, typeof(Transform), propertyName + ".z"); + AnimationUtility.SetEditorCurve(clip, zBind, curve); + + } + + static string GetPath (BoneData b) { + return GetPathRecurse(b).Substring(1); + } + + static string GetPathRecurse (BoneData b) { + if (b == null) { + return ""; + } + + return GetPathRecurse(b.Parent) + "/" + b.Name; + } +} diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs index a47765d54..5394c75e7 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs @@ -48,6 +48,10 @@ public class SkeletonDataAssetInspector : Editor { static bool showAnimationList = true; static bool showSlotList = false; static bool showAttachments = false; + static bool showBaking = true; + static bool bakeAnimations = true; + static bool bakeIK = true; + static SendMessageOptions bakeEventOptions = SendMessageOptions.DontRequireReceiver; private SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix; @@ -58,10 +62,10 @@ public class SkeletonDataAssetInspector : Editor { List warnings = new List(); - void OnEnable() { + void OnEnable () { + + SpineEditorUtilities.ConfirmInitialization(); - SpineEditorUtilities.ConfirmInitialization(); - try { atlasAssets = serializedObject.FindProperty("atlasAssets"); skeletonJSON = serializedObject.FindProperty("skeletonJSON"); @@ -82,10 +86,12 @@ public class SkeletonDataAssetInspector : Editor { m_skeletonData = m_skeletonDataAsset.GetSkeletonData(true); + showBaking = EditorPrefs.GetBool("SkeletonDataAssetInspector_showBaking", true); + RepopulateWarnings(); } - void OnDestroy() { + void OnDestroy () { m_initialized = false; EditorApplication.update -= Update; this.DestroyPreviewInstances(); @@ -95,7 +101,7 @@ public class SkeletonDataAssetInspector : Editor { } } - override public void OnInspectorGUI() { + override public void OnInspectorGUI () { serializedObject.Update(); SkeletonDataAsset asset = (SkeletonDataAsset)target; @@ -115,16 +121,15 @@ public class SkeletonDataAssetInspector : Editor { OnEnable(); return; } - + } - - if (m_skeletonData != null) { + if (m_skeletonData != null) { DrawAnimationStateInfo(); DrawAnimationList(); DrawSlotList(); - + DrawBaking(); } else { DrawReimportButton(); @@ -142,25 +147,105 @@ public class SkeletonDataAssetInspector : Editor { } } - void DrawReimportButton() { + void DrawBaking () { + + bool pre = showBaking; + showBaking = EditorGUILayout.Foldout(showBaking, new GUIContent("Baking", SpineEditorUtilities.Icons.unityIcon)); + if (pre != showBaking) + EditorPrefs.SetBool("SkeletonDataAssetInspector_showBaking", showBaking); + + if (showBaking) { + EditorGUI.indentLevel++; + bakeAnimations = EditorGUILayout.Toggle("Bake Animations", bakeAnimations); + EditorGUI.BeginDisabledGroup(bakeAnimations == false); + { + EditorGUI.indentLevel++; + bakeIK = EditorGUILayout.Toggle("Bake IK", bakeIK); + bakeEventOptions = (SendMessageOptions)EditorGUILayout.EnumPopup("Event Options", bakeEventOptions); + EditorGUI.indentLevel--; + } + EditorGUI.EndDisabledGroup(); + + EditorGUI.indentLevel++; + GUILayout.BeginHorizontal(); + { + + + if (GUILayout.Button(new GUIContent("Bake All Skins", SpineEditorUtilities.Icons.unityIcon), GUILayout.Height(32), GUILayout.Width(150))) + SkeletonBaker.BakeToPrefab(m_skeletonDataAsset, m_skeletonData.Skins, "", bakeAnimations, bakeIK, bakeEventOptions); + + string skinName = ""; + + if (m_skeletonAnimation != null && m_skeletonAnimation.skeleton != null) { + + Skin bakeSkin = m_skeletonAnimation.skeleton.Skin; + if (bakeSkin == null){ + skinName = "Default"; + bakeSkin = m_skeletonData.Skins[0]; + } + else + skinName = m_skeletonAnimation.skeleton.Skin.Name; + + bool oops = false; + + try { + GUILayout.BeginVertical(); + if (GUILayout.Button(new GUIContent("Bake " + skinName, SpineEditorUtilities.Icons.unityIcon), GUILayout.Height(32), GUILayout.Width(250))) + SkeletonBaker.BakeToPrefab(m_skeletonDataAsset, new List(new Skin[] { bakeSkin }), "", bakeAnimations, bakeIK, bakeEventOptions); + + GUILayout.BeginHorizontal(); + GUILayout.Label(new GUIContent("Skins", SpineEditorUtilities.Icons.skinsRoot), GUILayout.Width(50)); + if (GUILayout.Button(skinName, EditorStyles.popup, GUILayout.Width(196))) { + SelectSkinContext(); + } + GUILayout.EndHorizontal(); + + + + } catch { + oops = true; + //GUILayout.BeginVertical(); + } + + + + if(!oops) + GUILayout.EndVertical(); + } + + } + GUILayout.EndHorizontal(); + EditorGUI.indentLevel--; + + EditorGUI.indentLevel--; + } + + + } + void DrawReimportButton () { EditorGUI.BeginDisabledGroup(skeletonJSON.objectReferenceValue == null); if (GUILayout.Button(new GUIContent("Attempt Reimport", SpineEditorUtilities.Icons.warning))) { - SpineEditorUtilities.ImportSpineContent(new string[] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true); - - if (m_previewUtility != null) { - m_previewUtility.Cleanup(); - m_previewUtility = null; - } - - RepopulateWarnings(); - OnEnable(); + DoReimport(); return; - } EditorGUI.EndDisabledGroup(); } - void DrawAnimationStateInfo() { + void DoReimport () { + SpineEditorUtilities.ImportSpineContent(new string[] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true); + + if (m_previewUtility != null) { + m_previewUtility.Cleanup(); + m_previewUtility = null; + } + + RepopulateWarnings(); + OnEnable(); + + EditorUtility.SetDirty(m_skeletonDataAsset); + } + + void DrawAnimationStateInfo () { showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data"); if (!showAnimationStateData) return; @@ -196,9 +281,9 @@ public class SkeletonDataAssetInspector : Editor { } EditorGUILayout.Space(); EditorGUILayout.EndHorizontal(); - + } - void DrawAnimationList() { + void DrawAnimationList () { showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot)); if (!showAnimationList) return; @@ -233,8 +318,8 @@ public class SkeletonDataAssetInspector : Editor { } } - - void DrawSlotList() { + + void DrawSlotList () { showSlotList = EditorGUILayout.Foldout(showSlotList, new GUIContent("Slots", SpineEditorUtilities.Icons.slotRoot)); if (!showSlotList) @@ -250,7 +335,7 @@ public class SkeletonDataAssetInspector : Editor { return; } - + List slotAttachments = new List(); List slotAttachmentNames = new List(); List defaultSkinAttachmentNames = new List(); @@ -260,11 +345,11 @@ public class SkeletonDataAssetInspector : Editor { skin = defaultSkin; } - for (int i = m_skeletonAnimation.skeleton.Slots.Count-1; i >= 0; i--) { + for (int i = m_skeletonAnimation.skeleton.Slots.Count - 1; i >= 0; i--) { Slot slot = m_skeletonAnimation.skeleton.Slots[i]; EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, SpineEditorUtilities.Icons.slot)); if (showAttachments) { - + EditorGUI.indentLevel++; slotAttachments.Clear(); @@ -274,7 +359,7 @@ public class SkeletonDataAssetInspector : Editor { skin.FindNamesForSlot(i, slotAttachmentNames); skin.FindAttachmentsForSlot(i, slotAttachments); - + if (skin != defaultSkin) { defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames); defaultSkin.FindNamesForSlot(i, slotAttachmentNames); @@ -282,7 +367,7 @@ public class SkeletonDataAssetInspector : Editor { } else { defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames); } - + for (int a = 0; a < slotAttachments.Count; a++) { @@ -305,10 +390,10 @@ public class SkeletonDataAssetInspector : Editor { //TODO: Waterboard Nate //if (name != attachment.Name) - //icon = SpineEditorUtilities.Icons.skinPlaceholder; + //icon = SpineEditorUtilities.Icons.skinPlaceholder; bool initialState = slot.Attachment == attachment; - + bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(name, icon), slot.Attachment == attachment); if (!defaultSkinAttachmentNames.Contains(name)) { @@ -317,7 +402,7 @@ public class SkeletonDataAssetInspector : Editor { skinPlaceHolderIconRect.height = SpineEditorUtilities.Icons.skinPlaceholder.height; GUI.DrawTexture(skinPlaceHolderIconRect, SpineEditorUtilities.Icons.skinPlaceholder); } - + if (toggled != initialState) { if (toggled) { @@ -328,8 +413,8 @@ public class SkeletonDataAssetInspector : Editor { m_requireRefresh = true; } } - - + + EditorGUI.indentLevel--; } } @@ -338,7 +423,7 @@ public class SkeletonDataAssetInspector : Editor { } - void RepopulateWarnings() { + void RepopulateWarnings () { warnings.Clear(); if (skeletonJSON.objectReferenceValue == null) @@ -398,7 +483,7 @@ public class SkeletonDataAssetInspector : Editor { private bool m_requireRefresh; private Color m_originColor = new Color(0.3f, 0.3f, 0.3f, 1); - private void StopAnimation() { + private void StopAnimation () { m_skeletonAnimation.state.ClearTrack(0); m_playing = false; } @@ -406,7 +491,7 @@ public class SkeletonDataAssetInspector : Editor { List m_animEvents = new List(); List m_animEventFrames = new List(); - private void PlayAnimation(string animName, bool loop) { + private void PlayAnimation (string animName, bool loop) { m_animEvents.Clear(); m_animEventFrames.Clear(); @@ -428,18 +513,20 @@ public class SkeletonDataAssetInspector : Editor { m_playing = true; } - private void InitPreview() { + private void InitPreview () { if (this.m_previewUtility == null) { this.m_lastTime = Time.realtimeSinceStartup; this.m_previewUtility = new PreviewRenderUtility(true); this.m_previewUtility.m_Camera.isOrthoGraphic = true; this.m_previewUtility.m_Camera.orthographicSize = 1; this.m_previewUtility.m_Camera.cullingMask = -2147483648; + this.m_previewUtility.m_Camera.nearClipPlane = 0.01f; + this.m_previewUtility.m_Camera.farClipPlane = 1000f; this.CreatePreviewInstances(); } } - private void CreatePreviewInstances() { + private void CreatePreviewInstances () { this.DestroyPreviewInstances(); if (this.m_previewInstance == null) { try { @@ -466,7 +553,7 @@ public class SkeletonDataAssetInspector : Editor { } } - private void DestroyPreviewInstances() { + private void DestroyPreviewInstances () { if (this.m_previewInstance != null) { DestroyImmediate(this.m_previewInstance); m_previewInstance = null; @@ -474,7 +561,7 @@ public class SkeletonDataAssetInspector : Editor { m_initialized = false; } - public override bool HasPreviewGUI() { + public override bool HasPreviewGUI () { //TODO: validate json data for (int i = 0; i < atlasAssets.arraySize; i++) { @@ -488,7 +575,7 @@ public class SkeletonDataAssetInspector : Editor { Texture m_previewTex = new Texture(); - public override void OnInteractivePreviewGUI(Rect r, GUIStyle background) { + public override void OnInteractivePreviewGUI (Rect r, GUIStyle background) { this.InitPreview(); if (UnityEngine.Event.current.type == EventType.Repaint) { @@ -513,7 +600,7 @@ public class SkeletonDataAssetInspector : Editor { Vector3 m_posGoal = new Vector3(0, 0, -10); double m_adjustFrameEndTime = 0; - private void AdjustCameraGoals(bool calculateMixTime) { + private void AdjustCameraGoals (bool calculateMixTime) { if (this.m_previewInstance == null) return; @@ -532,11 +619,11 @@ public class SkeletonDataAssetInspector : Editor { m_posGoal = bounds.center + new Vector3(0, 0, -10); } - private void AdjustCameraGoals() { + private void AdjustCameraGoals () { AdjustCameraGoals(false); } - private void AdjustCamera() { + private void AdjustCamera () { if (m_previewUtility == null) return; @@ -559,7 +646,7 @@ public class SkeletonDataAssetInspector : Editor { } } - private void DoRenderPreview(bool drawHandles) { + private void DoRenderPreview (bool drawHandles) { GameObject go = this.m_previewInstance; if (m_requireRefresh && go != null) { @@ -591,7 +678,7 @@ public class SkeletonDataAssetInspector : Editor { } - void Update() { + void Update () { AdjustCamera(); if (m_playing) { @@ -604,7 +691,7 @@ public class SkeletonDataAssetInspector : Editor { } } - void DrawSkinToolbar(Rect r) { + void DrawSkinToolbar (Rect r) { if (m_skeletonAnimation == null) return; @@ -628,7 +715,7 @@ public class SkeletonDataAssetInspector : Editor { } } - void SelectSkinContext() { + void SelectSkinContext () { GenericMenu menu = new GenericMenu(); foreach (Skin s in m_skeletonData.Skins) { @@ -638,7 +725,7 @@ public class SkeletonDataAssetInspector : Editor { menu.ShowAsContext(); } - void SetSkin(object o) { + void SetSkin (object o) { Skin skin = (Skin)o; m_skeletonAnimation.initialSkinName = skin.Name; @@ -648,7 +735,7 @@ public class SkeletonDataAssetInspector : Editor { EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name); } - void NormalizedTimeBar(Rect r) { + void NormalizedTimeBar (Rect r) { if (m_skeletonAnimation == null) return; @@ -707,7 +794,7 @@ public class SkeletonDataAssetInspector : Editor { } } - void MouseScroll(Rect position) { + void MouseScroll (Rect position) { UnityEngine.Event current = UnityEngine.Event.current; int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive); @@ -766,11 +853,11 @@ public class SkeletonDataAssetInspector : Editor { } */ - public override GUIContent GetPreviewTitle() { + public override GUIContent GetPreviewTitle () { return new GUIContent("Preview"); } - public override void OnPreviewSettings() { + public override void OnPreviewSettings () { if (!m_initialized) { GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(64)); } else { @@ -787,7 +874,7 @@ public class SkeletonDataAssetInspector : Editor { //TODO: Fix first-import error //TODO: Update preview without thumbnail - public override Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height) { + public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) { Texture2D tex = new Texture2D(width, height, TextureFormat.ARGB32, false); this.InitPreview(); diff --git a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs index 5624ad02d..0ed1a3752 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs @@ -43,6 +43,8 @@ using System.Linq; using System.Reflection; using Spine; +using System.Security.Cryptography; + [InitializeOnLoad] public class SpineEditorUtilities : AssetPostprocessor { @@ -70,6 +72,7 @@ public class SpineEditorUtilities : AssetPostprocessor { public static Texture2D skeletonUtility; public static Texture2D hingeChain; public static Texture2D subMeshRenderer; + public static Texture2D unityIcon; public static Mesh boneMesh { get { @@ -136,6 +139,8 @@ public class SpineEditorUtilities : AssetPostprocessor { skeletonUtility = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeletonUtility.png"); hingeChain = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-hingeChain.png"); subMeshRenderer = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-subMeshRenderer.png"); + + unityIcon = EditorGUIUtility.FindTexture("SceneAsset Icon"); } } @@ -225,6 +230,8 @@ public class SpineEditorUtilities : AssetPostprocessor { } public static void ImportSpineContent(string[] imported, bool reimport = false) { + MD5 md5 = MD5.Create(); + List atlasPaths = new List(); List imagePaths = new List(); List skeletonPaths = new List(); @@ -267,7 +274,7 @@ public class SpineEditorUtilities : AssetPostprocessor { bool abortSkeletonImport = false; foreach (string sp in skeletonPaths) { if (!reimport && CheckForValidSkeletonData(sp)) { - Debug.Log("Automatically skipping: " + sp); + ResetExistingSkeletonData(sp); continue; } @@ -351,6 +358,32 @@ public class SpineEditorUtilities : AssetPostprocessor { return false; } + static void ResetExistingSkeletonData (string skeletonJSONPath) { + + string dir = Path.GetDirectoryName(skeletonJSONPath); + TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset)); + DirectoryInfo dirInfo = new DirectoryInfo(dir); + + FileInfo[] files = dirInfo.GetFiles("*.asset"); + + foreach (var f in files) { + string localPath = dir + "/" + f.Name; + var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object)); + if (obj is SkeletonDataAsset) { + var skeletonDataAsset = (SkeletonDataAsset)obj; + + if (skeletonDataAsset.skeletonJSON == textAsset) { + if (Selection.activeObject == skeletonDataAsset) + Selection.activeObject = null; + + skeletonDataAsset.Reset(); + } + + } + } + } + + static bool CheckForValidAtlas(string atlasPath) { string dir = Path.GetDirectoryName(atlasPath);