소스 검색

[haxe] Initial port complete. WIP.

Mario Zechner 2 년 전
부모
커밋
fafb585882

+ 13 - 9
spine-haxe/spine-haxe/spine/SequenceMode.hx

@@ -1,20 +1,24 @@
 package spine;
 
+import openfl.Vector;
+
 class SequenceMode {
-	public static var hold(default, never):RotateMode = new SequenceMode("hold");
-	public static var once(default, never):RotateMode = new SequenceMode("once");
-	public static var loop(default, never):RotateMode = new SequenceMode("loop");
-	public static var pingpong(default, never):RotateMode = new SequenceMode("pingpong");
-	public static var onceReverse(default, never):RotateMode = new SequenceMode("onceReverse");
-	public static var loopReverse(default, never):RotateMode = new SequenceMode("loopReverse");
-	public static var pingpongReverse(default, never):RotateMode = new SequenceMode("pingpongReverse");
+	public static var hold(default, never):SequenceMode = new SequenceMode("hold", 0);
+	public static var once(default, never):SequenceMode = new SequenceMode("once", 1);
+	public static var loop(default, never):SequenceMode = new SequenceMode("loop", 2);
+	public static var pingpong(default, never):SequenceMode = new SequenceMode("pingpong", 3);
+	public static var onceReverse(default, never):SequenceMode = new SequenceMode("onceReverse", 4);
+	public static var loopReverse(default, never):SequenceMode = new SequenceMode("loopReverse", 5);
+	public static var pingpongReverse(default, never):SequenceMode = new SequenceMode("pingpongReverse", 6);
 
-	public static var values(default, never):Vector<SequenceMode> = Vector.ofArray([tangent, chain, chainScale]);
+	public static var values(default, never):Vector<SequenceMode> = Vector.ofArray([hold, once, loop, pingpong, onceReverse, loopReverse, pingpongReverse]);
 
 	public var name(default, null):String;
+	public var value:Int;
 
-	public function new(name:String) {
+	public function new(name:String, value:Int) {
 		this.name = name;
+		this.value = value;
 	}
 
 	public static function fromName(name:String):SequenceMode {

+ 101 - 64
spine-haxe/spine-haxe/spine/SkeletonBinary.hx

@@ -1,5 +1,6 @@
 package spine;
 
+import spine.animation.SequenceTimeline;
 import openfl.errors.ArgumentError;
 import openfl.errors.Error;
 import openfl.utils.ByteArray;
@@ -69,6 +70,9 @@ class SkeletonBinary {
 	private static inline var SLOT_RGB2:Int = 4;
 	private static inline var SLOT_ALPHA:Int = 5;
 
+	private static inline var ATTACHMENT_DEFORM = 0;
+	private static inline var ATTACHMENT_SEQUENCE = 1;
+
 	private static inline var PATH_POSITION:Int = 0;
 	private static inline var PATH_SPACING:Int = 1;
 	private static inline var PATH_MIX:Int = 2;
@@ -253,13 +257,14 @@ class SkeletonBinary {
 		for (linkedMesh in linkedMeshes) {
 			var skin:Skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
 			if (skin == null)
-				throw new Error("Skin not found: " + linkedMesh.skin);
+				throw new SpineException("Skin not found: " + linkedMesh.skin);
 			var parent:Attachment = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
 			if (parent == null)
-				throw new Error("Parent mesh not found: " + linkedMesh.parent);
-			linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? cast(parent, VertexAttachment) : linkedMesh.mesh;
+				throw new SpineException("Parent mesh not found: " + linkedMesh.parent);
+			linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? cast(parent, VertexAttachment) : linkedMesh.mesh;
 			linkedMesh.mesh.parentMesh = cast(parent, MeshAttachment);
-			linkedMesh.mesh.updateUVs();
+			if (linkedMesh.mesh.region != null)
+				linkedMesh.mesh.updateRegion();
 		}
 		linkedMeshes.length = 0;
 
@@ -327,6 +332,16 @@ class SkeletonBinary {
 		return skin;
 	}
 
+	private function readSequence(input:BinaryInput):Sequence {
+		if (!input.readBoolean())
+			return null;
+		var sequence = new Sequence(input.readInt(true));
+		sequence.start = input.readInt(true);
+		sequence.digits = input.readInt(true);
+		sequence.setupIndex = input.readInt(true);
+		return sequence;
+	}
+
 	private function readAttachment(input:BinaryInput, skeletonData:SkeletonData, skin:Skin, slotIndex:Int, attachmentName:String,
 			nonessential:Bool):Attachment {
 		var vertexCount:Int;
@@ -357,10 +372,11 @@ class SkeletonBinary {
 				width = input.readFloat();
 				height = input.readFloat();
 				color = input.readInt32();
+				var sequence = readSequence(input);
 
 				if (path == null)
 					path = name;
-				var region:RegionAttachment = attachmentLoader.newRegionAttachment(skin, name, path);
+				var region:RegionAttachment = attachmentLoader.newRegionAttachment(skin, name, path, sequence);
 				if (region == null)
 					return null;
 				region.path = path;
@@ -372,7 +388,9 @@ class SkeletonBinary {
 				region.width = width * scale;
 				region.height = height * scale;
 				region.color.setFromRgba8888(color);
-				region.updateOffset();
+				region.sequence = sequence;
+				if (sequence == null)
+					region.updateRegion();
 				return region;
 			case AttachmentType.boundingbox:
 				vertexCount = input.readInt(true);
@@ -397,6 +415,7 @@ class SkeletonBinary {
 				var triangles:Vector<Int> = readShortArray(input);
 				vertices = readVertices(input, vertexCount);
 				var hullLength:Int = input.readInt(true);
+				var sequence = readSequence(input);
 				var edges:Vector<Int> = null;
 				if (nonessential) {
 					edges = readShortArray(input);
@@ -406,7 +425,7 @@ class SkeletonBinary {
 
 				if (path == null)
 					path = name;
-				mesh = attachmentLoader.newMeshAttachment(skin, name, path);
+				mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence);
 				if (mesh == null)
 					return null;
 				mesh.path = path;
@@ -417,8 +436,10 @@ class SkeletonBinary {
 				mesh.worldVerticesLength = vertexCount << 1;
 				mesh.triangles = triangles;
 				mesh.regionUVs = uvs;
-				mesh.updateUVs();
+				if (sequence == null)
+					mesh.updateRegion();
 				mesh.hullLength = hullLength << 1;
+				mesh.sequence = sequence;
 				if (nonessential) {
 					mesh.edges = edges;
 					mesh.width = width * scale;
@@ -430,7 +451,8 @@ class SkeletonBinary {
 				color = input.readInt32();
 				var skinName:String = input.readStringRef();
 				var parent:String = input.readStringRef();
-				var inheritDeform:Bool = input.readBoolean();
+				var inheritTimelines:Bool = input.readBoolean();
+				var sequence = readSequence(input);
 				if (nonessential) {
 					width = input.readFloat();
 					height = input.readFloat();
@@ -438,16 +460,17 @@ class SkeletonBinary {
 
 				if (path == null)
 					path = name;
-				mesh = attachmentLoader.newMeshAttachment(skin, name, path);
+				mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence);
 				if (mesh == null)
 					return null;
 				mesh.path = path;
 				mesh.color.setFromRgba8888(color);
+				mesh.sequence = sequence;
 				if (nonessential) {
 					mesh.width = width * scale;
 					mesh.height = height * scale;
 				}
-				this.linkedMeshes.push(new LinkedMeshBinary(mesh, skinName, slotIndex, parent, inheritDeform));
+				this.linkedMeshes.push(new LinkedMeshBinary(mesh, skinName, slotIndex, parent, inheritTimelines));
 				return mesh;
 			case AttachmentType.path:
 				var closed:Bool = input.readBoolean();
@@ -985,64 +1008,78 @@ class SkeletonBinary {
 					var attachmentName:String = input.readStringRef();
 					var attachment:VertexAttachment = cast(skin.getAttachment(slotIndex, attachmentName), VertexAttachment);
 					if (attachment == null)
-						throw new Error("Vertex attachment not found: " + attachmentName);
-					var weighted:Bool = attachment.bones != null;
-					var vertices:Vector<Float> = attachment.vertices;
-					var deformLength:Int = weighted ? Std.int(vertices.length / 3 * 2) : vertices.length;
-
+						throw new SpineException("Vertex attachment not found: " + attachmentName);
+					var timelineType = input.readByte();
 					frameCount = input.readInt(true);
 					frameLast = frameCount - 1;
-					bezierCount = input.readInt(true);
-					var deformTimeline:DeformTimeline = new DeformTimeline(frameCount, bezierCount, slotIndex, attachment);
-
-					time = input.readFloat();
-					frame = 0;
-					bezier = 0;
-					while (true) {
-						var deform:Vector<Float>;
-						var end:Int = input.readInt(true);
-						if (end == 0) {
-							if (weighted) {
-								deform = new Vector<Float>(deformLength, true);
-							} else {
-								deform = vertices;
-							}
-						} else {
-							var v:Int, vn:Int;
-							deform = new Vector<Float>(deformLength, true);
-							var start:Int = input.readInt(true);
-							end += start;
-							if (scale == 1) {
-								for (v in start...end) {
-									deform[v] = input.readFloat();
+
+					switch (timelineType) {
+						case ATTACHMENT_DEFORM:
+							var weighted:Bool = attachment.bones != null;
+							var vertices:Vector<Float> = attachment.vertices;
+							var deformLength:Int = weighted ? Std.int(vertices.length / 3 * 2) : vertices.length;
+
+							bezierCount = input.readInt(true);
+							var deformTimeline:DeformTimeline = new DeformTimeline(frameCount, bezierCount, slotIndex, attachment);
+
+							time = input.readFloat();
+							frame = 0;
+							bezier = 0;
+							while (true) {
+								var deform:Vector<Float>;
+								var end:Int = input.readInt(true);
+								if (end == 0) {
+									if (weighted) {
+										deform = new Vector<Float>(deformLength, true);
+									} else {
+										deform = vertices;
+									}
+								} else {
+									var v:Int, vn:Int;
+									deform = new Vector<Float>(deformLength, true);
+									var start:Int = input.readInt(true);
+									end += start;
+									if (scale == 1) {
+										for (v in start...end) {
+											deform[v] = input.readFloat();
+										}
+									} else {
+										for (v in start...end) {
+											deform[v] = input.readFloat() * scale;
+										}
+									}
+									if (!weighted) {
+										for (v in 0...deform.length) {
+											deform[v] += vertices[v];
+										}
+									}
 								}
-							} else {
-								for (v in start...end) {
-									deform[v] = input.readFloat() * scale;
+
+								deformTimeline.setFrame(frame, time, deform);
+								if (frame == frameLast)
+									break;
+								time2 = input.readFloat();
+								switch (input.readByte()) {
+									case CURVE_STEPPED:
+										deformTimeline.setStepped(frame);
+									case CURVE_BEZIER:
+										SkeletonBinary.setBezier(input, deformTimeline, bezier++, frame, 0, time, time2, 0, 1, 1);
 								}
+								time = time2;
+
+								frame++;
 							}
-							if (!weighted) {
-								for (v in 0...deform.length) {
-									deform[v] += vertices[v];
-								}
+							timelines.push(deformTimeline);
+						case ATTACHMENT_SEQUENCE:
+							var timeline = new SequenceTimeline(frameCount, slotIndex, cast(attachment, HasTextureRegion));
+							for (frame in 0...frameCount) {
+								var time = input.readFloat();
+								var modeAndIndex = input.readInt32();
+								timeline.setFrame(frame, time, SequenceMode.values[modeAndIndex & 0xf], modeAndIndex >> 4, input.readFloat());
 							}
-						}
-
-						deformTimeline.setFrame(frame, time, deform);
-						if (frame == frameLast)
+							timelines.push(timeline);
 							break;
-						time2 = input.readFloat();
-						switch (input.readByte()) {
-							case CURVE_STEPPED:
-								deformTimeline.setStepped(frame);
-							case CURVE_BEZIER:
-								SkeletonBinary.setBezier(input, deformTimeline, bezier++, frame, 0, time, time2, 0, 1, 1);
-						}
-						time = time2;
-
-						frame++;
 					}
-					timelines.push(deformTimeline);
 				}
 			}
 		}
@@ -1193,13 +1230,13 @@ class LinkedMeshBinary {
 	public var skin(default, null):String;
 	public var slotIndex(default, null):Int;
 	public var mesh(default, null):MeshAttachment;
-	public var inheritDeform(default, null):Bool;
+	public var inheritTimeline(default, null):Bool;
 
-	public function new(mesh:MeshAttachment, skin:String, slotIndex:Int, parent:String, inheritDeform:Bool) {
+	public function new(mesh:MeshAttachment, skin:String, slotIndex:Int, parent:String, inheritTimeline:Bool) {
 		this.mesh = mesh;
 		this.skin = skin;
 		this.slotIndex = slotIndex;
 		this.parent = parent;
-		this.inheritDeform = inheritDeform;
+		this.inheritTimeline = inheritTimeline;
 	}
 }

+ 159 - 100
spine-haxe/spine-haxe/spine/SkeletonJson.hx

@@ -1,5 +1,6 @@
 package spine;
 
+import spine.animation.SequenceTimeline;
 import haxe.Json;
 import openfl.errors.ArgumentError;
 import openfl.errors.Error;
@@ -97,7 +98,7 @@ class SkeletonJson {
 			if (parentName != null) {
 				parent = skeletonData.findBone(parentName);
 				if (parent == null)
-					throw new Error("Parent bone not found: " + parentName);
+					throw new SpineException("Parent bone not found: " + parentName);
 			}
 			boneData = new BoneData(skeletonData.bones.length, Reflect.getProperty(boneMap, "name"), parent);
 			boneData.length = getFloat(Reflect.getProperty(boneMap, "length")) * scale;
@@ -126,7 +127,7 @@ class SkeletonJson {
 			var boneName:String = Reflect.getProperty(slotMap, "bone");
 			boneData = skeletonData.findBone(boneName);
 			if (boneData == null)
-				throw new Error("Slot bone not found: " + boneName);
+				throw new SpineException("Slot bone not found: " + boneName);
 			var slotData:SlotData = new SlotData(skeletonData.slots.length, slotName, boneData);
 
 			var color:String = Reflect.getProperty(slotMap, "color");
@@ -155,22 +156,22 @@ class SkeletonJson {
 				for (boneName in cast(Reflect.getProperty(constraintMap, "bones"), Array<Dynamic>)) {
 					var bone:BoneData = skeletonData.findBone(boneName);
 					if (bone == null)
-						throw new Error("IK constraint bone not found: " + boneName);
+						throw new SpineException("IK constraint bone not found: " + boneName);
 					ikData.bones.push(bone);
 				}
 
 				ikData.target = skeletonData.findBone(Reflect.getProperty(constraintMap, "target"));
 				if (ikData.target == null)
-					throw new Error("Target bone not found: " + Reflect.getProperty(constraintMap, "target"));
+					throw new SpineException("Target bone not found: " + Reflect.getProperty(constraintMap, "target"));
 
+				ikData.mix = getFloat(Reflect.getProperty(constraintMap, "mix"), 1);
+				ikData.softness = getFloat(Reflect.getProperty(constraintMap, "softness"), 0) * scale;
 				ikData.bendDirection = (!Reflect.hasField(constraintMap, "bendPositive")
 					|| cast(Reflect.getProperty(constraintMap, "bendPositive"), Bool)) ? 1 : -1;
 				ikData.compress = (Reflect.hasField(constraintMap, "compress")
 					&& cast(Reflect.getProperty(constraintMap, "compress"), Bool));
 				ikData.stretch = (Reflect.hasField(constraintMap, "stretch") && cast(Reflect.getProperty(constraintMap, "stretch"), Bool));
 				ikData.uniform = (Reflect.hasField(constraintMap, "uniform") && cast(Reflect.getProperty(constraintMap, "uniform"), Bool));
-				ikData.softness = getFloat(Reflect.getProperty(constraintMap, "softness")) * scale;
-				ikData.mix = getFloat(Reflect.getProperty(constraintMap, "mix"), 1);
 
 				skeletonData.ikConstraints.push(ikData);
 			}
@@ -186,13 +187,13 @@ class SkeletonJson {
 				for (boneName in cast(Reflect.getProperty(constraintMap, "bones"), Array<Dynamic>)) {
 					var bone = skeletonData.findBone(boneName);
 					if (bone == null)
-						throw new Error("Transform constraint bone not found: " + boneName);
+						throw new SpineException("Transform constraint bone not found: " + boneName);
 					transformData.bones.push(bone);
 				}
 
 				transformData.target = skeletonData.findBone(Reflect.getProperty(constraintMap, "target"));
 				if (transformData.target == null)
-					throw new Error("Target bone not found: " + Reflect.getProperty(constraintMap, "target"));
+					throw new SpineException("Target bone not found: " + Reflect.getProperty(constraintMap, "target"));
 
 				transformData.local = Reflect.hasField(constraintMap, "local") ? cast(Reflect.getProperty(constraintMap, "local"), Bool) : false;
 				transformData.relative = Reflect.hasField(constraintMap, "relative") ? cast(Reflect.getProperty(constraintMap, "relative"), Bool) : false;
@@ -225,13 +226,13 @@ class SkeletonJson {
 				for (boneName in cast(Reflect.getProperty(constraintMap, "bones"), Array<Dynamic>)) {
 					var bone = skeletonData.findBone(boneName);
 					if (bone == null)
-						throw new Error("Path constraint bone not found: " + boneName);
+						throw new SpineException("Path constraint bone not found: " + boneName);
 					pathData.bones.push(bone);
 				}
 
 				pathData.target = skeletonData.findSlot(Reflect.getProperty(constraintMap, "target"));
 				if (pathData.target == null)
-					throw new Error("Path target slot not found: " + Reflect.getProperty(constraintMap, "target"));
+					throw new SpineException("Path target slot not found: " + Reflect.getProperty(constraintMap, "target"));
 
 				pathData.positionMode = Reflect.hasField(constraintMap,
 					"positionMode") ? PositionMode.fromName(Reflect.getProperty(constraintMap, "positionMode")) : PositionMode.percent;
@@ -264,7 +265,7 @@ class SkeletonJson {
 					for (ii in 0...bones.length) {
 						var boneData:BoneData = skeletonData.findBone(bones[ii]);
 						if (boneData == null)
-							throw new Error("Skin bone not found: " + bones[ii]);
+							throw new SpineException("Skin bone not found: " + bones[ii]);
 						skin.bones.push(boneData);
 					}
 				}
@@ -274,7 +275,7 @@ class SkeletonJson {
 					for (ii in 0...ik.length) {
 						var constraint:ConstraintData = skeletonData.findIkConstraint(ik[ii]);
 						if (constraint == null)
-							throw new Error("Skin IK constraint not found: " + ik[ii]);
+							throw new SpineException("Skin IK constraint not found: " + ik[ii]);
 						skin.constraints.push(constraint);
 					}
 				}
@@ -284,7 +285,7 @@ class SkeletonJson {
 					for (ii in 0...transform.length) {
 						var constraint:ConstraintData = skeletonData.findTransformConstraint(transform[ii]);
 						if (constraint == null)
-							throw new Error("Skin transform constraint not found: " + transform[ii]);
+							throw new SpineException("Skin transform constraint not found: " + transform[ii]);
 						skin.constraints.push(constraint);
 					}
 				}
@@ -294,7 +295,7 @@ class SkeletonJson {
 					for (ii in 0...path.length) {
 						var constraint:ConstraintData = skeletonData.findPathConstraint(path[ii]);
 						if (constraint == null)
-							throw new Error("Skin path constraint not found: " + path[ii]);
+							throw new SpineException("Skin path constraint not found: " + path[ii]);
 						skin.constraints.push(constraint);
 					}
 				}
@@ -325,13 +326,14 @@ class SkeletonJson {
 		for (linkedMesh in linkedMeshes) {
 			var parentSkin:Skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
 			if (parentSkin == null)
-				throw new Error("Skin not found: " + linkedMesh.skin);
+				throw new SpineException("Skin not found: " + linkedMesh.skin);
 			var parentMesh:Attachment = parentSkin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
 			if (parentMesh == null)
-				throw new Error("Parent mesh not found: " + linkedMesh.parent);
-			linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? cast(parentMesh, VertexAttachment) : linkedMesh.mesh;
+				throw new SpineException("Parent mesh not found: " + linkedMesh.parent);
+			linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimeline ? cast(parentMesh, VertexAttachment) : linkedMesh.mesh;
 			linkedMesh.mesh.parentMesh = cast(parentMesh, MeshAttachment);
-			linkedMesh.mesh.updateUVs();
+			if (linkedMesh.mesh.region != null)
+				linkedMesh.mesh.updateRegion();
 		}
 		linkedMeshes.length = 0;
 
@@ -359,6 +361,16 @@ class SkeletonJson {
 		return skeletonData;
 	}
 
+	private function readSequence(map:Object) {
+		if (map == null)
+			return null;
+		var sequence = new Sequence(getInt(map["count"], 0));
+		sequence.start = getInt(map["start"], 1);
+		sequence.digits = getInt(map["digits"], 0);
+		sequence.setupIndex = getInt(map["setup"], 0);
+		return sequence;
+	}
+
 	private function readAttachment(map:Object, skin:Skin, slotIndex:Int, name:String, skeletonData:SkeletonData):Attachment {
 		if (map["name"] != null)
 			name = map["name"];
@@ -366,10 +378,12 @@ class SkeletonJson {
 		var color:String;
 		switch (AttachmentType.fromName(Reflect.hasField(map, "type") ? Reflect.getProperty(map, "type") : "region")) {
 			case AttachmentType.region:
-				var region:RegionAttachment = attachmentLoader.newRegionAttachment(skin, name, map["path"] != null ? map["path"] : name);
+				var path = getString(map, "path", name);
+				var sequence = readSequence(map["sequence"]);
+				var region:RegionAttachment = attachmentLoader.newRegionAttachment(skin, name, path, sequence);
 				if (region == null)
 					return null;
-				region.path = map["path"] != null ? map["path"] : name;
+				region.path = path;
 				region.x = getFloat(map["x"]) * scale;
 				region.y = getFloat(map["y"]) * scale;
 				region.scaleX = getFloat(map["scaleX"], 1);
@@ -377,36 +391,48 @@ class SkeletonJson {
 				region.rotation = getFloat(map["rotation"]);
 				region.width = getFloat(map["width"]) * scale;
 				region.height = getFloat(map["height"]) * scale;
+				region.sequence = sequence;
+
 				color = Reflect.getProperty(map, "color");
 				if (color != null) {
 					region.color.setFromString(color);
 				}
-				region.updateOffset();
+				if (region.region != null)
+					region.updateRegion();
 				return region;
 			case AttachmentType.mesh, AttachmentType.linkedmesh:
-				var mesh:MeshAttachment = attachmentLoader.newMeshAttachment(skin, name, map["path"] != null ? map["path"] : name);
+				var path = getString(map, "path", name);
+				var sequence = readSequence(map["sequence"]);
+				var mesh:MeshAttachment = attachmentLoader.newMeshAttachment(skin, name, path, sequence);
 				if (mesh == null)
 					return null;
-				mesh.path = map["path"] != null ? map["path"] : name;
+				mesh.path = path;
+
 				color = Reflect.getProperty(map, "color");
 				if (color != null) {
 					mesh.color.setFromString(color);
 				}
+
 				mesh.width = getFloat(map["width"]) * scale;
 				mesh.height = getFloat(map["height"]) * scale;
+				mesh.sequence = sequence;
+
 				if (map["parent"] != null) {
-					var inheritDeform:Bool = map.hasOwnProperty("deform") ? cast(map["deform"], Bool) : true;
-					linkedMeshes.push(new LinkedMesh(mesh, map["skin"], slotIndex, map["parent"], inheritDeform));
+					var inheritTimelines:Bool = map.hasOwnProperty("timelines") ? cast(map["timelines"], Bool) : true;
+					linkedMeshes.push(new LinkedMesh(mesh, map["skin"], slotIndex, map["parent"], inheritTimelines));
 					return mesh;
 				}
+
 				var uvs:Vector<Float> = getFloatArray(map, "uvs");
 				readVertices(map, mesh, uvs.length);
 				mesh.triangles = getIntArray(map, "triangles");
 				mesh.regionUVs = uvs;
-				mesh.updateUVs();
-				mesh.hullLength = (getInt(map["hull"])) * 2;
+				if (mesh.region != null)
+					mesh.updateRegion();
+
 				if (map["edges"] != null)
 					mesh.edges = getIntArray(map, "edges");
+				mesh.hullLength = (getInt(map["hull"])) * 2;
 				return mesh;
 			case AttachmentType.boundingbox:
 				var box:BoundingBoxAttachment = attachmentLoader.newBoundingBoxAttachment(skin, name);
@@ -448,7 +474,7 @@ class SkeletonJson {
 				if (end != null) {
 					var slot:SlotData = skeletonData.findSlot(end);
 					if (slot == null)
-						throw new Error("Clipping end slot not found: " + end);
+						throw new SpineException("Clipping end slot not found: " + end);
 					clip.endSlot = slot;
 				}
 				var vertexCount:Int = Std.parseInt(map["vertexCount"]);
@@ -524,7 +550,7 @@ class SkeletonJson {
 					var attachmentTimeline:AttachmentTimeline = new AttachmentTimeline(timelineMap.length, slotIndex);
 					for (frame in 0...timelineMap.length) {
 						keyMap = timelineMap[frame];
-						attachmentTimeline.setFrame(frame, getFloat(Reflect.getProperty(keyMap, "time")), keyMap.name);
+						attachmentTimeline.setFrame(frame, getFloat(Reflect.getProperty(keyMap, "time")), getString(keyMap, "name", null));
 					}
 					timelines.push(attachmentTimeline);
 				} else if (timelineName == "rgba") {
@@ -672,7 +698,7 @@ class SkeletonJson {
 
 					timelines.push(rgb2Timeline);
 				} else {
-					throw new Error("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
+					throw new SpineException("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
 				}
 			}
 		}
@@ -682,7 +708,7 @@ class SkeletonJson {
 		for (boneName in bones) {
 			var boneIndex:Int = skeletonData.findBoneIndex(boneName);
 			if (boneIndex == -1)
-				throw new Error("Bone not found: " + boneName);
+				throw new SpineException("Bone not found: " + boneName);
 			var boneMap:Object = bones[boneName];
 			for (timelineName in boneMap) {
 				timelineMap = boneMap[timelineName];
@@ -719,7 +745,7 @@ class SkeletonJson {
 					var shearYTimeline:ShearYTimeline = new ShearYTimeline(timelineMap.length, timelineMap.length, boneIndex);
 					timelines.push(readTimeline(timelineMap, shearYTimeline, 0, 1));
 				} else {
-					throw new Error("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
+					throw new SpineException("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
 				}
 			}
 		}
@@ -839,7 +865,7 @@ class SkeletonJson {
 		for (pathName in paths) {
 			var index:Int = skeletonData.findPathConstraintIndex(pathName);
 			if (index == -1)
-				throw new Error("Path constraint not found: " + pathName);
+				throw new SpineException("Path constraint not found: " + pathName);
 			var pathData:PathConstraintData = skeletonData.pathConstraints[index];
 
 			var pathMap:Object = paths[pathName];
@@ -896,78 +922,99 @@ class SkeletonJson {
 			}
 		}
 
-		// Deform timelines.
-		var deforms:Object = Reflect.getProperty(map, "deform");
-		for (deformName in deforms) {
-			var deformMap:Object = deforms[deformName];
-			var skin:Skin = skeletonData.findSkin(deformName);
+		// Attachment timelines.
+		var attachments:Object = Reflect.getProperty(map, "attachments");
+		for (attachmentsName in attachments) {
+			var attachmentsMap:Object = attachments[attachmentsName];
+			var skin:Skin = skeletonData.findSkin(attachmentsName);
 			if (skin == null)
-				throw new Error("Skin not found: " + deformName);
+				throw new SpineException("Skin not found: " + attachmentsName);
 
-			for (slotName in deformMap) {
-				slotMap = deformMap[slotName];
+			for (slotMapName in attachmentsMap) {
+				slotMap = attachmentsMap[slotMapName];
 				slotIndex = skeletonData.findSlot(slotName).index;
 				if (slotIndex == -1)
-					throw new Error("Slot not found: " + slotName);
-				for (timelineName in slotMap) {
-					timelineMap = slotMap[timelineName];
-					keyMap = timelineMap[0];
-					if (keyMap == null)
-						continue;
-
-					var attachment:VertexAttachment = cast(skin.getAttachment(slotIndex, timelineName), VertexAttachment);
+					throw new SpineException("Slot not found: " + slotName);
+				for (attachmentMapName in slotMap) {
+					var attachmentMap = slotMap[attachmentMapName];
+					var attachment:VertexAttachment = cast(skin.getAttachment(slotIndex, attachmentMapName), VertexAttachment);
 					if (attachment == null)
-						throw new Error("Deform attachment not found: " + timelineName);
-					var weighted:Bool = attachment.bones != null;
-					var vertices:Vector<Float> = attachment.vertices;
-					var deformLength:Int = weighted ? Std.int(vertices.length / 3 * 2) : vertices.length;
+						throw new SpineException("Timeline attachment not found: " + timelineName);
+
+					for (timelineMapName in attachmentMap) {
+						var timelineMap = attachmentMap[timelineMapName];
+						var keyMap = timelineMap[0];
+						if (keyMap == null)
+							continue;
+
+						if (timelineMapName == "deform") {
+							var weighted:Bool = attachment.bones != null;
+							var vertices:Vector<Float> = attachment.vertices;
+							var deformLength:Int = weighted ? Std.int(vertices.length / 3 * 2) : vertices.length;
+
+							var deformTimeline:DeformTimeline = new DeformTimeline(timelineMap.length, timelineMap.length, slotIndex, attachment);
+							time = getFloat(Reflect.getProperty(keyMap, "time"));
+							frame = 0;
+							bezier = 0;
+							while (true) {
+								var deform:Vector<Float>;
+								var verticesValue:Vector<Float> = Reflect.getProperty(keyMap, "vertices");
+								if (verticesValue == null) {
+									deform = weighted ? new Vector<Float>(deformLength, true) : vertices;
+								} else {
+									deform = new Vector<Float>(deformLength, true);
+									var start:Int = getInt(Reflect.getProperty(keyMap, "offset"));
+									var temp:Vector<Float> = getFloatArray(keyMap, "vertices");
+									for (i in 0...temp.length) {
+										deform[start + i] = temp[i];
+									}
+									if (scale != 1) {
+										for (i in start...start + temp.length) {
+											deform[i] *= scale;
+										}
+									}
+									if (!weighted) {
+										for (i in 0...deformLength) {
+											deform[i] += vertices[i];
+										}
+									}
+								}
 
-					var deformTimeline:DeformTimeline = new DeformTimeline(timelineMap.length, timelineMap.length, slotIndex, attachment);
-					time = getFloat(Reflect.getProperty(keyMap, "time"));
-					frame = 0;
-					bezier = 0;
-					while (true) {
-						var deform:Vector<Float>;
-						var verticesValue:Vector<Float> = Reflect.getProperty(keyMap, "vertices");
-						if (verticesValue == null) {
-							deform = weighted ? new Vector<Float>(deformLength, true) : vertices;
-						} else {
-							deform = new Vector<Float>(deformLength, true);
-							var start:Int = getInt(Reflect.getProperty(keyMap, "offset"));
-							var temp:Vector<Float> = getFloatArray(keyMap, "vertices");
-							for (i in 0...temp.length) {
-								deform[start + i] = temp[i];
-							}
-							if (scale != 1) {
-								for (i in start...start + temp.length) {
-									deform[i] *= scale;
+								deformTimeline.setFrame(frame, time, deform);
+								nextMap = timelineMap[frame + 1];
+								if (nextMap == null) {
+									deformTimeline.shrink(bezier);
+									break;
 								}
-							}
-							if (!weighted) {
-								for (i in 0...deformLength) {
-									deform[i] += vertices[i];
+								time2 = getFloat(Reflect.getProperty(nextMap, "time"));
+								curve = keyMap.curve;
+								if (curve != null) {
+									bezier = readCurve(curve, deformTimeline, bezier, frame, 0, time, time2, 0, 1, 1);
 								}
+								time = time2;
+								keyMap = nextMap;
+
+								frame++;
 							}
-						}
 
-						deformTimeline.setFrame(frame, time, deform);
-						nextMap = timelineMap[frame + 1];
-						if (nextMap == null) {
-							deformTimeline.shrink(bezier);
-							break;
-						}
-						time2 = getFloat(Reflect.getProperty(nextMap, "time"));
-						curve = keyMap.curve;
-						if (curve != null) {
-							bezier = readCurve(curve, deformTimeline, bezier, frame, 0, time, time2, 0, 1, 1);
+							timelines.push(deformTimeline);
+						} else if (timelineMapName == "sequence") {
+							var timeline = new SequenceTimeline(timelineMap.length, slotIndex, cast(attachment, HasTextureRegion));
+							var lastDelay:Float = 0;
+							var frame:Int = 0;
+							while (frame < timelineMap.length) {
+								var delay = getFloat(keyMap["delay"], lastDelay);
+								var time = getFloat(keyMap["time"], 0);
+								var mode = SequenceMode.fromName(getString(keyMap, "mode", "hold"));
+								var index = getInt(keyMap["index"], 0);
+								timeline.setFrame(frame, time, mode, index, delay);
+								lastDelay = delay;
+								keyMap = timelineMap[frame + 1];
+								frame++;
+							}
+							timelines.push(timeline);
 						}
-						time = time2;
-						keyMap = nextMap;
-
-						frame++;
 					}
-
-					timelines.push(deformTimeline);
 				}
 			}
 		}
@@ -993,7 +1040,7 @@ class SkeletonJson {
 						for (offsetMap in offsets) {
 							slotIndex = skeletonData.findSlot(Reflect.getProperty(offsetMap, "slot")).index;
 							if (slotIndex == -1)
-								throw new Error("Slot not found: " + Reflect.getProperty(offsetMap, "slot"));
+								throw new SpineException("Slot not found: " + Reflect.getProperty(offsetMap, "slot"));
 							// Collect unchanged items.
 							while (originalIndex != slotIndex) {
 								unchanged[unchangedIndex++] = originalIndex++;
@@ -1028,7 +1075,7 @@ class SkeletonJson {
 				for (eventMap in eventsMap) {
 					var eventData:EventData = skeletonData.findEvent(Reflect.getProperty(eventMap, "name"));
 					if (eventData == null)
-						throw new Error("Event not found: " + Reflect.getProperty(eventMap, "name"));
+						throw new SpineException("Event not found: " + Reflect.getProperty(eventMap, "name"));
 					var event:Event = new Event(getFloat(Reflect.getProperty(eventMap, "time")), eventData);
 					event.intValue = Reflect.hasField(eventMap, "int") ? getInt(Reflect.getProperty(eventMap, "int")) : eventData.intValue;
 					event.floatValue = Reflect.hasField(eventMap, "float") ? getFloat(Reflect.getProperty(eventMap, "float")) : eventData.floatValue;
@@ -1128,6 +1175,18 @@ class SkeletonJson {
 		return bezier + 1;
 	}
 
+	static private function getValue(map:Object, name:String, defaultValue:Dynamic):Dynamic {
+		if (map.hasOwnProperty(name))
+			return map[name];
+		return defaultValue;
+	}
+
+	static private function getString(value:Object, name:String, defaultValue:String):String {
+		if (Std.isOfType(value[name], String))
+			return cast(value[name], String);
+		return defaultValue;
+	}
+
 	static private function getFloat(value:Object, defaultValue:Float = 0):Float {
 		if (Std.isOfType(value, Float))
 			return cast(value, Float);
@@ -1146,12 +1205,12 @@ class SkeletonJson {
 		return values;
 	}
 
-	static private function getInt(value:Object):Int {
+	static private function getInt(value:Object, defaultValue:Int = 0):Int {
 		if (Std.isOfType(value, Int))
 			return cast(value, Int);
 		var intValue:Null<Int> = Std.parseInt(value);
 		if (intValue == null)
-			intValue = 0;
+			intValue = defaultValue;
 		return intValue;
 	}
 
@@ -1170,13 +1229,13 @@ class LinkedMesh {
 	public var skin(default, null):String;
 	public var slotIndex(default, null):Int;
 	public var mesh(default, null):MeshAttachment;
-	public var inheritDeform(default, null):Bool;
+	public var inheritTimeline(default, null):Bool;
 
-	public function new(mesh:MeshAttachment, skin:String, slotIndex:Int, parent:String, inheritDeform:Bool) {
+	public function new(mesh:MeshAttachment, skin:String, slotIndex:Int, parent:String, inheritTimeline:Bool) {
 		this.mesh = mesh;
 		this.skin = skin;
 		this.slotIndex = slotIndex;
 		this.parent = parent;
-		this.inheritDeform = inheritDeform;
+		this.inheritTimeline = inheritTimeline;
 	}
 }

+ 2 - 1
spine-haxe/spine-haxe/spine/Skin.hx

@@ -100,7 +100,8 @@ class Skin {
 			if (attachment.attachment == null)
 				continue;
 			if (Std.isOfType(attachment.attachment, MeshAttachment)) {
-				attachment.attachment = new MeshAttachment(attachment.attachment.name).newLinkedMesh();
+				var mesh = cast(attachment.attachment, MeshAttachment);
+				attachment.attachment = new MeshAttachment(mesh.name, mesh.path).newLinkedMesh();
 				setAttachment(attachment.slotIndex, attachment.name, attachment.attachment);
 			} else {
 				attachment.attachment = attachment.attachment.copy();

+ 7 - 0
spine-haxe/spine-haxe/spine/SpineException.hx

@@ -0,0 +1,7 @@
+package spine;
+
+class SpineException extends haxe.Exception {
+	public function new(message:String) {
+		super(message);
+	}
+}

+ 9 - 4
spine-haxe/spine-haxe/spine/animation/Animation.hx

@@ -15,18 +15,23 @@ class Animation {
 
 	public function new(name:String, timelines:Vector<Timeline>, duration:Float) {
 		if (name == null)
-			throw new ArgumentError("name cannot be null.");
-		if (timelines == null)
-			throw new ArgumentError("timelines cannot be null.");
+			throw new SpineException("name cannot be null.");
 		_name = name;
+		setTimelines(timelines);
+		this.duration = duration;
+	}
+
+	public function setTimelines(timelines:Vector<Timeline>) {
+		if (timelines == null)
+			throw new SpineException("timelines cannot be null.");
 		_timelines = timelines;
+		_timelineIds = new Dictionary<String, Bool>();
 		for (timeline in timelines) {
 			var ids:Vector<String> = timeline.propertyIds;
 			for (id in ids) {
 				_timelineIds[id] = true;
 			}
 		}
-		this.duration = duration;
 	}
 
 	public function hasTimeline(ids:Vector<String>):Bool {

+ 103 - 7
spine-haxe/spine-haxe/spine/animation/AnimationState.hx

@@ -181,14 +181,22 @@ class AnimationState {
 			} else {
 				var timelineMode:Vector<Int> = current.timelineMode;
 
-				var firstFrame:Bool = current.timelinesRotation.length == 0;
+				var shortestRotation = current.shortestRotation;
+				var firstFrame:Bool = !shortestRotation && current.timelinesRotation.length != timelineCount << 1;
 				if (firstFrame)
 					current.timelinesRotation.length = timelineCount << 1;
 
 				for (ii in 0...timelineCount) {
 					var timeline:Timeline = timelines[ii];
 					var timelineBlend:MixBlend = timelineMode[ii] == SUBSEQUENT ? blend : MixBlend.setup;
-					timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.mixIn);
+					if (!shortestRotation && Std.isOfType(timeline, RotateTimeline)) {
+						this.applyRotateTimeline(cast(timeline, RotateTimeline), skeleton, applyTime, mix, timelineBlend, current.timelinesRotation, ii << 1,
+							firstFrame);
+					} else if (Std.isOfType(timeline, AttachmentTimeline)) {
+						this.applyAttachmentTimeline(cast(timeline, AttachmentTimeline), skeleton, applyTime, blend, true);
+					} else {
+						timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.mixIn);
+					}
 				}
 			}
 			queueEvents(current, animationTime);
@@ -255,8 +263,9 @@ class AnimationState {
 		} else {
 			var timelineMode:Vector<Int> = from.timelineMode;
 			var timelineHoldMix:Vector<TrackEntry> = from.timelineHoldMix;
+			var shortestRotation = from.shortestRotation;
 
-			var firstFrame:Bool = from.timelinesRotation.length != timelineCount << 1;
+			var firstFrame:Bool = !shortestRotation && from.timelinesRotation.length != timelineCount << 1;
 			if (firstFrame)
 				from.timelinesRotation.length = timelineCount << 1;
 			var timelinesRotation:Vector<Float> = from.timelinesRotation;
@@ -290,9 +299,15 @@ class AnimationState {
 
 				from.totalAlpha += alpha;
 
-				if (drawOrder && Std.isOfType(timeline, DrawOrderTimeline) && timelineBlend == MixBlend.setup)
-					direction = MixDirection.mixIn;
-				timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, direction);
+				if (!shortestRotation && Std.isOfType(timeline, RotateTimeline)) {
+					applyRotateTimeline(cast(timeline, RotateTimeline), skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame);
+				} else if (Std.isOfType(timeline, AttachmentTimeline)) {
+					applyAttachmentTimeline(cast(timeline, AttachmentTimeline), skeleton, applyTime, timelineBlend, attachments);
+				} else {
+					if (drawOrder && Std.isOfType(timeline, DrawOrderTimeline) && timelineBlend == MixBlend.setup)
+						direction = MixDirection.mixIn;
+					timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction);
+				}
 			}
 		}
 
@@ -305,6 +320,83 @@ class AnimationState {
 		return mix;
 	}
 
+	public function applyAttachmentTimeline(timeline:AttachmentTimeline, skeleton:Skeleton, time:Float, blend:MixBlend, attachments:Bool) {
+		var slot = skeleton.slots[timeline.slotIndex];
+		if (!slot.bone.active)
+			return;
+
+		if (time < timeline.frames[0]) { // Time is before first frame.
+			if (blend == MixBlend.setup || blend == MixBlend.first)
+				this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
+		} else
+			this.setAttachment(skeleton, slot, timeline.attachmentNames[Timeline.search1(timeline.frames, time)], attachments);
+
+		// If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
+		if (slot.attachmentState <= this.unkeyedState)
+			slot.attachmentState = this.unkeyedState + SETUP;
+	}
+
+	public function applyRotateTimeline(timeline:RotateTimeline, skeleton:Skeleton, time:Float, alpha:Float, blend:MixBlend, timelinesRotation:Vector<Float>,
+			i:Int, firstFrame:Bool) {
+		if (firstFrame)
+			timelinesRotation[i] = 0;
+
+		if (alpha == 1) {
+			timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.mixIn);
+			return;
+		}
+
+		var bone = skeleton.bones[timeline.boneIndex];
+		if (!bone.active)
+			return;
+		var frames = timeline.frames;
+		var r1:Float = 0, r2:Float = 0;
+		if (time < frames[0]) {
+			switch (blend) {
+				case MixBlend.setup:
+					bone.rotation = bone.data.rotation;
+				default:
+					return;
+				case MixBlend.first:
+					r1 = bone.rotation;
+					r2 = bone.data.rotation;
+			}
+		} else {
+			r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation;
+			r2 = bone.data.rotation + timeline.getCurveValue(time);
+		}
+
+		// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
+		var total:Float = 0, diff:Float = r2 - r1;
+		diff -= (16384.0 - Std.int((16384.499999999996 - diff / 360.0))) * 360.0;
+		if (diff == 0) {
+			total = timelinesRotation[i];
+		} else {
+			var lastTotal:Float = 0, lastDiff:Float = 0;
+			if (firstFrame) {
+				lastTotal = 0;
+				lastDiff = diff;
+			} else {
+				lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
+				lastDiff = timelinesRotation[i + 1]; // Difference between bones.
+			}
+			var current = diff > 0, dir = lastTotal >= 0;
+			// Detect cross at 0 (not 180).
+			if (MathUtils.signum(lastDiff) != MathUtils.signum(diff) && Math.abs(lastDiff) <= 90) {
+				// A cross after a 360 rotation is a loop.
+				if (Math.abs(lastTotal) > 180)
+					lastTotal += 360 * MathUtils.signum(lastTotal);
+				dir = current;
+			}
+			total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
+			if (dir != current)
+				total += 360 * MathUtils.signum(lastTotal);
+			timelinesRotation[i] = total;
+		}
+		timelinesRotation[i + 1] = diff;
+		bone.rotation = r1 + total * alpha;
+	}
+
 	private function setAttachment(skeleton:Skeleton, slot:Slot, attachmentName:String, attachments:Bool):Void {
 		slot.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slot.data.index, attachmentName);
 		if (attachments)
@@ -519,6 +611,9 @@ class AnimationState {
 		entry.loop = loop;
 		entry.holdPrevious = false;
 
+		entry.reverse = false;
+		entry.shortestRotation = false;
+
 		entry.eventThreshold = 0;
 		entry.attachmentThreshold = 0;
 		entry.drawOrderThreshold = 0;
@@ -536,9 +631,10 @@ class AnimationState {
 		entry.timeScale = 1;
 
 		entry.alpha = 1;
-		entry.interruptAlpha = 1;
 		entry.mixTime = 0;
 		entry.mixDuration = last == null ? 0 : data.getMix(last.animation, animation);
+		entry.interruptAlpha = 1;
+		entry.totalAlpha = 0;
 		entry.mixBlend = MixBlend.replace;
 		return entry;
 	}

+ 10 - 3
spine-haxe/spine-haxe/spine/animation/DeformTimeline.hx

@@ -106,10 +106,10 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
 		if (!slot.bone.active)
 			return;
 		var slotAttachment:Attachment = slot.attachment;
-
-		if (!Std.isOfType(slotAttachment, VertexAttachment) || cast(slotAttachment, VertexAttachment).deformAttachment != attachment)
+		if (slotAttachment == null)
+			return;
+		if (!Std.isOfType(slotAttachment, VertexAttachment) || cast(slotAttachment, VertexAttachment).timelineAttachment != attachment)
 			return;
-		var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
 
 		var deform:Vector<Float> = slot.deform;
 		if (deform.length == 0)
@@ -128,6 +128,7 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
 						return;
 					}
 					deform.length = vertexCount;
+					var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
 					if (vertexAttachment.bones == null) {
 						// Unweighted vertex positions.
 						setupVertices = vertexAttachment.vertices;
@@ -152,6 +153,7 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
 			var lastVertices:Vector<Float> = vertices[frames.length - 1];
 			if (alpha == 1) {
 				if (blend == MixBlend.add) {
+					var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
 					if (vertexAttachment.bones == null) {
 						// Unweighted vertex positions, with alpha.
 						setupVertices = vertexAttachment.vertices;
@@ -172,6 +174,7 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
 			} else {
 				switch (blend) {
 					case MixBlend.setup:
+						var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
 						if (vertexAttachment.bones == null) {
 							// Unweighted vertex positions, with alpha.
 							setupVertices = vertexAttachment.vertices;
@@ -190,6 +193,7 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
 							deform[i] += (lastVertices[i] - deform[i]) * alpha;
 						}
 					case MixBlend.add:
+						var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
 						if (vertexAttachment.bones == null) {
 							// Unweighted vertex positions, with alpha.
 							setupVertices = vertexAttachment.vertices;
@@ -215,6 +219,7 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
 
 		if (alpha == 1) {
 			if (blend == MixBlend.add) {
+				var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
 				if (vertexAttachment.bones == null) {
 					// Unweighted vertex positions, with alpha.
 					setupVertices = vertexAttachment.vertices;
@@ -238,6 +243,7 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
 		} else {
 			switch (blend) {
 				case MixBlend.setup:
+					var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
 					if (vertexAttachment.bones == null) {
 						// Unweighted vertex positions, with alpha.
 						setupVertices = vertexAttachment.vertices;
@@ -259,6 +265,7 @@ class DeformTimeline extends CurveTimeline implements SlotTimeline {
 						deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha;
 					}
 				case MixBlend.add:
+					var vertexAttachment:VertexAttachment = cast(slotAttachment, VertexAttachment);
 					if (vertexAttachment.bones == null) {
 						// Unweighted vertex positions, with alpha.
 						setupVertices = vertexAttachment.vertices;

+ 2 - 0
spine-haxe/spine-haxe/spine/animation/Property.hx

@@ -26,5 +26,7 @@ class Property {
 	public static inline var pathConstraintSpacing:Int = 17;
 	public static inline var pathConstraintMix:Int = 18;
 
+	public static inline var sequence:Int = 19;
+
 	public function new() {}
 }

+ 1 - 1
spine-haxe/spine-haxe/spine/animation/RotateTimeline.hx

@@ -6,7 +6,7 @@ import spine.Event;
 import spine.Skeleton;
 
 class RotateTimeline extends CurveTimeline1 implements BoneTimeline {
-	private var boneIndex:Int = 0;
+	public var boneIndex:Int = 0;
 
 	public function new(frameCount:Int, bezierCount:Int, boneIndex:Int) {
 		super(frameCount, bezierCount, Vector.ofArray([Property.rotate + "|" + boneIndex]));

+ 98 - 0
spine-haxe/spine-haxe/spine/animation/SequenceTimeline.hx

@@ -0,0 +1,98 @@
+package spine.animation;
+
+import openfl.Vector;
+import spine.attachments.VertexAttachment;
+import spine.attachments.Attachment;
+
+class SequenceTimeline extends Timeline implements SlotTimeline {
+	static var ENTRIES = 3;
+	static var MODE = 1;
+	static var DELAY = 2;
+
+	var slotIndex:Int;
+	var attachment:HasTextureRegion;
+
+	public function new(frameCount:Int, slotIndex:Int, attachment:HasTextureRegion) {
+		super(frameCount, Vector.ofArray([
+			Std.string(Property.sequence) + "|" + Std.string(slotIndex) + "|" + Std.string(attachment.sequence.id)
+		]));
+		this.slotIndex = slotIndex;
+		this.attachment = attachment;
+	}
+
+	public override function getFrameEntries():Int {
+		return SequenceTimeline.ENTRIES;
+	}
+
+	public function getSlotIndex():Int {
+		return this.slotIndex;
+	}
+
+	public function getAttachment():Attachment {
+		return cast(attachment, Attachment);
+	}
+
+	/** Sets the time, mode, index, and frame time for the specified frame.
+	 * @param frame Between 0 and <code>frameCount</code>, inclusive.
+	 * @param time Seconds between frames. */
+	public function setFrame(frame:Int, time:Float, mode:SequenceMode, index:Int, delay:Float) {
+		frame *= SequenceTimeline.ENTRIES;
+		frames[frame] = time;
+		frames[frame + SequenceTimeline.MODE] = mode.value | (index << 4);
+		frames[frame + SequenceTimeline.DELAY] = delay;
+	}
+
+	public override function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Vector<Event>, alpha:Float, blend:MixBlend,
+			direction:MixDirection):Void {
+		var slot = skeleton.slots[this.slotIndex];
+		if (!slot.bone.active)
+			return;
+		var slotAttachment = slot.attachment;
+		var attachment = cast(this.attachment, Attachment);
+		if (slotAttachment != attachment) {
+			if (!Std.isOfType(slotAttachment, VertexAttachment) || cast(slotAttachment, VertexAttachment).timelineAttachment != attachment)
+				return;
+		}
+
+		if (time < frames[0]) { // Time is before first frame.
+			if (blend == MixBlend.setup || blend == MixBlend.first)
+				slot.sequenceIndex = -1;
+			return;
+		}
+
+		var i = Timeline.search(frames, time, SequenceTimeline.ENTRIES);
+		var before = frames[i];
+		var modeAndIndex = Std.int(frames[i + SequenceTimeline.MODE]);
+		var delay = frames[i + SequenceTimeline.DELAY];
+
+		if (this.attachment.sequence == null)
+			return;
+		var index = modeAndIndex >> 4,
+			count = this.attachment.sequence.regions.length;
+		var mode = SequenceMode.values[modeAndIndex & 0xf];
+		if (mode != SequenceMode.hold) {
+			index += Std.int(((time - before) / delay + 0.00001));
+			switch (mode) {
+				case SequenceMode.once:
+					index = Std.int(Math.min(count - 1, index));
+				case SequenceMode.loop:
+					index %= count;
+				case SequenceMode.pingpong:
+					var n = (count << 1) - 2;
+					index = n == 0 ? 0 : index % n;
+					if (index >= count)
+						index = n - index;
+				case SequenceMode.onceReverse:
+					index = Std.int(Math.max(count - 1 - index, 0));
+				case SequenceMode.loopReverse:
+					index = count - 1 - (index % count);
+				case SequenceMode.pingpongReverse:
+					var n = (count << 1) - 2;
+					index = n == 0 ? 0 : (index + count - 1) % n;
+					if (index >= count)
+						index = n - index;
+			}
+		}
+		slot.sequenceIndex = index;
+	}
+}

+ 1 - 1
spine-haxe/spine-haxe/spine/animation/Timeline.hx

@@ -26,7 +26,7 @@ class Timeline {
 	}
 
 	public function apply(skeleton:Skeleton, lastTime:Float, time:Float, events:Vector<Event>, alpha:Float, blend:MixBlend, direction:MixDirection):Void {
-		trace("Timeline implementations must override apply()");
+		throw new SpineException("Timeline implementations must override apply()");
 	}
 
 	public static function search1(frames:Vector<Float>, time:Float):Int {

+ 1 - 0
spine-haxe/spine-haxe/spine/animation/TrackEntry.hx

@@ -42,6 +42,7 @@ class TrackEntry implements Poolable {
 	public var timelineMode:Vector<Int> = new Vector<Int>();
 	public var timelineHoldMix:Vector<TrackEntry> = new Vector<TrackEntry>();
 	public var timelinesRotation:Vector<Float> = new Vector<Float>();
+	public var shortestRotation = false;
 
 	public function new() {}
 

+ 19 - 44
spine-haxe/spine-haxe/spine/attachments/AtlasAttachmentLoader.hx

@@ -20,49 +20,34 @@ class AtlasAttachmentLoader implements AttachmentLoader {
 			var path = sequence.getPath(basePath, i);
 			var region = this.atlas.findRegion(path);
 			if (region == null)
-				trace("Region not found in atlas: " + path + " (sequence: " + name + ")");
+				throw new SpineException("Region not found in atlas: " + path + " (sequence: " + name + ")");
 			regions[i] = region;
 		}
 	}
 
-	public function newRegionAttachment(skin:Skin, name:String, path:String):RegionAttachment {
-		var region = atlas.findRegion(path);
-		if (region == null) {
-			trace("Region not found in atlas: " + path + " (region attachment: " + name + ")");
-			return null;
+	public function newRegionAttachment(skin:Skin, name:String, path:String, sequence:Sequence):RegionAttachment {
+		var attachment = new RegionAttachment(name, path);
+		if (sequence != null) {
+			this.loadSequence(name, path, sequence);
+		} else {
+			var region = this.atlas.findRegion(path);
+			if (region == null)
+				throw new SpineException("Region not found in atlas: " + path + " (region attachment: " + name + ")");
+			attachment.region = region;
 		}
-		var attachment:RegionAttachment = new RegionAttachment(name);
-		attachment.rendererObject = region;
-		attachment.setUVs(region.u, region.v, region.u2, region.v2, region.degrees);
-		attachment.regionOffsetX = region.offsetX;
-		attachment.regionOffsetY = region.offsetY;
-		attachment.regionWidth = region.width;
-		attachment.regionHeight = region.height;
-		attachment.regionOriginalWidth = region.originalWidth;
-		attachment.regionOriginalHeight = region.originalHeight;
 		return attachment;
 	}
 
-	public function newMeshAttachment(skin:Skin, name:String, path:String):MeshAttachment {
-		var region = atlas.findRegion(path);
-		if (region == null) {
-			trace("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
-			return null;
+	public function newMeshAttachment(skin:Skin, name:String, path:String, sequence:Sequence):MeshAttachment {
+		var attachment = new MeshAttachment(name, path);
+		if (sequence != null) {
+			this.loadSequence(name, path, sequence);
+		} else {
+			var region = atlas.findRegion(path);
+			if (region == null)
+				throw new SpineException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
+			attachment.region = region;
 		}
-
-		var attachment:MeshAttachment = new MeshAttachment(name);
-		attachment.rendererObject = region;
-		attachment.regionU = region.u;
-		attachment.regionV = region.v;
-		attachment.regionU2 = region.u2;
-		attachment.regionV2 = region.v2;
-		attachment.regionDegrees = region.degrees;
-		attachment.regionOffsetX = region.offsetX;
-		attachment.regionOffsetY = region.offsetY;
-		attachment.regionWidth = region.width;
-		attachment.regionHeight = region.height;
-		attachment.regionOriginalWidth = region.originalWidth;
-		attachment.regionOriginalHeight = region.originalHeight;
 		return attachment;
 	}
 
@@ -81,14 +66,4 @@ class AtlasAttachmentLoader implements AttachmentLoader {
 	public function newClippingAttachment(skin:Skin, name:String):ClippingAttachment {
 		return new ClippingAttachment(name);
 	}
-
-	static public function nextPOT(value:Int):Int {
-		value--;
-		value |= value >> 1;
-		value |= value >> 2;
-		value |= value >> 4;
-		value |= value >> 8;
-		value |= value >> 16;
-		return value + 1;
-	}
 }

+ 1 - 1
spine-haxe/spine-haxe/spine/attachments/MeshAttachment.hx

@@ -27,7 +27,7 @@ class MeshAttachment extends VertexAttachment implements HasTextureRegion {
 
 	public function updateRegion():Void {
 		if (region == null) {
-			trace("Region not set.");
+			throw new SpineException("Region not set.");
 			return;
 		}
 		var regionUVs = this.regionUVs;

+ 1 - 1
spine-haxe/spine-haxe/spine/attachments/RegionAttachment.hx

@@ -38,7 +38,7 @@ class RegionAttachment extends Attachment implements HasTextureRegion {
 
 	public function updateRegion():Void {
 		if (region == null) {
-			trace("Region not set.");
+			throw new SpineException("Region not set.");
 			uvs[0] = 0;
 			uvs[1] = 0;
 			uvs[2] = 0;

+ 0 - 1
spine-haxe/spine-haxe/spine/starling/SkeletonAnimation.hx

@@ -18,7 +18,6 @@ class SkeletonAnimation extends SkeletonSprite implements IAnimatable {
 
 	public function advanceTime(time:Float):Void {
 		var stage = Starling.current.stage;
-		skeleton.update(time);
 		state.update(time);
 		state.apply(skeleton);
 		skeleton.updateWorldTransform();

+ 2 - 2
spine-haxe/spine-haxe/spine/starling/SkeletonSprite.hx

@@ -80,7 +80,7 @@ class SkeletonSprite extends DisplayObject {
 				verticesCount = verticesLength >> 1;
 				if (worldVertices.length < verticesLength)
 					worldVertices.length = verticesLength;
-				region.computeWorldVertices(slot.bone, worldVertices, 0, 2);
+				region.computeWorldVertices(slot, worldVertices, 0, 2);
 
 				mesh = null;
 				if (Std.isOfType(region.rendererObject, SkeletonMesh)) {
@@ -220,7 +220,7 @@ class SkeletonSprite extends DisplayObject {
 			if (Std.isOfType(attachment, RegionAttachment)) {
 				var region:RegionAttachment = cast(slot.attachment, RegionAttachment);
 				verticesLength = 8;
-				region.computeWorldVertices(slot.bone, worldVertices, 0, 2);
+				region.computeWorldVertices(slot, worldVertices, 0, 2);
 			} else if (Std.isOfType(attachment, MeshAttachment)) {
 				var mesh:MeshAttachment = cast(attachment, MeshAttachment);
 				verticesLength = mesh.worldVerticesLength;