Ver código fonte

Merge pull request #353 from Fenrisul/master

SkeletonBaker for Unity
Fenrisul 10 anos atrás
pai
commit
6789e7985c

+ 3 - 0
spine-csharp/src/Animation.cs

@@ -202,6 +202,9 @@ namespace Spine {
 			float y = curves[i - 1];
 			float y = curves[i - 1];
 			return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
 			return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
 		}
 		}
+		public float GetCurveType (int frameIndex) {
+			return curves[frameIndex * BEZIER_SIZE];
+		}
 	}
 	}
 
 
 	public class RotateTimeline : CurveTimeline {
 	public class RotateTimeline : CurveTimeline {

+ 1355 - 0
spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs

@@ -0,0 +1,1355 @@
+/******************************************************************************
+ * 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;
+
+
+/// <summary>
+/// [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)
+
+/// </summary>
+public static class SkeletonBaker {
+	/// <summary>
+	/// Interval between key sampling for Bezier curves, IK controlled bones, and Inherit Rotation effected bones.
+	/// </summary>
+	const float bakeIncrement = 1 / 60f;
+
+	public static void BakeToPrefab (SkeletonDataAsset skeletonDataAsset, List<Skin> 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<string, AnimationClip> existingClipTable = new Dictionary<string, AnimationClip>();
+			List<string> unusedClipNames = new List<string>();
+			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<int, List<string>> slotLookup = new Dictionary<int, List<string>>();
+
+			int skinCount = skins.Count;
+
+			for (int s = 0; s < skeletonData.Slots.Count; s++) {
+				List<string> attachmentNames = new List<string>();
+				for (int i = 0; i < skinCount; i++) {
+					var skin = skins[i];
+					List<string> temp = new List<string>();
+					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) {
+				EditorUtility.SetDirty(controller);
+				AssetDatabase.SaveAssets();
+				AssetDatabase.ImportAsset(controllerPath, ImportAssetOptions.ForceUpdate);
+				AssetDatabase.Refresh();
+			} 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<string, Mesh> meshTable = new Dictionary<string, Mesh>();
+			List<string> unusedMeshNames = new List<string>();
+			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<string, Transform> slotTable = new Dictionary<string, Transform>();
+			Dictionary<string, Transform> boneTable = new Dictionary<string, Transform>();
+			List<Transform> boneList = new List<Transform>();
+
+			//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<Attachment> attachments = new List<Attachment>();
+				List<string> attachmentNames = new List<string>();
+
+				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>();
+						skinnedMeshRenderer.rootBone = boneList[0];
+						skinnedMeshRenderer.bones = boneList.ToArray();
+						skinnedMeshRenderer.sharedMesh = mesh;
+
+					} else {
+						attachmentTransform.gameObject.AddComponent<MeshFilter>().sharedMesh = mesh;
+						attachmentTransform.gameObject.AddComponent<MeshRenderer>();
+					}
+
+					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>();
+				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<Transform> bones;
+		public List<float> weights;
+		public List<Pair> pairs;
+
+
+		public BoneWeightContainer () {
+			this.bones = new List<Transform>();
+			this.weights = new List<float>();
+			this.pairs = new List<Pair>();
+		}
+
+		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<Transform> 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<int, BoneWeightContainer> weightTable = new Dictionary<int, BoneWeightContainer>();
+		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 });
+
+		EditorUtility.SetDirty(clip);
+	}
+
+	static AnimationClip ExtractAnimation (string name, SkeletonData skeletonData, Dictionary<int, List<string>> 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<int> ignoreRotateTimelineIndexes = new List<int>();
+
+		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();
+
+		EditorUtility.SetDirty(clip);
+
+		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<AnimationEvent> animEvents = new List<AnimationEvent>();
+		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<int, List<string>> 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<string, AnimationCurve> curveTable = new Dictionary<string, AnimationCurve>();
+
+		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<Keyframe> keys = new List<Keyframe>();
+
+		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<Keyframe> xKeys = new List<Keyframe>();
+		List<Keyframe> yKeys = new List<Keyframe>();
+
+		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<Keyframe> xKeys = new List<Keyframe>();
+		List<Keyframe> yKeys = new List<Keyframe>();
+
+		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<Keyframe> keys = new List<Keyframe>();
+
+		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;
+	}
+}

+ 142 - 55
spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs

@@ -48,6 +48,10 @@ public class SkeletonDataAssetInspector : Editor {
 	static bool showAnimationList = true;
 	static bool showAnimationList = true;
 	static bool showSlotList = false;
 	static bool showSlotList = false;
 	static bool showAttachments = 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;
 	private SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
 
 
@@ -58,10 +62,10 @@ public class SkeletonDataAssetInspector : Editor {
 
 
 	List<string> warnings = new List<string>();
 	List<string> warnings = new List<string>();
 
 
-	void OnEnable() {
+	void OnEnable () {
+
+		SpineEditorUtilities.ConfirmInitialization();
 
 
-	SpineEditorUtilities.ConfirmInitialization();
-	
 		try {
 		try {
 			atlasAssets = serializedObject.FindProperty("atlasAssets");
 			atlasAssets = serializedObject.FindProperty("atlasAssets");
 			skeletonJSON = serializedObject.FindProperty("skeletonJSON");
 			skeletonJSON = serializedObject.FindProperty("skeletonJSON");
@@ -82,10 +86,12 @@ public class SkeletonDataAssetInspector : Editor {
 
 
 		m_skeletonData = m_skeletonDataAsset.GetSkeletonData(true);
 		m_skeletonData = m_skeletonDataAsset.GetSkeletonData(true);
 
 
+		showBaking = EditorPrefs.GetBool("SkeletonDataAssetInspector_showBaking", true);
+
 		RepopulateWarnings();
 		RepopulateWarnings();
 	}
 	}
 
 
-	void OnDestroy() {
+	void OnDestroy () {
 		m_initialized = false;
 		m_initialized = false;
 		EditorApplication.update -= Update;
 		EditorApplication.update -= Update;
 		this.DestroyPreviewInstances();
 		this.DestroyPreviewInstances();
@@ -95,7 +101,7 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 		}
 	}
 	}
 
 
-	override public void OnInspectorGUI() {
+	override public void OnInspectorGUI () {
 		serializedObject.Update();
 		serializedObject.Update();
 		SkeletonDataAsset asset = (SkeletonDataAsset)target;
 		SkeletonDataAsset asset = (SkeletonDataAsset)target;
 
 
@@ -115,16 +121,15 @@ public class SkeletonDataAssetInspector : Editor {
 				OnEnable();
 				OnEnable();
 				return;
 				return;
 			}
 			}
-			
+
 		}
 		}
 
 
-		
-		if (m_skeletonData != null) {
 
 
+		if (m_skeletonData != null) {
 			DrawAnimationStateInfo();
 			DrawAnimationStateInfo();
 			DrawAnimationList();
 			DrawAnimationList();
 			DrawSlotList();
 			DrawSlotList();
-			
+			DrawBaking();
 		} else {
 		} else {
 
 
 			DrawReimportButton();
 			DrawReimportButton();
@@ -142,25 +147,105 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 		}
 	}
 	}
 
 
-	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);
+	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 = "<No Skin>";
+
+				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<Skin>(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 (m_previewUtility != null) {
-				m_previewUtility.Cleanup();
-				m_previewUtility = null;
+					if(!oops)
+						GUILayout.EndVertical();
+				}
+				
 			}
 			}
+			GUILayout.EndHorizontal();
+			EditorGUI.indentLevel--;
 
 
-			RepopulateWarnings();
-			OnEnable();
+			EditorGUI.indentLevel--;
+		}
+		
+		
+	}
+	void DrawReimportButton () {
+		EditorGUI.BeginDisabledGroup(skeletonJSON.objectReferenceValue == null);
+		if (GUILayout.Button(new GUIContent("Attempt Reimport", SpineEditorUtilities.Icons.warning))) {
+			DoReimport();
 			return;
 			return;
-			
 		}
 		}
 		EditorGUI.EndDisabledGroup();
 		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");
 		showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data");
 		if (!showAnimationStateData)
 		if (!showAnimationStateData)
 			return;
 			return;
@@ -196,9 +281,9 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 		}
 		EditorGUILayout.Space();
 		EditorGUILayout.Space();
 		EditorGUILayout.EndHorizontal();
 		EditorGUILayout.EndHorizontal();
-		
+
 	}
 	}
-	void DrawAnimationList() {
+	void DrawAnimationList () {
 		showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
 		showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
 		if (!showAnimationList)
 		if (!showAnimationList)
 			return;
 			return;
@@ -233,8 +318,8 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 		}
 	}
 	}
 
 
-	
-	void DrawSlotList() {
+
+	void DrawSlotList () {
 		showSlotList = EditorGUILayout.Foldout(showSlotList, new GUIContent("Slots", SpineEditorUtilities.Icons.slotRoot));
 		showSlotList = EditorGUILayout.Foldout(showSlotList, new GUIContent("Slots", SpineEditorUtilities.Icons.slotRoot));
 
 
 		if (!showSlotList)
 		if (!showSlotList)
@@ -250,7 +335,7 @@ public class SkeletonDataAssetInspector : Editor {
 			return;
 			return;
 		}
 		}
 
 
-		
+
 		List<Attachment> slotAttachments = new List<Attachment>();
 		List<Attachment> slotAttachments = new List<Attachment>();
 		List<string> slotAttachmentNames = new List<string>();
 		List<string> slotAttachmentNames = new List<string>();
 		List<string> defaultSkinAttachmentNames = new List<string>();
 		List<string> defaultSkinAttachmentNames = new List<string>();
@@ -260,11 +345,11 @@ public class SkeletonDataAssetInspector : Editor {
 			skin = defaultSkin;
 			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];
 			Slot slot = m_skeletonAnimation.skeleton.Slots[i];
 			EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, SpineEditorUtilities.Icons.slot));
 			EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, SpineEditorUtilities.Icons.slot));
 			if (showAttachments) {
 			if (showAttachments) {
-				
+
 
 
 				EditorGUI.indentLevel++;
 				EditorGUI.indentLevel++;
 				slotAttachments.Clear();
 				slotAttachments.Clear();
@@ -274,7 +359,7 @@ public class SkeletonDataAssetInspector : Editor {
 				skin.FindNamesForSlot(i, slotAttachmentNames);
 				skin.FindNamesForSlot(i, slotAttachmentNames);
 				skin.FindAttachmentsForSlot(i, slotAttachments);
 				skin.FindAttachmentsForSlot(i, slotAttachments);
 
 
-				
+
 				if (skin != defaultSkin) {
 				if (skin != defaultSkin) {
 					defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
 					defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
 					defaultSkin.FindNamesForSlot(i, slotAttachmentNames);
 					defaultSkin.FindNamesForSlot(i, slotAttachmentNames);
@@ -282,7 +367,7 @@ public class SkeletonDataAssetInspector : Editor {
 				} else {
 				} else {
 					defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
 					defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
 				}
 				}
-				
+
 
 
 
 
 				for (int a = 0; a < slotAttachments.Count; a++) {
 				for (int a = 0; a < slotAttachments.Count; a++) {
@@ -305,10 +390,10 @@ public class SkeletonDataAssetInspector : Editor {
 
 
 					//TODO:  Waterboard Nate
 					//TODO:  Waterboard Nate
 					//if (name != attachment.Name)
 					//if (name != attachment.Name)
-						//icon = SpineEditorUtilities.Icons.skinPlaceholder;
+					//icon = SpineEditorUtilities.Icons.skinPlaceholder;
 
 
 					bool initialState = slot.Attachment == attachment;
 					bool initialState = slot.Attachment == attachment;
-					
+
 					bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(name, icon), slot.Attachment == attachment);
 					bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(name, icon), slot.Attachment == attachment);
 
 
 					if (!defaultSkinAttachmentNames.Contains(name)) {
 					if (!defaultSkinAttachmentNames.Contains(name)) {
@@ -317,7 +402,7 @@ public class SkeletonDataAssetInspector : Editor {
 						skinPlaceHolderIconRect.height = SpineEditorUtilities.Icons.skinPlaceholder.height;
 						skinPlaceHolderIconRect.height = SpineEditorUtilities.Icons.skinPlaceholder.height;
 						GUI.DrawTexture(skinPlaceHolderIconRect, SpineEditorUtilities.Icons.skinPlaceholder);
 						GUI.DrawTexture(skinPlaceHolderIconRect, SpineEditorUtilities.Icons.skinPlaceholder);
 					}
 					}
-					
+
 
 
 					if (toggled != initialState) {
 					if (toggled != initialState) {
 						if (toggled) {
 						if (toggled) {
@@ -328,8 +413,8 @@ public class SkeletonDataAssetInspector : Editor {
 						m_requireRefresh = true;
 						m_requireRefresh = true;
 					}
 					}
 				}
 				}
-				
-				
+
+
 				EditorGUI.indentLevel--;
 				EditorGUI.indentLevel--;
 			}
 			}
 		}
 		}
@@ -338,7 +423,7 @@ public class SkeletonDataAssetInspector : Editor {
 	}
 	}
 
 
 
 
-	void RepopulateWarnings() {
+	void RepopulateWarnings () {
 		warnings.Clear();
 		warnings.Clear();
 
 
 		if (skeletonJSON.objectReferenceValue == null)
 		if (skeletonJSON.objectReferenceValue == null)
@@ -398,7 +483,7 @@ public class SkeletonDataAssetInspector : Editor {
 	private bool m_requireRefresh;
 	private bool m_requireRefresh;
 	private Color m_originColor = new Color(0.3f, 0.3f, 0.3f, 1);
 	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_skeletonAnimation.state.ClearTrack(0);
 		m_playing = false;
 		m_playing = false;
 	}
 	}
@@ -406,7 +491,7 @@ public class SkeletonDataAssetInspector : Editor {
 	List<Spine.Event> m_animEvents = new List<Spine.Event>();
 	List<Spine.Event> m_animEvents = new List<Spine.Event>();
 	List<float> m_animEventFrames = new List<float>();
 	List<float> m_animEventFrames = new List<float>();
 
 
-	private void PlayAnimation(string animName, bool loop) {
+	private void PlayAnimation (string animName, bool loop) {
 		m_animEvents.Clear();
 		m_animEvents.Clear();
 		m_animEventFrames.Clear();
 		m_animEventFrames.Clear();
 
 
@@ -428,18 +513,20 @@ public class SkeletonDataAssetInspector : Editor {
 		m_playing = true;
 		m_playing = true;
 	}
 	}
 
 
-	private void InitPreview() {
+	private void InitPreview () {
 		if (this.m_previewUtility == null) {
 		if (this.m_previewUtility == null) {
 			this.m_lastTime = Time.realtimeSinceStartup;
 			this.m_lastTime = Time.realtimeSinceStartup;
 			this.m_previewUtility = new PreviewRenderUtility(true);
 			this.m_previewUtility = new PreviewRenderUtility(true);
 			this.m_previewUtility.m_Camera.isOrthoGraphic = true;
 			this.m_previewUtility.m_Camera.isOrthoGraphic = true;
 			this.m_previewUtility.m_Camera.orthographicSize = 1;
 			this.m_previewUtility.m_Camera.orthographicSize = 1;
 			this.m_previewUtility.m_Camera.cullingMask = -2147483648;
 			this.m_previewUtility.m_Camera.cullingMask = -2147483648;
+			this.m_previewUtility.m_Camera.nearClipPlane = 0.01f;
+			this.m_previewUtility.m_Camera.farClipPlane = 1000f;
 			this.CreatePreviewInstances();
 			this.CreatePreviewInstances();
 		}
 		}
 	}
 	}
 
 
-	private void CreatePreviewInstances() {
+	private void CreatePreviewInstances () {
 		this.DestroyPreviewInstances();
 		this.DestroyPreviewInstances();
 		if (this.m_previewInstance == null) {
 		if (this.m_previewInstance == null) {
 			try {
 			try {
@@ -466,7 +553,7 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 		}
 	}
 	}
 
 
-	private void DestroyPreviewInstances() {
+	private void DestroyPreviewInstances () {
 		if (this.m_previewInstance != null) {
 		if (this.m_previewInstance != null) {
 			DestroyImmediate(this.m_previewInstance);
 			DestroyImmediate(this.m_previewInstance);
 			m_previewInstance = null;
 			m_previewInstance = null;
@@ -474,7 +561,7 @@ public class SkeletonDataAssetInspector : Editor {
 		m_initialized = false;
 		m_initialized = false;
 	}
 	}
 
 
-	public override bool HasPreviewGUI() {
+	public override bool HasPreviewGUI () {
 		//TODO: validate json data
 		//TODO: validate json data
 
 
 		for (int i = 0; i < atlasAssets.arraySize; i++) {
 		for (int i = 0; i < atlasAssets.arraySize; i++) {
@@ -488,7 +575,7 @@ public class SkeletonDataAssetInspector : Editor {
 
 
 	Texture m_previewTex = new Texture();
 	Texture m_previewTex = new Texture();
 
 
-	public override void OnInteractivePreviewGUI(Rect r, GUIStyle background) {
+	public override void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
 		this.InitPreview();
 		this.InitPreview();
 
 
 		if (UnityEngine.Event.current.type == EventType.Repaint) {
 		if (UnityEngine.Event.current.type == EventType.Repaint) {
@@ -513,7 +600,7 @@ public class SkeletonDataAssetInspector : Editor {
 	Vector3 m_posGoal = new Vector3(0, 0, -10);
 	Vector3 m_posGoal = new Vector3(0, 0, -10);
 	double m_adjustFrameEndTime = 0;
 	double m_adjustFrameEndTime = 0;
 
 
-	private void AdjustCameraGoals(bool calculateMixTime) {
+	private void AdjustCameraGoals (bool calculateMixTime) {
 		if (this.m_previewInstance == null)
 		if (this.m_previewInstance == null)
 			return;
 			return;
 
 
@@ -532,11 +619,11 @@ public class SkeletonDataAssetInspector : Editor {
 		m_posGoal = bounds.center + new Vector3(0, 0, -10);
 		m_posGoal = bounds.center + new Vector3(0, 0, -10);
 	}
 	}
 
 
-	private void AdjustCameraGoals() {
+	private void AdjustCameraGoals () {
 		AdjustCameraGoals(false);
 		AdjustCameraGoals(false);
 	}
 	}
 
 
-	private void AdjustCamera() {
+	private void AdjustCamera () {
 		if (m_previewUtility == null)
 		if (m_previewUtility == null)
 			return;
 			return;
 
 
@@ -559,7 +646,7 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 		}
 	}
 	}
 
 
-	private void DoRenderPreview(bool drawHandles) {
+	private void DoRenderPreview (bool drawHandles) {
 		GameObject go = this.m_previewInstance;
 		GameObject go = this.m_previewInstance;
 
 
 		if (m_requireRefresh && go != null) {
 		if (m_requireRefresh && go != null) {
@@ -591,7 +678,7 @@ public class SkeletonDataAssetInspector : Editor {
 
 
 	}
 	}
 
 
-	void Update() {
+	void Update () {
 		AdjustCamera();
 		AdjustCamera();
 
 
 		if (m_playing) {
 		if (m_playing) {
@@ -604,7 +691,7 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 		}
 	}
 	}
 
 
-	void DrawSkinToolbar(Rect r) {
+	void DrawSkinToolbar (Rect r) {
 		if (m_skeletonAnimation == null)
 		if (m_skeletonAnimation == null)
 			return;
 			return;
 
 
@@ -628,7 +715,7 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 		}
 	}
 	}
 
 
-	void SelectSkinContext() {
+	void SelectSkinContext () {
 		GenericMenu menu = new GenericMenu();
 		GenericMenu menu = new GenericMenu();
 
 
 		foreach (Skin s in m_skeletonData.Skins) {
 		foreach (Skin s in m_skeletonData.Skins) {
@@ -638,7 +725,7 @@ public class SkeletonDataAssetInspector : Editor {
 		menu.ShowAsContext();
 		menu.ShowAsContext();
 	}
 	}
 
 
-	void SetSkin(object o) {
+	void SetSkin (object o) {
 		Skin skin = (Skin)o;
 		Skin skin = (Skin)o;
 
 
 		m_skeletonAnimation.initialSkinName = skin.Name;
 		m_skeletonAnimation.initialSkinName = skin.Name;
@@ -648,7 +735,7 @@ public class SkeletonDataAssetInspector : Editor {
 		EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
 		EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
 	}
 	}
 
 
-	void NormalizedTimeBar(Rect r) {
+	void NormalizedTimeBar (Rect r) {
 		if (m_skeletonAnimation == null)
 		if (m_skeletonAnimation == null)
 			return;
 			return;
 
 
@@ -707,7 +794,7 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 		}
 	}
 	}
 
 
-	void MouseScroll(Rect position) {
+	void MouseScroll (Rect position) {
 		UnityEngine.Event current = UnityEngine.Event.current;
 		UnityEngine.Event current = UnityEngine.Event.current;
 		int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
 		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");
 		return new GUIContent("Preview");
 	}
 	}
 
 
-	public override void OnPreviewSettings() {
+	public override void OnPreviewSettings () {
 		if (!m_initialized) {
 		if (!m_initialized) {
 			GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(64));
 			GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(64));
 		} else {
 		} else {
@@ -787,7 +874,7 @@ public class SkeletonDataAssetInspector : Editor {
 
 
 	//TODO:  Fix first-import error
 	//TODO:  Fix first-import error
 	//TODO:  Update preview without thumbnail
 	//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);
 		Texture2D tex = new Texture2D(width, height, TextureFormat.ARGB32, false);
 
 
 		this.InitPreview();
 		this.InitPreview();

+ 34 - 1
spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs

@@ -43,6 +43,8 @@ using System.Linq;
 using System.Reflection;
 using System.Reflection;
 using Spine;
 using Spine;
 
 
+using System.Security.Cryptography;
+
 [InitializeOnLoad]
 [InitializeOnLoad]
 public class SpineEditorUtilities : AssetPostprocessor {
 public class SpineEditorUtilities : AssetPostprocessor {
 
 
@@ -70,6 +72,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		public static Texture2D skeletonUtility;
 		public static Texture2D skeletonUtility;
 		public static Texture2D hingeChain;
 		public static Texture2D hingeChain;
 		public static Texture2D subMeshRenderer;
 		public static Texture2D subMeshRenderer;
+		public static Texture2D unityIcon;
 
 
 		public static Mesh boneMesh {
 		public static Mesh boneMesh {
 			get {
 			get {
@@ -136,6 +139,8 @@ public class SpineEditorUtilities : AssetPostprocessor {
 			skeletonUtility = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeletonUtility.png");
 			skeletonUtility = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeletonUtility.png");
 			hingeChain = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-hingeChain.png");
 			hingeChain = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-hingeChain.png");
 			subMeshRenderer = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-subMeshRenderer.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) {
 	public static void ImportSpineContent(string[] imported, bool reimport = false) {
 
 
+		MD5 md5 = MD5.Create();
+
 		List<string> atlasPaths = new List<string>();
 		List<string> atlasPaths = new List<string>();
 		List<string> imagePaths = new List<string>();
 		List<string> imagePaths = new List<string>();
 		List<string> skeletonPaths = new List<string>();
 		List<string> skeletonPaths = new List<string>();
@@ -267,7 +274,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		bool abortSkeletonImport = false;
 		bool abortSkeletonImport = false;
 		foreach (string sp in skeletonPaths) {
 		foreach (string sp in skeletonPaths) {
 			if (!reimport && CheckForValidSkeletonData(sp)) {
 			if (!reimport && CheckForValidSkeletonData(sp)) {
-				Debug.Log("Automatically skipping: " + sp);
+				ResetExistingSkeletonData(sp);
 				continue;
 				continue;
 			}
 			}
 				
 				
@@ -351,6 +358,32 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return false;
 		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) {
 	static bool CheckForValidAtlas(string atlasPath) {
 
 
 		string dir = Path.GetDirectoryName(atlasPath);
 		string dir = Path.GetDirectoryName(atlasPath);