Browse Source

Updated spine-as3 to Spine v3.

NathanSweet 9 years ago
parent
commit
4b5322d5d6

+ 166 - 84
spine-as3/spine-as3/src/spine/Bone.as

@@ -31,7 +31,7 @@
 
 package spine {
 
-public class Bone {
+public class Bone implements Updatable {
 	static public var yDown:Boolean;
 
 	internal var _data:BoneData;
@@ -40,23 +40,20 @@ public class Bone {
 	public var x:Number;
 	public var y:Number;
 	public var rotation:Number;
-	public var rotationIK:Number;
 	public var scaleX:Number;
 	public var scaleY:Number;
-	public var flipX:Boolean;
-	public var flipY:Boolean;
+	public var appliedRotation:Number;
+	public var appliedScaleX:Number;
+	public var appliedScaleY:Number;
 
-	internal var _m00:Number;
-	internal var _m01:Number;
-	internal var _m10:Number;
-	internal var _m11:Number;
+	internal var _a:Number;
+	internal var _b:Number;
+	internal var _c:Number;
+	internal var _d:Number;
 	internal var _worldX:Number;
 	internal var _worldY:Number;
-	internal var _worldRotation:Number;
-	internal var _worldScaleX:Number;
-	internal var _worldScaleY:Number;
-	internal var _worldFlipX:Boolean;
-	internal var _worldFlipY:Boolean;
+	internal var _worldSignX:Number;
+	internal var _worldSignY:Number;
 
 	/** @param parent May be null. */
 	public function Bone (data:BoneData, skeleton:Skeleton, parent:Bone) {
@@ -68,48 +65,136 @@ public class Bone {
 		setToSetupPose();
 	}
 
-	/** Computes the world SRT using the parent bone and the local SRT. */
+	/** Computes the world SRT using the parent bone and this bone's local SRT. */
 	public function updateWorldTransform () : void {
+		updateWorldTransformWith(x, y, rotation, scaleX, scaleY);
+	}
+
+	/** Same as updateWorldTransform(). This method exists for Bone to implement Updatable. */
+	public function update () : void {
+		updateWorldTransformWith(x, y, rotation, scaleX, scaleY);
+	}
+
+	/** Computes the world SRT using the parent bone and the specified local SRT. */
+	public function updateWorldTransformWith (x:Number, y:Number, rotation:Number, scaleX:Number, scaleY:Number) : void {
+		appliedRotation = rotation;
+		appliedScaleX = scaleX;
+		appliedScaleY = scaleY;
+
+		var radians:Number = rotation * MathUtils.degRad;
+		var cos:Number = Math.cos(radians), sin:Number = Math.sin(radians);
+		var la:Number = cos * scaleX, lb:Number = -sin * scaleY, lc:Number = sin * scaleX, ld:Number = cos * scaleY;
 		var parent:Bone = _parent;
-		if (parent) {
-			_worldX = x * parent._m00 + y * parent._m01 + parent._worldX;
-			_worldY = x * parent._m10 + y * parent._m11 + parent._worldY;
-			if (_data.inheritScale) {
-				_worldScaleX = parent._worldScaleX * scaleX;
-				_worldScaleY = parent._worldScaleY * scaleY;
-			} else {
-				_worldScaleX = scaleX;
-				_worldScaleY = scaleY;
+		if (!parent) { // Root bone.
+			var skeleton:Skeleton = this.skeleton;
+			if (skeleton.flipX) {
+				x = -x;
+				la = -la;
+				lb = -lb;
 			}
-			_worldRotation = _data.inheritRotation ? parent._worldRotation + rotationIK : rotationIK;
-			_worldFlipX = parent._worldFlipX != flipX;
-			_worldFlipY = parent._worldFlipY != flipY;
-		} else {
-			var skeletonFlipX:Boolean = _skeleton.flipX, skeletonFlipY:Boolean = _skeleton.flipY;
-			_worldX = skeletonFlipX ? -x : x;
-			_worldY = skeletonFlipY != yDown ? -y : y;
-			_worldScaleX = scaleX;
-			_worldScaleY = scaleY;
-			_worldRotation = rotationIK;
-			_worldFlipX = skeletonFlipX != flipX;
-			_worldFlipY = skeletonFlipY != flipY;
-		}
-		var radians:Number = _worldRotation * (Math.PI / 180);
-		var cos:Number = Math.cos(radians);
-		var sin:Number = Math.sin(radians);
-		if (_worldFlipX) {
-			_m00 = -cos * _worldScaleX;
-			_m01 = sin * _worldScaleY;
-		} else {
-			_m00 = cos * _worldScaleX;
-			_m01 = -sin * _worldScaleY;
+			if (skeleton.flipY != yDown) {
+				y = -y;
+				lc = -lc;
+				ld = -ld;
+			}
+			_a = la;
+			_b = lb;
+			_c = lc;
+			_d = ld;
+			_worldX = x;
+			_worldY = y;
+			_worldSignX = scaleX < 0 ? -1 : 1;
+			_worldSignY = scaleY < 0 ? -1 : 1;
+			return;
 		}
-		if (_worldFlipY != yDown) {
-			_m10 = -sin * _worldScaleX;
-			_m11 = -cos * _worldScaleY;
+
+		var pa:Number = parent._a, pb:Number = parent._b, pc:Number = parent._c, pd:Number = parent._d;
+		_worldX = pa * x + pb * y + parent._worldX;
+		_worldY = pc * x + pd * y + parent._worldY;
+		_worldSignX = parent._worldSignX * (scaleX < 0 ? -1 : 1);
+		_worldSignY = parent._worldSignY * (scaleY < 0 ? -1 : 1);
+
+		if (data.inheritRotation && data.inheritScale) {
+			_a = pa * la + pb * lc;
+			_b = pa * lb + pb * ld;
+			_c = pc * la + pd * lc;
+			_d = pc * lb + pd * ld;
+		} else if (data.inheritRotation) { // No scale inheritance.
+			pa = 1;
+			pb = 0;
+			pc = 0;
+			pd = 1;
+			while (parent != null) {
+				radians = parent.appliedRotation * MathUtils.degRad;
+				cos = Math.cos(radians);
+				sin = Math.sin(radians);
+				var temp1:Number = pa * cos + pb * sin;
+				pb = pa * -sin + pb * cos;
+				pa = temp1;
+				temp1 = pc * cos + pd * sin;
+				pd = pc * -sin + pd * cos;
+				pc = temp1;
+				parent = parent.parent;
+			}
+			_a = pa * la + pb * lc;
+			_b = pa * lb + pb * ld;
+			_c = pc * la + pd * lc;
+			_d = pc * lb + pd * ld;
+			if (skeleton.flipX) {
+				_a = -_a;
+				_b = -_b;
+			}
+			if (skeleton.flipY != yDown) {
+				_c = -_c;
+				_d = -_d;
+			}
+		} else if (data.inheritScale) { // No rotation inheritance.
+			pa = 1;
+			pb = 0;
+			pc = 0;
+			pd = 1;
+			while (parent) {
+				radians = parent.rotation * MathUtils.degRad;
+				cos = Math.cos(radians);
+				sin = Math.sin(radians);
+				var psx:Number = parent.appliedScaleX, psy:Number = parent.appliedScaleY;
+				var za:Number = cos * psx, zb:Number = -sin * psy, zc:Number = sin * psx, zd:Number = cos * psy;
+				var temp2:Number = pa * za + pb * zc;
+				pb = pa * zb + pb * zd;
+				pa = temp2;
+				temp2 = pc * za + pd * zc;
+				pd = pc * zb + pd * zd;
+				pc = temp2;
+
+				if (psx < 0) radians = -radians;
+				cos = Math.cos(-radians);
+				sin = Math.sin(-radians);
+				temp2 = pa * cos + pb * sin;
+				pb = pa * -sin + pb * cos;
+				pa = temp2;
+				temp2 = pc * cos + pd * sin;
+				pd = pc * -sin + pd * cos;
+				pc = temp2;
+
+				parent = parent.parent;
+			}
+			_a = pa * la + pb * lc;
+			_b = pa * lb + pb * ld;
+			_c = pc * la + pd * lc;
+			_d = pc * lb + pd * ld;
+			if (skeleton.flipX) {
+				_a = -_a;
+				_b = -_b;
+			}
+			if (skeleton.flipY != yDown) {
+				_c = -_c;
+				_d = -_d;
+			}
 		} else {
-			_m10 = sin * _worldScaleX;
-			_m11 = cos * _worldScaleY;
+			_a = la;
+			_b = lb;
+			_c = lc;
+			_d = ld;
 		}
 	}
 
@@ -117,11 +202,8 @@ public class Bone {
 		x = _data.x;
 		y = _data.y;
 		rotation = _data.rotation;
-		rotationIK = rotation;
 		scaleX = _data.scaleX;
 		scaleY = _data.scaleY;
-		flipX = _data.flipX;
-		flipY = _data.flipY;
 	}
 
 	public function get data () : BoneData {
@@ -136,20 +218,20 @@ public class Bone {
 		return _skeleton;
 	}
 
-	public function get m00 () : Number {
-		return _m00;
+	public function get a () : Number {
+		return _a;
 	}
 
-	public function get m01 () : Number {
-		return _m01;
+	public function get b () : Number {
+		return _b;
 	}
 
-	public function get m10 () : Number {
-		return _m10;
+	public function get c () : Number {
+		return _c;
 	}
 
-	public function get m11 () : Number {
-		return _m11;
+	public function get d () : Number {
+		return _d;
 	}
 
 	public function get worldX () : Number {
@@ -160,42 +242,42 @@ public class Bone {
 		return _worldY;
 	}
 
-	public function get worldRotation () : Number {
-		return _worldRotation;
+	public function get worldSignX () : Number {
+		return _worldSignX;
 	}
 
-	public function get worldScaleX () : Number {
-		return _worldScaleX;
+	public function get worldSignY () : Number {
+		return _worldSignY;
 	}
 
-	public function get worldScaleY () : Number {
-		return _worldScaleY;
+	public function get worldRotationX () : Number {
+		return Math.atan2(_c, _a) * MathUtils.radDeg;
 	}
-	
-	public function get worldFlipX () : Boolean {
-		return _worldFlipX;
+
+	public function get worldRotationY () : Number {
+		return Math.atan2(_d, _b) * MathUtils.radDeg;
 	}
-	
-	public function get worldFlipY () : Boolean {
-		return _worldFlipY;
+
+	public function get worldScaleX () : Number {
+		return Math.sqrt(_a * _a + _b * _b) * _worldSignX;
+	}
+
+	public function get worldScaleY () : Number {
+		return Math.sqrt(_c * _c + _d * _d) * _worldSignY;
 	}
 
 	public function worldToLocal (world:Vector.<Number>) : void {
-		var dx:Number = world[0] - _worldX, dy:Number = world[1] - _worldY;
-		var m00:Number = _m00, m10:Number = _m10, m01:Number = _m01, m11:Number = _m11;
-		if (_worldFlipX != (_worldFlipY != yDown)) {
-			m00 = -m00;
-			m11 = -m11;
-		}
-		var invDet:Number = 1 / (m00 * m11 - m01 * m10);
-		world[0] = (dx * m00 * invDet - dy * m01 * invDet);
-		world[1] = (dy * m11 * invDet - dx * m10 * invDet);
+		var x:Number = world[0] - _worldX, y:Number = world[1] - _worldY;
+		var a:Number = _a, b:Number = _b, c:Number = _c, d:Number = _d;
+		var invDet:Number = 1 / (a * d - b * c);
+		world[0] = (x * a * invDet - y * b * invDet);
+		world[1] = (y * d * invDet - x * c * invDet);
 	}
 
 	public function localToWorld (local:Vector.<Number>) : void {
 		var localX:Number = local[0], localY:Number = local[1];
-		local[0] = localX * _m00 + localY * _m01 + _worldX;
-		local[1] = localX * _m10 + localY * _m11 + _worldY;
+		local[0] = localX * _a + localY * _b + _worldX;
+		local[1] = localX * _c + localY * _d + _worldY;
 	}
 
 	public function toString () : String {

+ 0 - 2
spine-as3/spine-as3/src/spine/BoneData.as

@@ -42,8 +42,6 @@ public class BoneData {
 	public var scaleY:Number = 1;
 	public var inheritScale:Boolean = true;
 	public var inheritRotation:Boolean = true;
-	public var flipX:Boolean;
-	public var flipY:Boolean;
 
 	/** @param parent May be null. */
 	public function BoneData (name:String, parent:BoneData) {

+ 4 - 2
spine-as3/spine-as3/src/spine/Event.as

@@ -33,12 +33,14 @@ package spine {
 
 public class Event {
 	internal var _data:EventData;
-	public var intValue:int;;
+	public var time:Number;
+	public var intValue:int;
 	public var floatValue:Number;
 	public var stringValue:String;
 
-	public function Event (data:EventData) {
+	public function Event (time:Number, data:EventData) {
 		if (data == null) throw new ArgumentError("data cannot be null.");
+		this.time = time;
 		_data = data;
 	}
 

+ 125 - 63
spine-as3/spine-as3/src/spine/IkConstraint.as

@@ -31,10 +31,7 @@
 
 package spine {
 
-public class IkConstraint {
-	static private const tempPosition:Vector.<Number> = new Vector.<Number>(2, true);
-	static private const radDeg:Number = 180 / Math.PI;
-
+public class IkConstraint implements Updatable {
 	internal var _data:IkConstraintData;
 	public var bones:Vector.<Bone>;
 	public var target:Bone;
@@ -55,6 +52,10 @@ public class IkConstraint {
 	}
 
 	public function apply () : void {
+		update();
+	}
+
+	public function update () : void {
 		switch (bones.length) {
 		case 1:
 			apply1(bones[0], target._worldX, target._worldY, mix);
@@ -76,76 +77,137 @@ public class IkConstraint {
 	/** Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified in the world
 	 * coordinate system. */
 	static public function apply1 (bone:Bone, targetX:Number, targetY:Number, alpha:Number) : void {
-		var parentRotation:Number = (!bone._data.inheritRotation || bone._parent == null) ? 0 : bone._parent._worldRotation;
+		var parentRotation:Number = bone.parent == null ? 0 : bone.parent.worldRotationX;
 		var rotation:Number = bone.rotation;
-		var rotationIK:Number = Math.atan2(targetY - bone._worldY, targetX - bone._worldX) * radDeg;
-		if (bone._worldFlipX != (bone._worldFlipY != Bone.yDown)) rotationIK = -rotationIK;
-		rotationIK -= parentRotation;
-		bone.rotationIK = rotation + (rotationIK - rotation) * alpha;
+		var rotationIK:Number = Math.atan2(targetY - bone.worldY, targetX - bone.worldX) * MathUtils.radDeg - parentRotation;
+		if (bone.worldSignX != bone.worldSignY) rotationIK = 360 - rotationIK;
+		if (rotationIK > 180) rotationIK -= 360;
+		else if (rotationIK < -180) rotationIK += 360;
+		bone.updateWorldTransformWith(bone.x, bone.y, rotation + (rotationIK - rotation) * alpha, bone.scaleX, bone.scaleY);
 	}
 
 	/** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The
 	 * target is specified in the world coordinate system.
 	 * @param child Any descendant bone of the parent. */
-	static public function apply2 (parent:Bone, child:Bone, targetX:Number, targetY:Number, bendDirection:int, alpha:Number) : void {
-		var childRotation:Number = child.rotation, parentRotation:Number = parent.rotation;
-		if (alpha == 0) {
-			child.rotationIK = childRotation;
-			parent.rotationIK = parentRotation;
-			return;
-		}
-		var positionX:Number, positionY:Number;
-		var parentParent:Bone = parent._parent;
-		if (parentParent) {
-			tempPosition[0] = targetX;
-			tempPosition[1] = targetY;
-			parentParent.worldToLocal(tempPosition);
-			targetX = (tempPosition[0] - parent.x) * parentParent._worldScaleX;
-			targetY = (tempPosition[1] - parent.y) * parentParent._worldScaleY;
+	static public function apply2 (parent:Bone, child:Bone, targetX:Number, targetY:Number, bendDir:int, alpha:Number) : void {
+		if (alpha == 0) return;
+		var px:Number = parent.x, py:Number = parent.y, psx:Number = parent.scaleX, psy:Number = parent.scaleY;
+		var csx:Number = child.scaleX, cy:Number = child.y;
+		var offset1:int, offset2:int, sign2:int;
+		if (psx < 0) {
+			psx = -psx;
+			offset1 = 180;
+			sign2 = -1;
 		} else {
-			targetX -= parent.x;
-			targetY -= parent.y;
+			offset1 = 0;
+			sign2 = 1;
+		}
+		if (psy < 0) {
+			psy = -psy;
+			sign2 = -sign2;
 		}
-		if (child._parent == parent) {
-			positionX = child.x;
-			positionY = child.y;
+		if (csx < 0) {
+			csx = -csx;
+			offset2 = 180;
+		} else
+			offset2 = 0;
+		var pp:Bone = parent.parent;
+		var tx:Number, ty:Number, dx:Number, dy:Number;
+		if (!pp) {
+			tx = targetX - px;
+			ty = targetY - py;
+			dx = child.worldX - px;
+			dy = child.worldY - py;
 		} else {
-			tempPosition[0] = child.x;
-			tempPosition[1] = child.y;
-			child._parent.localToWorld(tempPosition);
-			parent.worldToLocal(tempPosition);
-			positionX = tempPosition[0];
-			positionY = tempPosition[1];
+			var ppa:Number = pp.a, ppb:Number = pp.b, ppc:Number = pp.c, ppd:Number = pp.d;
+			var invDet:Number = 1 / (ppa * ppd - ppb * ppc);
+			var wx:Number = pp.worldX, wy:Number = pp.worldY, twx:Number = targetX - wx, twy:Number = targetY - wy;
+			tx = (twx * ppd - twy * ppb) * invDet - px;
+			ty = (twy * ppa - twx * ppc) * invDet - py;
+			twx = child.worldX - wx;
+			twy = child.worldY - wy;
+			dx = (twx * ppd - twy * ppb) * invDet - px;
+			dy = (twy * ppa - twx * ppc) * invDet - py;
 		}
-		var childX:Number = positionX * parent._worldScaleX, childY:Number = positionY * parent._worldScaleY;
-		var offset:Number = Math.atan2(childY, childX);
-		var len1:Number = Math.sqrt(childX * childX + childY * childY), len2:Number = child.data.length * child._worldScaleX;
-		// Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/
-		var cosDenom:Number = 2 * len1 * len2;
-		if (cosDenom < 0.0001) {
-			child.rotationIK = childRotation + (Math.atan2(targetY, targetX) * radDeg - parentRotation - childRotation) * alpha;
-			return;
+		var l1:Number = Math.sqrt(dx * dx + dy * dy), l2:Number = child.data.length * csx, a1:Number, a2:Number;
+		outer:
+		if (Math.abs(psx - psy) <= 0.0001) {
+			l2 *= psx;
+			var cos:Number = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
+			if (cos < -1) cos = -1;
+			else if (cos > 1) cos = 1;
+			a2 = Math.acos(cos) * bendDir;
+			var ad:Number = l1 + l2 * cos, o:Number = l2 * Math.sin(a2);
+			a1 = Math.atan2(ty * ad - tx * o, tx * ad + ty * o);
+		} else {
+			cy = 0;
+			var a:Number = psx * l2, b:Number = psy * l2, ta:Number = Math.atan2(ty, tx);
+			var aa:Number = a * a, bb:Number = b * b, ll:Number = l1 * l1, dd:Number = tx * tx + ty * ty;
+			var c0:Number = bb * ll + aa * dd - aa * bb, c1:Number = -2 * bb * l1, c2:Number = bb - aa;
+			var d:Number = c1 * c1 - 4 * c2 * c0;
+			if (d >= 0) {
+				var q:Number = Math.sqrt(d);
+				if (c1 < 0) q = -q;
+				q = -(c1 + q) / 2;
+				var r0:Number = q / c2, r1:Number = c0 / q;
+				var r:Number = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
+				if (r * r <= dd) {
+					var y1:Number = Math.sqrt(dd - r * r) * bendDir;
+					a1 = ta - Math.atan2(y1, r);
+					a2 = Math.atan2(y1 / psy, (r - l1) / psx);
+					break outer;
+				}
+			}
+			var minAngle:Number = 0, minDist:Number = Number.MAX_VALUE, minX:Number = 0, minY:Number = 0;
+			var maxAngle:Number = 0, maxDist:Number = 0, maxX:Number = 0, maxY:Number = 0;
+			var x:Number = l1 + a, dist:Number = x * x;
+			if (dist > maxDist) {
+				maxAngle = 0;
+				maxDist = dist;
+				maxX = x;
+			}
+			x = l1 - a;
+			dist = x * x;
+			if (dist < minDist) {
+				minAngle = Math.PI;
+				minDist = dist;
+				minX = x;
+			}
+			var angle:Number = Math.acos(-a * l1 / (aa - bb));
+			x = a * Math.cos(angle) + l1;
+			var y:Number = b * Math.sin(angle);
+			dist = x * x + y * y;
+			if (dist < minDist) {
+				minAngle = angle;
+				minDist = dist;
+				minX = x;
+				minY = y;
+			}
+			if (dist > maxDist) {
+				maxAngle = angle;
+				maxDist = dist;
+				maxX = x;
+				maxY = y;
+			}
+			if (dd <= (minDist + maxDist) / 2) {
+				a1 = ta - Math.atan2(minY * bendDir, minX);
+				a2 = minAngle * bendDir;
+			} else {
+				a1 = ta - Math.atan2(maxY * bendDir, maxX);
+				a2 = maxAngle * bendDir;
+			}
 		}
-		var cos:Number = (targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom;
-		if (cos < -1)
-			cos = -1;
-		else if (cos > 1)
-			cos = 1;
-		var childAngle:Number = Math.acos(cos) * bendDirection;
-		var adjacent:Number = len1 + len2 * cos, opposite:Number = len2 * Math.sin(childAngle);
-		var parentAngle:Number = Math.atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite);
-		var rotation:Number = (parentAngle - offset) * radDeg - parentRotation;
-		if (rotation > 180)
-			rotation -= 360;
-		else if (rotation < -180) //
-			rotation += 360;
-		parent.rotationIK = parentRotation + rotation * alpha;
-		rotation = (childAngle + offset) * radDeg - childRotation;
-		if (rotation > 180)
-			rotation -= 360;
-		else if (rotation < -180) //
-			rotation += 360;
-		child.rotationIK = childRotation + (rotation + parent._worldRotation - child._parent._worldRotation) * alpha;
+		var offset:Number = Math.atan2(cy, child.x) * sign2;
+		a1 = (a1 - offset) * MathUtils.radDeg + offset1;
+		a2 = (a2 + offset) * MathUtils.radDeg * sign2 + offset2;
+		if (a1 > 180) a1 -= 360;
+		else if (a1 < -180) a1 += 360;
+		if (a2 > 180) a2 -= 360;
+		else if (a2 < -180) a2 += 360;
+		var rotation:Number = parent.rotation;
+		parent.updateWorldTransformWith(parent.x, parent.y, rotation + (a1 - rotation) * alpha, parent.scaleX, parent.scaleY);
+		rotation = child.rotation;
+		child.updateWorldTransformWith(child.x, cy, rotation + (a2 - rotation) * alpha, child.scaleX, child.scaleY);
 	}
 }
 

+ 4 - 10
spine-as3/spine-as3/src/spine/animation/FlipYTimeline.as → spine-as3/spine-as3/src/spine/MathUtils.as

@@ -29,17 +29,11 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-package spine.animation {
-import spine.Bone;
+package spine {
 
-public class FlipYTimeline extends FlipXTimeline {
-	public function FlipYTimeline (frameCount:int) {
-		super(frameCount);
-	}
-
-	override protected function setFlip (bone:Bone, flip:Boolean) : void {
-		bone.flipY = flip;
-	}
+public class MathUtils {
+	static public var radDeg:Number = 180 / Math.PI;
+	static public var degRad:Number = Math.PI / 180;
 }
 
 }

+ 46 - 49
spine-as3/spine-as3/src/spine/Skeleton.as

@@ -38,7 +38,8 @@ public class Skeleton {
 	public var slots:Vector.<Slot>;
 	public var drawOrder:Vector.<Slot>;
 	public var ikConstraints:Vector.<IkConstraint>;
-	private var _boneCache:Vector.<Vector.<Bone>> = new Vector.<Vector.<Bone>>();
+	public var transformConstraints:Vector.<TransformConstraint>;
+	private var _updateCache:Vector.<Updatable> = new Vector.<Updatable>();
 	private var _skin:Skin;
 	public var r:Number = 1, g:Number = 1, b:Number = 1, a:Number = 1;
 	public var time:Number = 0;
@@ -68,69 +69,51 @@ public class Skeleton {
 		ikConstraints = new Vector.<IkConstraint>();
 		for each (var ikConstraintData:IkConstraintData in data.ikConstraints)
 			ikConstraints[ikConstraints.length] = new IkConstraint(ikConstraintData, this);
+		
+		transformConstraints = new Vector.<TransformConstraint>();
+		for each (var transformConstraintData:TransformConstraintData in data.transformConstraints)
+			transformConstraints[transformConstraints.length] = new TransformConstraint(transformConstraintData, this);
 
 		updateCache();
 	}
 
-	/** Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or removed. */
+	/** Caches information about bones and constraints. Must be called if bones or constraints are added or removed. */
 	public function updateCache () : void {
-		var ikConstraintsCount:int = ikConstraints.length;
-
-		var arrayCount:int = ikConstraintsCount + 1;
-		if (_boneCache.length > arrayCount) _boneCache.splice(arrayCount, _boneCache.length - arrayCount);
-		for each (var cachedBones:Vector.<Bone> in _boneCache)
-			cachedBones.length = 0;
-		while (_boneCache.length < arrayCount)
-			_boneCache[_boneCache.length] = new Vector.<Bone>();
-
-		var nonIkBones:Vector.<Bone> = _boneCache[0];
-
-		outer:
+		var updateCache:Vector.<Updatable> = _updateCache;
+		var ikConstraints:Vector.<IkConstraint> = this.ikConstraints;
+		var transformConstraints:Vector.<TransformConstraint> = this.transformConstraints;
+		updateCache.length = bones.length + ikConstraints.length + transformConstraints.length;
+		var i:int = 0;
 		for each (var bone:Bone in bones) {
-			var current:Bone = bone;
-			do {
-				var ii:int = 0;
-				for each (var ikConstraint:IkConstraint in ikConstraints) {
-					var parent:Bone = ikConstraint.bones[0];
-					var child:Bone = ikConstraint.bones[int(ikConstraint.bones.length - 1)];
-					while (true) {
-						if (current == child) {
-							_boneCache[ii].push(bone);
-							_boneCache[int(ii + 1)].push(bone);
-							continue outer;
-						}
-						if (child == parent) break;
-						child = child.parent;
-					}
-					ii++;
+			updateCache[i++] = bone;
+			for each (var transformConstraint:TransformConstraint in transformConstraints) {
+				if (bone == transformConstraint.bone) {
+					updateCache[i++] = transformConstraint;
+					break;
+				}
+			}
+			for each (var ikConstraint:IkConstraint in ikConstraints) {
+				if (bone == ikConstraint.bones[ikConstraint.bones.length - 1]) {
+					updateCache[i++] = ikConstraint;
+					break;
 				}
-				current = current.parent;
-			} while (current != null);
-			nonIkBones[nonIkBones.length] = bone;
+			}
 		}
 	}
 
-	/** Updates the world transform for each bone and applies IK constraints. */
+	/** Updates the world transform for each bone and applies constraints. */
 	public function updateWorldTransform () : void {
-		var bone:Bone;
-		for each (bone in bones)
-			bone.rotationIK = bone.rotation;
-		var i:int = 0, last:int = _boneCache.length - 1;
-		while (true) {
-			for each (bone in _boneCache[i])
-				bone.updateWorldTransform();
-			if (i == last) break;
-			ikConstraints[i].apply();
-			i++;
-		}
+		for each (var updatable:Updatable in _updateCache)
+			updatable.update();
 	}
 
-	/** Sets the bones and slots to their setup pose values. */
+	/** Sets the bones, constraints, and slots to their setup pose values. */
 	public function setToSetupPose () : void {
 		setBonesToSetupPose();
 		setSlotsToSetupPose();
 	}
 
+	/** Sets the bones and constraints to their setup pose values. */
 	public function setBonesToSetupPose () : void {
 		for each (var bone:Bone in bones)
 			bone.setToSetupPose();
@@ -139,6 +122,12 @@ public class Skeleton {
 			ikConstraint.bendDirection = ikConstraint._data.bendDirection;
 			ikConstraint.mix = ikConstraint._data.mix;
 		}
+
+		for each (var transformConstraint:TransformConstraint in transformConstraints) {
+			transformConstraint.translateMix = transformConstraint._data.translateMix;
+			transformConstraint.x = transformConstraint._data.x;
+			transformConstraint.y = transformConstraint._data.y;
+		}
 	}
 
 	public function setSlotsToSetupPose () : void {
@@ -275,10 +264,18 @@ public class Skeleton {
 	}
 
 	/** @return May be null. */
-	public function findIkConstraint (ikConstraintName:String) : IkConstraint {
-		if (ikConstraintName == null) throw new ArgumentError("ikConstraintName cannot be null.");
+	public function findIkConstraint (constraintName:String) : IkConstraint {
+		if (constraintName == null) throw new ArgumentError("constraintName cannot be null.");
 		for each (var ikConstraint:IkConstraint in ikConstraints)
-			if (ikConstraint._data._name == ikConstraintName) return ikConstraint;
+			if (ikConstraint._data._name == constraintName) return ikConstraint;
+		return null;
+	}
+
+	/** @return May be null. */
+	public function findTransformConstraint (constraintName:String) : TransformConstraint {
+		if (constraintName == null) throw new ArgumentError("constraintName cannot be null.");
+		for each (var transformConstraint:TransformConstraint in transformConstraints)
+			if (transformConstraint._data._name == constraintName) return transformConstraint;
 		return null;
 	}
 

+ 14 - 3
spine-as3/spine-as3/src/spine/SkeletonData.as

@@ -42,6 +42,7 @@ public class SkeletonData {
 	public var events:Vector.<EventData> = new Vector.<EventData>();
 	public var animations:Vector.<Animation> = new Vector.<Animation>();
 	public var ikConstraints:Vector.<IkConstraintData> = new Vector.<IkConstraintData>();
+	public var transformConstraints:Vector.<TransformConstraintData> = new Vector.<TransformConstraintData>();
 	public var width:Number, height:Number;
 	public var version:String, hash:String;
 
@@ -118,10 +119,20 @@ public class SkeletonData {
 	// --- IK constraints.
 
 	/** @return May be null. */
-	public function findIkConstraint (ikConstraintName:String) : IkConstraintData {
-		if (ikConstraintName == null) throw new ArgumentError("ikConstraintName cannot be null.");
+	public function findIkConstraint (constraintName:String) : IkConstraintData {
+		if (constraintName == null) throw new ArgumentError("constraintName cannot be null.");
 		for each (var ikConstraintData:IkConstraintData in ikConstraints)
-			if (ikConstraintData._name == ikConstraintName) return ikConstraintData;
+			if (ikConstraintData._name == constraintName) return ikConstraintData;
+		return null;
+	}
+	
+	// --- Transform constraints.
+
+	/** @return May be null. */
+	public function findTransformConstraint (constraintName:String) : TransformConstraintData {
+		if (constraintName == null) throw new ArgumentError("constraintName cannot be null.");
+		for each (var transformConstraintData:TransformConstraintData in transformConstraints)
+			if (transformConstraintData._name == constraintName) return transformConstraintData;
 		return null;
 	}
 

+ 486 - 499
spine-as3/spine-as3/src/spine/SkeletonJson.as

@@ -28,560 +28,547 @@
  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
-
 package spine {
-import flash.utils.ByteArray;
-
-import spine.animation.Animation;
-import spine.animation.AttachmentTimeline;
-import spine.animation.ColorTimeline;
-import spine.animation.CurveTimeline;
-import spine.animation.DrawOrderTimeline;
-import spine.animation.EventTimeline;
-import spine.animation.FfdTimeline;
-import spine.animation.FlipXTimeline;
-import spine.animation.FlipYTimeline;
-import spine.animation.IkConstraintTimeline;
-import spine.animation.RotateTimeline;
-import spine.animation.ScaleTimeline;
-import spine.animation.Timeline;
-import spine.animation.TranslateTimeline;
-import spine.attachments.Attachment;
-import spine.attachments.AttachmentLoader;
-import spine.attachments.AttachmentType;
-import spine.attachments.BoundingBoxAttachment;
-import spine.attachments.MeshAttachment;
-import spine.attachments.RegionAttachment;
-import spine.attachments.SkinnedMeshAttachment;
-
-public class SkeletonJson {
-	public var attachmentLoader:AttachmentLoader;
-	public var scale:Number = 1;
-
-	public function SkeletonJson (attachmentLoader:AttachmentLoader = null) {
-		this.attachmentLoader = attachmentLoader;
-	}
-
-	/** @param object A String or ByteArray. */
-	public function readSkeletonData (object:*, name:String = null) : SkeletonData {
-		if (object == null) throw new ArgumentError("object cannot be null.");
-
-		var root:Object;
-		if (object is String)
-			root = JSON.parse(String(object));
-		else if (object is ByteArray)
-			root = JSON.parse(ByteArray(object).readUTFBytes(ByteArray(object).length));
-		else if (object is Object)
-			root = object;
-		else
-			throw new ArgumentError("object must be a String, ByteArray or Object.");
-
-		var skeletonData:SkeletonData = new SkeletonData();
-		skeletonData.name = name;
-
-		// Skeleton.
-		var skeletonMap:Object = root["skeleton"];
-		if (skeletonMap) {
-			skeletonData.hash = skeletonMap["hash"];
-			skeletonData.version = skeletonMap["spine"];
-			skeletonData.width = skeletonMap["width"] || 0;
-			skeletonData.height = skeletonMap["height"] || 0;
+	import flash.utils.ByteArray;
+
+	import spine.animation.Animation;
+	import spine.animation.AttachmentTimeline;
+	import spine.animation.ColorTimeline;
+	import spine.animation.CurveTimeline;
+	import spine.animation.DrawOrderTimeline;
+	import spine.animation.EventTimeline;
+	import spine.animation.FfdTimeline;
+	import spine.animation.IkConstraintTimeline;
+	import spine.animation.RotateTimeline;
+	import spine.animation.ScaleTimeline;
+	import spine.animation.Timeline;
+	import spine.animation.TranslateTimeline;
+	import spine.attachments.Attachment;
+	import spine.attachments.AttachmentLoader;
+	import spine.attachments.AttachmentType;
+	import spine.attachments.BoundingBoxAttachment;
+	import spine.attachments.MeshAttachment;
+	import spine.attachments.RegionAttachment;
+	import spine.attachments.WeightedMeshAttachment;
+
+	public class SkeletonJson {
+		public var attachmentLoader : AttachmentLoader;
+		public var scale : Number = 1;
+
+		public function SkeletonJson(attachmentLoader : AttachmentLoader = null) {
+			this.attachmentLoader = attachmentLoader;
 		}
 
-		// Bones.
-		var boneData:BoneData;
-		for each (var boneMap:Object in root["bones"]) {
-			var parent:BoneData = null;
-			var parentName:String = boneMap["parent"];
-			if (parentName) {
-				parent = skeletonData.findBone(parentName);
-				if (!parent) throw new Error("Parent bone not found: " + parentName);
+		/** @param object A String or ByteArray. */
+		public function readSkeletonData(object : *, name : String = null) : SkeletonData {
+			if (object == null) throw new ArgumentError("object cannot be null.");
+
+			var root : Object;
+			if (object is String)
+				root = JSON.parse(String(object));
+			else if (object is ByteArray)
+				root = JSON.parse(ByteArray(object).readUTFBytes(ByteArray(object).length));
+			else if (object is Object)
+				root = object;
+			else
+				throw new ArgumentError("object must be a String, ByteArray or Object.");
+
+			var skeletonData : SkeletonData = new SkeletonData();
+			skeletonData.name = name;
+
+			// Skeleton.
+			var skeletonMap : Object = root["skeleton"];
+			if (skeletonMap) {
+				skeletonData.hash = skeletonMap["hash"];
+				skeletonData.version = skeletonMap["spine"];
+				skeletonData.width = skeletonMap["width"] || 0;
+				skeletonData.height = skeletonMap["height"] || 0;
 			}
-			boneData = new BoneData(boneMap["name"], parent);
-			boneData.length = Number(boneMap["length"] || 0) * scale;
-			boneData.x = Number(boneMap["x"] || 0) * scale;
-			boneData.y = Number(boneMap["y"] || 0) * scale;
-			boneData.rotation = (boneMap["rotation"] || 0);
-			boneData.scaleX = boneMap.hasOwnProperty("scaleX") ? boneMap["scaleX"] : 1;
-			boneData.scaleY = boneMap.hasOwnProperty("scaleY") ? boneMap["scaleY"] : 1;
-			boneData.flipX = boneMap["flipX"] || false;
-			boneData.flipY = boneMap["flipY"] || false;
-			boneData.inheritScale = boneMap.hasOwnProperty("inheritScale") ? boneMap["inheritScale"] : true;
-			boneData.inheritRotation = boneMap.hasOwnProperty("inheritRotation") ? boneMap["inheritRotation"] : true;
-			skeletonData.bones[skeletonData.bones.length] = boneData;
-		}
-
-		// IK constraints.
-		for each (var ikMap:Object in root["ik"]) {
-			var ikConstraintData:IkConstraintData = new IkConstraintData(ikMap["name"]);
 
-			for each (var boneName:String in ikMap["bones"]) {
-				var bone:BoneData = skeletonData.findBone(boneName);
-				if (!bone) throw new Error("IK bone not found: " + boneName);
-				ikConstraintData.bones[ikConstraintData.bones.length] = bone;
+			// Bones.
+			var boneData : BoneData;
+			for each (var boneMap : Object in root["bones"]) {
+				var parent : BoneData = null;
+				var parentName : String = boneMap["parent"];
+				if (parentName) {
+					parent = skeletonData.findBone(parentName);
+					if (!parent) throw new Error("Parent bone not found: " + parentName);
+				}
+				boneData = new BoneData(boneMap["name"], parent);
+				boneData.length = Number(boneMap["length"] || 0) * scale;
+				boneData.x = Number(boneMap["x"] || 0) * scale;
+				boneData.y = Number(boneMap["y"] || 0) * scale;
+				boneData.rotation = (boneMap["rotation"] || 0);
+				boneData.scaleX = boneMap.hasOwnProperty("scaleX") ? boneMap["scaleX"] : 1;
+				boneData.scaleY = boneMap.hasOwnProperty("scaleY") ? boneMap["scaleY"] : 1;
+				boneData.inheritScale = boneMap.hasOwnProperty("inheritScale") ? boneMap["inheritScale"] : true;
+				boneData.inheritRotation = boneMap.hasOwnProperty("inheritRotation") ? boneMap["inheritRotation"] : true;
+				skeletonData.bones[skeletonData.bones.length] = boneData;
 			}
 
-			ikConstraintData.target = skeletonData.findBone(ikMap["target"]);
-			if (!ikConstraintData.target) throw new Error("Target bone not found: " + ikMap["target"]);
+			// IK constraints.
+			for each (var ikMap : Object in root["ik"]) {
+				var ikConstraintData : IkConstraintData = new IkConstraintData(ikMap["name"]);
+
+				for each (var boneName : String in ikMap["bones"]) {
+					var bone : BoneData = skeletonData.findBone(boneName);
+					if (!bone) throw new Error("IK bone not found: " + boneName);
+					ikConstraintData.bones[ikConstraintData.bones.length] = bone;
+				}
 
-			ikConstraintData.bendDirection = (!ikMap.hasOwnProperty("bendPositive") || ikMap["bendPositive"]) ? 1 : -1;
-			ikConstraintData.mix = ikMap.hasOwnProperty("mix") ? ikMap["mix"] : 1;
+				ikConstraintData.target = skeletonData.findBone(ikMap["target"]);
+				if (!ikConstraintData.target) throw new Error("Target bone not found: " + ikMap["target"]);
 
-			skeletonData.ikConstraints[skeletonData.ikConstraints.length] = ikConstraintData;
-		}
+				ikConstraintData.bendDirection = (!ikMap.hasOwnProperty("bendPositive") || ikMap["bendPositive"]) ? 1 : -1;
+				ikConstraintData.mix = ikMap.hasOwnProperty("mix") ? ikMap["mix"] : 1;
 
-		// Slots.
-		for each (var slotMap:Object in root["slots"]) {
-			boneName = slotMap["bone"];
-			boneData = skeletonData.findBone(boneName);
-			if (!boneData) throw new Error("Slot bone not found: " + boneName);
-			var slotData:SlotData = new SlotData(slotMap["name"], boneData);
-
-			var color:String = slotMap["color"];
-			if (color) {
-				slotData.r = toColor(color, 0);
-				slotData.g = toColor(color, 1);
-				slotData.b = toColor(color, 2);
-				slotData.a = toColor(color, 3);
+				skeletonData.ikConstraints[skeletonData.ikConstraints.length] = ikConstraintData;
 			}
 
-			slotData.attachmentName = slotMap["attachment"];
-			slotData.blendMode = BlendMode[slotMap["blend"] || "normal"];
+			// Transform constraints.
+			for each (var transformMap : Object in root["transform"]) {
+				var transformConstraintData : TransformConstraintData = new TransformConstraintData(transformMap["name"]);
 
-			skeletonData.slots[skeletonData.slots.length] = slotData;
-		}
+				transformConstraintData.bone = skeletonData.findBone(transformMap["bone"]);
+				if (!transformConstraintData.bone) throw new Error("Bone not found: " + transformMap["bone"]);
 
-		// Skins.
-		var skins:Object = root["skins"];
-		for (var skinName:String in skins) {
-			var skinMap:Object = skins[skinName];
-			var skin:Skin = new Skin(skinName);
-			for (var slotName:String in skinMap) {
-				var slotIndex:int = skeletonData.findSlotIndex(slotName);
-				var slotEntry:Object = skinMap[slotName];
-				for (var attachmentName:String in slotEntry) {
-					var attachment:Attachment = readAttachment(skin, attachmentName, slotEntry[attachmentName]);
-					if (attachment != null)
-						skin.addAttachment(slotIndex, attachmentName, attachment);
-				}
-			}
-			skeletonData.skins[skeletonData.skins.length] = skin;
-			if (skin.name == "default")
-				skeletonData.defaultSkin = skin;
-		}
+				transformConstraintData.target = skeletonData.findBone(transformMap["target"]);
+				if (!transformConstraintData.target) throw new Error("Target bone not found: " + transformMap["target"]);
 
-		// Events.
-		var events:Object = root["events"];
-		if (events) {
-			for (var eventName:String in events) {
-				var eventMap:Object = events[eventName];
-				var eventData:EventData = new EventData(eventName);
-				eventData.intValue = eventMap["int"] || 0;
-				eventData.floatValue = eventMap["float"] || 0;
-				eventData.stringValue = eventMap["string"] || null;
-				skeletonData.events[skeletonData.events.length] = eventData;
+				transformConstraintData.translateMix = transformMap.hasOwnProperty("translateMix") ? transformMap["translateMix"] : 1;
+				transformConstraintData.x = Number(boneMap["x"] || 0) * scale;
+				transformConstraintData.y = Number(boneMap["y"] || 0) * scale;
+
+				skeletonData.transformConstraints[skeletonData.transformConstraints.length] = transformConstraintData;
 			}
-		}
 
-		// Animations.
-		var animations:Object = root["animations"];
-		for (var animationName:String in animations)
-			readAnimation(animationName, animations[animationName], skeletonData);
+			// Slots.
+			for each (var slotMap : Object in root["slots"]) {
+				boneName = slotMap["bone"];
+				boneData = skeletonData.findBone(boneName);
+				if (!boneData) throw new Error("Slot bone not found: " + boneName);
+				var slotData : SlotData = new SlotData(slotMap["name"], boneData);
+
+				var color : String = slotMap["color"];
+				if (color) {
+					slotData.r = toColor(color, 0);
+					slotData.g = toColor(color, 1);
+					slotData.b = toColor(color, 2);
+					slotData.a = toColor(color, 3);
+				}
 
-		return skeletonData;
-	}
+				slotData.attachmentName = slotMap["attachment"];
+				slotData.blendMode = BlendMode[slotMap["blend"] || "normal"];
 
-	private function readAttachment (skin:Skin, name:String, map:Object) : Attachment {
-		name = map["name"] || name;
-
-		var type:AttachmentType = AttachmentType[map["type"] || "region"];
-		var path:String = map["path"] || name;
-
-		var scale:Number = this.scale;
-		var color:String, vertices:Vector.<Number>;
-		switch (type) {
-		case AttachmentType.region:
-			var region:RegionAttachment = attachmentLoader.newRegionAttachment(skin, name, path);
-			if (!region) return null;
-			region.path = path;
-			region.x = Number(map["x"] || 0) * scale;
-			region.y = Number(map["y"] || 0) * scale;
-			region.scaleX = map.hasOwnProperty("scaleX") ? map["scaleX"] : 1;
-			region.scaleY = map.hasOwnProperty("scaleY") ? map["scaleY"] : 1;
-			region.rotation = map["rotation"] || 0;
-			region.width = Number(map["width"] || 0) * scale;
-			region.height = Number(map["height"] || 0) * scale;
-			
-			color = map["color"];
-			if (color) {
-				region.r = toColor(color, 0);
-				region.g = toColor(color, 1);
-				region.b = toColor(color, 2);
-				region.a = toColor(color, 3);
-			}
-			
-			region.updateOffset();
-			return region;
-
-		case AttachmentType.mesh:
-			var mesh:MeshAttachment = attachmentLoader.newMeshAttachment(skin, name, path);
-			if (!mesh) return null;
-			mesh.path = path; 
-			mesh.vertices = getFloatArray(map, "vertices", scale);
-			mesh.triangles = getUintArray(map, "triangles");
-			mesh.regionUVs = getFloatArray(map, "uvs", 1);
-			mesh.updateUVs();
-
-			color = map["color"];
-			if (color) {
-				mesh.r = toColor(color, 0);
-				mesh.g = toColor(color, 1);
-				mesh.b = toColor(color, 2);
-				mesh.a = toColor(color, 3);
+				skeletonData.slots[skeletonData.slots.length] = slotData;
 			}
 
-			mesh.hullLength = int(map["hull"] || 0) * 2;
-			if (map["edges"]) mesh.edges = getIntArray(map, "edges");
-			mesh.width = Number(map["width"] || 0) * scale;
-			mesh.height = Number(map["height"] || 0) * scale;
-			return mesh;
-		case AttachmentType.skinnedmesh:
-			var weightedMesh:SkinnedMeshAttachment = attachmentLoader.newSkinnedMeshAttachment(skin, name, path);
-			if (!weightedMesh) return null;
-			weightedMesh.path = path;
-
-			var uvs:Vector.<Number> = getFloatArray(map, "uvs", 1);
-			vertices = getFloatArray(map, "vertices", 1);
-			var weights:Vector.<Number> = new Vector.<Number>();
-			var bones:Vector.<int> = new Vector.<int>();
-			for (var i:int = 0, n:int = vertices.length; i < n; ) {
-				var boneCount:int = int(vertices[i++]);
-				bones[bones.length] = boneCount;
-				for (var nn:int = i + boneCount * 4; i < nn; ) {
-					bones[bones.length] = vertices[i];
-					weights[weights.length] = vertices[i + 1] * scale;
-					weights[weights.length] = vertices[i + 2] * scale;
-					weights[weights.length] = vertices[i + 3];
-					i += 4;
+			// Skins.
+			var skins : Object = root["skins"];
+			for (var skinName : String in skins) {
+				var skinMap : Object = skins[skinName];
+				var skin : Skin = new Skin(skinName);
+				for (var slotName : String in skinMap) {
+					var slotIndex : int = skeletonData.findSlotIndex(slotName);
+					var slotEntry : Object = skinMap[slotName];
+					for (var attachmentName : String in slotEntry) {
+						var attachment : Attachment = readAttachment(skin, attachmentName, slotEntry[attachmentName]);
+						if (attachment != null)
+							skin.addAttachment(slotIndex, attachmentName, attachment);
+					}
 				}
+				skeletonData.skins[skeletonData.skins.length] = skin;
+				if (skin.name == "default")
+					skeletonData.defaultSkin = skin;
 			}
-			weightedMesh.bones = bones;
-			weightedMesh.weights = weights;
-			weightedMesh.triangles = getUintArray(map, "triangles");
-			weightedMesh.regionUVs = uvs;
-			weightedMesh.updateUVs();
-			
-			color = map["color"];
-			if (color) {
-				weightedMesh.r = toColor(color, 0);
-				weightedMesh.g = toColor(color, 1);
-				weightedMesh.b = toColor(color, 2);
-				weightedMesh.a = toColor(color, 3);
+
+			// Events.
+			var events : Object = root["events"];
+			if (events) {
+				for (var eventName : String in events) {
+					var eventMap : Object = events[eventName];
+					var eventData : EventData = new EventData(eventName);
+					eventData.intValue = eventMap["int"] || 0;
+					eventData.floatValue = eventMap["float"] || 0;
+					eventData.stringValue = eventMap["string"] || null;
+					skeletonData.events[skeletonData.events.length] = eventData;
+				}
 			}
-			
-			weightedMesh.hullLength = int(map["hull"] || 0) * 2;
-			if (map["edges"]) weightedMesh.edges = getIntArray(map, "edges");
-			weightedMesh.width = Number(map["width"] || 0) * scale;
-			weightedMesh.height = Number(map["height"] || 0) * scale;
-			return weightedMesh;
-		case AttachmentType.boundingbox:
-			var box:BoundingBoxAttachment = attachmentLoader.newBoundingBoxAttachment(skin, name);
-			vertices = box.vertices;
-			for each (var point:Number in map["vertices"])
-				vertices[vertices.length] = point * scale;
-			return box;
-		}
 
-		return null;
-	}
+			// Animations.
+			var animations : Object = root["animations"];
+			for (var animationName : String in animations)
+				readAnimation(animationName, animations[animationName], skeletonData);
 
-	private function readAnimation (name:String, map:Object, skeletonData:SkeletonData) : void {
-		var timelines:Vector.<Timeline> = new Vector.<Timeline>();
-		var duration:Number = 0;
-
-		var slotMap:Object, slotIndex:int, slotName:String;
-		var values:Array, valueMap:Object, frameIndex:int;
-		var i:int;
-		var timelineName:String;
-
-		var slots:Object = map["slots"];
-		for (slotName in slots) {
-			slotMap = slots[slotName];
-			slotIndex = skeletonData.findSlotIndex(slotName);
-
-			for (timelineName in slotMap) {
-				values = slotMap[timelineName];
-				if (timelineName == "color") {
-					var colorTimeline:ColorTimeline = new ColorTimeline(values.length);
-					colorTimeline.slotIndex = slotIndex;
-					
-					frameIndex = 0;
-					for each (valueMap in values) {
-						var color:String = valueMap["color"];
-						var r:Number = toColor(color, 0);
-						var g:Number = toColor(color, 1);
-						var b:Number = toColor(color, 2);
-						var a:Number = toColor(color, 3);
-						colorTimeline.setFrame(frameIndex, valueMap["time"], r, g, b, a);
-						readCurve(colorTimeline, frameIndex, valueMap);
-						frameIndex++;
-					}
-					timelines[timelines.length] = colorTimeline;
-					duration = Math.max(duration, colorTimeline.frames[colorTimeline.frameCount * 5 - 5]);
-					
-				} else if (timelineName == "attachment") {
-					var attachmentTimeline:AttachmentTimeline = new AttachmentTimeline(values.length);
-					attachmentTimeline.slotIndex = slotIndex;
-					
-					frameIndex = 0;
-					for each (valueMap in values)
-						attachmentTimeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]);
-					timelines[timelines.length] = attachmentTimeline;
-					duration = Math.max(duration, attachmentTimeline.frames[attachmentTimeline.frameCount - 1]);
-
-				} else
-					throw new Error("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
-			}
+			return skeletonData;
 		}
 
-		var bones:Object = map["bones"];
-		for (var boneName:String in bones) {
-			var boneIndex:int = skeletonData.findBoneIndex(boneName);
-			if (boneIndex == -1) throw new Error("Bone not found: " + boneName);
-			var boneMap:Object = bones[boneName];
-
-			for (timelineName in boneMap) {
-				values = boneMap[timelineName];
-				if (timelineName == "rotate") {
-					var rotateTimeline:RotateTimeline = new RotateTimeline(values.length);
-					rotateTimeline.boneIndex = boneIndex;
-
-					frameIndex = 0;
-					for each (valueMap in values) {
-						rotateTimeline.setFrame(frameIndex, valueMap["time"], valueMap["angle"]);
-						readCurve(rotateTimeline, frameIndex, valueMap);
-						frameIndex++;
+		private function readAttachment(skin : Skin, name : String, map : Object) : Attachment {
+			name = map["name"] || name;
+
+			var typeName : String = map["type"] || "region";
+			if (typeName == "skinnedmesh") typeName = "weightedmesh";
+			var type : AttachmentType = AttachmentType[typeName];
+			var path : String = map["path"] || name;
+
+			var scale : Number = this.scale;
+			var color : String, vertices : Vector.<Number>;
+			switch (type) {
+				case AttachmentType.region:
+					var region : RegionAttachment = attachmentLoader.newRegionAttachment(skin, name, path);
+					if (!region) return null;
+					region.path = path;
+					region.x = Number(map["x"] || 0) * scale;
+					region.y = Number(map["y"] || 0) * scale;
+					region.scaleX = map.hasOwnProperty("scaleX") ? map["scaleX"] : 1;
+					region.scaleY = map.hasOwnProperty("scaleY") ? map["scaleY"] : 1;
+					region.rotation = map["rotation"] || 0;
+					region.width = Number(map["width"] || 0) * scale;
+					region.height = Number(map["height"] || 0) * scale;
+					color = map["color"];
+					if (color) {
+						region.r = toColor(color, 0);
+						region.g = toColor(color, 1);
+						region.b = toColor(color, 2);
+						region.a = toColor(color, 3);
 					}
-					timelines[timelines.length] = rotateTimeline;
-					duration = Math.max(duration, rotateTimeline.frames[rotateTimeline.frameCount * 2 - 2]);
-
-				} else if (timelineName == "translate" || timelineName == "scale") {
-					var timeline:TranslateTimeline;
-					var timelineScale:Number = 1;
-					if (timelineName == "scale")
-						timeline = new ScaleTimeline(values.length);
-					else {
-						timeline = new TranslateTimeline(values.length);
-						timelineScale = scale;
+					region.updateOffset();
+					return region;
+				case AttachmentType.mesh:
+					var mesh : MeshAttachment = attachmentLoader.newMeshAttachment(skin, name, path);
+					if (!mesh) return null;
+					mesh.path = path;
+					mesh.vertices = getFloatArray(map, "vertices", scale);
+					mesh.triangles = getUintArray(map, "triangles");
+					mesh.regionUVs = getFloatArray(map, "uvs", 1);
+					mesh.updateUVs();
+					color = map["color"];
+					if (color) {
+						mesh.r = toColor(color, 0);
+						mesh.g = toColor(color, 1);
+						mesh.b = toColor(color, 2);
+						mesh.a = toColor(color, 3);
 					}
-					timeline.boneIndex = boneIndex;
-
-					frameIndex = 0;
-					for each (valueMap in values) {
-						var x:Number = Number(valueMap["x"] || 0) * timelineScale;
-						var y:Number = Number(valueMap["y"] || 0) * timelineScale;
-						timeline.setFrame(frameIndex, valueMap["time"], x, y);
-						readCurve(timeline, frameIndex, valueMap);
-						frameIndex++;
+					mesh.hullLength = int(map["hull"] || 0) * 2;
+					if (map["edges"]) mesh.edges = getIntArray(map, "edges");
+					mesh.width = Number(map["width"] || 0) * scale;
+					mesh.height = Number(map["height"] || 0) * scale;
+					return mesh;
+				case AttachmentType.weightedmesh:
+					var weightedMesh : WeightedMeshAttachment = attachmentLoader.newWeightedMeshAttachment(skin, name, path);
+					if (!weightedMesh) return null;
+					weightedMesh.path = path;
+					var uvs : Vector.<Number> = getFloatArray(map, "uvs", 1);
+					vertices = getFloatArray(map, "vertices", 1);
+					var weights : Vector.<Number> = new Vector.<Number>();
+					var bones : Vector.<int> = new Vector.<int>();
+					for (var i : int = 0, n : int = vertices.length; i < n;) {
+						var boneCount : int = int(vertices[i++]);
+						bones[bones.length] = boneCount;
+						for (var nn : int = i + boneCount * 4; i < nn;) {
+							bones[bones.length] = vertices[i];
+							weights[weights.length] = vertices[i + 1] * scale;
+							weights[weights.length] = vertices[i + 2] * scale;
+							weights[weights.length] = vertices[i + 3];
+							i += 4;
+						}
 					}
-					timelines[timelines.length] = timeline;
-					duration = Math.max(duration, timeline.frames[timeline.frameCount * 3 - 3]);
-
-				} else if (timelineName == "flipX" || timelineName == "flipY") {
-					var flipX:Boolean = timelineName == "flipX";
-					var flipTimeline:FlipXTimeline = flipX ? new FlipXTimeline(values.length) : new FlipYTimeline(values.length);
-					flipTimeline.boneIndex = boneIndex;
-					
-					var field:String = flipX ? "x" : "y";
-					frameIndex = 0;
-					for each (valueMap in values) {
-						flipTimeline.setFrame(frameIndex, valueMap["time"], valueMap[field] || false);
-						frameIndex++;
+					weightedMesh.bones = bones;
+					weightedMesh.weights = weights;
+					weightedMesh.triangles = getUintArray(map, "triangles");
+					weightedMesh.regionUVs = uvs;
+					weightedMesh.updateUVs();
+					color = map["color"];
+					if (color) {
+						weightedMesh.r = toColor(color, 0);
+						weightedMesh.g = toColor(color, 1);
+						weightedMesh.b = toColor(color, 2);
+						weightedMesh.a = toColor(color, 3);
 					}
-					timelines[timelines.length] = flipTimeline;
-					duration = Math.max(duration, flipTimeline.frames[flipTimeline.frameCount * 3 - 3]);
-
-				} else
-					throw new Error("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
+					weightedMesh.hullLength = int(map["hull"] || 0) * 2;
+					if (map["edges"]) weightedMesh.edges = getIntArray(map, "edges");
+					weightedMesh.width = Number(map["width"] || 0) * scale;
+					weightedMesh.height = Number(map["height"] || 0) * scale;
+					return weightedMesh;
+				case AttachmentType.boundingbox:
+					var box : BoundingBoxAttachment = attachmentLoader.newBoundingBoxAttachment(skin, name);
+					vertices = box.vertices;
+					for each (var point : Number in map["vertices"])
+						vertices[vertices.length] = point * scale;
+					return box;
 			}
-		}
 
-		var ikMap:Object = map["ik"];
-		for (var ikConstraintName:String in ikMap) {
-			var ikConstraint:IkConstraintData = skeletonData.findIkConstraint(ikConstraintName);
-			values = ikMap[ikConstraintName];
-			var ikTimeline:IkConstraintTimeline = new IkConstraintTimeline(values.length);
-			ikTimeline.ikConstraintIndex = skeletonData.ikConstraints.indexOf(ikConstraint);
-			frameIndex = 0;
-			for each (valueMap in values) {
-				var mix:Number = valueMap.hasOwnProperty("mix") ? valueMap["mix"] : 1;
-				var bendDirection:int = (!valueMap.hasOwnProperty("bendPositive") || valueMap["bendPositive"]) ? 1 : -1;
-				ikTimeline.setFrame(frameIndex, valueMap["time"], mix, bendDirection);
-				readCurve(ikTimeline, frameIndex, valueMap);
-				frameIndex++;
-			}
-			timelines[timelines.length] = ikTimeline;
-			duration = Math.max(duration, ikTimeline.frames[ikTimeline.frameCount * 3 - 3]);
+			return null;
 		}
 
-		var ffd:Object = map["ffd"];
-		for (var skinName:String in ffd) {
-			var skin:Skin = skeletonData.findSkin(skinName);
-			slotMap = ffd[skinName];
-			for (slotName in slotMap) {
+		private function readAnimation(name : String, map : Object, skeletonData : SkeletonData) : void {
+			var timelines : Vector.<Timeline> = new Vector.<Timeline>();
+			var duration : Number = 0;
+
+			var slotMap : Object, slotIndex : int, slotName : String;
+			var values : Array, valueMap : Object, frameIndex : int;
+			var i : int;
+			var timelineName : String;
+
+			var slots : Object = map["slots"];
+			for (slotName in slots) {
+				slotMap = slots[slotName];
 				slotIndex = skeletonData.findSlotIndex(slotName);
-				var meshMap:Object = slotMap[slotName];
-				for (var meshName:String in meshMap) {
-					values = meshMap[meshName];
-					var ffdTimeline:FfdTimeline = new FfdTimeline(values.length);
-					var attachment:Attachment = skin.getAttachment(slotIndex, meshName);
-					if (!attachment) throw new Error("FFD attachment not found: " + meshName);
-					ffdTimeline.slotIndex = slotIndex;
-					ffdTimeline.attachment = attachment;
-
-					var vertexCount:int;
-					if (attachment is MeshAttachment)
-						vertexCount = (attachment as MeshAttachment).vertices.length;
-					else
-						vertexCount = (attachment as SkinnedMeshAttachment).weights.length / 3 * 2;
-
-					frameIndex = 0;
-					for each (valueMap in values) {
-						var vertices:Vector.<Number>;
-						if (!valueMap["vertices"]) {
-							if (attachment is MeshAttachment)
-								vertices = (attachment as MeshAttachment).vertices;
-							else
-								vertices = new Vector.<Number>(vertexCount, true);
-						} else {
-							var verticesValue:Array = valueMap["vertices"];
-							vertices = new Vector.<Number>(vertexCount, true);
-							var start:int = valueMap["offset"] || 0;
-							var n:int = verticesValue.length;
-							if (scale == 1) {
-								for (i = 0; i < n; i++)
-									vertices[i + start] = verticesValue[i];
+
+				for (timelineName in slotMap) {
+					values = slotMap[timelineName];
+					if (timelineName == "color") {
+						var colorTimeline : ColorTimeline = new ColorTimeline(values.length);
+						colorTimeline.slotIndex = slotIndex;
+
+						frameIndex = 0;
+						for each (valueMap in values) {
+							var color : String = valueMap["color"];
+							var r : Number = toColor(color, 0);
+							var g : Number = toColor(color, 1);
+							var b : Number = toColor(color, 2);
+							var a : Number = toColor(color, 3);
+							colorTimeline.setFrame(frameIndex, valueMap["time"], r, g, b, a);
+							readCurve(colorTimeline, frameIndex, valueMap);
+							frameIndex++;
+						}
+						timelines[timelines.length] = colorTimeline;
+						duration = Math.max(duration, colorTimeline.frames[colorTimeline.frameCount * 5 - 5]);
+					} else if (timelineName == "attachment") {
+						var attachmentTimeline : AttachmentTimeline = new AttachmentTimeline(values.length);
+						attachmentTimeline.slotIndex = slotIndex;
+
+						frameIndex = 0;
+						for each (valueMap in values)
+							attachmentTimeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]);
+						timelines[timelines.length] = attachmentTimeline;
+						duration = Math.max(duration, attachmentTimeline.frames[attachmentTimeline.frameCount - 1]);
+					} else
+						throw new Error("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
+				}
+			}
+
+			var bones : Object = map["bones"];
+			for (var boneName : String in bones) {
+				var boneIndex : int = skeletonData.findBoneIndex(boneName);
+				if (boneIndex == -1) throw new Error("Bone not found: " + boneName);
+				var boneMap : Object = bones[boneName];
+
+				for (timelineName in boneMap) {
+					values = boneMap[timelineName];
+					if (timelineName == "rotate") {
+						var rotateTimeline : RotateTimeline = new RotateTimeline(values.length);
+						rotateTimeline.boneIndex = boneIndex;
+
+						frameIndex = 0;
+						for each (valueMap in values) {
+							rotateTimeline.setFrame(frameIndex, valueMap["time"], valueMap["angle"]);
+							readCurve(rotateTimeline, frameIndex, valueMap);
+							frameIndex++;
+						}
+						timelines[timelines.length] = rotateTimeline;
+						duration = Math.max(duration, rotateTimeline.frames[rotateTimeline.frameCount * 2 - 2]);
+					} else if (timelineName == "translate" || timelineName == "scale") {
+						var timeline : TranslateTimeline;
+						var timelineScale : Number = 1;
+						if (timelineName == "scale")
+							timeline = new ScaleTimeline(values.length);
+						else {
+							timeline = new TranslateTimeline(values.length);
+							timelineScale = scale;
+						}
+						timeline.boneIndex = boneIndex;
+
+						frameIndex = 0;
+						for each (valueMap in values) {
+							var x : Number = Number(valueMap["x"] || 0) * timelineScale;
+							var y : Number = Number(valueMap["y"] || 0) * timelineScale;
+							timeline.setFrame(frameIndex, valueMap["time"], x, y);
+							readCurve(timeline, frameIndex, valueMap);
+							frameIndex++;
+						}
+						timelines[timelines.length] = timeline;
+						duration = Math.max(duration, timeline.frames[timeline.frameCount * 3 - 3]);
+					} else
+						throw new Error("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
+				}
+			}
+
+			var ikMap : Object = map["ik"];
+			for (var ikConstraintName : String in ikMap) {
+				var ikConstraint : IkConstraintData = skeletonData.findIkConstraint(ikConstraintName);
+				values = ikMap[ikConstraintName];
+				var ikTimeline : IkConstraintTimeline = new IkConstraintTimeline(values.length);
+				ikTimeline.ikConstraintIndex = skeletonData.ikConstraints.indexOf(ikConstraint);
+				frameIndex = 0;
+				for each (valueMap in values) {
+					var mix : Number = valueMap.hasOwnProperty("mix") ? valueMap["mix"] : 1;
+					var bendDirection : int = (!valueMap.hasOwnProperty("bendPositive") || valueMap["bendPositive"]) ? 1 : -1;
+					ikTimeline.setFrame(frameIndex, valueMap["time"], mix, bendDirection);
+					readCurve(ikTimeline, frameIndex, valueMap);
+					frameIndex++;
+				}
+				timelines[timelines.length] = ikTimeline;
+				duration = Math.max(duration, ikTimeline.frames[ikTimeline.frameCount * 3 - 3]);
+			}
+
+			var ffd : Object = map["ffd"];
+			for (var skinName : String in ffd) {
+				var skin : Skin = skeletonData.findSkin(skinName);
+				slotMap = ffd[skinName];
+				for (slotName in slotMap) {
+					slotIndex = skeletonData.findSlotIndex(slotName);
+					var meshMap : Object = slotMap[slotName];
+					for (var meshName : String in meshMap) {
+						values = meshMap[meshName];
+						var ffdTimeline : FfdTimeline = new FfdTimeline(values.length);
+						var attachment : Attachment = skin.getAttachment(slotIndex, meshName);
+						if (!attachment) throw new Error("FFD attachment not found: " + meshName);
+						ffdTimeline.slotIndex = slotIndex;
+						ffdTimeline.attachment = attachment;
+
+						var vertexCount : int;
+						if (attachment is MeshAttachment)
+							vertexCount = (attachment as MeshAttachment).vertices.length;
+						else
+							vertexCount = (attachment as WeightedMeshAttachment).weights.length / 3 * 2;
+
+						frameIndex = 0;
+						for each (valueMap in values) {
+							var vertices : Vector.<Number>;
+							if (!valueMap["vertices"]) {
+								if (attachment is MeshAttachment)
+									vertices = (attachment as MeshAttachment).vertices;
+								else
+									vertices = new Vector.<Number>(vertexCount, true);
 							} else {
-								for (i = 0; i < n; i++)
-									vertices[i + start] = verticesValue[i] * scale;
-							}
-							if (attachment is MeshAttachment) {
-								var meshVertices:Vector.<Number> = (attachment as MeshAttachment).vertices;
-								for (i = 0; i < vertexCount; i++)
-									vertices[i] += meshVertices[i];
+								var verticesValue : Array = valueMap["vertices"];
+								vertices = new Vector.<Number>(vertexCount, true);
+								var start : int = valueMap["offset"] || 0;
+								var n : int = verticesValue.length;
+								if (scale == 1) {
+									for (i = 0; i < n; i++)
+										vertices[i + start] = verticesValue[i];
+								} else {
+									for (i = 0; i < n; i++)
+										vertices[i + start] = verticesValue[i] * scale;
+								}
+								if (attachment is MeshAttachment) {
+									var meshVertices : Vector.<Number> = (attachment as MeshAttachment).vertices;
+									for (i = 0; i < vertexCount; i++)
+										vertices[i] += meshVertices[i];
+								}
 							}
+
+							ffdTimeline.setFrame(frameIndex, valueMap["time"], vertices);
+							readCurve(ffdTimeline, frameIndex, valueMap);
+							frameIndex++;
 						}
-						
-						ffdTimeline.setFrame(frameIndex, valueMap["time"], vertices);
-						readCurve(ffdTimeline, frameIndex, valueMap);
-						frameIndex++;
+						timelines[timelines.length] = ffdTimeline;
+						duration = Math.max(duration, ffdTimeline.frames[ffdTimeline.frameCount - 1]);
 					}
-					timelines[timelines.length] = ffdTimeline;
-					duration = Math.max(duration, ffdTimeline.frames[ffdTimeline.frameCount - 1]);
 				}
 			}
-		}
 
-		var drawOrderValues:Array = map["drawOrder"];
-		if (!drawOrderValues) drawOrderValues = map["draworder"];
-		if (drawOrderValues) {
-			var drawOrderTimeline:DrawOrderTimeline = new DrawOrderTimeline(drawOrderValues.length);
-			var slotCount:int = skeletonData.slots.length;
-			frameIndex = 0;
-			for each (var drawOrderMap:Object in drawOrderValues) {
-				var drawOrder:Vector.<int> = null;
-				if (drawOrderMap["offsets"]) {
-					drawOrder = new Vector.<int>(slotCount);
-					for (i = slotCount - 1; i >= 0; i--)
-						drawOrder[i] = -1;
-					var offsets:Array = drawOrderMap["offsets"];
-					var unchanged:Vector.<int> = new Vector.<int>(slotCount - offsets.length);
-					var originalIndex:int = 0, unchangedIndex:int = 0;
-					for each (var offsetMap:Object in offsets) {
-						slotIndex = skeletonData.findSlotIndex(offsetMap["slot"]);
-						if (slotIndex == -1) throw new Error("Slot not found: " + offsetMap["slot"]);
-						// Collect unchanged items.
-						while (originalIndex != slotIndex)
+			var drawOrderValues : Array = map["drawOrder"];
+			if (!drawOrderValues) drawOrderValues = map["draworder"];
+			if (drawOrderValues) {
+				var drawOrderTimeline : DrawOrderTimeline = new DrawOrderTimeline(drawOrderValues.length);
+				var slotCount : int = skeletonData.slots.length;
+				frameIndex = 0;
+				for each (var drawOrderMap : Object in drawOrderValues) {
+					var drawOrder : Vector.<int> = null;
+					if (drawOrderMap["offsets"]) {
+						drawOrder = new Vector.<int>(slotCount);
+						for (i = slotCount - 1; i >= 0; i--)
+							drawOrder[i] = -1;
+						var offsets : Array = drawOrderMap["offsets"];
+						var unchanged : Vector.<int> = new Vector.<int>(slotCount - offsets.length);
+						var originalIndex : int = 0, unchangedIndex : int = 0;
+						for each (var offsetMap : Object in offsets) {
+							slotIndex = skeletonData.findSlotIndex(offsetMap["slot"]);
+							if (slotIndex == -1) throw new Error("Slot not found: " + offsetMap["slot"]);
+							// Collect unchanged items.
+							while (originalIndex != slotIndex)
+								unchanged[unchangedIndex++] = originalIndex++;
+							// Set changed items.
+							drawOrder[originalIndex + offsetMap["offset"]] = originalIndex++;
+						}
+						// Collect remaining unchanged items.
+						while (originalIndex < slotCount)
 							unchanged[unchangedIndex++] = originalIndex++;
-						// Set changed items.
-						drawOrder[originalIndex + offsetMap["offset"]] = originalIndex++;
+						// Fill in unchanged items.
+						for (i = slotCount - 1; i >= 0; i--)
+							if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
 					}
-					// Collect remaining unchanged items.
-					while (originalIndex < slotCount)
-						unchanged[unchangedIndex++] = originalIndex++;
-					// Fill in unchanged items.
-					for (i = slotCount - 1; i >= 0; i--)
-						if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
+					drawOrderTimeline.setFrame(frameIndex++, drawOrderMap["time"], drawOrder);
 				}
-				drawOrderTimeline.setFrame(frameIndex++, drawOrderMap["time"], drawOrder);
+				timelines[timelines.length] = drawOrderTimeline;
+				duration = Math.max(duration, drawOrderTimeline.frames[drawOrderTimeline.frameCount - 1]);
 			}
-			timelines[timelines.length] = drawOrderTimeline;
-			duration = Math.max(duration, drawOrderTimeline.frames[drawOrderTimeline.frameCount - 1]);
-		}
 
-		var eventsMap:Array = map["events"];
-		if (eventsMap) {
-			var eventTimeline:EventTimeline = new EventTimeline(eventsMap.length);
-			frameIndex = 0;
-			for each (var eventMap:Object in eventsMap) {
-				var eventData:EventData = skeletonData.findEvent(eventMap["name"]);
-				if (!eventData) throw new Error("Event not found: " + eventMap["name"]);
-				var event:Event = new Event(eventData);
-				event.intValue = eventMap.hasOwnProperty("int") ? eventMap["int"] : eventData.intValue;
-				event.floatValue = eventMap.hasOwnProperty("float") ? eventMap["float"] : eventData.floatValue;
-				event.stringValue = eventMap.hasOwnProperty("string") ? eventMap["string"] : eventData.stringValue;
-				eventTimeline.setFrame(frameIndex++, eventMap["time"], event);
+			var eventsMap : Array = map["events"];
+			if (eventsMap) {
+				var eventTimeline : EventTimeline = new EventTimeline(eventsMap.length);
+				frameIndex = 0;
+				for each (var eventMap : Object in eventsMap) {
+					var eventData : EventData = skeletonData.findEvent(eventMap["name"]);
+					if (!eventData) throw new Error("Event not found: " + eventMap["name"]);
+					var event : Event = new Event(eventMap["time"], eventData);
+					event.intValue = eventMap.hasOwnProperty("int") ? eventMap["int"] : eventData.intValue;
+					event.floatValue = eventMap.hasOwnProperty("float") ? eventMap["float"] : eventData.floatValue;
+					event.stringValue = eventMap.hasOwnProperty("string") ? eventMap["string"] : eventData.stringValue;
+					eventTimeline.setFrame(frameIndex++, event);
+				}
+				timelines[timelines.length] = eventTimeline;
+				duration = Math.max(duration, eventTimeline.frames[eventTimeline.frameCount - 1]);
 			}
-			timelines[timelines.length] = eventTimeline;
-			duration = Math.max(duration, eventTimeline.frames[eventTimeline.frameCount - 1]);
+
+			skeletonData.animations[skeletonData.animations.length] = new Animation(name, timelines, duration);
 		}
 
-		skeletonData.animations[skeletonData.animations.length] = new Animation(name, timelines, duration);
-	}
+		static private function readCurve(timeline : CurveTimeline, frameIndex : int, valueMap : Object) : void {
+			var curve : Object = valueMap["curve"];
+			if (!curve) return;
+			if (curve == "stepped")
+				timeline.setStepped(frameIndex);
+			else if (curve is Array)
+				timeline.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]);
+		}
 
-	static private function readCurve (timeline:CurveTimeline, frameIndex:int, valueMap:Object) : void {
-		var curve:Object = valueMap["curve"];
-		if (!curve) return;
-		if (curve == "stepped")
-			timeline.setStepped(frameIndex);
-		else if (curve is Array)
-			timeline.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]);
-	}
+		static private function toColor(hexString : String, colorIndex : int) : Number {
+			if (hexString.length != 8) throw new ArgumentError("Color hexidecimal length must be 8, recieved: " + hexString);
+			return parseInt(hexString.substring(colorIndex * 2, colorIndex * 2 + 2), 16) / 255;
+		}
 
-	static private function toColor (hexString:String, colorIndex:int) : Number {
-		if (hexString.length != 8) throw new ArgumentError("Color hexidecimal length must be 8, recieved: " + hexString);
-		return parseInt(hexString.substring(colorIndex * 2, colorIndex * 2 + 2), 16) / 255;
-	}
+		static private function getFloatArray(map : Object, name : String, scale : Number) : Vector.<Number> {
+			var list : Array = map[name];
+			var values : Vector.<Number> = new Vector.<Number>(list.length, true);
+			var i : int = 0, n : int = list.length;
+			if (scale == 1) {
+				for (; i < n; i++)
+					values[i] = list[i];
+			} else {
+				for (; i < n; i++)
+					values[i] = list[i] * scale;
+			}
+			return values;
+		}
 
-	static private function getFloatArray (map:Object, name:String, scale:Number) : Vector.<Number> {
-		var list:Array = map[name];
-		var values:Vector.<Number> = new Vector.<Number>(list.length, true);
-		var i:int = 0, n:int = list.length;
-		if (scale == 1) {
-			for (; i < n; i++)
-				values[i] = list[i];
-		} else {
-			for (; i < n; i++)
-				values[i] = list[i] * scale;
+		static private function getIntArray(map : Object, name : String) : Vector.<int> {
+			var list : Array = map[name];
+			var values : Vector.<int> = new Vector.<int>(list.length, true);
+			for (var i : int = 0, n : int = list.length; i < n; i++)
+				values[i] = int(list[i]);
+			return values;
 		}
-		return values;
-	}
-	
-	static private function getIntArray (map:Object, name:String) : Vector.<int> {
-		var list:Array = map[name];
-		var values:Vector.<int> = new Vector.<int>(list.length, true);
-		for (var i:int = 0, n:int = list.length; i < n; i++)
-			values[i] = int(list[i]);
-		return values;
-	}
-	
-	static private function getUintArray (map:Object, name:String) : Vector.<uint> {
-		var list:Array = map[name];
-		var values:Vector.<uint> = new Vector.<uint>(list.length, true);
-		for (var i:int = 0, n:int = list.length; i < n; i++)
-			values[i] = int(list[i]);
-		return values;
-	}
-}
 
+		static private function getUintArray(map : Object, name : String) : Vector.<uint> {
+			var list : Array = map[name];
+			var values : Vector.<uint> = new Vector.<uint>(list.length, true);
+			for (var i : int = 0, n : int = list.length; i < n; i++)
+				values[i] = int(list[i]);
+			return values;
+		}
+	}
 }

+ 34 - 29
spine-as3/spine-as3/src/spine/animation/FlipXTimeline.as → spine-as3/spine-as3/src/spine/TransformConstraint.as

@@ -29,45 +29,50 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-package spine.animation {
-import spine.Bone;
-import spine.Event;
-import spine.Skeleton;
+package spine {
 
-public class FlipXTimeline implements Timeline {
-	public var boneIndex:int;
-	public var frames:Vector.<Number>; // time, flip, ...
+public class TransformConstraint implements Updatable {
+	internal var _data:TransformConstraintData;
+	public var bone:Bone;
+	public var target:Bone;
+	public var translateMix:Number;
+	public var x:Number;
+	public var y:Number;
 
-	public function FlipXTimeline (frameCount:int) {
-		frames = new Vector.<Number>(frameCount * 2, true);
-	}
+	public function TransformConstraint (data:TransformConstraintData, skeleton:Skeleton) {
+		if (data == null) throw new ArgumentError("data cannot be null.");
+		if (skeleton == null) throw new ArgumentError("skeleton cannot be null.");
+		_data = data;
+		translateMix = data.translateMix;
+		x = data.x;
+		y = data.y;
 
-	public function get frameCount () : int {
-		return frames.length / 2;
+		bone = skeleton.findBone(data.bone._name);
+		target = skeleton.findBone(data.target._name);
 	}
 
-	/** Sets the time and angle of the specified keyframe. */
-	public function setFrame (frameIndex:int, time:Number, flip:Boolean) : void {
-		frameIndex *= 2;
-		frames[frameIndex] = time;
-		frames[int(frameIndex + 1)] = flip ? 1 : 0;
+	public function apply () : void {
+		update();
 	}
 
-	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0]) {
-			if (lastTime > time) apply(skeleton, lastTime, int.MAX_VALUE, null, 0);
-			return;
-		} else if (lastTime > time) //
-			lastTime = -1;
-
-		var frameIndex:int = (time >= frames[frames.length - 2] ? frames.length : Animation.binarySearch(frames, time, 2)) - 2;
-		if (frames[frameIndex] < lastTime) return;
+	public function update () : void {
+		var translateMix:Number = translateMix;
+		if (translateMix > 0) {
+			var local:Vector.<Number> = new Vector.<Number>(2, true);
+			local[0] = x;
+			local[1] = y;
+			target.localToWorld(local);
+			bone._worldX += (local[0] - bone._worldX) * translateMix;
+			bone._worldY += (local[1] - bone._worldY) * translateMix;
+		}
+	}
 
-		setFlip(skeleton.bones[boneIndex], frames[frameIndex + 1] != 0);
+	public function get data () : TransformConstraintData {
+		return _data;
 	}
 
-	protected function setFlip (bone:Bone, flip:Boolean) : void {
-		bone.flipX = flip;
+	public function toString () : String {
+		return _data._name;
 	}
 }
 

+ 56 - 0
spine-as3/spine-as3/src/spine/TransformConstraintData.as

@@ -0,0 +1,56 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), 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 SOFTWARE 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.
+ *****************************************************************************/
+
+package spine {
+
+public class TransformConstraintData {
+	internal var _name:String;
+	public var bone:BoneData;
+	public var target:BoneData;
+	public var translateMix:Number;
+	public var x:Number;
+	public var y:Number;
+
+	public function TransformConstraintData (name:String) {
+		if (name == null) throw new ArgumentError("name cannot be null.");
+		_name = name;
+	}
+
+	public function get name () : String {
+		return _name;
+	}
+
+	public function toString () : String {
+		return _name;
+	}
+}
+
+}

+ 38 - 0
spine-as3/spine-as3/src/spine/Updatable.as

@@ -0,0 +1,38 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), 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 SOFTWARE 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.
+ *****************************************************************************/
+
+package spine {
+
+public interface Updatable {
+	function update () : void;
+}
+
+}

+ 2 - 2
spine-as3/spine-as3/src/spine/animation/Animation.as

@@ -56,7 +56,7 @@ public class Animation {
 
 		if (loop && duration != 0) {
 			time %= duration;
-			lastTime %= duration;
+			if (lastTime > 0) lastTime %= duration;
 		}
 
 		for (var i:int = 0, n:int = timelines.length; i < n; i++)
@@ -70,7 +70,7 @@ public class Animation {
 
 		if (loop && duration != 0) {
 			time %= duration;
-			lastTime %= duration;
+			if (lastTime > 0) lastTime %= duration;
 		}
 
 		for (var i:int = 0, n:int = timelines.length; i < n; i++)

+ 2 - 2
spine-as3/spine-as3/src/spine/animation/EventTimeline.as

@@ -47,8 +47,8 @@ public class EventTimeline implements Timeline {
 	}
 
 	/** Sets the time and value of the specified keyframe. */
-	public function setFrame (frameIndex:int, time:Number, event:Event) : void {
-		frames[frameIndex] = time;
+	public function setFrame (frameIndex:int, event:Event) : void {
+		frames[frameIndex] = event.time;
 		events[frameIndex] = event;
 	}
 

+ 3 - 3
spine-as3/spine-as3/src/spine/attachments/AtlasAttachmentLoader.as

@@ -83,11 +83,11 @@ public class AtlasAttachmentLoader implements AttachmentLoader {
 		return attachment;
 	}
 	
-	public function newSkinnedMeshAttachment (skin:Skin, name:String, path:String) : SkinnedMeshAttachment {
+	public function newWeightedMeshAttachment (skin:Skin, name:String, path:String) : WeightedMeshAttachment {
 		var region:AtlasRegion = atlas.findRegion(path);
 		if (region == null)
-			throw new Error("Region not found in atlas: " + path + " (skinned mesh attachment: " + name + ")");
-		var attachment:SkinnedMeshAttachment = new SkinnedMeshAttachment(name);
+			throw new Error("Region not found in atlas: " + path + " (weighted mesh attachment: " + name + ")");
+		var attachment:WeightedMeshAttachment = new WeightedMeshAttachment(name);
 		attachment.rendererObject = region;
 		var scaleX:Number = region.page.width / nextPOT(region.page.width);
 		var scaleY:Number = region.page.height / nextPOT(region.page.height);

+ 1 - 1
spine-as3/spine-as3/src/spine/attachments/AttachmentLoader.as

@@ -40,7 +40,7 @@ public interface AttachmentLoader {
 	function newMeshAttachment (skin:Skin, name:String, path:String) : MeshAttachment;
 
 	/** @return May be null to not load an attachment. */
-	function newSkinnedMeshAttachment (skin:Skin, name:String, path:String) : SkinnedMeshAttachment;
+	function newWeightedMeshAttachment (skin:Skin, name:String, path:String) : WeightedMeshAttachment;
 
 	/** @return May be null to not load an attachment. */
 	function newBoundingBoxAttachment (skin:Skin, name:String) : BoundingBoxAttachment;

+ 1 - 1
spine-as3/spine-as3/src/spine/attachments/AttachmentType.as

@@ -36,7 +36,7 @@ public class AttachmentType {
 	public static const regionsequence:AttachmentType = new AttachmentType(1, "regionsequence");
 	public static const boundingbox:AttachmentType = new AttachmentType(2, "boundingbox");
 	public static const mesh:AttachmentType = new AttachmentType(3, "mesh");
-	public static const skinnedmesh:AttachmentType = new AttachmentType(4, "skinnedmesh");
+	public static const weightedmesh:AttachmentType = new AttachmentType(4, "weightedmesh");
 
 	public var ordinal:int;
 	public var name:String;

+ 4 - 4
spine-as3/spine-as3/src/spine/attachments/BoundingBoxAttachment.as

@@ -42,10 +42,10 @@ public dynamic class BoundingBoxAttachment extends Attachment {
 	public function computeWorldVertices (x:Number, y:Number, bone:Bone, worldVertices:Vector.<Number>) : void {
 		x += bone.worldX;
 		y += bone.worldY;
-		var m00:Number = bone.m00;
-		var m01:Number = bone.m01;
-		var m10:Number = bone.m10;
-		var m11:Number = bone.m11;
+		var m00:Number = bone.a;
+		var m01:Number = bone.b;
+		var m10:Number = bone.c;
+		var m11:Number = bone.d;
 		var vertices:Vector.<Number> = this.vertices;
 		for (var i:int = 0, n:int = vertices.length; i < n; i += 2) {
 			var ii:int = i + 1;

+ 4 - 4
spine-as3/spine-as3/src/spine/attachments/MeshAttachment.as

@@ -88,10 +88,10 @@ public dynamic class MeshAttachment extends Attachment {
 		var bone:Bone = slot.bone;
 		x += bone.worldX;
 		y += bone.worldY;
-		var m00:Number = bone.m00;
-		var m01:Number = bone.m01;
-		var m10:Number = bone.m10;
-		var m11:Number = bone.m11;
+		var m00:Number = bone.a;
+		var m01:Number = bone.b;
+		var m10:Number = bone.c;
+		var m11:Number = bone.d;
 		var vertices:Vector.<Number> = this.vertices;
 		var verticesCount:int = vertices.length;
 		if (slot.attachmentVertices.length == verticesCount) vertices = slot.attachmentVertices;

+ 4 - 4
spine-as3/spine-as3/src/spine/attachments/RegionAttachment.as

@@ -125,10 +125,10 @@ public dynamic class RegionAttachment extends Attachment {
 	public function computeWorldVertices (x:Number, y:Number, bone:Bone, worldVertices:Vector.<Number>) : void {
 		x += bone.worldX;
 		y += bone.worldY;
-		var m00:Number = bone.m00;
-		var m01:Number = bone.m01;
-		var m10:Number = bone.m10;
-		var m11:Number = bone.m11;
+		var m00:Number = bone.a;
+		var m01:Number = bone.b;
+		var m10:Number = bone.c;
+		var m11:Number = bone.d;
 		var x1:Number = offset[X1];
 		var y1:Number = offset[Y1];
 		var x2:Number = offset[X2];

+ 6 - 6
spine-as3/spine-as3/src/spine/attachments/SkinnedMeshAttachment.as → spine-as3/spine-as3/src/spine/attachments/WeightedMeshAttachment.as

@@ -33,7 +33,7 @@ package spine.attachments {
 import spine.Slot;
 import spine.Bone;
 
-public dynamic class SkinnedMeshAttachment extends Attachment {
+public dynamic class WeightedMeshAttachment extends Attachment {
 	public var bones:Vector.<int>;
 	public var weights:Vector.<Number>;
 	public var uvs:Vector.<Number>;
@@ -64,7 +64,7 @@ public dynamic class SkinnedMeshAttachment extends Attachment {
 	public var width:Number;
 	public var height:Number;
 
-	public function SkinnedMeshAttachment (name:String) {
+	public function WeightedMeshAttachment (name:String) {
 		super(name);
 	}
 
@@ -102,8 +102,8 @@ public dynamic class SkinnedMeshAttachment extends Attachment {
 					vx = weights[b];
 					vy = weights[int(b + 1)];
 					weight = weights[int(b + 2)];
-					wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight;
-					wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight;
+					wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
+					wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
 				}
 				worldVertices[w] = wx + x;
 				worldVertices[int(w + 1)] = wy + y;
@@ -119,8 +119,8 @@ public dynamic class SkinnedMeshAttachment extends Attachment {
 					vx = weights[b] + ffd[f];
 					vy = weights[int(b + 1)] + ffd[int(f + 1)];
 					weight = weights[int(b + 2)];
-					wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight;
-					wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight;
+					wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
+					wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
 				}
 				worldVertices[w] = wx + x;
 				worldVertices[int(w + 1)] = wy + y;

+ 1 - 3
spine-as3/spine-as3/src/spine/flash/SkeletonSprite.as

@@ -129,12 +129,10 @@ public class SkeletonSprite extends Sprite {
 				var bone:Bone = slot.bone;
 				var flipX:int = skeleton.flipX ? -1 : 1;
 				var flipY:int = skeleton.flipY ? -1 : 1;
-				if (bone.worldFlipX) flipX = -flipX;
-				if (bone.worldFlipY) flipY = -flipY;
 
 				wrapper.x = bone.worldX;
 				wrapper.y = bone.worldY;
-				wrapper.rotation = -bone.worldRotation * flipX * flipY;
+				wrapper.rotation = -bone.worldRotationX * flipX * flipY;
 				wrapper.scaleX = bone.worldScaleX * flipX;
 				wrapper.scaleY = bone.worldScaleY * flipY;
 				addChild(wrapper);