#

API Docs for: 1.3.0
Show: Inherited Protected Private Deprecated

# File: src/pixi/extras/Spine.js

    /**
     * @author Mat Groves http://matgroves.com/ @Doormat23
     * based on pixi impact spine implementation made by Eemeli Kelokorpi (@ekelokorpi) https://github.com/ekelokorpi
     *
     * Awesome JS run time provided by EsotericSoftware
     * https://github.com/EsotericSoftware/spine-runtimes
     *
     */

    /**
     * A class that enables the you to import and run your spine animations in pixi.
     * Spine animation data needs to be loaded using the PIXI.AssetLoader or PIXI.SpineLoader before it can be used by this class
     * Also due to a clash of names  You will need to change the extension of the spine file from *.json to *.anim for it to load
     * See example 12 (http://www.goodboydigital.com/pixijs/examples/12/) to see a working example and check out the source
     * @class Spine
     * @constructor
     * @extends DisplayObjectContainer
     * @param {String} url the url of the spine anim file to be used
     */
    PIXI.Spine = function(url)
    {
    	PIXI.DisplayObjectContainer.call(this);

    	this.spineData = PIXI.AnimCache[url];

    	if(!this.spineData)
    	{
    		throw new Error("Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: " + url);
    		return;
    	}

    	this.count = 0;

    	this.sprites = [];

    	this.skeleton = new spine.Skeleton(this.spineData);
    	this.skeleton.updateWorldTransform();

    	this.stateData = new spine.AnimationStateData(this.spineData);
    	this.state = new spine.AnimationState(this.stateData);

    	// add the sprites..
    	for (var i = 0; i < this.skeleton.drawOrder.length; i++) {

    		var attachmentName = this.skeleton.drawOrder[i].data.attachmentName;

    		// kind of an assumtion here. that its a png
    		if(!PIXI.TextureCache[attachmentName])
    		{
    			attachmentName += ".png";
    		}

    		var sprite = new PIXI.Sprite(PIXI.Texture.fromFrame(attachmentName));
    		sprite.anchor.x = sprite.anchor.y = 0.5;
    		this.addChild(sprite);
    		this.sprites.push(sprite);
    	};
    }

    PIXI.Spine.constructor = PIXI.Spine;
    PIXI.Spine.prototype = Object.create( PIXI.DisplayObjectContainer.prototype );
    PIXI.Spine.prototype.updateTransform = function()
    {
    	// TODO should make this time based really..
    	this.state.update(1/60);
    	this.state.apply(this.skeleton);
    	this.skeleton.updateWorldTransform();

    	for (var i = 0; i < this.skeleton.drawOrder.length; i++)
    	{
    		var slot = this.skeleton.drawOrder[i];

    		var x = slot.bone.worldX + slot.attachment.x * slot.bone.m00 + slot.attachment.y * slot.bone.m01 + slot.attachment.width * 0.5;
    		var y = slot.bone.worldY + slot.attachment.x * slot.bone.m10 + slot.attachment.y * slot.bone.m11 + slot.attachment.height * 0.5;
    		//console.log(x + ' : ' + y);

    			//console.log(slot.attachment.name)
    			if(slot.cacheName != slot.attachment.name)
    			{
    				var attachmentName = slot.attachment.name;

    				if(!PIXI.TextureCache[attachmentName])
    				{
    					attachmentName += ".png";
    				}

    				this.sprites[i].setTexture(PIXI.TextureCache[attachmentName]);

    				slot.cacheName = slot.attachment.name;
    			}

    		x += -((slot.attachment.width * (slot.bone.worldScaleX + slot.attachment.scaleX - 1))>>1);
    		y += -((slot.attachment.height * (slot.bone.worldScaleY + slot.attachment.scaleY - 1))>>1);

    		this.sprites[i].position.x = x;
    		this.sprites[i].position.y = y;
    		this.sprites[i].rotation = (-(slot.bone.worldRotation + slot.attachment.rotation)) * (Math.PI/180);
    	}

    	PIXI.DisplayObjectContainer.prototype.updateTransform.call(this);
    }

    /*
     * Awesome JS run time provided by EsotericSoftware
     *
     * https://github.com/EsotericSoftware/spine-runtimes
     *
     */

    var spine = {};

    spine.BoneData = function (name, parent) {
    	this.name = name;
    	this.parent = parent;
    };
    spine.BoneData.prototype = {
    	length: 0,
    	x: 0, y: 0,
    	rotation: 0,
    	scaleX: 1, scaleY: 1
    };

    spine.SlotData = function (name, boneData) {
    	this.name = name;
    	this.boneData = boneData;
    };
    spine.SlotData.prototype = {
    	r: 1, g: 1, b: 1, a: 1,
    	attachmentName: null
    };

    spine.Bone = function (boneData, parent) {
    	this.data = boneData;
    	this.parent = parent;
    	this.setToSetupPose();
    };
    spine.Bone.yDown = false;
    spine.Bone.prototype = {
    	x: 0, y: 0,
    	rotation: 0,
    	scaleX: 1, scaleY: 1,
    	m00: 0, m01: 0, worldX: 0, // a b x
    	m10: 0, m11: 0, worldY: 0, // c d y
    	worldRotation: 0,
    	worldScaleX: 1, worldScaleY: 1,
    	updateWorldTransform: function (flipX, flipY) {
    		var parent = this.parent;
    		if (parent != null) {
    			this.worldX = this.x * parent.m00 + this.y * parent.m01 + parent.worldX;
    			this.worldY = this.x * parent.m10 + this.y * parent.m11 + parent.worldY;
    			this.worldScaleX = parent.worldScaleX * this.scaleX;
    			this.worldScaleY = parent.worldScaleY * this.scaleY;
    			this.worldRotation = parent.worldRotation + this.rotation;
    		} else {
    			this.worldX = this.x;
    			this.worldY = this.y;
    			this.worldScaleX = this.scaleX;
    			this.worldScaleY = this.scaleY;
    			this.worldRotation = this.rotation;
    		}
    		var radians = this.worldRotation * Math.PI / 180;
    		var cos = Math.cos(radians);
    		var sin = Math.sin(radians);
    		this.m00 = cos * this.worldScaleX;
    		this.m10 = sin * this.worldScaleX;
    		this.m01 = -sin * this.worldScaleY;
    		this.m11 = cos * this.worldScaleY;
    		if (flipX) {
    			this.m00 = -this.m00;
    			this.m01 = -this.m01;
    		}
    		if (flipY) {
    			this.m10 = -this.m10;
    			this.m11 = -this.m11;
    		}
    		if (spine.Bone.yDown) {
    			this.m10 = -this.m10;
    			this.m11 = -this.m11;
    		}
    	},
    	setToSetupPose: function () {
    		var data = this.data;
    		this.x = data.x;
    		this.y = data.y;
    		this.rotation = data.rotation;
    		this.scaleX = data.scaleX;
    		this.scaleY = data.scaleY;
    	}
    };

    spine.Slot = function (slotData, skeleton, bone) {
    	this.data = slotData;
    	this.skeleton = skeleton;
    	this.bone = bone;
    	this.setToSetupPose();
    };
    spine.Slot.prototype = {
    	r: 1, g: 1, b: 1, a: 1,
    	_attachmentTime: 0,
    	attachment: null,
    	setAttachment: function (attachment) {
    		this.attachment = attachment;
    		this._attachmentTime = this.skeleton.time;
    	},
    	setAttachmentTime: function (time) {
    		this._attachmentTime = this.skeleton.time - time;
    	},
    	getAttachmentTime: function () {
    		return this.skeleton.time - this._attachmentTime;
    	},
    	setToSetupPose: function () {
    		var data = this.data;
    		this.r = data.r;
    		this.g = data.g;
    		this.b = data.b;
    		this.a = data.a;

    		var slotDatas = this.skeleton.data.slots;
    		for (var i = 0, n = slotDatas.length; i < n; i++) {
    			if (slotDatas[i] == data) {
    				this.setAttachment(!data.attachmentName ? null : this.skeleton.getAttachmentBySlotIndex(i, data.attachmentName));
    				break;
    			}
    		}
    	}
    };

    spine.Skin = function (name) {
    	this.name = name;
    	this.attachments = {};
    };
    spine.Skin.prototype = {
    	addAttachment: function (slotIndex, name, attachment) {
    		this.attachments[slotIndex + ":" + name] = attachment;
    	},
    	getAttachment: function (slotIndex, name) {
    		return this.attachments[slotIndex + ":" + name];
    	},
    	_attachAll: function (skeleton, oldSkin) {
    		for (var key in oldSkin.attachments) {
    			var colon = key.indexOf(":");
    			var slotIndex = parseInt(key.substring(0, colon));
    			var name = key.substring(colon + 1);
    			var slot = skeleton.slots[slotIndex];
    			if (slot.attachment && slot.attachment.name == name) {
    				var attachment = this.getAttachment(slotIndex, name);
    				if (attachment) slot.setAttachment(attachment);
    			}
    		}
    	}
    };

    spine.Animation = function (name, timelines, duration) {
    	this.name = name;
    	this.timelines = timelines;
    	this.duration = duration;
    };
    spine.Animation.prototype = {
    	apply: function (skeleton, time, loop) {
    		if (loop && this.duration != 0) time %= this.duration;
    		var timelines = this.timelines;
    		for (var i = 0, n = timelines.length; i < n; i++)
    			timelines[i].apply(skeleton, time, 1);
    	},
    	mix: function (skeleton, time, loop, alpha) {
    		if (loop && this.duration != 0) time %= this.duration;
    		var timelines = this.timelines;
    		for (var i = 0, n = timelines.length; i < n; i++)
    			timelines[i].apply(skeleton, time, alpha);
    	}
    };

    spine.binarySearch = function (values, target, step) {
    	var low = 0;
    	var high = Math.floor(values.length / step) - 2;
    	if (high == 0) return step;
    	var current = high >>> 1;
    	while (true) {
    		if (values[(current + 1) * step] <= target)
    			low = current + 1;
    		else
    			high = current;
    		if (low == high) return (low + 1) * step;
    		current = (low + high) >>> 1;
    	}
    };
    spine.linearSearch = function (values, target, step) {
    	for (var i = 0, last = values.length - step; i <= last; i += step)
    		if (values[i] > target) return i;
    	return -1;
    };

    spine.Curves = function (frameCount) {
    	this.curves = []; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ...
    	this.curves.length = (frameCount - 1) * 6;
    };
    spine.Curves.prototype = {
    	setLinear: function (frameIndex) {
    		this.curves[frameIndex * 6] = 0/*LINEAR*/;
    	},
    	setStepped: function (frameIndex) {
    		this.curves[frameIndex * 6] = -1/*STEPPED*/;
    	},
    	/** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
    	 * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
    	 * the difference between the keyframe's values. */
    	setCurve: function (frameIndex, cx1, cy1, cx2, cy2) {
    		var subdiv_step = 1 / 10/*BEZIER_SEGMENTS*/;
    		var subdiv_step2 = subdiv_step * subdiv_step;
    		var subdiv_step3 = subdiv_step2 * subdiv_step;
    		var pre1 = 3 * subdiv_step;
    		var pre2 = 3 * subdiv_step2;
    		var pre4 = 6 * subdiv_step2;
    		var pre5 = 6 * subdiv_step3;
    		var tmp1x = -cx1 * 2 + cx2;
    		var tmp1y = -cy1 * 2 + cy2;
    		var tmp2x = (cx1 - cx2) * 3 + 1;
    		var tmp2y = (cy1 - cy2) * 3 + 1;
    		var i = frameIndex * 6;
    		var curves = this.curves;
    		curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
    		curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
    		curves[i + 2] = tmp1x * pre4 + tmp2x * pre5;
    		curves[i + 3] = tmp1y * pre4 + tmp2y * pre5;
    		curves[i + 4] = tmp2x * pre5;
    		curves[i + 5] = tmp2y * pre5;
    	},
    	getCurvePercent: function (frameIndex, percent) {
    		percent = percent < 0 ? 0 : (percent > 1 ? 1 : percent);
    		var curveIndex = frameIndex * 6;
    		var curves = this.curves;
    		var dfx = curves[curveIndex];
    		if (!dfx/*LINEAR*/) return percent;
    		if (dfx == -1/*STEPPED*/) return 0;
    		var dfy = curves[curveIndex + 1];
    		var ddfx = curves[curveIndex + 2];
    		var ddfy = curves[curveIndex + 3];
    		var dddfx = curves[curveIndex + 4];
    		var dddfy = curves[curveIndex + 5];
    		var x = dfx, y = dfy;
    		var i = 10/*BEZIER_SEGMENTS*/ - 2;
    		while (true) {
    			if (x >= percent) {
    				var lastX = x - dfx;
    				var lastY = y - dfy;
    				return lastY + (y - lastY) * (percent - lastX) / (x - lastX);
    			}
    			if (i == 0) break;
    			i--;
    			dfx += ddfx;
    			dfy += ddfy;
    			ddfx += dddfx;
    			ddfy += dddfy;
    			x += dfx;
    			y += dfy;
    		}
    		return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
    	}
    };

    spine.RotateTimeline = function (frameCount) {
    	this.curves = new spine.Curves(frameCount);
    	this.frames = []; // time, angle, ...
    	this.frames.length = frameCount * 2;
    };
    spine.RotateTimeline.prototype = {
    	boneIndex: 0,
    	getFrameCount: function () {
    		return this.frames.length / 2;
    	},
    	setFrame: function (frameIndex, time, angle) {
    		frameIndex *= 2;
    		this.frames[frameIndex] = time;
    		this.frames[frameIndex + 1] = angle;
    	},
    	apply: function (skeleton, time, alpha) {
    		var frames = this.frames;
    		if (time < frames[0]) return; // Time is before first frame.

    		var bone = skeleton.bones[this.boneIndex];

    		if (time >= frames[frames.length - 2]) { // Time is after last frame.
    			var amount = bone.data.rotation + frames[frames.length - 1] - bone.rotation;
    			while (amount > 180)
    				amount -= 360;
    			while (amount < -180)
    				amount += 360;
    			bone.rotation += amount * alpha;
    			return;
    		}

    		// Interpolate between the last frame and the current frame.
    		var frameIndex = spine.binarySearch(frames, time, 2);
    		var lastFrameValue = frames[frameIndex - 1];
    		var frameTime = frames[frameIndex];
    		var percent = 1 - (time - frameTime) / (frames[frameIndex - 2/*LAST_FRAME_TIME*/] - frameTime);
    		percent = this.curves.getCurvePercent(frameIndex / 2 - 1, percent);

    		var amount = frames[frameIndex + 1/*FRAME_VALUE*/] - lastFrameValue;
    		while (amount > 180)
    			amount -= 360;
    		while (amount < -180)
    			amount += 360;
    		amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation;
    		while (amount > 180)
    			amount -= 360;
    		while (amount < -180)
    			amount += 360;
    		bone.rotation += amount * alpha;
    	}
    };

    spine.TranslateTimeline = function (frameCount) {
    	this.curves = new spine.Curves(frameCount);
    	this.frames = []; // time, x, y, ...
    	this.frames.length = frameCount * 3;
    };
    spine.TranslateTimeline.prototype = {
    	boneIndex: 0,
    	getFrameCount: function () {
    		return this.frames.length / 3;
    	},
    	setFrame: function (frameIndex, time, x, y) {
    		frameIndex *= 3;
    		this.frames[frameIndex] = time;
    		this.frames[frameIndex + 1] = x;
    		this.frames[frameIndex + 2] = y;
    	},
    	apply: function (skeleton, time, alpha) {
    		var frames = this.frames;
    		if (time < frames[0]) return; // Time is before first frame.

    		var bone = skeleton.bones[this.boneIndex];

    		if (time >= frames[frames.length - 3]) { // Time is after last frame.
    			bone.x += (bone.data.x + frames[frames.length - 2] - bone.x) * alpha;
    			bone.y += (bone.data.y + frames[frames.length - 1] - bone.y) * alpha;
    			return;
    		}

    		// Interpolate between the last frame and the current frame.
    		var frameIndex = spine.binarySearch(frames, time, 3);
    		var lastFrameX = frames[frameIndex - 2];
    		var lastFrameY = frames[frameIndex - 1];
    		var frameTime = frames[frameIndex];
    		var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime);
    		percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent);
    		bone.x += (bone.data.x + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.x) * alpha;
    		bone.y += (bone.data.y + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.y) * alpha;
    	}
    };

    spine.ScaleTimeline = function (frameCount) {
    	this.curves = new spine.Curves(frameCount);
    	this.frames = []; // time, x, y, ...
    	this.frames.length = frameCount * 3;
    };
    spine.ScaleTimeline.prototype = {
    	boneIndex: 0,
    	getFrameCount: function () {
    		return this.frames.length / 3;
    	},
    	setFrame: function (frameIndex, time, x, y) {
    		frameIndex *= 3;
    		this.frames[frameIndex] = time;
    		this.frames[frameIndex + 1] = x;
    		this.frames[frameIndex + 2] = y;
    	},
    	apply: function (skeleton, time, alpha) {
    		var frames = this.frames;
    		if (time < frames[0]) return; // Time is before first frame.

    		var bone = skeleton.bones[this.boneIndex];

    		if (time >= frames[frames.length - 3]) { // Time is after last frame.
    			bone.scaleX += (bone.data.scaleX - 1 + frames[frames.length - 2] - bone.scaleX) * alpha;
    			bone.scaleY += (bone.data.scaleY - 1 + frames[frames.length - 1] - bone.scaleY) * alpha;

    			return;
    		}

    		// Interpolate between the last frame and the current frame.
    		var frameIndex = spine.binarySearch(frames, time, 3);
    		var lastFrameX = frames[frameIndex - 2];
    		var lastFrameY = frames[frameIndex - 1];
    		var frameTime = frames[frameIndex];
    		var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime);
    		percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent);

    		bone.scaleX += (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.scaleX) * alpha;
    		bone.scaleY += (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.scaleY) * alpha;
    	}
    };

    spine.ColorTimeline = function (frameCount) {
    	this.curves = new spine.Curves(frameCount);
    	this.frames = []; // time, r, g, b, a, ...
    	this.frames.length = frameCount * 5;
    };
    spine.ColorTimeline.prototype = {
    	slotIndex: 0,
    	getFrameCount: function () {
    		return this.frames.length / 2;
    	},
    	setFrame: function (frameIndex, time, x, y) {
    		frameIndex *= 5;
    		this.frames[frameIndex] = time;
    		this.frames[frameIndex + 1] = r;
    		this.frames[frameIndex + 2] = g;
    		this.frames[frameIndex + 3] = b;
    		this.frames[frameIndex + 4] = a;
    	},
    	apply: function (skeleton, time, alpha) {
    		var frames = this.frames;
    		if (time < frames[0]) return; // Time is before first frame.
    		var slot = skeleton.slots[this.slotIndex];

    		if (time >= frames[frames.length - 5]) { // Time is after last frame.
    			var i = frames.length - 1;
    			slot.r = frames[i - 3];
    			slot.g = frames[i - 2];
    			slot.b = frames[i - 1];
    			slot.a = frames[i];
    			return;
    		}

    		// Interpolate between the last frame and the current frame.
    		var frameIndex = spine.binarySearch(frames, time, 5);
    		var lastFrameR = frames[frameIndex - 4];
    		var lastFrameG = frames[frameIndex - 3];
    		var lastFrameB = frames[frameIndex - 2];
    		var lastFrameA = frames[frameIndex - 1];
    		var frameTime = frames[frameIndex];
    		var percent = 1 - (time - frameTime) / (frames[frameIndex - 5/*LAST_FRAME_TIME*/] - frameTime);
    		percent = this.curves.getCurvePercent(frameIndex / 5 - 1, percent);

    		var r = lastFrameR + (frames[frameIndex + 1/*FRAME_R*/] - lastFrameR) * percent;
    		var g = lastFrameG + (frames[frameIndex + 2/*FRAME_G*/] - lastFrameG) * percent;
    		var b = lastFrameB + (frames[frameIndex + 3/*FRAME_B*/] - lastFrameB) * percent;
    		var a = lastFrameA + (frames[frameIndex + 4/*FRAME_A*/] - lastFrameA) * percent;
    		if (alpha < 1) {
    			slot.r += (r - slot.r) * alpha;
    			slot.g += (g - slot.g) * alpha;
    			slot.b += (b - slot.b) * alpha;
    			slot.a += (a - slot.a) * alpha;
    		} else {
    			slot.r = r;
    			slot.g = g;
    			slot.b = b;
    			slot.a = a;
    		}
    	}
    };

    spine.AttachmentTimeline = function (frameCount) {
    	this.curves = new spine.Curves(frameCount);
    	this.frames = []; // time, ...
    	this.frames.length = frameCount;
    	this.attachmentNames = []; // time, ...
    	this.attachmentNames.length = frameCount;
    };
    spine.AttachmentTimeline.prototype = {
    	slotIndex: 0,
    	getFrameCount: function () {
    		return this.frames.length / 2;
    	},
    	setFrame: function (frameIndex, time, attachmentName) {
    		this.frames[frameIndex] = time;
    		this.attachmentNames[frameIndex] = attachmentName;
    	},
    	apply: function (skeleton, time, alpha) {
    		var frames = this.frames;
    		if (time < frames[0]) return; // Time is before first frame.

    		var frameIndex;
    		if (time >= frames[frames.length - 1]) // Time is after last frame.
    			frameIndex = frames.length - 1;
    		else
    			frameIndex = spine.binarySearch(frames, time, 1) - 1;

    		var attachmentName = this.attachmentNames[frameIndex];
    		//console.log(skeleton.slots[this.slotIndex])

    		// change the name!
    	//	skeleton.slots[this.slotIndex].attachmentName = attachmentName;

    		skeleton.slots[this.slotIndex].setAttachment(!attachmentName ? null : skeleton.getAttachmentBySlotIndex(this.slotIndex, attachmentName));
    	}
    };

    spine.SkeletonData = function () {
    	this.bones = [];
    	this.slots = [];
    	this.skins = [];
    	this.animations = [];
    };
    spine.SkeletonData.prototype = {
    	defaultSkin: null,
    	/** @return May be null. */
    	findBone: function (boneName) {
    		var bones = this.bones;
    		for (var i = 0, n = bones.length; i < n; i++)
    			if (bones[i].name == boneName) return bones[i];
    		return null;
    	},
    	/** @return -1 if the bone was not found. */
    	findBoneIndex: function (boneName) {
    		var bones = this.bones;
    		for (var i = 0, n = bones.length; i < n; i++)
    			if (bones[i].name == boneName) return i;
    		return -1;
    	},
    	/** @return May be null. */
    	findSlot: function (slotName) {
    		var slots = this.slots;
    		for (var i = 0, n = slots.length; i < n; i++) {
    			if (slots[i].name == slotName) return slot[i];
    		}
    		return null;
    	},
    	/** @return -1 if the bone was not found. */
    	findSlotIndex: function (slotName) {
    		var slots = this.slots;
    		for (var i = 0, n = slots.length; i < n; i++)
    			if (slots[i].name == slotName) return i;
    		return -1;
    	},
    	/** @return May be null. */
    	findSkin: function (skinName) {
    		var skins = this.skins;
    		for (var i = 0, n = skins.length; i < n; i++)
    			if (skins[i].name == skinName) return skins[i];
    		return null;
    	},
    	/** @return May be null. */
    	findAnimation: function (animationName) {
    		var animations = this.animations;
    		for (var i = 0, n = animations.length; i < n; i++)
    			if (animations[i].name == animationName) return animations[i];
    		return null;
    	}
    };

    spine.Skeleton = function (skeletonData) {
    	this.data = skeletonData;

    	this.bones = [];
    	for (var i = 0, n = skeletonData.bones.length; i < n; i++) {
    		var boneData = skeletonData.bones[i];
    		var parent = !boneData.parent ? null : this.bones[skeletonData.bones.indexOf(boneData.parent)];
    		this.bones.push(new spine.Bone(boneData, parent));
    	}

    	this.slots = [];
    	this.drawOrder = [];
    	for (var i = 0, n = skeletonData.slots.length; i < n; i++) {
    		var slotData = skeletonData.slots[i];
    		var bone = this.bones[skeletonData.bones.indexOf(slotData.boneData)];
    		var slot = new spine.Slot(slotData, this, bone);
    		this.slots.push(slot);
    		this.drawOrder.push(slot);
    	}
    };
    spine.Skeleton.prototype = {
    	x: 0, y: 0,
    	skin: null,
    	r: 1, g: 1, b: 1, a: 1,
    	time: 0,
    	flipX: false, flipY: false,
    	/** Updates the world transform for each bone. */
    	updateWorldTransform: function () {
    		var flipX = this.flipX;
    		var flipY = this.flipY;
    		var bones = this.bones;
    		for (var i = 0, n = bones.length; i < n; i++)
    			bones[i].updateWorldTransform(flipX, flipY);
    	},
    	/** Sets the bones and slots to their setup pose values. */
    	setToSetupPose: function () {
    		this.setBonesToSetupPose();
    		this.setSlotsToSetupPose();
    	},
    	setBonesToSetupPose: function () {
    		var bones = this.bones;
    		for (var i = 0, n = bones.length; i < n; i++)
    			bones[i].setToSetupPose();
    	},
    	setSlotsToSetupPose: function () {
    		var slots = this.slots;
    		for (var i = 0, n = slots.length; i < n; i++)
    			slots[i].setToSetupPose(i);
    	},
    	/** @return May return null. */
    	getRootBone: function () {
    		return this.bones.length == 0 ? null : this.bones[0];
    	},
    	/** @return May be null. */
    	findBone: function (boneName) {
    		var bones = this.bones;
    		for (var i = 0, n = bones.length; i < n; i++)
    			if (bones[i].data.name == boneName) return bones[i];
    		return null;
    	},
    	/** @return -1 if the bone was not found. */
    	findBoneIndex: function (boneName) {
    		var bones = this.bones;
    		for (var i = 0, n = bones.length; i < n; i++)
    			if (bones[i].data.name == boneName) return i;
    		return -1;
    	},
    	/** @return May be null. */
    	findSlot: function (slotName) {
    		var slots = this.slots;
    		for (var i = 0, n = slots.length; i < n; i++)
    			if (slots[i].data.name == slotName) return slots[i];
    		return null;
    	},
    	/** @return -1 if the bone was not found. */
    	findSlotIndex: function (slotName) {
    		var slots = this.slots;
    		for (var i = 0, n = slots.length; i < n; i++)
    			if (slots[i].data.name == slotName) return i;
    		return -1;
    	},
    	setSkinByName: function (skinName) {
    		var skin = this.data.findSkin(skinName);
    		if (!skin) throw "Skin not found: " + skinName;
    		this.setSkin(skin);
    	},
    	/** Sets the skin used to look up attachments not found in the {@link SkeletonData#getDefaultSkin() default skin}. Attachments
    	 * from the new skin are attached if the corresponding attachment from the old skin was attached.
    	 * @param newSkin May be null. */
    	setSkin: function (newSkin) {
    		if (this.skin && newSkin) newSkin._attachAll(this, this.skin);
    		this.skin = newSkin;
    	},
    	/** @return May be null. */
    	getAttachmentBySlotName: function (slotName, attachmentName) {
    		return this.getAttachmentBySlotIndex(this.data.findSlotIndex(slotName), attachmentName);
    	},
    	/** @return May be null. */
    	getAttachmentBySlotIndex: function (slotIndex, attachmentName) {
    		if (this.skin) {
    			var attachment = this.skin.getAttachment(slotIndex, attachmentName);
    			if (attachment) return attachment;
    		}
    		if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName);
    		return null;
    	},
    	/** @param attachmentName May be null. */
    	setAttachment: function (slotName, attachmentName) {
    		var slots = this.slots;
    		for (var i = 0, n = slots.size; i < n; i++) {
    			var slot = slots[i];
    			if (slot.data.name == slotName) {
    				var attachment = null;
    				if (attachmentName) {

    					attachment = this.getAttachment(i, attachmentName);
    					if (attachment == null) throw "Attachment not found: " + attachmentName + ", for slot: " + slotName;
    				}

    				slot.setAttachment(attachment);
    				return;
    			}
    		}
    		throw "Slot not found: " + slotName;
    	},
    	update: function (delta) {
    		time += delta;
    	}
    };

    spine.AttachmentType = {
    	region: 0
    };

    spine.RegionAttachment = function () {
    	this.offset = [];
    	this.offset.length = 8;
    	this.uvs = [];
    	this.uvs.length = 8;
    };
    spine.RegionAttachment.prototype = {
    	x: 0, y: 0,
    	rotation: 0,
    	scaleX: 1, scaleY: 1,
    	width: 0, height: 0,
    	rendererObject: null,
    	regionOffsetX: 0, regionOffsetY: 0,
    	regionWidth: 0, regionHeight: 0,
    	regionOriginalWidth: 0, regionOriginalHeight: 0,
    	setUVs: function (u, v, u2, v2, rotate) {
    		var uvs = this.uvs;
    		if (rotate) {
    			uvs[2/*X2*/] = u;
    			uvs[3/*Y2*/] = v2;
    			uvs[4/*X3*/] = u;
    			uvs[5/*Y3*/] = v;
    			uvs[6/*X4*/] = u2;
    			uvs[7/*Y4*/] = v;
    			uvs[0/*X1*/] = u2;
    			uvs[1/*Y1*/] = v2;
    		} else {
    			uvs[0/*X1*/] = u;
    			uvs[1/*Y1*/] = v2;
    			uvs[2/*X2*/] = u;
    			uvs[3/*Y2*/] = v;
    			uvs[4/*X3*/] = u2;
    			uvs[5/*Y3*/] = v;
    			uvs[6/*X4*/] = u2;
    			uvs[7/*Y4*/] = v2;
    		}
    	},
    	updateOffset: function () {
    		var regionScaleX = this.width / this.regionOriginalWidth * this.scaleX;
    		var regionScaleY = this.height / this.regionOriginalHeight * this.scaleY;
    		var localX = -this.width / 2 * this.scaleX + this.regionOffsetX * regionScaleX;
    		var localY = -this.height / 2 * this.scaleY + this.regionOffsetY * regionScaleY;
    		var localX2 = localX + this.regionWidth * regionScaleX;
    		var localY2 = localY + this.regionHeight * regionScaleY;
    		var radians = this.rotation * Math.PI / 180;
    		var cos = Math.cos(radians);
    		var sin = Math.sin(radians);
    		var localXCos = localX * cos + this.x;
    		var localXSin = localX * sin;
    		var localYCos = localY * cos + this.y;
    		var localYSin = localY * sin;
    		var localX2Cos = localX2 * cos + this.x;
    		var localX2Sin = localX2 * sin;
    		var localY2Cos = localY2 * cos + this.y;
    		var localY2Sin = localY2 * sin;
    		var offset = this.offset;
    		offset[0/*X1*/] = localXCos - localYSin;
    		offset[1/*Y1*/] = localYCos + localXSin;
    		offset[2/*X2*/] = localXCos - localY2Sin;
    		offset[3/*Y2*/] = localY2Cos + localXSin;
    		offset[4/*X3*/] = localX2Cos - localY2Sin;
    		offset[5/*Y3*/] = localY2Cos + localX2Sin;
    		offset[6/*X4*/] = localX2Cos - localYSin;
    		offset[7/*Y4*/] = localYCos + localX2Sin;
    	},
    	computeVertices: function (x, y, bone, vertices) {

    		x += bone.worldX;
    		y += bone.worldY;
    		var m00 = bone.m00;
    		var m01 = bone.m01;
    		var m10 = bone.m10;
    		var m11 = bone.m11;
    		var offset = this.offset;
    		vertices[0/*X1*/] = offset[0/*X1*/] * m00 + offset[1/*Y1*/] * m01 + x;
    		vertices[1/*Y1*/] = offset[0/*X1*/] * m10 + offset[1/*Y1*/] * m11 + y;
    		vertices[2/*X2*/] = offset[2/*X2*/] * m00 + offset[3/*Y2*/] * m01 + x;
    		vertices[3/*Y2*/] = offset[2/*X2*/] * m10 + offset[3/*Y2*/] * m11 + y;
    		vertices[4/*X3*/] = offset[4/*X3*/] * m00 + offset[5/*X3*/] * m01 + x;
    		vertices[5/*X3*/] = offset[4/*X3*/] * m10 + offset[5/*X3*/] * m11 + y;
    		vertices[6/*X4*/] = offset[6/*X4*/] * m00 + offset[7/*Y4*/] * m01 + x;
    		vertices[7/*Y4*/] = offset[6/*X4*/] * m10 + offset[7/*Y4*/] * m11 + y;
    	}
    }

    spine.AnimationStateData = function (skeletonData) {
    	this.skeletonData = skeletonData;
    	this.animationToMixTime = {};
    };
    spine.AnimationStateData.prototype = {
    	setMixByName: function (fromName, toName, duration) {
    		var from = this.skeletonData.findAnimation(fromName);
    		if (!from) throw "Animation not found: " + fromName;
    		var to = this.skeletonData.findAnimation(toName);
    		if (!to) throw "Animation not found: " + toName;
    		this.setMix(from, to, duration);
    	},
    	setMix: function (from, to, duration) {
    		this.animationToMixTime[from.name + ":" + to.name] = duration;
    	},
    	getMix: function (from, to) {
    		var time = this.animationToMixTime[from.name + ":" + to.name];
    		return time ? time : 0;
    	}
    };

    spine.AnimationState = function (stateData) {
    	this.data = stateData;
    	this.queue = [];
    };
    spine.AnimationState.prototype = {
    	current: null,
    	previous: null,
    	currentTime: 0,
    	previousTime: 0,
    	currentLoop: false,
    	previousLoop: false,
    	mixTime: 0,
    	mixDuration: 0,
    	update: function (delta) {
    		this.currentTime += delta;
    		this.previousTime += delta;
    		this.mixTime += delta;

    		if (this.queue.length > 0) {
    			var entry = this.queue[0];
    			if (this.currentTime >= entry.delay) {
    				this._setAnimation(entry.animation, entry.loop);
    				this.queue.shift();
    			}
    		}
    	},
    	apply: function (skeleton) {
    		if (!this.current) return;
    		if (this.previous) {
    			this.previous.apply(skeleton, this.previousTime, this.previousLoop);
    			var alpha = this.mixTime / this.mixDuration;
    			if (alpha >= 1) {
    				alpha = 1;
    				this.previous = null;
    			}
    			this.current.mix(skeleton, this.currentTime, this.currentLoop, alpha);
    		} else
    			this.current.apply(skeleton, this.currentTime, this.currentLoop);
    	},
    	clearAnimation: function () {
    		this.previous = null;
    		this.current = null;
    		this.queue.length = 0;
    	},
    	_setAnimation: function (animation, loop) {
    		this.previous = null;
    		if (animation && this.current) {
    			this.mixDuration = this.data.getMix(this.current, animation);
    			if (this.mixDuration > 0) {
    				this.mixTime = 0;
    				this.previous = this.current;
    				this.previousTime = this.currentTime;
    				this.previousLoop = this.currentLoop;
    			}
    		}
    		this.current = animation;
    		this.currentLoop = loop;
    		this.currentTime = 0;
    	},
    	/** @see #setAnimation(Animation, Boolean) */
    	setAnimationByName: function (animationName, loop) {
    		var animation = this.data.skeletonData.findAnimation(animationName);
    		if (!animation) throw "Animation not found: " + animationName;
    		this.setAnimation(animation, loop);
    	},
    	/** Set the current animation. Any queued animations are cleared and the current animation time is set to 0.
    	 * @param animation May be null. */
    	setAnimation: function (animation, loop) {
    		this.queue.length = 0;
    		this._setAnimation(animation, loop);
    	},
    	/** @see #addAnimation(Animation, Boolean, Number) */
    	addAnimationByName: function (animationName, loop, delay) {
    		var animation = this.data.skeletonData.findAnimation(animationName);
    		if (!animation) throw "Animation not found: " + animationName;
    		this.addAnimation(animation, loop, delay);
    	},
    	/** Adds an animation to be played delay seconds after the current or last queued animation.
    	 * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */
    	addAnimation: function (animation, loop, delay) {
    		var entry = {};
    		entry.animation = animation;
    		entry.loop = loop;

    		if (!delay || delay <= 0) {
    			var previousAnimation = this.queue.length == 0 ? this.current : this.queue[this.queue.length - 1].animation;
    			if (previousAnimation != null)
    				delay = previousAnimation.duration - this.data.getMix(previousAnimation, animation) + (delay || 0);
    			else
    				delay = 0;
    		}
    		entry.delay = delay;

    		this.queue.push(entry);
    	},
    	/** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */
    	isComplete: function () {
    		return !this.current || this.currentTime >= this.current.duration;
    	}
    };

    spine.SkeletonJson = function (attachmentLoader) {
    	this.attachmentLoader = attachmentLoader;
    };
    spine.SkeletonJson.prototype = {
    	scale: 1,
    	readSkeletonData: function (root) {
    		var skeletonData = new spine.SkeletonData();

    		// Bones.
    		var bones = root["bones"];
    		for (var i = 0, n = bones.length; i < n; i++) {
    			var boneMap = bones[i];
    			var parent = null;
    			if (boneMap["parent"]) {
    				parent = skeletonData.findBone(boneMap["parent"]);
    				if (!parent) throw "Parent bone not found: " + boneMap["parent"];
    			}
    			var boneData = new spine.BoneData(boneMap["name"], parent);
    			boneData.length = (boneMap["length"] || 0) * this.scale;
    			boneData.x = (boneMap["x"] || 0) * this.scale;
    			boneData.y = (boneMap["y"] || 0) * this.scale;
    			boneData.rotation = (boneMap["rotation"] || 0);
    			boneData.scaleX = boneMap["scaleX"] || 1;
    			boneData.scaleY = boneMap["scaleY"] || 1;
    			skeletonData.bones.push(boneData);
    		}

    		// Slots.
    		var slots = root["slots"];
    		for (var i = 0, n = slots.length; i < n; i++) {
    			var slotMap = slots[i];
    			var boneData = skeletonData.findBone(slotMap["bone"]);
    			if (!boneData) throw "Slot bone not found: " + slotMap["bone"];
    			var slotData = new spine.SlotData(slotMap["name"], boneData);

    			var color = slotMap["color"];
    			if (color) {
    				slotData.r = spine.SkeletonJson.toColor(color, 0);
    				slotData.g = spine.SkeletonJson.toColor(color, 1);
    				slotData.b = spine.SkeletonJson.toColor(color, 2);
    				slotData.a = spine.SkeletonJson.toColor(color, 3);
    			}

    			slotData.attachmentName = slotMap["attachment"];

    			skeletonData.slots.push(slotData);
    		}

    		// Skins.
    		var skins = root["skins"];
    		for (var skinName in skins) {
    			if (!skins.hasOwnProperty(skinName)) continue;
    			var skinMap = skins[skinName];
    			var skin = new spine.Skin(skinName);
    			for (var slotName in skinMap) {
    				if (!skinMap.hasOwnProperty(slotName)) continue;
    				var slotIndex = skeletonData.findSlotIndex(slotName);
    				var slotEntry = skinMap[slotName];
    				for (var attachmentName in slotEntry) {
    					if (!slotEntry.hasOwnProperty(attachmentName)) continue;
    					var attachment = this.readAttachment(skin, attachmentName, slotEntry[attachmentName]);
    					if (attachment != null) skin.addAttachment(slotIndex, attachmentName, attachment);
    				}
    			}
    			skeletonData.skins.push(skin);
    			if (skin.name == "default") skeletonData.defaultSkin = skin;
    		}

    		// Animations.
    		var animations = root["animations"];
    		for (var animationName in animations) {
    			if (!animations.hasOwnProperty(animationName)) continue;
    			this.readAnimation(animationName, animations[animationName], skeletonData);
    		}

    		return skeletonData;
    	},
    	readAttachment: function (skin, name, map) {
    		name = map["name"] || name;

    		var type = spine.AttachmentType[map["type"] || "region"];

    		// @ekelokorpi
    		// var attachment = this.attachmentLoader.newAttachment(skin, type, name);
    		var attachment = new spine.RegionAttachment();

    		// @Doormat23
    		// add the name of the attachment
    		attachment.name = name;

    		if (type == spine.AttachmentType.region) {
    			attachment.x = (map["x"] || 0) * this.scale;
    			attachment.y = (map["y"] || 0) * this.scale;
    			attachment.scaleX = map["scaleX"] || 1;
    			attachment.scaleY = map["scaleY"] || 1;
    			attachment.rotation = map["rotation"] || 0;
    			attachment.width = (map["width"] || 32) * this.scale;
    			attachment.height = (map["height"] || 32) * this.scale;
    			attachment.updateOffset();
    		}

    		return attachment;
    	},
    	readAnimation: function (name, map, skeletonData) {
    		var timelines = [];
    		var duration = 0;

    		var bones = map["bones"];
    		for (var boneName in bones) {
    			if (!bones.hasOwnProperty(boneName)) continue;
    			var boneIndex = skeletonData.findBoneIndex(boneName);
    			if (boneIndex == -1) throw "Bone not found: " + boneName;
    			var boneMap = bones[boneName];

    			for (var timelineName in boneMap) {
    				if (!boneMap.hasOwnProperty(timelineName)) continue;
    				var values = boneMap[timelineName];
    				if (timelineName == "rotate") {
    					var timeline = new spine.RotateTimeline(values.length);
    					timeline.boneIndex = boneIndex;

    					var frameIndex = 0;
    					for (var i = 0, n = values.length; i < n; i++) {
    						var valueMap = values[i];
    						timeline.setFrame(frameIndex, valueMap["time"], valueMap["angle"]);
    						spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap);
    						frameIndex++;
    					}
    					timelines.push(timeline);
    					duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]);

    				} else if (timelineName == "translate" || timelineName == "scale") {
    					var timeline;
    					var timelineScale = 1;
    					if (timelineName == "scale")
    						timeline = new spine.ScaleTimeline(values.length);
    					else {
    						timeline = new spine.TranslateTimeline(values.length);
    						timelineScale = this.scale;
    					}
    					timeline.boneIndex = boneIndex;

    					var frameIndex = 0;
    					for (var i = 0, n = values.length; i < n; i++) {
    						var valueMap = values[i];
    						var x = (valueMap["x"] || 0) * timelineScale;
    						var y = (valueMap["y"] || 0) * timelineScale;
    						timeline.setFrame(frameIndex, valueMap["time"], x, y);
    						spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap);
    						frameIndex++;
    					}
    					timelines.push(timeline);
    					duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]);

    				} else
    					throw "Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")";
    			}
    		}
    		var slots = map["slots"];
    		for (var slotName in slots) {
    			if (!slots.hasOwnProperty(slotName)) continue;
    			var slotMap = slots[slotName];
    			var slotIndex = skeletonData.findSlotIndex(slotName);

    			for (var timelineName in slotMap) {
    				if (!slotMap.hasOwnProperty(timelineName)) continue;
    				var values = slotMap[timelineName];
    				if (timelineName == "color") {
    					var timeline = new spine.ColorTimeline(values.length);
    					timeline.slotIndex = slotIndex;

    					var frameIndex = 0;
    					for (var i = 0, n = values.length; i < n; i++) {
    						var valueMap = values[i];
    						var color = valueMap["color"];
    						var r = spine.SkeletonJson.toColor(color, 0);
    						var g = spine.SkeletonJson.toColor(color, 1);
    						var b = spine.SkeletonJson.toColor(color, 2);
    						var a = spine.SkeletonJson.toColor(color, 3);
    						timeline.setFrame(frameIndex, valueMap["time"], r, g, b, a);
    						spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap);
    						frameIndex++;
    					}
    					timelines.push(timeline);
    					duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 5 - 5]);

    				} else if (timelineName == "attachment") {
    					var timeline = new spine.AttachmentTimeline(values.length);
    					timeline.slotIndex = slotIndex;

    					var frameIndex = 0;
    					for (var i = 0, n = values.length; i < n; i++) {
    						var valueMap = values[i];
    						timeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]);
    					}
    					timelines.push(timeline);
    					// PIXI FIX
    					duration = Math.max(duration, timeline.frames[Math.floor(timeline.getFrameCount()) - 1]);
    				} else
    					throw "Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")";
    			}
    		}
    		skeletonData.animations.push(new spine.Animation(name, timelines, duration));
    	}
    };
    spine.SkeletonJson.readCurve = function (timeline, frameIndex, valueMap) {
    	var curve = valueMap["curve"];
    	if (!curve) return;
    	if (curve == "stepped")
    		timeline.curves.setStepped(frameIndex);
    	else if (curve instanceof Array)
    		timeline.curves.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]);
    };
    spine.SkeletonJson.toColor = function (hexString, colorIndex) {
    	if (hexString.length != 8) throw "Color hexidecimal length must be 8, recieved: " + hexString;
    	return parseInt(hexString.substring(colorIndex * 2, 2), 16) / 255;
    };

    spine.Atlas = function (atlasText, textureLoader) {
    	this.textureLoader = textureLoader;
    	this.pages = [];
    	this.regions = [];

    	var reader = new spine.AtlasReader(atlasText);
    	var tuple = [];
    	tuple.length = 4;
    	var page = null;
    	while (true) {
    		var line = reader.readLine();
    		if (line == null) break;
    		line = reader.trim(line);
    		if (line.length == 0)
    			page = null;
    		else if (!page) {
    			page = new spine.AtlasPage();
    			page.name = line;

    			page.format = spine.Atlas.Format[reader.readValue()];

    			reader.readTuple(tuple);
    			page.minFilter = spine.Atlas.TextureFilter[tuple[0]];
    			page.magFilter = spine.Atlas.TextureFilter[tuple[1]];

    			var direction = reader.readValue();
    			page.uWrap = spine.Atlas.TextureWrap.clampToEdge;
    			page.vWrap = spine.Atlas.TextureWrap.clampToEdge;
    			if (direction == "x")
    				page.uWrap = spine.Atlas.TextureWrap.repeat;
    			else if (direction == "y")
    				page.vWrap = spine.Atlas.TextureWrap.repeat;
    			else if (direction == "xy")
    				page.uWrap = page.vWrap = spine.Atlas.TextureWrap.repeat;

    			textureLoader.load(page, line);

    			this.pages.push(page);

    		} else {
    			var region = new spine.AtlasRegion();
    			region.name = line;
    			region.page = page;

    			region.rotate = reader.readValue() == "true";

    			reader.readTuple(tuple);
    			var x = parseInt(tuple[0]);
    			var y = parseInt(tuple[1]);

    			reader.readTuple(tuple);
    			var width = parseInt(tuple[0]);
    			var height = parseInt(tuple[1]);

    			region.u = x / page.width;
    			region.v = y / page.height;
    			if (region.rotate) {
    				region.u2 = (x + height) / page.width;
    				region.v2 = (y + width) / page.height;
    			} else {
    				region.u2 = (x + width) / page.width;
    				region.v2 = (y + height) / page.height;
    			}
    			region.x = x;
    			region.y = y;
    			region.width = Math.abs(width);
    			region.height = Math.abs(height);

    			if (reader.readTuple(tuple) == 4) { // split is optional
    				region.splits = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])];

    				if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits
    					region.pads = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])];

    					reader.readTuple(tuple);
    				}
    			}

    			region.originalWidth = parseInt(tuple[0]);
    			region.originalHeight = parseInt(tuple[1]);

    			reader.readTuple(tuple);
    			region.offsetX = parseInt(tuple[0]);
    			region.offsetY = parseInt(tuple[1]);

    			region.index = parseInt(reader.readValue());

    			this.regions.push(region);
    		}
    	}
    };
    spine.Atlas.prototype = {
    	findRegion: function (name) {
    		var regions = this.regions;
    		for (var i = 0, n = regions.length; i < n; i++)
    			if (regions[i].name == name) return regions[i];
    		return null;
    	},
    	dispose: function () {
    		var pages = this.pages;
    		for (var i = 0, n = pages.length; i < n; i++)
    			this.textureLoader.unload(pages[i].rendererObject);
    	},
    	updateUVs: function (page) {
    		var regions = this.regions;
    		for (var i = 0, n = regions.length; i < n; i++) {
    			var region = regions[i];
    			if (region.page != page) continue;
    			region.u = region.x / page.width;
    			region.v = region.y / page.height;
    			if (region.rotate) {
    				region.u2 = (region.x + region.height) / page.width;
    				region.v2 = (region.y + region.width) / page.height;
    			} else {
    				region.u2 = (region.x + region.width) / page.width;
    				region.v2 = (region.y + region.height) / page.height;
    			}
    		}
    	}
    };

    spine.Atlas.Format = {
    	alpha: 0,
    	intensity: 1,
    	luminanceAlpha: 2,
    	rgb565: 3,
    	rgba4444: 4,
    	rgb888: 5,
    	rgba8888: 6
    };

    spine.Atlas.TextureFilter = {
    	nearest: 0,
    	linear: 1,
    	mipMap: 2,
    	mipMapNearestNearest: 3,
    	mipMapLinearNearest: 4,
    	mipMapNearestLinear: 5,
    	mipMapLinearLinear: 6
    };

    spine.Atlas.TextureWrap = {
    	mirroredRepeat: 0,
    	clampToEdge: 1,
    	repeat: 2
    };

    spine.AtlasPage = function () {};
    spine.AtlasPage.prototype = {
    	name: null,
    	format: null,
    	minFilter: null,
    	magFilter: null,
    	uWrap: null,
    	vWrap: null,
    	rendererObject: null,
    	width: 0,
    	height: 0
    };

    spine.AtlasRegion = function () {};
    spine.AtlasRegion.prototype = {
    	page: null,
    	name: null,
    	x: 0, y: 0,
    	width: 0, height: 0,
    	u: 0, v: 0, u2: 0, v2: 0,
    	offsetX: 0, offsetY: 0,
    	originalWidth: 0, originalHeight: 0,
    	index: 0,
    	rotate: false,
    	splits: null,
    	pads: null,
    };

    spine.AtlasReader = function (text) {
    	this.lines = text.split(/\r\n|\r|\n/);
    };
    spine.AtlasReader.prototype = {
    	index: 0,
    	trim: function (value) {
    		return value.replace(/^\s+|\s+$/g, "");
    	},
    	readLine: function () {
    		if (this.index >= this.lines.length) return null;
    		return this.lines[this.index++];
    	},
    	readValue: function () {
    		var line = this.readLine();
    		var colon = line.indexOf(":");
    		if (colon == -1) throw "Invalid line: " + line;
    		return this.trim(line.substring(colon + 1));
    	},
    	/** Returns the number of tuple values read (2 or 4). */
    	readTuple: function (tuple) {
    		var line = this.readLine();
    		var colon = line.indexOf(":");
    		if (colon == -1) throw "Invalid line: " + line;
    		var i = 0, lastMatch= colon + 1;
    		for (; i < 3; i++) {
    			var comma = line.indexOf(",", lastMatch);
    			if (comma == -1) {
    				if (i == 0) throw "Invalid line: " + line;
    				break;
    			}
    			tuple[i] = this.trim(line.substr(lastMatch, comma - lastMatch));
    			lastMatch = comma + 1;
    		}
    		tuple[i] = this.trim(line.substring(lastMatch));
    		return i + 1;
    	}
    }

    spine.AtlasAttachmentLoader = function (atlas) {
    	this.atlas = atlas;
    }
    spine.AtlasAttachmentLoader.prototype = {
    	newAttachment: function (skin, type, name) {
    		switch (type) {
    		case spine.AttachmentType.region:
    			var region = this.atlas.findRegion(name);
    			if (!region) throw "Region not found in atlas: " + name + " (" + type + ")";
    			var attachment = new spine.RegionAttachment(name);
    			attachment.rendererObject = region;
    			attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate);
    			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;
    		}
    		throw "Unknown attachment type: " + type;
    	}
    }

    PIXI.AnimCache = {};
    spine.Bone.yDown = true;