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);