From 2faa06c71d3c13b21f82561a290fa02468fc5c5a Mon Sep 17 00:00:00 2001 From: Mat Groves Date: Wed, 12 Jun 2013 12:55:47 +0100 Subject: [PATCH] Spine Example updates Fix merge issue Updated spine examples --- bin/pixi.dev.js | 1695 ++++++++++++++++- bin/pixi.js | 7 +- examples/example 1 - Basics/pixi.js | 1695 ++++++++++++++++- examples/example 10 - Text/pixi.js | 1695 ++++++++++++++++- examples/example 11 - RenderTexture/pixi.js | 1695 ++++++++++++++++- .../data/dragonBonesData.json | 779 ++++++++ .../example 12 - Spine/data/iP4_BGtile.jpg | Bin 0 -> 172812 bytes .../example 12 - Spine/data/iP4_ground.jpg | Bin 0 -> 52005 bytes examples/example 12 - Spine/data/powerUp.json | 36 - examples/example 12 - Spine/data/powerUp.png | Bin 56594 -> 0 bytes .../{spineboy.anim => spineboySpineData.json} | 0 examples/example 12 - Spine/index.html | 8 +- examples/example 12 - Spine/index_pixie.html | 20 +- examples/example 12 - Spine/pixi.js | 77 +- examples/example 2 - SpriteSheet/pixi.js | 1695 ++++++++++++++++- examples/example 3 - MovieClip/pixi.js | 1695 ++++++++++++++++- examples/example 4 - Balls/pixi.js | 1695 ++++++++++++++++- examples/example 5 - Morph/pixi.js | 1695 ++++++++++++++++- examples/example 6 - Interactivity/pixi.js | 1695 ++++++++++++++++- .../pixi.js | 1695 ++++++++++++++++- examples/example 8 - Dragging/pixi.js | 1695 ++++++++++++++++- examples/example 9 - Tiling Texture/pixi.js | 1695 ++++++++++++++++- src/pixi/renderers/WebGLRenderGroup.js | 5 +- 23 files changed, 21140 insertions(+), 132 deletions(-) create mode 100644 examples/example 12 - Spine/data/dragonBonesData.json create mode 100644 examples/example 12 - Spine/data/iP4_BGtile.jpg create mode 100644 examples/example 12 - Spine/data/iP4_ground.jpg delete mode 100644 examples/example 12 - Spine/data/powerUp.json delete mode 100644 examples/example 12 - Spine/data/powerUp.png rename examples/example 12 - Spine/data/{spineboy.anim => spineboySpineData.json} (100%) diff --git a/bin/pixi.dev.js b/bin/pixi.dev.js index 2028846..c7005f9 100644 --- a/bin/pixi.dev.js +++ b/bin/pixi.dev.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/bin/pixi.js b/bin/pixi.js index 63e46d9..fe824fa 100644 --- a/bin/pixi.js +++ b/bin/pixi.js @@ -4,10 +4,11 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php */ -(function(){function t(t){return[(255&t>>16)/255,(255&t>>8)/255,(255&t)/255]}function e(){return r.Matrix="undefined"!=typeof Float32Array?Float32Array:Array,r.Matrix}var i=this,r=r||{};r.Point=function(t,e){this.x=t||0,this.y=e||0},r.Point.prototype.clone=function(){return new r.Point(this.x,this.y)},r.Point.constructor=r.Point,r.Rectangle=function(t,e,i,r){this.x=t||0,this.y=e||0,this.width=i||0,this.height=r||0},r.Rectangle.prototype.clone=function(){return new r.Rectangle(this.x,this.y,this.width,this.height)},r.Rectangle.constructor=r.Rectangle,r.DisplayObject=function(){this.position=new r.Point,this.scale=new r.Point(1,1),this.pivot=new r.Point(0,0),this.rotation=0,this.alpha=1,this.visible=!0,this.worldVisible=!1,this.parent=null,this.stage=null,this.hitArea=null,this.worldAlpha=1,this.color=[],this.worldTransform=r.mat3.create(),this.localTransform=r.mat3.create(),this.dynamic=!0,this._sr=0,this._cr=1,this.childIndex=0,this.renderable=!1,this.interactive=!1,this.buttonMode=!1},r.DisplayObject.constructor=r.DisplayObject,r.DisplayObject.prototype.setInteractive=function(t){this.interactive=t,this.stage&&(this.stage.dirty=!0)},r.DisplayObject.prototype.updateTransform=function(){this.rotation!=this.rotationCache&&(this.rotationCache=this.rotation,this._sr=Math.sin(this.rotation),this._cr=Math.cos(this.rotation));var t=this.localTransform,e=this.parent.worldTransform,i=this.worldTransform;t[0]=this._cr*this.scale.x,t[1]=-this._sr*this.scale.y,t[3]=this._sr*this.scale.x,t[4]=this._cr*this.scale.y;var r=this.pivot.x,s=this.pivot.y;t[2]=this.position.x-t[0]*r-s*t[1],t[5]=this.position.y-t[4]*s-r*t[3];var n=t[0],o=t[1],a=t[2],h=t[3],u=t[4],d=t[5],c=e[0],l=e[1],p=e[2],f=e[3],x=e[4],v=e[5];i[0]=c*n+l*h,i[1]=c*o+l*u,i[2]=c*a+l*d+p,i[3]=f*n+x*h,i[4]=f*o+x*u,i[5]=f*a+x*d+v,this.worldAlpha=this.alpha*this.parent.worldAlpha},r.DisplayObjectContainer=function(){r.DisplayObject.call(this),this.children=[],this.renderable=!1},r.DisplayObjectContainer.constructor=r.DisplayObjectContainer,r.DisplayObjectContainer.prototype=Object.create(r.DisplayObject.prototype),r.DisplayObjectContainer.prototype.addChild=function(t){void 0!=t.parent&&t.parent.removeChild(t),t.parent=this,t.childIndex=this.children.length,this.children.push(t),this.stage&&this.stage.__addChild(t),this.__renderGroup&&(t.__renderGroup&&t.__renderGroup.removeDisplayObjectAndChildren(t),this.__renderGroup.addDisplayObjectAndChildren(t))},r.DisplayObjectContainer.prototype.addChildAt=function(t,e){if(!(e>=0&&this.children.length>=e))throw Error(t+" The index "+e+" supplied is out of bounds "+this.children.length);void 0!=t.parent&&t.parent.removeChild(t),e==this.children.length?this.children.push(t):this.children.splice(e,0,t),t.parent=this,t.childIndex=e;for(var i=this.children.length,r=e;i>r;r++)this.children[r].childIndex=r;this.stage&&this.stage.__addChild(t),this.__renderGroup&&(t.__renderGroup&&t.__renderGroup.removeDisplayObjectAndChildren(t),this.__renderGroup.addDisplayObjectAndChildren(t))},r.DisplayObjectContainer.prototype.swapChildren=function(t,e){var i=this.children.indexOf(t),r=this.children.indexOf(e);if(-1===i||-1===r)throw Error(t+" Both the supplied DisplayObjects must be a child of the caller "+this);this.stage&&(this.stage.__removeChild(t),this.stage.__removeChild(e),this.stage.__addChild(t),this.stage.__addChild(e)),t.childIndex=r,e.childIndex=i,this.children[i]=e,this.children[r]=t},r.DisplayObjectContainer.prototype.getChildAt=function(t){if(t>=0&&this.children.length>t)return this.children[t];throw Error(child+" Both the supplied DisplayObjects must be a child of the caller "+this)},r.DisplayObjectContainer.prototype.removeChild=function(t){var e=this.children.indexOf(t);if(-1===e)throw Error(t+" The supplied DisplayObject must be a child of the caller "+this);this.stage&&this.stage.__removeChild(t),t.__renderGroup&&t.__renderGroup.removeDisplayObjectAndChildren(t),t.parent=void 0,this.children.splice(e,1);for(var i=e,r=this.children.length;r>i;i++)this.children[i].childIndex-=1},r.DisplayObjectContainer.prototype.updateTransform=function(){if(this.visible){r.DisplayObject.prototype.updateTransform.call(this);for(var t=0,e=this.children.length;e>t;t++)this.children[t].updateTransform()}},r.blendModes={},r.blendModes.NORMAL=0,r.blendModes.SCREEN=1,r.Sprite=function(t){r.DisplayObjectContainer.call(this),this.anchor=new r.Point,this.texture=t,this.blendMode=r.blendModes.NORMAL,this._width=0,this._height=0,t.baseTexture.hasLoaded?this.updateFrame=!0:(this.onTextureUpdateBind=this.onTextureUpdate.bind(this),this.texture.addEventListener("update",this.onTextureUpdateBind)),this.renderable=!0},r.Sprite.constructor=r.Sprite,r.Sprite.prototype=Object.create(r.DisplayObjectContainer.prototype),Object.defineProperty(r.Sprite.prototype,"width",{get:function(){return this.scale.x*this.texture.frame.width},set:function(t){this.scale.x=t/this.texture.frame.width,this._width=t}}),Object.defineProperty(r.Sprite.prototype,"height",{get:function(){return this.scale.y*this.texture.frame.height},set:function(t){this.scale.y=t/this.texture.frame.height,this._height=t}}),r.Sprite.prototype.setTexture=function(t){this.texture.baseTexture!=t.baseTexture&&(this.textureChange=!0),this.texture=t,this.updateFrame=!0},r.Sprite.prototype.onTextureUpdate=function(){this._width&&(this.scale.x=this._width/this.texture.frame.width),this._height&&(this.scale.y=this._height/this.texture.frame.height),this.updateFrame=!0},r.Sprite.fromFrame=function(t){var e=r.TextureCache[t];if(!e)throw Error("The frameId '"+t+"' does not exist in the texture cache"+this);return new r.Sprite(e)},r.Sprite.fromImage=function(t){var e=r.Texture.fromImage(t);return new r.Sprite(e)},r.MovieClip=function(t){r.Sprite.call(this,t[0]),this.textures=t,this.currentFrame=0,this.animationSpeed=1,this.loop=!0,this.onComplete=null,this.playing},r.MovieClip.constructor=r.MovieClip,r.MovieClip.prototype=Object.create(r.Sprite.prototype),r.MovieClip.prototype.stop=function(){this.playing=!1},r.MovieClip.prototype.play=function(){this.playing=!0},r.MovieClip.prototype.gotoAndStop=function(t){this.playing=!1,this.currentFrame=t;var e=0|this.currentFrame+.5;this.setTexture(this.textures[e%this.textures.length])},r.MovieClip.prototype.gotoAndPlay=function(t){this.currentFrame=t,this.playing=!0},r.MovieClip.prototype.updateTransform=function(){if(r.Sprite.prototype.updateTransform.call(this),this.playing){this.currentFrame+=this.animationSpeed;var t=0|this.currentFrame+.5;this.loop||this.textures.length>t?this.setTexture(this.textures[t%this.textures.length]):t>=this.textures.length&&(this.gotoAndStop(this.textures.length-1),this.onComplete&&this.onComplete())}},r.Text=function(t,e){this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),r.Sprite.call(this,r.Texture.fromCanvas(this.canvas)),this.setText(t),this.setStyle(e),this.updateText(),this.dirty=!1},r.Text.constructor=r.Text,r.Text.prototype=Object.create(r.Sprite.prototype),r.Text.prototype.setStyle=function(t){t=t||{},t.font=t.font||"bold 20pt Arial",t.fill=t.fill||"black",t.align=t.align||"left",t.strokeThickness=t.strokeThickness||0,t.wordWrap=t.wordWrap||!1,t.wordWrapWidth=t.wordWrapWidth||100,this.style=t,this.dirty=!0},r.Sprite.prototype.setText=function(t){this.text=""+t||" ",this.dirty=!0},r.Text.prototype.updateText=function(){this.context.font=this.style.font;var t=this.text;this.style.wordWrap&&(t=this.wordWrap(this.text));for(var e=t.split(/(?:\r\n|\r|\n)/),i=[],s=0,n=0;e.length>n;n++){var o=this.context.measureText(e[n]).width;i[n]=o,s=Math.max(s,o)}this.canvas.width=s+this.style.strokeThickness;var a=this.determineFontHeight("font: "+this.style.font+";")+this.style.strokeThickness;for(this.canvas.height=a*e.length,this.context.fillStyle=this.style.fill,this.context.font=this.style.font,this.context.strokeStyle=this.style.stroke,this.context.lineWidth=this.style.strokeThickness,this.context.textBaseline="top",n=0;e.length>n;n++){var h=new r.Point(this.style.strokeThickness/2,this.style.strokeThickness/2+n*a);"right"==this.style.align?h.x+=s-i[n]:"center"==this.style.align&&(h.x+=(s-i[n])/2),this.style.stroke&&this.style.strokeThickness&&this.context.strokeText(e[n],h.x,h.y),this.style.fill&&this.context.fillText(e[n],h.x,h.y)}this.updateTexture()},r.Text.prototype.updateTexture=function(){this.texture.baseTexture.width=this.canvas.width,this.texture.baseTexture.height=this.canvas.height,this.texture.frame.width=this.canvas.width,this.texture.frame.height=this.canvas.height,this._width=this.canvas.width,this._height=this.canvas.height,r.texturesToUpdate.push(this.texture.baseTexture)},r.Text.prototype.updateTransform=function(){this.dirty&&(this.updateText(),this.dirty=!1),r.Sprite.prototype.updateTransform.call(this)},r.Text.prototype.determineFontHeight=function(t){var e=r.Text.heightCache[t];if(!e){var i=document.getElementsByTagName("body")[0],s=document.createElement("div"),n=document.createTextNode("M");s.appendChild(n),s.setAttribute("style",t),i.appendChild(s),e=s.offsetHeight,r.Text.heightCache[t]=e,i.removeChild(s)}return e},r.Text.prototype.wordWrap=function(t){for(var e=function(t,e,i,r,s){var n=Math.floor((r-i)/2)+i;return n==i?1:s>=t.measureText(e.substring(0,n)).width?t.measureText(e.substring(0,n+1)).width>s?n:arguments.callee(t,e,n,r,s):arguments.callee(t,e,i,n,s)},i=function(t,i,r){if(r>=t.measureText(i).width||1>i.length)return i;var s=e(t,i,0,i.length,r);return i.substring(0,s)+"\n"+arguments.callee(t,i.substring(s),r)},r="",s=t.split("\n"),n=0;s.length>n;n++)r+=i(this.context,s[n],this.style.wordWrapWidth)+"\n";return r},r.Text.prototype.destroy=function(t){t&&this.texture.destroy()},r.Text.heightCache={},r.BitmapText=function(t,e){r.DisplayObjectContainer.call(this),this.setText(t),this.setStyle(e),this.updateText(),this.dirty=!1},r.BitmapText.constructor=r.BitmapText,r.BitmapText.prototype=Object.create(r.DisplayObjectContainer.prototype),r.BitmapText.prototype.setText=function(t){this.text=t||" ",this.dirty=!0},r.BitmapText.prototype.setStyle=function(t){t=t||{},t.align=t.align||"left",this.style=t;var e=t.font.split(" ");this.fontName=e[e.length-1],this.fontSize=e.length>=2?parseInt(e[e.length-2],10):r.BitmapText.fonts[this.fontName].size,this.dirty=!0},r.BitmapText.prototype.updateText=function(){for(var t=r.BitmapText.fonts[this.fontName],e=new r.Point,i=null,s=[],n=0,o=[],a=0,h=this.fontSize/t.size,u=0;this.text.length>u;u++){var d=this.text.charCodeAt(u);if(/(?:\r\n|\r|\n)/.test(this.text.charAt(u)))o.push(e.x),n=Math.max(n,e.x),a++,e.x=0,e.y+=t.lineHeight,i=null;else{var c=t.chars[d];c&&(i&&c[i]&&(e.x+=c.kerning[i]),s.push({texture:c.texture,line:a,charCode:d,position:new r.Point(e.x+c.xOffset,e.y+c.yOffset)}),e.x+=c.xAdvance,i=d)}}o.push(e.x),n=Math.max(n,e.x);var l=[];for(u=0;a>=u;u++){var p=0;"right"==this.style.align?p=n-o[u]:"center"==this.style.align&&(p=(n-o[u])/2),l.push(p)}for(u=0;s.length>u;u++){var f=new r.Sprite(s[u].texture);f.position.x=(s[u].position.x+l[s[u].line])*h,f.position.y=s[u].position.y*h,f.scale.x=f.scale.y=h,this.addChild(f)}this.width=e.x*h,this.height=(e.y+t.lineHeight)*h},r.BitmapText.prototype.updateTransform=function(){if(this.dirty){for(;this.children.length>0;)this.removeChild(this.getChildAt(0));this.updateText(),this.dirty=!1}r.DisplayObjectContainer.prototype.updateTransform.call(this)},r.BitmapText.fonts={},r.InteractionManager=function(t){this.stage=t,this.tempPoint=new r.Point,this.mouseoverEnabled=!0,this.mouse=new r.InteractionData,this.touchs={},this.pool=[],this.interactiveItems=[],this.last=0},r.InteractionManager.constructor=r.InteractionManager,r.InteractionManager.prototype.collectInteractiveSprite=function(t,e){for(var i=t.children,r=i.length,s=r-1;s>=0;s--){var n=i[s];n.interactive?(e.interactiveChildren=!0,this.interactiveItems.push(n),n.children.length>0&&this.collectInteractiveSprite(n,n)):(n.__iParent=null,n.children.length>0&&this.collectInteractiveSprite(n,e))}},r.InteractionManager.prototype.setTarget=function(t){window.navigator.msPointerEnabled&&(t.view.style["-ms-content-zooming"]="none",t.view.style["-ms-touch-action"]="none"),this.target=t,t.view.addEventListener("mousemove",this.onMouseMove.bind(this),!0),t.view.addEventListener("mousedown",this.onMouseDown.bind(this),!0),document.body.addEventListener("mouseup",this.onMouseUp.bind(this),!0),t.view.addEventListener("mouseout",this.onMouseUp.bind(this),!0),t.view.addEventListener("touchstart",this.onTouchStart.bind(this),!0),t.view.addEventListener("touchend",this.onTouchEnd.bind(this),!0),t.view.addEventListener("touchmove",this.onTouchMove.bind(this),!0)},r.InteractionManager.prototype.update=function(){if(this.target){var t=Date.now(),e=t-this.last;if(e=30*e/1e3,!(1>e)){if(this.last=t,this.dirty){this.dirty=!1,this.interactiveItems.length;for(var i=0;this.interactiveItems.length>i;i++)this.interactiveItems[i].interactiveChildren=!1;this.interactiveItems=[],this.stage.interactive&&this.interactiveItems.push(this.stage),this.collectInteractiveSprite(this.stage,this.stage)}var r=this.interactiveItems.length;this.target.view.style.cursor="default";for(var i=0;r>i;i++){var s=this.interactiveItems[i];s.visible&&(s.mouseover||s.mouseout||s.buttonMode)&&(s.__hit=this.hitTest(s,this.mouse),s.__hit?(s.buttonMode&&(this.target.view.style.cursor="pointer"),s.__isOver||(s.mouseover&&s.mouseover(this.mouse),s.__isOver=!0)):s.__isOver&&(s.mouseout&&s.mouseout(this.mouse),s.__isOver=!1))}}}},r.InteractionManager.prototype.onMouseMove=function(t){t.preventDefault();var e=this.target.view.getBoundingClientRect();this.mouse.global.x=(t.clientX-e.left)*(this.target.width/e.width),this.mouse.global.y=(t.clientY-e.top)*(this.target.height/e.height);var i=this.interactiveItems.length;this.mouse.global;for(var r=0;i>r;r++){var s=this.interactiveItems[r];s.mousemove&&s.mousemove(this.mouse)}},r.InteractionManager.prototype.onMouseDown=function(t){t.preventDefault();var e=this.interactiveItems.length;this.mouse.global,this.stage;for(var i=0;e>i;i++){var r=this.interactiveItems[i];if((r.mousedown||r.click)&&(r.__mouseIsDown=!0,r.__hit=this.hitTest(r,this.mouse),r.__hit&&(r.mousedown&&r.mousedown(this.mouse),r.__isDown=!0,!r.interactiveChildren)))break}},r.InteractionManager.prototype.onMouseUp=function(t){t.preventDefault(),this.mouse.global;for(var e=this.interactiveItems.length,i=!1,r=0;e>r;r++){var s=this.interactiveItems[r];(s.mouseup||s.mouseupoutside||s.click)&&(s.__hit=this.hitTest(s,this.mouse),s.__hit&&!i?(s.mouseup&&s.mouseup(this.mouse),s.__isDown&&s.click&&s.click(this.mouse),s.interactiveChildren||(i=!0)):s.__isDown&&s.mouseupoutside&&s.mouseupoutside(this.mouse),s.__isDown=!1)}},r.InteractionManager.prototype.hitTest=function(t,e){var i=e.global;if(!t.visible)return!1;if(t instanceof r.Sprite){var s=t.worldTransform,n=s[0],o=s[1],a=s[2],h=s[3],u=s[4],d=s[5],c=1/(n*u+o*-h),l=u*c*i.x+-o*c*i.y+(d*o-a*u)*c,p=n*c*i.y+-h*c*i.x+(-d*n+a*h)*c,f=t.texture.frame.width,x=t.texture.frame.height,v=-f*t.anchor.x;if(l>v&&v+f>l){var g=-x*t.anchor.y;if(p>g&&g+x>p)return e.target=t,!0}}else if(t.hitArea){var s=t.worldTransform,b=t.hitArea,n=s[0],o=s[1],a=s[2],h=s[3],u=s[4],d=s[5],c=1/(n*u+o*-h),l=u*c*i.x+-o*c*i.y+(d*o-a*u)*c,p=n*c*i.y+-h*c*i.x+(-d*n+a*h)*c,v=b.x;if(l>v&&v+b.width>l){var g=b.y;if(p>g&&g+b.height>p)return!0}}for(var T=t.children.length,m=0;T>m;m++){var y=t.children[m],_=this.hitTest(y,e);if(_)return!0}return!1},r.InteractionManager.prototype.onTouchMove=function(t){t.preventDefault();for(var e=this.target.view.getBoundingClientRect(),i=t.changedTouches,r=0;i.length>r;r++){var s=i[r],n=this.touchs[s.identifier];n.global.x=(s.clientX-e.left)*(this.target.width/e.width),n.global.y=(s.clientY-e.top)*(this.target.height/e.height)}for(var o=this.interactiveItems.length,r=0;o>r;r++){var a=this.interactiveItems[r];a.touchmove&&a.touchmove(n)}},r.InteractionManager.prototype.onTouchStart=function(t){t.preventDefault();for(var e=this.target.view.getBoundingClientRect(),i=t.changedTouches,s=0;i.length>s;s++){var n=i[s],o=this.pool.pop();o||(o=new r.InteractionData),this.touchs[n.identifier]=o,o.global.x=(n.clientX-e.left)*(this.target.width/e.width),o.global.y=(n.clientY-e.top)*(this.target.height/e.height);for(var a=this.interactiveItems.length,h=0;a>h;h++){var u=this.interactiveItems[h];if((u.touchstart||u.tap)&&(u.__hit=this.hitTest(u,o),u.__hit&&(u.touchstart&&u.touchstart(o),u.__isDown=!0,u.__touchData=o,!u.interactiveChildren)))break}}},r.InteractionManager.prototype.onTouchEnd=function(t){t.preventDefault();for(var e=this.target.view.getBoundingClientRect(),i=t.changedTouches,r=0;i.length>r;r++){var s=i[r],n=this.touchs[s.identifier],o=!1;n.global.x=(s.clientX-e.left)*(this.target.width/e.width),n.global.y=(s.clientY-e.top)*(this.target.height/e.height);for(var a=this.interactiveItems.length,h=0;a>h;h++){var u=this.interactiveItems[h],d=u.__touchData;u.__hit=this.hitTest(u,n),d==n&&((u.touchend||u.tap)&&(u.__hit&&!o?(u.touchend&&u.touchend(n),u.__isDown&&u.tap&&u.tap(n),u.interactiveChildren||(o=!0)):u.__isDown&&u.touchendoutside&&u.touchendoutside(n),u.__isDown=!1),u.__touchData=null)}this.pool.push(n),this.touchs[s.identifier]=null}},r.InteractionData=function(){this.global=new r.Point,this.local=new r.Point,this.target},r.InteractionData.prototype.getLocalPosition=function(t){var e=t.worldTransform,i=this.global,s=e[0],n=e[1],o=e[2],a=e[3],h=e[4],u=e[5],d=1/(s*h+n*-a);return new r.Point(h*d*i.x+-n*d*i.y+(u*n-o*h)*d,s*d*i.y+-a*d*i.x+(-u*s+o*a)*d)},r.InteractionData.constructor=r.InteractionData,r.Stage=function(t,e){r.DisplayObjectContainer.call(this),this.worldTransform=r.mat3.create(),this.__childrenAdded=[],this.__childrenRemoved=[],this.childIndex=0,this.stage=this,this.stage.hitArea=new r.Rectangle(0,0,1e5,1e5),this.interactive=!!e,this.interactionManager=new r.InteractionManager(this),this.setBackgroundColor(t),this.worldVisible=!0},r.Stage.constructor=r.Stage,r.Stage.prototype=Object.create(r.DisplayObjectContainer.prototype),r.Stage.prototype.updateTransform=function(){this.worldAlpha=1;for(var t=0,e=this.children.length;e>t;t++)this.children[t].updateTransform();this.dirty&&(this.dirty=!1,this.interactionManager.dirty=!0),this.interactive&&this.interactionManager.update()},r.Stage.prototype.setBackgroundColor=function(e){this.backgroundColor=e||0,this.backgroundColorSplit=t(this.backgroundColor),this.backgroundColorString="#"+this.backgroundColor.toString(16)},r.Stage.prototype.getMousePosition=function(){return this.interactionManager.mouse.global},r.Stage.prototype.__addChild=function(t){if(t.interactive&&(this.dirty=!0),t.stage=this,t.children)for(var e=0;t.children.length>e;e++)this.__addChild(t.children[e])},r.Stage.prototype.__removeChild=function(t){if(t.interactive&&(this.dirty=!0),t.stage=void 0,t.children)for(var e=0,i=t.children.length;i>e;e++)this.__removeChild(t.children[e])},window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){window.setTimeout(t,1e3/60)}}(),"function"!=typeof Function.prototype.bind&&(Function.prototype.bind=function(){var t=Array.prototype.slice;return function(e){function i(){var n=s.concat(t.call(arguments));r.apply(this instanceof i?this:e,n)}var r=this,s=t.call(arguments,1);if("function"!=typeof r)throw new TypeError;return i.prototype=function n(t){return t&&(n.prototype=t),this instanceof n?void 0:new n}(r.prototype),i}}());var s=function(){var t=["Msxml2.XMLHTTP","Microsoft.XMLHTTP"];if(!window.ActiveXObject)return window.XMLHttpRequest?new XMLHttpRequest:!1;for(var e=0;t.length>e;e++)try{return new ActiveXObject(t[e])}catch(i){}};r.EventTarget=function(){var t={};this.addEventListener=this.on=function(e,i){void 0===t[e]&&(t[e]=[]),-1===t[e].indexOf(i)&&t[e].push(i)},this.dispatchEvent=this.emit=function(e){for(var i in t[e.type])t[e.type][i](e)},this.removeEventListener=this.off=function(e,i){var r=t[e].indexOf(i);-1!==r&&t[e].splice(r,1)}},e(),r.mat3={},r.mat3.create=function(){var t=new r.Matrix(9);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},r.mat4={},r.mat4.create=function(){var t=new r.Matrix(16);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},r.mat3.multiply=function(t,e,i){i||(i=t);var r=t[0],s=t[1],n=t[2],o=t[3],a=t[4],h=t[5],u=t[6],d=t[7],c=t[8],l=e[0],p=e[1],f=e[2],x=e[3],v=e[4],g=e[5],b=e[6],T=e[7],m=e[8];return i[0]=l*r+p*o+f*u,i[1]=l*s+p*a+f*d,i[2]=l*n+p*h+f*c,i[3]=x*r+v*o+g*u,i[4]=x*s+v*a+g*d,i[5]=x*n+v*h+g*c,i[6]=b*r+T*o+m*u,i[7]=b*s+T*a+m*d,i[8]=b*n+T*h+m*c,i},r.mat3.toMat4=function(t,e){return e||(e=r.mat4.create()),e[15]=1,e[14]=0,e[13]=0,e[12]=0,e[11]=0,e[10]=t[8],e[9]=t[7],e[8]=t[6],e[7]=0,e[6]=t[5],e[5]=t[4],e[4]=t[3],e[3]=0,e[2]=t[2],e[1]=t[1],e[0]=t[0],e},r.mat4.create=function(){var t=new r.Matrix(16);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},r.mat4.transpose=function(t,e){if(!e||t===e){var i=t[1],r=t[2],s=t[3],n=t[6],o=t[7],a=t[11];return t[1]=t[4],t[2]=t[8],t[3]=t[12],t[4]=i,t[6]=t[9],t[7]=t[13],t[8]=r,t[9]=n,t[11]=t[14],t[12]=s,t[13]=o,t[14]=a,t}return e[0]=t[0],e[1]=t[4],e[2]=t[8],e[3]=t[12],e[4]=t[1],e[5]=t[5],e[6]=t[9],e[7]=t[13],e[8]=t[2],e[9]=t[6],e[10]=t[10],e[11]=t[14],e[12]=t[3],e[13]=t[7],e[14]=t[11],e[15]=t[15],e},r.mat4.multiply=function(t,e,i){i||(i=t);var r=t[0],s=t[1],n=t[2],o=t[3],a=t[4],h=t[5],u=t[6],d=t[7],c=t[8],l=t[9],p=t[10],f=t[11],x=t[12],v=t[13],g=t[14],b=t[15],T=e[0],m=e[1],y=e[2],_=e[3];return i[0]=T*r+m*a+y*c+_*x,i[1]=T*s+m*h+y*l+_*v,i[2]=T*n+m*u+y*p+_*g,i[3]=T*o+m*d+y*f+_*b,T=e[4],m=e[5],y=e[6],_=e[7],i[4]=T*r+m*a+y*c+_*x,i[5]=T*s+m*h+y*l+_*v,i[6]=T*n+m*u+y*p+_*g,i[7]=T*o+m*d+y*f+_*b,T=e[8],m=e[9],y=e[10],_=e[11],i[8]=T*r+m*a+y*c+_*x,i[9]=T*s+m*h+y*l+_*v,i[10]=T*n+m*u+y*p+_*g,i[11]=T*o+m*d+y*f+_*b,T=e[12],m=e[13],y=e[14],_=e[15],i[12]=T*r+m*a+y*c+_*x,i[13]=T*s+m*h+y*l+_*v,i[14]=T*n+m*u+y*p+_*g,i[15]=T*o+m*d+y*f+_*b,i},r.autoDetectRenderer=function(t,e,i,s){t||(t=800),e||(e=600);var n=function(){try{return!!window.WebGLRenderingContext&&!!document.createElement("canvas").getContext("experimental-webgl")}catch(t){return!1}}();return n?new r.WebGLRenderer(t,e,i,s):new r.CanvasRenderer(t,e,i,s)},r.shaderFragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying float vColor;","uniform sampler2D uSampler;","void main(void) {","gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y));","gl_FragColor = gl_FragColor * vColor;","}"],r.shaderVertexSrc=["attribute vec2 aVertexPosition;","attribute vec2 aTextureCoord;","attribute float aColor;","uniform mat4 uMVMatrix;","varying vec2 vTextureCoord;","varying float vColor;","void main(void) {","gl_Position = uMVMatrix * vec4(aVertexPosition, 1.0, 1.0);","vTextureCoord = aTextureCoord;","vColor = aColor;","}"],r.CompileVertexShader=function(t,e){return r._CompileShader(t,e,t.VERTEX_SHADER)},r.CompileFragmentShader=function(t,e){return r._CompileShader(t,e,t.FRAGMENT_SHADER)},r._CompileShader=function(t,e,i){var r=e.join("\n"),s=t.createShader(i);return t.shaderSource(s,r),t.compileShader(s),t.getShaderParameter(s,t.COMPILE_STATUS)?s:(alert(t.getShaderInfoLog(s)),null)},r._defaultFrame=new r.Rectangle(0,0,1,1),r.gl,r.WebGLRenderer=function(t,e,i,s){this.transparent=!!s,this.width=t||800,this.height=e||600,this.view=i||document.createElement("canvas"),this.view.width=this.width,this.view.height=this.height;var n=this;this.view.addEventListener("webglcontextlost",function(t){n.handleContextLost(t)},!1),this.view.addEventListener("webglcontextrestored",function(t){n.handleContextRestored(t)},!1),this.batchs=[];try{r.gl=this.gl=this.view.getContext("experimental-webgl",{alpha:this.transparent,antialias:!1,premultipliedAlpha:!1})}catch(o){throw Error(" This browser does not support webGL. Try using the canvas renderer"+this)}this.initShaders();var a=this.gl;r.WebGLRenderer.gl=a,this.batch=new r.WebGLBatch(a),a.disable(a.DEPTH_TEST),a.disable(a.CULL_FACE),a.enable(a.BLEND),a.colorMask(!0,!0,!0,this.transparent),this.projectionMatrix=r.mat4.create(),this.resize(this.width,this.height),this.contextLost=!1,this.stageRenderGroup=new r.WebGLRenderGroup(this.gl)},r.WebGLRenderer.constructor=r.WebGLRenderer,r.WebGLRenderer.getBatch=function(){return 0==r._batchs.length?new r.WebGLBatch(r.WebGLRenderer.gl):r._batchs.pop()},r.WebGLRenderer.returnBatch=function(t){t.clean(),r._batchs.push(t)},r.WebGLRenderer.prototype.initShaders=function(){var t=this.gl,e=r.CompileFragmentShader(t,r.shaderFragmentSrc),i=r.CompileVertexShader(t,r.shaderVertexSrc);r.shaderProgram=t.createProgram();var s=r.shaderProgram;t.attachShader(s,i),t.attachShader(s,e),t.linkProgram(s),t.getProgramParameter(s,t.LINK_STATUS)||alert("Could not initialise shaders"),t.useProgram(s),s.vertexPositionAttribute=t.getAttribLocation(s,"aVertexPosition"),t.enableVertexAttribArray(s.vertexPositionAttribute),s.textureCoordAttribute=t.getAttribLocation(s,"aTextureCoord"),t.enableVertexAttribArray(s.textureCoordAttribute),s.colorAttribute=t.getAttribLocation(s,"aColor"),t.enableVertexAttribArray(s.colorAttribute),s.mvMatrixUniform=t.getUniformLocation(s,"uMVMatrix"),s.samplerUniform=t.getUniformLocation(s,"uSampler")},r.WebGLRenderer.prototype.render=function(t){if(!this.contextLost){this.__stage!==t&&(this.__stage=t,this.stageRenderGroup.setRenderable(t)),r.WebGLRenderer.updateTextures(),t.updateTransform();var e=this.gl;if(e.colorMask(!0,!0,!0,this.transparent),e.viewport(0,0,this.width,this.height),e.bindFramebuffer(e.FRAMEBUFFER,null),e.clearColor(t.backgroundColorSplit[0],t.backgroundColorSplit[1],t.backgroundColorSplit[2],!this.transparent),e.clear(e.COLOR_BUFFER_BIT),this.stageRenderGroup.backgroundColor=t.backgroundColorSplit,this.stageRenderGroup.render(this.projectionMatrix),t.interactive&&(t._interactiveEventsAdded||(t._interactiveEventsAdded=!0,t.interactionManager.setTarget(this))),r.Texture.frameUpdates.length>0){for(var i=0;r.Texture.frameUpdates.length>i;i++)r.Texture.frameUpdates[i].updateFrame=!1;r.Texture.frameUpdates=[]}}},r.WebGLRenderer.updateTextures=function(){for(var t=0;r.texturesToUpdate.length>t;t++)this.updateTexture(r.texturesToUpdate[t]);for(var t=0;r.texturesToDestroy.length>t;t++)this.destroyTexture(r.texturesToDestroy[t]);r.texturesToUpdate=[],r.texturesToDestroy=[]},r.WebGLRenderer.updateTexture=function(t){var e=r.gl;t._glTexture||(t._glTexture=e.createTexture()),t.hasLoaded&&(e.bindTexture(e.TEXTURE_2D,t._glTexture),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t.source),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),t._powerOf2?(e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.REPEAT),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.REPEAT)):(e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE)),e.bindTexture(e.TEXTURE_2D,null))},r.WebGLRenderer.prototype.destroyTexture=function(t){var e=this.gl;t._glTexture&&(t._glTexture=e.createTexture(),e.deleteTexture(e.TEXTURE_2D,t._glTexture))},r.WebGLRenderer.prototype.resize=function(t,e){this.width=t,this.height=e,this.view.width=t,this.view.height=e,this.gl.viewport(0,0,this.width,this.height);var i=this.projectionMatrix;i[0]=2/this.width,i[5]=-2/this.height,i[12]=-1,i[13]=1},r.WebGLRenderer.prototype.handleContextLost=function(t){t.preventDefault(),this.contextLost=!0},r.WebGLRenderer.prototype.handleContextRestored=function(){this.gl=this.view.getContext("experimental-webgl",{alpha:!0}),this.initShaders();for(var t=0;r.TextureCache.length>t;t++)this.updateTexture(r.TextureCache[t]);for(var t=0;this.batchs.length>t;t++)this.batchs[t].restoreLostContext(this.gl),this.batchs[t].dirty=!0;r._restoreBatchs(this.gl),this.contextLost=!1},r._batchs=[],r._getBatch=function(t){return 0==r._batchs.length?new r.WebGLBatch(t):r._batchs.pop()},r._returnBatch=function(t){t.clean(),r._batchs.push(t)},r._restoreBatchs=function(t){for(var e=0;r._batchs.length>e;e++)r._batchs[e].restoreLostContext(t)},r.WebGLBatch=function(t){this.gl=t,this.size=0,this.vertexBuffer=t.createBuffer(),this.indexBuffer=t.createBuffer(),this.uvBuffer=t.createBuffer(),this.colorBuffer=t.createBuffer(),this.blendMode=r.blendModes.NORMAL,this.dynamicSize=1},r.WebGLBatch.constructor=r.WebGLBatch,r.WebGLBatch.prototype.clean=function(){this.verticies=[],this.uvs=[],this.indices=[],this.colors=[],this.dynamicSize=1,this.texture=null,this.last=null,this.size=0,this.head,this.tail},r.WebGLBatch.prototype.restoreLostContext=function(t){this.gl=t,this.vertexBuffer=t.createBuffer(),this.indexBuffer=t.createBuffer(),this.uvBuffer=t.createBuffer(),this.colorBuffer=t.createBuffer()},r.WebGLBatch.prototype.init=function(t){t.batch=this,this.dirty=!0,this.blendMode=t.blendMode,this.texture=t.texture.baseTexture,this.head=t,this.tail=t,this.size=1,this.growBatch()},r.WebGLBatch.prototype.insertBefore=function(t,e){this.size++,t.batch=this,this.dirty=!0;var i=e.__prev;e.__prev=t,t.__next=e,i?(t.__prev=i,i.__next=t):this.head=t},r.WebGLBatch.prototype.insertAfter=function(t,e){this.size++,t.batch=this,this.dirty=!0;var i=e.__next;e.__next=t,t.__prev=e,i?(t.__next=i,i.__prev=t):this.tail=t},r.WebGLBatch.prototype.remove=function(t){return this.size--,0==this.size?(t.batch=null,t.__prev=null,t.__next=null,void 0):(t.__prev?t.__prev.__next=t.__next:(this.head=t.__next,this.head.__prev=null),t.__next?t.__next.__prev=t.__prev:(this.tail=t.__prev,this.tail.__next=null),t.batch=null,t.__next=null,t.__prev=null,this.dirty=!0,void 0)},r.WebGLBatch.prototype.split=function(t){this.dirty=!0;var e=new r.WebGLBatch(this.gl);e.init(t),e.texture=this.texture,e.tail=this.tail,this.tail=t.__prev,this.tail.__next=null,t.__prev=null;for(var i=0;t;)i++,t.batch=e,t=t.__next;return e.size=i,this.size-=i,e},r.WebGLBatch.prototype.merge=function(t){this.dirty=!0,this.tail.__next=t.head,t.head.__prev=this.tail,this.size+=t.size,this.tail=t.tail;for(var e=t.head;e;)e.batch=this,e=e.__next},r.WebGLBatch.prototype.growBatch=function(){var t=this.gl;this.dynamicSize=1==this.size?1:1.5*this.size,this.verticies=new Float32Array(8*this.dynamicSize),t.bindBuffer(t.ARRAY_BUFFER,this.vertexBuffer),t.bufferData(t.ARRAY_BUFFER,this.verticies,t.DYNAMIC_DRAW),this.uvs=new Float32Array(8*this.dynamicSize),t.bindBuffer(t.ARRAY_BUFFER,this.uvBuffer),t.bufferData(t.ARRAY_BUFFER,this.uvs,t.DYNAMIC_DRAW),this.dirtyUVS=!0,this.colors=new Float32Array(4*this.dynamicSize),t.bindBuffer(t.ARRAY_BUFFER,this.colorBuffer),t.bufferData(t.ARRAY_BUFFER,this.colors,t.DYNAMIC_DRAW),this.dirtyColors=!0,this.indices=new Uint16Array(6*this.dynamicSize);for(var e=this.indices.length/6,i=0;e>i;i++){var r=6*i,s=4*i;this.indices[r+0]=s+0,this.indices[r+1]=s+1,this.indices[r+2]=s+2,this.indices[r+3]=s+0,this.indices[r+4]=s+2,this.indices[r+5]=s+3}t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,this.indexBuffer),t.bufferData(t.ELEMENT_ARRAY_BUFFER,this.indices,t.STATIC_DRAW)},r.WebGLBatch.prototype.refresh=function(){this.gl,this.dynamicSizes;s++)i=this.batchs[s],i instanceof r.WebGLBatch?this.batchs[s].render():i instanceof r.TilingSprite?i.visible&&this.renderTilingSprite(i,t):i instanceof r.Strip&&i.visible&&this.renderStrip(i,t)},r.WebGLRenderGroup.prototype.renderSpecific=function(t,e){r.WebGLRenderer.updateTextures();var i=this.gl;this.checkVisibility(t,t.visible),i.uniformMatrix4fv(r.shaderProgram.mvMatrixUniform,!1,e);var s,n,o,a,h=t.renderable?t:this.getNextRenderable(t),u=h.batch;if(h instanceof r.Sprite){u=h.batch;var d=u.head;if(d==h)s=0;else for(s=1;d.__next!=h;)s++,d=d.__next}else u=h;for(var c,l=t,p=t;p.children.length>0;)p=p.children[p.children.length-1],p.renderable&&(l=p);if(l instanceof r.Sprite){c=l.batch;var d=c.head;if(d==l)o=0;else for(o=1;d.__next!=l;)o++,d=d.__next}else c=l;if(u==c)return u instanceof r.WebGLBatch?u.render(s,o+1):u instanceof r.TilingSprite?u.visible&&this.renderTilingSprite(u,e):u instanceof r.Strip?u.visible&&this.renderStrip(u,e):u instanceof r.CustomRenderable&&u.visible&&u.renderWebGL(this,e),void 0;n=this.batchs.indexOf(u),a=this.batchs.indexOf(c),u instanceof r.WebGLBatch?u.render(s):u instanceof r.TilingSprite?u.visible&&this.renderTilingSprite(u,e):u instanceof r.Strip?u.visible&&this.renderStrip(u,e):u instanceof r.CustomRenderable&&u.visible&&u.renderWebGL(this,e);for(var f=n+1;a>f;f++)renderable=this.batchs[f],renderable instanceof r.WebGLBatch?this.batchs[f].render():renderable instanceof r.TilingSprite?renderable.visible&&this.renderTilingSprite(renderable,e):renderable instanceof r.Strip?renderable.visible&&this.renderStrip(renderable,e):renderable instanceof r.CustomRenderable&&renderable.visible&&renderable.renderWebGL(this,e);c instanceof r.WebGLBatch?c.render(0,o+1):c instanceof r.TilingSprite?c.visible&&this.renderTilingSprite(c):c instanceof r.Strip?c.visible&&this.renderStrip(c):c instanceof r.CustomRenderable&&c.visible&&c.renderWebGL(this,e)},r.WebGLRenderGroup.prototype.checkVisibility=function(t,e){for(var i=t.children,r=0;i.length>r;r++){var s=i[r];s.worldVisible=s.visible&&e,s.textureChange&&(s.textureChange=!1,s.worldVisible&&(this.removeDisplayObject(s),this.addDisplayObject(s))),s.children.length>0&&this.checkVisibility(s,s.worldVisible)}},r.WebGLRenderGroup.prototype.addDisplayObject=function(t){if(t.__renderGroup&&t.__renderGroup.removeDisplayObjectAndChildren(t),t.__renderGroup=this,t.renderable){var e=this.getPreviousRenderable(t),i=this.getNextRenderable(t);if(t instanceof r.Sprite){var s,n;if(e instanceof r.Sprite){if(s=e.batch,s&&s.texture==t.texture.baseTexture&&s.blendMode==t.blendMode)return s.insertAfter(t,e),void 0}else s=e;if(i)if(i instanceof r.Sprite){if(n=i.batch){if(n.texture==t.texture.baseTexture&&n.blendMode==t.blendMode)return n.insertBefore(t,i),void 0;if(n==s){var o=s.split(i),a=r.WebGLRenderer.getBatch(),h=this.batchs.indexOf(s);return a.init(t),this.batchs.splice(h+1,0,a,o),void 0}}}else n=i;var a=r.WebGLRenderer.getBatch();if(a.init(t),s){var h=this.batchs.indexOf(s);this.batchs.splice(h+1,0,a)}else this.batchs.push(a)}else t instanceof r.TilingSprite?(this.initTilingSprite(t),this.batchs.push(t)):t instanceof r.Strip&&(this.initStrip(t),this.batchs.push(t));this.batchUpdate=!0}},r.WebGLRenderGroup.prototype.addDisplayObjectAndChildren=function(t){this.addDisplayObject(t);for(var e=t.children,i=0;e.length>i;i++)this.addDisplayObjectAndChildren(e[i])},r.WebGLRenderGroup.prototype.removeDisplayObject=function(t){if(t.__renderGroup=null,t.renderable){var e;if(t instanceof r.Sprite){var i=t.batch;if(!i)return;i.remove(t),0==i.size&&(e=i)}else e=t;if(e){var s=this.batchs.indexOf(e);if(-1==s)return;if(0==s||s==this.batchs.length-1)return this.batchs.splice(s,1),e instanceof r.WebGLBatch&&r.WebGLRenderer.returnBatch(e),void 0;if(this.batchs[s-1]instanceof r.WebGLBatch&&this.batchs[s+1]instanceof r.WebGLBatch&&this.batchs[s-1].texture==this.batchs[s+1].texture&&this.batchs[s-1].blendMode==this.batchs[s+1].blendMode)return this.batchs[s-1].merge(this.batchs[s+1]),e instanceof r.WebGLBatch&&r.WebGLRenderer.returnBatch(e),r.WebGLRenderer.returnBatch(this.batchs[s+1]),this.batchs.splice(s,2),void 0;this.batchs.splice(s,1),e instanceof r.WebGLBatch&&r.WebGLRenderer.returnBatch(e)}}},r.WebGLRenderGroup.prototype.removeDisplayObjectAndChildren=function(t){if(t.__renderGroup==this){this.removeDisplayObject(t);for(var e=t.children,i=0;e.length>i;i++)this.removeDisplayObjectAndChildren(e[i])}},r.WebGLRenderGroup.prototype.getNextRenderable=function(t){var e=t;do{if(0==e.children.length){if(!e.parent)return null;for(;e.childIndex==e.parent.children.length-1;)if(e=e.parent,e==this.root||!e.parent){e=null;break}e&&(e=e.parent.children[e.childIndex+1])}else e=e.children[0];if(!e)break}while(!e.renderable||!e.__renderGroup);return e},r.WebGLRenderGroup.prototype.getPreviousRenderable=function(t){var e=t;do{if(0==e.childIndex){if(e=e.parent,!e)return null}else for(e=e.parent.children[e.childIndex-1];0!=e.children.length;)e=e.children[e.children.length-1];if(e==this.root)break}while(!e.renderable||!e.__renderGroup);return e},r.WebGLRenderGroup.prototype.initTilingSprite=function(t){var e=this.gl;t.verticies=new Float32Array([0,0,t.width,0,t.width,t.height,0,t.height]),t.uvs=new Float32Array([0,0,1,0,1,1,0,1]),t.colors=new Float32Array([1,1,1,1]),t.indices=new Uint16Array([0,1,3,2]),t._vertexBuffer=e.createBuffer(),t._indexBuffer=e.createBuffer(),t._uvBuffer=e.createBuffer(),t._colorBuffer=e.createBuffer(),e.bindBuffer(e.ARRAY_BUFFER,t._vertexBuffer),e.bufferData(e.ARRAY_BUFFER,t.verticies,e.STATIC_DRAW),e.bindBuffer(e.ARRAY_BUFFER,t._uvBuffer),e.bufferData(e.ARRAY_BUFFER,t.uvs,e.DYNAMIC_DRAW),e.bindBuffer(e.ARRAY_BUFFER,t._colorBuffer),e.bufferData(e.ARRAY_BUFFER,t.colors,e.STATIC_DRAW),e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,t._indexBuffer),e.bufferData(e.ELEMENT_ARRAY_BUFFER,t.indices,e.STATIC_DRAW),t.texture.baseTexture._glTexture?(e.bindTexture(e.TEXTURE_2D,t.texture.baseTexture._glTexture),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.REPEAT),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.REPEAT),t.texture.baseTexture._powerOf2=!0):t.texture.baseTexture._powerOf2=!0},r.WebGLRenderGroup.prototype.renderStrip=function(t,e){var i=this.gl,s=r.shaderProgram,n=r.mat3.toMat4(t.worldTransform);r.mat4.transpose(n),r.mat4.multiply(e,n,n),i.uniformMatrix4fv(s.mvMatrixUniform,!1,n),t.blendMode==r.blendModes.NORMAL?i.blendFunc(i.ONE,i.ONE_MINUS_SRC_ALPHA):i.blendFunc(i.ONE,i.ONE_MINUS_SRC_COLOR),t.dirty?(t.dirty=!1,i.bindBuffer(i.ARRAY_BUFFER,t._vertexBuffer),i.bufferData(i.ARRAY_BUFFER,t.verticies,i.STATIC_DRAW),i.vertexAttribPointer(s.vertexPositionAttribute,2,i.FLOAT,!1,0,0),i.bindBuffer(i.ARRAY_BUFFER,t._uvBuffer),i.bufferData(i.ARRAY_BUFFER,t.uvs,i.STATIC_DRAW),i.vertexAttribPointer(s.textureCoordAttribute,2,i.FLOAT,!1,0,0),i.activeTexture(i.TEXTURE0),i.bindTexture(i.TEXTURE_2D,t.texture.baseTexture._glTexture),i.bindBuffer(i.ARRAY_BUFFER,t._colorBuffer),i.bufferData(i.ARRAY_BUFFER,t.colors,i.STATIC_DRAW),i.vertexAttribPointer(s.colorAttribute,1,i.FLOAT,!1,0,0),i.bindBuffer(i.ELEMENT_ARRAY_BUFFER,t._indexBuffer),i.bufferData(i.ELEMENT_ARRAY_BUFFER,t.indices,i.STATIC_DRAW)):(i.bindBuffer(i.ARRAY_BUFFER,t._vertexBuffer),i.bufferSubData(i.ARRAY_BUFFER,0,t.verticies),i.vertexAttribPointer(s.vertexPositionAttribute,2,i.FLOAT,!1,0,0),i.bindBuffer(i.ARRAY_BUFFER,t._uvBuffer),i.vertexAttribPointer(s.textureCoordAttribute,2,i.FLOAT,!1,0,0),i.activeTexture(i.TEXTURE0),i.bindTexture(i.TEXTURE_2D,t.texture.baseTexture._glTexture),i.bindBuffer(i.ARRAY_BUFFER,t._colorBuffer),i.vertexAttribPointer(s.colorAttribute,1,i.FLOAT,!1,0,0),i.bindBuffer(i.ELEMENT_ARRAY_BUFFER,t._indexBuffer)),i.drawElements(i.TRIANGLE_STRIP,t.indices.length,i.UNSIGNED_SHORT,0),i.uniformMatrix4fv(s.mvMatrixUniform,!1,e)},r.WebGLRenderGroup.prototype.renderTilingSprite=function(t,e){var i=this.gl;r.shaderProgram;var s=t.tilePosition,n=t.tileScale,o=s.x/t.texture.baseTexture.width,a=s.y/t.texture.baseTexture.height,h=t.width/t.texture.baseTexture.width/n.x,u=t.height/t.texture.baseTexture.height/n.y;t.uvs[0]=0-o,t.uvs[1]=0-a,t.uvs[2]=1*h-o,t.uvs[3]=0-a,t.uvs[4]=1*h-o,t.uvs[5]=1*u-a,t.uvs[6]=0-o,t.uvs[7]=1*u-a,i.bindBuffer(i.ARRAY_BUFFER,t._uvBuffer),i.bufferSubData(i.ARRAY_BUFFER,0,t.uvs),this.renderStrip(t,e)},r.WebGLRenderer.prototype.initStrip=function(t){var e=this.gl;this.shaderProgram,t._vertexBuffer=e.createBuffer(),t._indexBuffer=e.createBuffer(),t._uvBuffer=e.createBuffer(),t._colorBuffer=e.createBuffer(),e.bindBuffer(e.ARRAY_BUFFER,t._vertexBuffer),e.bufferData(e.ARRAY_BUFFER,t.verticies,e.DYNAMIC_DRAW),e.bindBuffer(e.ARRAY_BUFFER,t._uvBuffer),e.bufferData(e.ARRAY_BUFFER,t.uvs,e.STATIC_DRAW),e.bindBuffer(e.ARRAY_BUFFER,t._colorBuffer),e.bufferData(e.ARRAY_BUFFER,t.colors,e.STATIC_DRAW),e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,t._indexBuffer),e.bufferData(e.ELEMENT_ARRAY_BUFFER,t.indices,e.STATIC_DRAW)},r.CanvasRenderer=function(t,e,i,r){this.transparent=r,this.width=t||800,this.height=e||600,this.refresh=!0,this.view=i||document.createElement("canvas"),this.view.width=this.width,this.view.height=this.height,this.count=0,this.context=this.view.getContext("2d")},r.CanvasRenderer.constructor=r.CanvasRenderer,r.CanvasRenderer.prototype.render=function(t){r.texturesToUpdate=[],r.texturesToDestroy=[],t.updateTransform(),this.view.style.backgroundColor==t.backgroundColorString||this.transparent||(this.view.style.backgroundColor=t.backgroundColorString),this.context.setTransform(1,0,0,1,0,0),this.context.clearRect(0,0,this.width,this.height),this.renderDisplayObject(t),t.interactive&&(t._interactiveEventsAdded||(t._interactiveEventsAdded=!0,t.interactionManager.setTarget(this))),r.Texture.frameUpdates.length>0&&(r.Texture.frameUpdates=[])},r.CanvasRenderer.prototype.resize=function(t,e){this.width=t,this.height=e,this.view.width=t,this.view.height=e},r.CanvasRenderer.prototype.renderDisplayObject=function(t){var e=t.worldTransform,i=this.context;if(t.visible){if(t instanceof r.Sprite){var s=t.texture.frame;s&&(i.globalAlpha=t.worldAlpha,i.setTransform(e[0],e[3],e[1],e[4],e[2],e[5]),i.drawImage(t.texture.baseTexture.source,s.x,s.y,s.width,s.height,t.anchor.x*-s.width,t.anchor.y*-s.height,s.width,s.height))}else t instanceof r.Strip?(i.setTransform(e[0],e[3],e[1],e[4],e[2],e[5]),this.renderStrip(t)):t instanceof r.TilingSprite?(i.setTransform(e[0],e[3],e[1],e[4],e[2],e[5]),this.renderTilingSprite(t)):t instanceof r.CustomRenderable&&t.renderCanvas(this);for(var n=0;t.children.length>n;n++)this.renderDisplayObject(t.children[n]);this.context.setTransform(1,0,0,1,0,0)}},r.CanvasRenderer.prototype.renderStripFlat=function(t){var e=this.context,i=t.verticies;t.uvs;var r=i.length/2;this.count++,e.beginPath();for(var s=1;r-2>s;s++){var n=2*s,o=i[n],a=i[n+2],h=i[n+4],u=i[n+1],d=i[n+3],c=i[n+5];e.moveTo(o,u),e.lineTo(a,d),e.lineTo(h,c)}e.fillStyle="#FF0000",e.fill(),e.closePath()},r.CanvasRenderer.prototype.renderTilingSprite=function(t){var e=this.context;t.__tilePattern||(t.__tilePattern=e.createPattern(t.texture.baseTexture.source,"repeat")),e.beginPath();var i=t.tilePosition,r=t.tileScale;e.scale(r.x,r.y),e.translate(i.x,i.y),e.fillStyle=t.__tilePattern,e.fillRect(-i.x,-i.y,t.width/r.x,t.height/r.y),e.scale(1/r.x,1/r.y),e.translate(-i.x,-i.y),e.closePath()},r.CanvasRenderer.prototype.renderStrip=function(t){var e=this.context,i=t.verticies,r=t.uvs,s=i.length/2;this.count++;for(var n=1;s-2>n;n++){var o=2*n,a=i[o],h=i[o+2],u=i[o+4],d=i[o+1],c=i[o+3],l=i[o+5],p=r[o]*t.texture.width,f=r[o+2]*t.texture.width,x=r[o+4]*t.texture.width,v=r[o+1]*t.texture.height,g=r[o+3]*t.texture.height,b=r[o+5]*t.texture.height;e.save(),e.beginPath(),e.moveTo(a,d),e.lineTo(h,c),e.lineTo(u,l),e.closePath(),e.clip();var T=p*g+v*x+f*b-g*x-v*f-p*b,m=a*g+v*u+h*b-g*u-v*h-a*b,y=p*h+a*x+f*u-h*x-a*f-p*u,_=p*g*u+v*h*x+a*f*b-a*g*x-v*f*u-p*h*b,R=d*g+v*l+c*b-g*l-v*c-d*b,w=p*c+d*x+f*l-c*x-d*f-p*l,A=p*g*l+v*c*x+d*f*b-d*g*x-v*f*l-p*c*b;e.transform(m/T,R/T,y/T,w/T,_/T,A/T),e.drawImage(t.texture.baseTexture.source,0,0),e.restore()}},r.Strip=function(t,e,i){r.DisplayObjectContainer.call(this),this.texture=t,this.blendMode=r.blendModes.NORMAL;try{this.uvs=new Float32Array([0,1,1,1,1,0,0,1]),this.verticies=new Float32Array([0,0,0,0,0,0,0,0,0]),this.colors=new Float32Array([1,1,1,1]),this.indices=new Uint16Array([0,1,2,3])}catch(s){this.uvs=[0,1,1,1,1,0,0,1],this.verticies=[0,0,0,0,0,0,0,0,0],this.colors=[1,1,1,1],this.indices=[0,1,2,3]}this.width=e,this.height=i,t.baseTexture.hasLoaded?(this.width=this.texture.frame.width,this.height=this.texture.frame.height,this.updateFrame=!0):(this.onTextureUpdateBind=this.onTextureUpdate.bind(this),this.texture.addEventListener("update",this.onTextureUpdateBind)),this.renderable=!0},r.Strip.constructor=r.Strip,r.Strip.prototype=Object.create(r.DisplayObjectContainer.prototype),r.Strip.prototype.setTexture=function(t){this.texture=t,this.width=t.frame.width,this.height=t.frame.height,this.updateFrame=!0},r.Strip.prototype.onTextureUpdate=function(){this.updateFrame=!0},r.Rope=function(t,e){r.Strip.call(this,t),this.points=e;try{this.verticies=new Float32Array(4*e.length),this.uvs=new Float32Array(4*e.length),this.colors=new Float32Array(2*e.length),this.indices=new Uint16Array(2*e.length)}catch(i){this.verticies=verticies,this.uvs=uvs,this.colors=colors,this.indices=indices}this.refresh()},r.Rope.constructor=r.Rope,r.Rope.prototype=Object.create(r.Strip.prototype),r.Rope.prototype.refresh=function(){var t=this.points;if(!(1>t.length)){var e=this.uvs,i=this.indices,r=this.colors,s=t[0],n=t[0];this.count-=.2,e[0]=0,e[1]=1,e[2]=0,e[3]=1,r[0]=1,r[1]=1,i[0]=0,i[1]=1;for(var o=t.length,a=1;o>a;a++){var n=t[a],h=4*a,u=a/(o-1);a%2?(e[h]=u,e[h+1]=0,e[h+2]=u,e[h+3]=1):(e[h]=u,e[h+1]=0,e[h+2]=u,e[h+3]=1),h=2*a,r[h]=1,r[h+1]=1,h=2*a,i[h]=h,i[h+1]=h+1,s=n}}},r.Rope.prototype.updateTransform=function(){var t=this.points;if(!(1>t.length)){var e,i=this.verticies,s=t[0],n={x:0,y:0},o=t[0];this.count-=.2,i[0]=o.x+n.x,i[1]=o.y+n.y,i[2]=o.x-n.x,i[3]=o.y-n.y;for(var a=t.length,h=1;a>h;h++){var o=t[h],u=4*h;e=t.length-1>h?t[h+1]:o,n.y=-(e.x-s.x),n.x=e.y-s.y;var d=10*(1-h/(a-1));d>1&&(d=1);var c=Math.sqrt(n.x*n.x+n.y*n.y),l=this.texture.height/2;n.x/=c,n.y/=c,n.x*=l,n.y*=l,i[u]=o.x+n.x,i[u+1]=o.y+n.y,i[u+2]=o.x-n.x,i[u+3]=o.y-n.y,s=o}r.DisplayObjectContainer.prototype.updateTransform.call(this)}},r.Rope.prototype.setTexture=function(t){this.texture=t,this.updateFrame=!0},r.TilingSprite=function(t,e,i){r.DisplayObjectContainer.call(this),this.texture=t,this.width=e,this.height=i,this.renderable=!0,this.tileScale=new r.Point(1,1),this.tilePosition=new r.Point(0,0),this.blendMode=r.blendModes.NORMAL},r.TilingSprite.constructor=r.TilingSprite,r.TilingSprite.prototype=Object.create(r.DisplayObjectContainer.prototype),r.TilingSprite.prototype.setTexture=function(t){this.texture=t,this.updateFrame=!0},r.TilingSprite.prototype.onTextureUpdate=function(){this.updateFrame=!0},r.CustomRenderable=function(){r.DisplayObject.call(this)},r.CustomRenderable.constructor=r.CustomRenderable,r.CustomRenderable.prototype=Object.create(r.DisplayObject.prototype),r.CustomRenderable.prototype.renderCanvas=function(){},r.CustomRenderable.prototype.initWebGL=function(){},r.CustomRenderable.prototype.renderWebGL=function(){},r.BaseTextureCache={},r.texturesToUpdate=[],r.texturesToDestroy=[],r.BaseTexture=function(t){if(r.EventTarget.call(this),this.width=100,this.height=100,this.source=t,t){if(this.source instanceof Image)if(this.source.complete)this.hasLoaded=!0,this.width=this.source.width,this.height=this.source.height,r.texturesToUpdate.push(this);else{var e=this;this.source.onload=function(){e.hasLoaded=!0,e.width=e.source.width,e.height=e.source.height,r.texturesToUpdate.push(e),e.dispatchEvent({type:"loaded",content:e})}}else this.hasLoaded=!0,this.width=this.source.width,this.height=this.source.height,r.texturesToUpdate.push(this);this._powerOf2=!1}},r.BaseTexture.constructor=r.BaseTexture,r.BaseTexture.prototype.destroy=function(){this.source instanceof Image&&(this.source.src=null),this.source=null,r.texturesToDestroy.push(this)},r.BaseTexture.fromImage=function(t,e){var i=r.BaseTextureCache[t];if(!i){var s=new Image;e&&(s.crossOrigin=""),s.src=t,i=new r.BaseTexture(s),r.BaseTextureCache[t]=i}return i},r.TextureCache={},r.FrameCache={},r.Texture=function(t,e){if(r.EventTarget.call(this),e||(this.noFrame=!0,e=new r.Rectangle(0,0,1,1)),this.trim=new r.Point,t instanceof r.Texture&&(t=t.baseTexture),this.baseTexture=t,this.frame=e,this.scope=this,t.hasLoaded)this.noFrame&&(e=new r.Rectangle(0,0,t.width,t.height)),this.setFrame(e);else{var i=this;t.addEventListener("loaded",function(){i.onBaseTextureLoaded()})}},r.Texture.constructor=r.Texture,r.Texture.prototype.onBaseTextureLoaded=function(){var t=this.baseTexture;t.removeEventListener("loaded",this.onLoaded),this.noFrame&&(this.frame=new r.Rectangle(0,0,t.width,t.height)),this.noFrame=!1,this.width=this.frame.width,this.height=this.frame.height,this.scope.dispatchEvent({type:"update",content:this})},r.Texture.prototype.destroy=function(t){t&&this.baseTexture.destroy()},r.Texture.prototype.setFrame=function(t){if(this.frame=t,this.width=t.width,this.height=t.height,t.x+t.width>this.baseTexture.width||t.y+t.height>this.baseTexture.height)throw Error("Texture Error: frame does not fit inside the base Texture dimensions "+this);this.updateFrame=!0,r.Texture.frameUpdates.push(this)},r.Texture.fromImage=function(t,e){var i=r.TextureCache[t];return i||(i=new r.Texture(r.BaseTexture.fromImage(t,e)),r.TextureCache[t]=i),i},r.Texture.fromFrame=function(t){var e=r.TextureCache[t];if(!e)throw Error("The frameId '"+t+"' does not exist in the texture cache "+this);return e},r.Texture.fromCanvas=function(t){var e=new r.BaseTexture(t);return new r.Texture(e)},r.Texture.addTextureToCache=function(t,e){r.TextureCache[e]=t},r.Texture.removeTextureFromCache=function(t){var e=r.TextureCache[t];return r.TextureCache[t]=null,e},r.Texture.frameUpdates=[],r.RenderTexture=function(t,e){r.EventTarget.call(this),this.width=t||100,this.height=e||100,this.indetityMatrix=r.mat3.create(),this.frame=new r.Rectangle(0,0,this.width,this.height),r.gl?this.initWebGL():this.initCanvas()},r.RenderTexture.constructor=r.RenderTexture,r.RenderTexture.prototype=Object.create(r.Texture.prototype),r.RenderTexture.prototype.initWebGL=function(){var t=r.gl;this.glFramebuffer=t.createFramebuffer(),t.bindFramebuffer(t.FRAMEBUFFER,this.glFramebuffer),this.glFramebuffer.width=this.width,this.glFramebuffer.height=this.height,this.baseTexture=new r.BaseTexture,this.baseTexture.width=this.width,this.baseTexture.height=this.height,this.baseTexture._glTexture=t.createTexture(),t.bindTexture(t.TEXTURE_2D,this.baseTexture._glTexture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,this.width,this.height,0,t.RGBA,t.UNSIGNED_BYTE,null),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),this.baseTexture.isRender=!0,t.bindFramebuffer(t.FRAMEBUFFER,this.glFramebuffer),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,this.baseTexture._glTexture,0),this.projectionMatrix=r.mat4.create(),this.projectionMatrix[5]=2/this.height,this.projectionMatrix[13]=-1,this.projectionMatrix[0]=2/this.width,this.projectionMatrix[12]=-1,this.render=this.renderWebGL},r.RenderTexture.prototype.initCanvas=function(){this.renderer=new r.CanvasRenderer(this.width,this.height,null,0),this.baseTexture=new r.BaseTexture(this.renderer.view),this.frame=new r.Rectangle(0,0,this.width,this.height),this.render=this.renderCanvas},r.RenderTexture.prototype.renderWebGL=function(t,e){var i=r.gl;i.colorMask(!0,!0,!0,!0),i.viewport(0,0,this.width,this.height),i.bindFramebuffer(i.FRAMEBUFFER,this.glFramebuffer),e&&(i.clearColor(0,0,0,0),i.clear(i.COLOR_BUFFER_BIT));var s=t.children;t.worldTransform=r.mat3.create();for(var n=0,o=s.length;o>n;n++)s[n].updateTransform();var a=t.__renderGroup;a?t==a.root?a.render(this.projectionMatrix):a.renderSpecific(t,this.projectionMatrix):(this.renderGroup||(this.renderGroup=new r.WebGLRenderGroup(i)),this.renderGroup.setRenderable(t),this.renderGroup.render(this.projectionMatrix))},r.RenderTexture.prototype.renderCanvas=function(t,e){var i=t.children;t.worldTransform=r.mat3.create();for(var s=0,n=i.length;n>s;s++)i[s].updateTransform();e&&this.renderer.context.clearRect(0,0,this.width,this.height),this.renderer.renderDisplayObject(t),r.texturesToUpdate.push(this.baseTexture)},r.AssetLoader=function(t){r.EventTarget.call(this),this.assetURLs=t,this.crossorigin=!1,this.loadersByType={jpg:r.ImageLoader,jpeg:r.ImageLoader,png:r.ImageLoader,gif:r.ImageLoader,json:r.SpriteSheetLoader,xml:r.BitmapFontLoader,fnt:r.BitmapFontLoader}},r.AssetLoader.constructor=r.AssetLoader,r.AssetLoader.prototype.load=function(){var t=this;this.loadCount=this.assetURLs.length;for(var e=0;this.assetURLs.length>e;e++){var i=this.assetURLs[e],r=i.split(".").pop().toLowerCase(),s=this.loadersByType[r];if(!s)throw Error(r+" is an unsupported file type");var n=new s(i,this.crossorigin);n.addEventListener("loaded",function(){t.onAssetLoaded()}),n.load()}},r.AssetLoader.prototype.onAssetLoaded=function(){this.loadCount--,this.dispatchEvent({type:"onProgress",content:this}),this.onProgress&&this.onProgress(),0==this.loadCount&&(this.dispatchEvent({type:"onComplete",content:this}),this.onComplete&&this.onComplete())},r.JsonLoader=function(t,e){r.EventTarget.call(this),this.url=t,this.baseUrl=t.replace(/[^\/]*$/,""),this.crossorigin=e},r.JsonLoader.constructor=r.JsonLoader,r.JsonLoader.prototype.load=function(){this.ajaxRequest=new s;var t=this;this.ajaxRequest.onreadystatechange=function(){t.onJSONLoaded()},this.ajaxRequest.open("GET",this.url,!0),this.ajaxRequest.overrideMimeType&&this.ajaxRequest.overrideMimeType("application/json"),this.ajaxRequest.send(null)},r.JsonLoader.prototype.onJSONLoaded=function(){4==this.ajaxRequest.readyState&&(200==this.ajaxRequest.status||-1==window.location.href.indexOf("http")?(this.json=JSON.parse(this.ajaxRequest.responseText),this.onLoaded()):this.onError())},r.JsonLoader.prototype.onLoaded=function(){this.dispatchEvent({type:"loaded",content:this})},r.JsonLoader.prototype.onError=function(){this.dispatchEvent({type:"error",content:this})},r.SpriteSheetLoader=function(t,e){r.EventTarget.call(this),this.url=t,this.baseUrl=t.replace(/[^\/]*$/,""),this.texture=null,this.frames={},this.crossorigin=e},r.SpriteSheetLoader.constructor=r.SpriteSheetLoader,r.SpriteSheetLoader.prototype.load=function(){var t=this,e=new r.JsonLoader(this.url,this.crossorigin);e.addEventListener("loaded",function(e){t.json=e.content.json,t.onJSONLoaded()}),e.load()},r.SpriteSheetLoader.prototype.onJSONLoaded=function(){var t=this,e=this.baseUrl+this.json.meta.image,i=new r.ImageLoader(e,this.crossorigin),s=this.json.frames;this.texture=i.texture.baseTexture,i.addEventListener("loaded",function(){t.onLoaded()});for(var n in s){var o=s[n].frame;o&&(r.TextureCache[n]=new r.Texture(this.texture,{x:o.x,y:o.y,width:o.w,height:o.h}),s[n].trimmed&&(r.TextureCache[n].realSize=s[n].spriteSourceSize,r.TextureCache[n].trim.x=0))}i.load()},r.SpriteSheetLoader.prototype.onLoaded=function(){this.dispatchEvent({type:"loaded",content:this})},r.ImageLoader=function(t,e){r.EventTarget.call(this),this.texture=r.Texture.fromImage(t,e)},r.ImageLoader.constructor=r.ImageLoader,r.ImageLoader.prototype.load=function(){if(this.texture.baseTexture.hasLoaded)this.onLoaded();else{var t=this;this.texture.baseTexture.addEventListener("loaded",function(){t.onLoaded()})}},r.ImageLoader.prototype.onLoaded=function(){this.dispatchEvent({type:"loaded",content:this})},r.BitmapFontLoader=function(t,e){r.EventTarget.call(this),this.url=t,this.baseUrl=t.replace(/[^\/]*$/,""),this.texture=null,this.crossorigin=e},r.BitmapFontLoader.constructor=r.BitmapFontLoader,r.BitmapFontLoader.prototype.load=function(){this.ajaxRequest=new XMLHttpRequest;var t=this;this.ajaxRequest.onreadystatechange=function(){t.onXMLLoaded()},this.ajaxRequest.open("GET",this.url,!0),this.ajaxRequest.overrideMimeType&&this.ajaxRequest.overrideMimeType("application/xml"),this.ajaxRequest.send(null)},r.BitmapFontLoader.prototype.onXMLLoaded=function(){if(4==this.ajaxRequest.readyState&&(200==this.ajaxRequest.status||-1==window.location.href.indexOf("http"))){var t=this.baseUrl+this.ajaxRequest.responseXML.getElementsByTagName("page")[0].attributes.getNamedItem("file").nodeValue,e=new r.ImageLoader(t,this.crossorigin);this.texture=e.texture.baseTexture;var i={},s=this.ajaxRequest.responseXML.getElementsByTagName("info")[0],n=this.ajaxRequest.responseXML.getElementsByTagName("common")[0];i.font=s.attributes.getNamedItem("face").nodeValue,i.size=parseInt(s.attributes.getNamedItem("size").nodeValue,10),i.lineHeight=parseInt(n.attributes.getNamedItem("lineHeight").nodeValue,10),i.chars={};for(var o=this.ajaxRequest.responseXML.getElementsByTagName("char"),a=0;o.length>a;a++){var h=parseInt(o[a].attributes.getNamedItem("id").nodeValue,10),u={x:parseInt(o[a].attributes.getNamedItem("x").nodeValue,10),y:parseInt(o[a].attributes.getNamedItem("y").nodeValue,10),width:parseInt(o[a].attributes.getNamedItem("width").nodeValue,10),height:parseInt(o[a].attributes.getNamedItem("height").nodeValue,10)};r.TextureCache[h]=new r.Texture(this.texture,u),i.chars[h]={xOffset:parseInt(o[a].attributes.getNamedItem("xoffset").nodeValue,10),yOffset:parseInt(o[a].attributes.getNamedItem("yoffset").nodeValue,10),xAdvance:parseInt(o[a].attributes.getNamedItem("xadvance").nodeValue,10),kerning:{},texture:new r.Texture(this.texture,u)}}var d=this.ajaxRequest.responseXML.getElementsByTagName("kerning");for(a=0;d.length>a;a++){var c=parseInt(d[a].attributes.getNamedItem("first").nodeValue,10),l=parseInt(d[a].attributes.getNamedItem("second").nodeValue,10),p=parseInt(d[a].attributes.getNamedItem("amount").nodeValue,10);i.chars[l].kerning[c]=p}r.BitmapText.fonts[i.font]=i;var f=this;e.addEventListener("loaded",function(){f.onLoaded()}),e.load()}},r.BitmapFontLoader.prototype.onLoaded=function(){this.dispatchEvent({type:"loaded",content:this})},"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=r),exports.PIXI=r):i.PIXI=r}).call(this); \ No newline at end of file +(function(){function t(t){return[(255&t>>16)/255,(255&t>>8)/255,(255&t)/255]}function e(){return n.Matrix="undefined"!=typeof Float32Array?Float32Array:Array,n.Matrix}var i=this,n=n||{};n.Point=function(t,e){this.x=t||0,this.y=e||0},n.Point.prototype.clone=function(){return new n.Point(this.x,this.y)},n.Point.constructor=n.Point,n.Rectangle=function(t,e,i,r){this.x=t||0,this.y=e||0,this.width=i||0,this.height=r||0},n.Rectangle.prototype.clone=function(){return new n.Rectangle(this.x,this.y,this.width,this.height)},n.Rectangle.constructor=n.Rectangle,n.DisplayObject=function(){this.position=new n.Point,this.scale=new n.Point(1,1),this.pivot=new n.Point(0,0),this.rotation=0,this.alpha=1,this.visible=!0,this.worldVisible=!1,this.parent=null,this.stage=null,this.hitArea=null,this.worldAlpha=1,this.color=[],this.worldTransform=n.mat3.create(),this.localTransform=n.mat3.create(),this.dynamic=!0,this._sr=0,this._cr=1,this.childIndex=0,this.renderable=!1,this.interactive=!1,this.buttonMode=!1},n.DisplayObject.constructor=n.DisplayObject,n.DisplayObject.prototype.setInteractive=function(t){this.interactive=t,this.stage&&(this.stage.dirty=!0)},n.DisplayObject.prototype.updateTransform=function(){this.rotation!=this.rotationCache&&(this.rotationCache=this.rotation,this._sr=Math.sin(this.rotation),this._cr=Math.cos(this.rotation));var t=this.localTransform,e=this.parent.worldTransform,i=this.worldTransform;t[0]=this._cr*this.scale.x,t[1]=-this._sr*this.scale.y,t[3]=this._sr*this.scale.x,t[4]=this._cr*this.scale.y;var r=this.pivot.x,n=this.pivot.y;t[2]=this.position.x-t[0]*r-n*t[1],t[5]=this.position.y-t[4]*n-r*t[3];var s=t[0],a=t[1],o=t[2],h=t[3],l=t[4],u=t[5],c=e[0],d=e[1],p=e[2],f=e[3],m=e[4],v=e[5];i[0]=c*s+d*h,i[1]=c*a+d*l,i[2]=c*o+d*u+p,i[3]=f*s+m*h,i[4]=f*a+m*l,i[5]=f*o+m*u+v,this.worldAlpha=this.alpha*this.parent.worldAlpha},n.DisplayObjectContainer=function(){n.DisplayObject.call(this),this.children=[],this.renderable=!1},n.DisplayObjectContainer.constructor=n.DisplayObjectContainer,n.DisplayObjectContainer.prototype=Object.create(n.DisplayObject.prototype),n.DisplayObjectContainer.prototype.addChild=function(t){void 0!=t.parent&&t.parent.removeChild(t),t.parent=this,t.childIndex=this.children.length,this.children.push(t),this.stage&&this.stage.__addChild(t),this.__renderGroup&&(t.__renderGroup&&t.__renderGroup.removeDisplayObjectAndChildren(t),this.__renderGroup.addDisplayObjectAndChildren(t))},n.DisplayObjectContainer.prototype.addChildAt=function(t,e){if(!(e>=0&&this.children.length>=e))throw Error(t+" The index "+e+" supplied is out of bounds "+this.children.length);void 0!=t.parent&&t.parent.removeChild(t),e==this.children.length?this.children.push(t):this.children.splice(e,0,t),t.parent=this,t.childIndex=e;for(var i=this.children.length,r=e;i>r;r++)this.children[r].childIndex=r;this.stage&&this.stage.__addChild(t),this.__renderGroup&&(t.__renderGroup&&t.__renderGroup.removeDisplayObjectAndChildren(t),this.__renderGroup.addDisplayObjectAndChildren(t))},n.DisplayObjectContainer.prototype.swapChildren=function(t,e){var i=this.children.indexOf(t),r=this.children.indexOf(e);if(-1===i||-1===r)throw Error(t+" Both the supplied DisplayObjects must be a child of the caller "+this);this.stage&&(this.stage.__removeChild(t),this.stage.__removeChild(e),this.stage.__addChild(t),this.stage.__addChild(e)),t.childIndex=r,e.childIndex=i,this.children[i]=e,this.children[r]=t},n.DisplayObjectContainer.prototype.getChildAt=function(t){if(t>=0&&this.children.length>t)return this.children[t];throw Error(child+" Both the supplied DisplayObjects must be a child of the caller "+this)},n.DisplayObjectContainer.prototype.removeChild=function(t){var e=this.children.indexOf(t);if(-1===e)throw Error(t+" The supplied DisplayObject must be a child of the caller "+this);this.stage&&this.stage.__removeChild(t),t.__renderGroup&&t.__renderGroup.removeDisplayObjectAndChildren(t),t.parent=void 0,this.children.splice(e,1);for(var i=e,r=this.children.length;r>i;i++)this.children[i].childIndex-=1},n.DisplayObjectContainer.prototype.updateTransform=function(){if(this.visible){n.DisplayObject.prototype.updateTransform.call(this);for(var t=0,e=this.children.length;e>t;t++)this.children[t].updateTransform()}},n.blendModes={},n.blendModes.NORMAL=0,n.blendModes.SCREEN=1,n.Sprite=function(t){n.DisplayObjectContainer.call(this),this.anchor=new n.Point,this.texture=t,this.blendMode=n.blendModes.NORMAL,this._width=0,this._height=0,t.baseTexture.hasLoaded?this.updateFrame=!0:(this.onTextureUpdateBind=this.onTextureUpdate.bind(this),this.texture.addEventListener("update",this.onTextureUpdateBind)),this.renderable=!0},n.Sprite.constructor=n.Sprite,n.Sprite.prototype=Object.create(n.DisplayObjectContainer.prototype),Object.defineProperty(n.Sprite.prototype,"width",{get:function(){return this.scale.x*this.texture.frame.width},set:function(t){this.scale.x=t/this.texture.frame.width,this._width=t}}),Object.defineProperty(n.Sprite.prototype,"height",{get:function(){return this.scale.y*this.texture.frame.height},set:function(t){this.scale.y=t/this.texture.frame.height,this._height=t}}),n.Sprite.prototype.setTexture=function(t){this.texture.baseTexture!=t.baseTexture&&(this.textureChange=!0),this.texture=t,this.updateFrame=!0},n.Sprite.prototype.onTextureUpdate=function(){this._width&&(this.scale.x=this._width/this.texture.frame.width),this._height&&(this.scale.y=this._height/this.texture.frame.height),this.updateFrame=!0},n.Sprite.fromFrame=function(t){var e=n.TextureCache[t];if(!e)throw Error("The frameId '"+t+"' does not exist in the texture cache"+this);return new n.Sprite(e)},n.Sprite.fromImage=function(t){var e=n.Texture.fromImage(t);return new n.Sprite(e)},n.MovieClip=function(t){n.Sprite.call(this,t[0]),this.textures=t,this.currentFrame=0,this.animationSpeed=1,this.loop=!0,this.onComplete=null,this.playing},n.MovieClip.constructor=n.MovieClip,n.MovieClip.prototype=Object.create(n.Sprite.prototype),n.MovieClip.prototype.stop=function(){this.playing=!1},n.MovieClip.prototype.play=function(){this.playing=!0},n.MovieClip.prototype.gotoAndStop=function(t){this.playing=!1,this.currentFrame=t;var e=0|this.currentFrame+.5;this.setTexture(this.textures[e%this.textures.length])},n.MovieClip.prototype.gotoAndPlay=function(t){this.currentFrame=t,this.playing=!0},n.MovieClip.prototype.updateTransform=function(){if(n.Sprite.prototype.updateTransform.call(this),this.playing){this.currentFrame+=this.animationSpeed;var t=0|this.currentFrame+.5;this.loop||this.textures.length>t?this.setTexture(this.textures[t%this.textures.length]):t>=this.textures.length&&(this.gotoAndStop(this.textures.length-1),this.onComplete&&this.onComplete())}},n.Text=function(t,e){this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),n.Sprite.call(this,n.Texture.fromCanvas(this.canvas)),this.setText(t),this.setStyle(e),this.updateText(),this.dirty=!1},n.Text.constructor=n.Text,n.Text.prototype=Object.create(n.Sprite.prototype),n.Text.prototype.setStyle=function(t){t=t||{},t.font=t.font||"bold 20pt Arial",t.fill=t.fill||"black",t.align=t.align||"left",t.strokeThickness=t.strokeThickness||0,t.wordWrap=t.wordWrap||!1,t.wordWrapWidth=t.wordWrapWidth||100,this.style=t,this.dirty=!0},n.Sprite.prototype.setText=function(t){this.text=""+t||" ",this.dirty=!0},n.Text.prototype.updateText=function(){this.context.font=this.style.font;var t=this.text;this.style.wordWrap&&(t=this.wordWrap(this.text));for(var e=t.split(/(?:\r\n|\r|\n)/),i=[],r=0,s=0;e.length>s;s++){var a=this.context.measureText(e[s]).width;i[s]=a,r=Math.max(r,a)}this.canvas.width=r+this.style.strokeThickness;var o=this.determineFontHeight("font: "+this.style.font+";")+this.style.strokeThickness;for(this.canvas.height=o*e.length,this.context.fillStyle=this.style.fill,this.context.font=this.style.font,this.context.strokeStyle=this.style.stroke,this.context.lineWidth=this.style.strokeThickness,this.context.textBaseline="top",s=0;e.length>s;s++){var h=new n.Point(this.style.strokeThickness/2,this.style.strokeThickness/2+s*o);"right"==this.style.align?h.x+=r-i[s]:"center"==this.style.align&&(h.x+=(r-i[s])/2),this.style.stroke&&this.style.strokeThickness&&this.context.strokeText(e[s],h.x,h.y),this.style.fill&&this.context.fillText(e[s],h.x,h.y)}this.updateTexture()},n.Text.prototype.updateTexture=function(){this.texture.baseTexture.width=this.canvas.width,this.texture.baseTexture.height=this.canvas.height,this.texture.frame.width=this.canvas.width,this.texture.frame.height=this.canvas.height,this._width=this.canvas.width,this._height=this.canvas.height,n.texturesToUpdate.push(this.texture.baseTexture)},n.Text.prototype.updateTransform=function(){this.dirty&&(this.updateText(),this.dirty=!1),n.Sprite.prototype.updateTransform.call(this)},n.Text.prototype.determineFontHeight=function(t){var e=n.Text.heightCache[t];if(!e){var i=document.getElementsByTagName("body")[0],r=document.createElement("div"),s=document.createTextNode("M");r.appendChild(s),r.setAttribute("style",t),i.appendChild(r),e=r.offsetHeight,n.Text.heightCache[t]=e,i.removeChild(r)}return e},n.Text.prototype.wordWrap=function(t){for(var e=function(t,e,i,r,n){var s=Math.floor((r-i)/2)+i;return s==i?1:n>=t.measureText(e.substring(0,s)).width?t.measureText(e.substring(0,s+1)).width>n?s:arguments.callee(t,e,s,r,n):arguments.callee(t,e,i,s,n)},i=function(t,i,r){if(r>=t.measureText(i).width||1>i.length)return i;var n=e(t,i,0,i.length,r);return i.substring(0,n)+"\n"+arguments.callee(t,i.substring(n),r)},r="",n=t.split("\n"),s=0;n.length>s;s++)r+=i(this.context,n[s],this.style.wordWrapWidth)+"\n";return r},n.Text.prototype.destroy=function(t){t&&this.texture.destroy()},n.Text.heightCache={},n.BitmapText=function(t,e){n.DisplayObjectContainer.call(this),this.setText(t),this.setStyle(e),this.updateText(),this.dirty=!1},n.BitmapText.constructor=n.BitmapText,n.BitmapText.prototype=Object.create(n.DisplayObjectContainer.prototype),n.BitmapText.prototype.setText=function(t){this.text=t||" ",this.dirty=!0},n.BitmapText.prototype.setStyle=function(t){t=t||{},t.align=t.align||"left",this.style=t;var e=t.font.split(" ");this.fontName=e[e.length-1],this.fontSize=e.length>=2?parseInt(e[e.length-2],10):n.BitmapText.fonts[this.fontName].size,this.dirty=!0},n.BitmapText.prototype.updateText=function(){for(var t=n.BitmapText.fonts[this.fontName],e=new n.Point,i=null,r=[],s=0,a=[],o=0,h=this.fontSize/t.size,l=0;this.text.length>l;l++){var u=this.text.charCodeAt(l);if(/(?:\r\n|\r|\n)/.test(this.text.charAt(l)))a.push(e.x),s=Math.max(s,e.x),o++,e.x=0,e.y+=t.lineHeight,i=null;else{var c=t.chars[u];c&&(i&&c[i]&&(e.x+=c.kerning[i]),r.push({texture:c.texture,line:o,charCode:u,position:new n.Point(e.x+c.xOffset,e.y+c.yOffset)}),e.x+=c.xAdvance,i=u)}}a.push(e.x),s=Math.max(s,e.x);var d=[];for(l=0;o>=l;l++){var p=0;"right"==this.style.align?p=s-a[l]:"center"==this.style.align&&(p=(s-a[l])/2),d.push(p)}for(l=0;r.length>l;l++){var f=new n.Sprite(r[l].texture);f.position.x=(r[l].position.x+d[r[l].line])*h,f.position.y=r[l].position.y*h,f.scale.x=f.scale.y=h,this.addChild(f)}this.width=e.x*h,this.height=(e.y+t.lineHeight)*h},n.BitmapText.prototype.updateTransform=function(){if(this.dirty){for(;this.children.length>0;)this.removeChild(this.getChildAt(0));this.updateText(),this.dirty=!1}n.DisplayObjectContainer.prototype.updateTransform.call(this)},n.BitmapText.fonts={},n.InteractionManager=function(t){this.stage=t,this.tempPoint=new n.Point,this.mouseoverEnabled=!0,this.mouse=new n.InteractionData,this.touchs={},this.pool=[],this.interactiveItems=[],this.last=0},n.InteractionManager.constructor=n.InteractionManager,n.InteractionManager.prototype.collectInteractiveSprite=function(t,e){for(var i=t.children,r=i.length,n=r-1;n>=0;n--){var s=i[n];s.interactive?(e.interactiveChildren=!0,this.interactiveItems.push(s),s.children.length>0&&this.collectInteractiveSprite(s,s)):(s.__iParent=null,s.children.length>0&&this.collectInteractiveSprite(s,e))}},n.InteractionManager.prototype.setTarget=function(t){window.navigator.msPointerEnabled&&(t.view.style["-ms-content-zooming"]="none",t.view.style["-ms-touch-action"]="none"),this.target=t,t.view.addEventListener("mousemove",this.onMouseMove.bind(this),!0),t.view.addEventListener("mousedown",this.onMouseDown.bind(this),!0),document.body.addEventListener("mouseup",this.onMouseUp.bind(this),!0),t.view.addEventListener("mouseout",this.onMouseUp.bind(this),!0),t.view.addEventListener("touchstart",this.onTouchStart.bind(this),!0),t.view.addEventListener("touchend",this.onTouchEnd.bind(this),!0),t.view.addEventListener("touchmove",this.onTouchMove.bind(this),!0)},n.InteractionManager.prototype.update=function(){if(this.target){var t=Date.now(),e=t-this.last;if(e=30*e/1e3,!(1>e)){if(this.last=t,this.dirty){this.dirty=!1,this.interactiveItems.length;for(var i=0;this.interactiveItems.length>i;i++)this.interactiveItems[i].interactiveChildren=!1;this.interactiveItems=[],this.stage.interactive&&this.interactiveItems.push(this.stage),this.collectInteractiveSprite(this.stage,this.stage)}var r=this.interactiveItems.length;this.target.view.style.cursor="default";for(var i=0;r>i;i++){var n=this.interactiveItems[i];n.visible&&(n.mouseover||n.mouseout||n.buttonMode)&&(n.__hit=this.hitTest(n,this.mouse),n.__hit?(n.buttonMode&&(this.target.view.style.cursor="pointer"),n.__isOver||(n.mouseover&&n.mouseover(this.mouse),n.__isOver=!0)):n.__isOver&&(n.mouseout&&n.mouseout(this.mouse),n.__isOver=!1))}}}},n.InteractionManager.prototype.onMouseMove=function(t){t.preventDefault();var e=this.target.view.getBoundingClientRect();this.mouse.global.x=(t.clientX-e.left)*(this.target.width/e.width),this.mouse.global.y=(t.clientY-e.top)*(this.target.height/e.height);var i=this.interactiveItems.length;this.mouse.global;for(var r=0;i>r;r++){var n=this.interactiveItems[r];n.mousemove&&n.mousemove(this.mouse)}},n.InteractionManager.prototype.onMouseDown=function(t){t.preventDefault();var e=this.interactiveItems.length;this.mouse.global,this.stage;for(var i=0;e>i;i++){var r=this.interactiveItems[i];if((r.mousedown||r.click)&&(r.__mouseIsDown=!0,r.__hit=this.hitTest(r,this.mouse),r.__hit&&(r.mousedown&&r.mousedown(this.mouse),r.__isDown=!0,!r.interactiveChildren)))break}},n.InteractionManager.prototype.onMouseUp=function(t){t.preventDefault(),this.mouse.global;for(var e=this.interactiveItems.length,i=!1,r=0;e>r;r++){var n=this.interactiveItems[r];(n.mouseup||n.mouseupoutside||n.click)&&(n.__hit=this.hitTest(n,this.mouse),n.__hit&&!i?(n.mouseup&&n.mouseup(this.mouse),n.__isDown&&n.click&&n.click(this.mouse),n.interactiveChildren||(i=!0)):n.__isDown&&n.mouseupoutside&&n.mouseupoutside(this.mouse),n.__isDown=!1)}},n.InteractionManager.prototype.hitTest=function(t,e){var i=e.global;if(!t.visible)return!1;if(t instanceof n.Sprite){var r=t.worldTransform,s=r[0],a=r[1],o=r[2],h=r[3],l=r[4],u=r[5],c=1/(s*l+a*-h),d=l*c*i.x+-a*c*i.y+(u*a-o*l)*c,p=s*c*i.y+-h*c*i.x+(-u*s+o*h)*c,f=t.texture.frame.width,m=t.texture.frame.height,v=-f*t.anchor.x;if(d>v&&v+f>d){var g=-m*t.anchor.y;if(p>g&&g+m>p)return e.target=t,!0}}else if(t.hitArea){var r=t.worldTransform,x=t.hitArea,s=r[0],a=r[1],o=r[2],h=r[3],l=r[4],u=r[5],c=1/(s*l+a*-h),d=l*c*i.x+-a*c*i.y+(u*a-o*l)*c,p=s*c*i.y+-h*c*i.x+(-u*s+o*h)*c,v=x.x;if(d>v&&v+x.width>d){var g=x.y;if(p>g&&g+x.height>p)return!0}}for(var b=t.children.length,T=0;b>T;T++){var y=t.children[T],_=this.hitTest(y,e);if(_)return!0}return!1},n.InteractionManager.prototype.onTouchMove=function(t){t.preventDefault();for(var e=this.target.view.getBoundingClientRect(),i=t.changedTouches,r=0;i.length>r;r++){var n=i[r],s=this.touchs[n.identifier];s.global.x=(n.clientX-e.left)*(this.target.width/e.width),s.global.y=(n.clientY-e.top)*(this.target.height/e.height)}for(var a=this.interactiveItems.length,r=0;a>r;r++){var o=this.interactiveItems[r];o.touchmove&&o.touchmove(s)}},n.InteractionManager.prototype.onTouchStart=function(t){t.preventDefault();for(var e=this.target.view.getBoundingClientRect(),i=t.changedTouches,r=0;i.length>r;r++){var s=i[r],a=this.pool.pop();a||(a=new n.InteractionData),this.touchs[s.identifier]=a,a.global.x=(s.clientX-e.left)*(this.target.width/e.width),a.global.y=(s.clientY-e.top)*(this.target.height/e.height);for(var o=this.interactiveItems.length,h=0;o>h;h++){var l=this.interactiveItems[h];if((l.touchstart||l.tap)&&(l.__hit=this.hitTest(l,a),l.__hit&&(l.touchstart&&l.touchstart(a),l.__isDown=!0,l.__touchData=a,!l.interactiveChildren)))break}}},n.InteractionManager.prototype.onTouchEnd=function(t){t.preventDefault();for(var e=this.target.view.getBoundingClientRect(),i=t.changedTouches,r=0;i.length>r;r++){var n=i[r],s=this.touchs[n.identifier],a=!1;s.global.x=(n.clientX-e.left)*(this.target.width/e.width),s.global.y=(n.clientY-e.top)*(this.target.height/e.height);for(var o=this.interactiveItems.length,h=0;o>h;h++){var l=this.interactiveItems[h],u=l.__touchData;l.__hit=this.hitTest(l,s),u==s&&((l.touchend||l.tap)&&(l.__hit&&!a?(l.touchend&&l.touchend(s),l.__isDown&&l.tap&&l.tap(s),l.interactiveChildren||(a=!0)):l.__isDown&&l.touchendoutside&&l.touchendoutside(s),l.__isDown=!1),l.__touchData=null)}this.pool.push(s),this.touchs[n.identifier]=null}},n.InteractionData=function(){this.global=new n.Point,this.local=new n.Point,this.target},n.InteractionData.prototype.getLocalPosition=function(t){var e=t.worldTransform,i=this.global,r=e[0],s=e[1],a=e[2],o=e[3],h=e[4],l=e[5],u=1/(r*h+s*-o);return new n.Point(h*u*i.x+-s*u*i.y+(l*s-a*h)*u,r*u*i.y+-o*u*i.x+(-l*r+a*o)*u)},n.InteractionData.constructor=n.InteractionData,n.Stage=function(t,e){n.DisplayObjectContainer.call(this),this.worldTransform=n.mat3.create(),this.__childrenAdded=[],this.__childrenRemoved=[],this.childIndex=0,this.stage=this,this.stage.hitArea=new n.Rectangle(0,0,1e5,1e5),this.interactive=!!e,this.interactionManager=new n.InteractionManager(this),this.setBackgroundColor(t),this.worldVisible=!0,this.stage.dirty=!0},n.Stage.constructor=n.Stage,n.Stage.prototype=Object.create(n.DisplayObjectContainer.prototype),n.Stage.prototype.updateTransform=function(){this.worldAlpha=1;for(var t=0,e=this.children.length;e>t;t++)this.children[t].updateTransform();this.dirty&&(this.dirty=!1,this.interactionManager.dirty=!0),this.interactive&&this.interactionManager.update()},n.Stage.prototype.setBackgroundColor=function(e){this.backgroundColor=e||0,this.backgroundColorSplit=t(this.backgroundColor),this.backgroundColorString="#"+this.backgroundColor.toString(16)},n.Stage.prototype.getMousePosition=function(){return this.interactionManager.mouse.global},n.Stage.prototype.__addChild=function(t){if(t.interactive&&(this.dirty=!0),t.stage=this,t.children)for(var e=0;t.children.length>e;e++)this.__addChild(t.children[e])},n.Stage.prototype.__removeChild=function(t){if(t.interactive&&(this.dirty=!0),t.stage=void 0,t.children)for(var e=0,i=t.children.length;i>e;e++)this.__removeChild(t.children[e])},window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){window.setTimeout(t,1e3/60)}}(),"function"!=typeof Function.prototype.bind&&(Function.prototype.bind=function(){var t=Array.prototype.slice;return function(e){function i(){var s=n.concat(t.call(arguments));r.apply(this instanceof i?this:e,s)}var r=this,n=t.call(arguments,1);if("function"!=typeof r)throw new TypeError;return i.prototype=function s(t){return t&&(s.prototype=t),this instanceof s?void 0:new s}(r.prototype),i}}());var s=function(){var t=["Msxml2.XMLHTTP","Microsoft.XMLHTTP"];if(!window.ActiveXObject)return window.XMLHttpRequest?new XMLHttpRequest:!1;for(var e=0;t.length>e;e++)try{return new ActiveXObject(t[e])}catch(i){}};n.EventTarget=function(){var t={};this.addEventListener=this.on=function(e,i){void 0===t[e]&&(t[e]=[]),-1===t[e].indexOf(i)&&t[e].push(i)},this.dispatchEvent=this.emit=function(e){for(var i in t[e.type])t[e.type][i](e)},this.removeEventListener=this.off=function(e,i){var r=t[e].indexOf(i);-1!==r&&t[e].splice(r,1)}},e(),n.mat3={},n.mat3.create=function(){var t=new n.Matrix(9);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.mat4={},n.mat4.create=function(){var t=new n.Matrix(16);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.mat3.multiply=function(t,e,i){i||(i=t);var r=t[0],n=t[1],s=t[2],a=t[3],o=t[4],h=t[5],l=t[6],u=t[7],c=t[8],d=e[0],p=e[1],f=e[2],m=e[3],v=e[4],g=e[5],x=e[6],b=e[7],T=e[8];return i[0]=d*r+p*a+f*l,i[1]=d*n+p*o+f*u,i[2]=d*s+p*h+f*c,i[3]=m*r+v*a+g*l,i[4]=m*n+v*o+g*u,i[5]=m*s+v*h+g*c,i[6]=x*r+b*a+T*l,i[7]=x*n+b*o+T*u,i[8]=x*s+b*h+T*c,i},n.mat3.toMat4=function(t,e){return e||(e=n.mat4.create()),e[15]=1,e[14]=0,e[13]=0,e[12]=0,e[11]=0,e[10]=t[8],e[9]=t[7],e[8]=t[6],e[7]=0,e[6]=t[5],e[5]=t[4],e[4]=t[3],e[3]=0,e[2]=t[2],e[1]=t[1],e[0]=t[0],e},n.mat4.create=function(){var t=new n.Matrix(16);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.mat4.transpose=function(t,e){if(!e||t===e){var i=t[1],r=t[2],n=t[3],s=t[6],a=t[7],o=t[11];return t[1]=t[4],t[2]=t[8],t[3]=t[12],t[4]=i,t[6]=t[9],t[7]=t[13],t[8]=r,t[9]=s,t[11]=t[14],t[12]=n,t[13]=a,t[14]=o,t}return e[0]=t[0],e[1]=t[4],e[2]=t[8],e[3]=t[12],e[4]=t[1],e[5]=t[5],e[6]=t[9],e[7]=t[13],e[8]=t[2],e[9]=t[6],e[10]=t[10],e[11]=t[14],e[12]=t[3],e[13]=t[7],e[14]=t[11],e[15]=t[15],e},n.mat4.multiply=function(t,e,i){i||(i=t);var r=t[0],n=t[1],s=t[2],a=t[3],o=t[4],h=t[5],l=t[6],u=t[7],c=t[8],d=t[9],p=t[10],f=t[11],m=t[12],v=t[13],g=t[14],x=t[15],b=e[0],T=e[1],y=e[2],_=e[3];return i[0]=b*r+T*o+y*c+_*m,i[1]=b*n+T*h+y*d+_*v,i[2]=b*s+T*l+y*p+_*g,i[3]=b*a+T*u+y*f+_*x,b=e[4],T=e[5],y=e[6],_=e[7],i[4]=b*r+T*o+y*c+_*m,i[5]=b*n+T*h+y*d+_*v,i[6]=b*s+T*l+y*p+_*g,i[7]=b*a+T*u+y*f+_*x,b=e[8],T=e[9],y=e[10],_=e[11],i[8]=b*r+T*o+y*c+_*m,i[9]=b*n+T*h+y*d+_*v,i[10]=b*s+T*l+y*p+_*g,i[11]=b*a+T*u+y*f+_*x,b=e[12],T=e[13],y=e[14],_=e[15],i[12]=b*r+T*o+y*c+_*m,i[13]=b*n+T*h+y*d+_*v,i[14]=b*s+T*l+y*p+_*g,i[15]=b*a+T*u+y*f+_*x,i},n.autoDetectRenderer=function(t,e,i,r){t||(t=800),e||(e=600);var s=function(){try{return!!window.WebGLRenderingContext&&!!document.createElement("canvas").getContext("experimental-webgl")}catch(t){return!1}}();return s?new n.WebGLRenderer(t,e,i,r):new n.CanvasRenderer(t,e,i,r)},n.shaderFragmentSrc=["precision mediump float;","varying vec2 vTextureCoord;","varying float vColor;","uniform sampler2D uSampler;","void main(void) {","gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y));","gl_FragColor = gl_FragColor * vColor;","}"],n.shaderVertexSrc=["attribute vec2 aVertexPosition;","attribute vec2 aTextureCoord;","attribute float aColor;","uniform mat4 uMVMatrix;","varying vec2 vTextureCoord;","varying float vColor;","void main(void) {","gl_Position = uMVMatrix * vec4(aVertexPosition, 1.0, 1.0);","vTextureCoord = aTextureCoord;","vColor = aColor;","}"],n.CompileVertexShader=function(t,e){return n._CompileShader(t,e,t.VERTEX_SHADER)},n.CompileFragmentShader=function(t,e){return n._CompileShader(t,e,t.FRAGMENT_SHADER)},n._CompileShader=function(t,e,i){var r=e.join("\n"),n=t.createShader(i);return t.shaderSource(n,r),t.compileShader(n),t.getShaderParameter(n,t.COMPILE_STATUS)?n:(alert(t.getShaderInfoLog(n)),null)},n._defaultFrame=new n.Rectangle(0,0,1,1),n.gl,n.WebGLRenderer=function(t,e,i,r){this.transparent=!!r,this.width=t||800,this.height=e||600,this.view=i||document.createElement("canvas"),this.view.width=this.width,this.view.height=this.height;var s=this;this.view.addEventListener("webglcontextlost",function(t){s.handleContextLost(t)},!1),this.view.addEventListener("webglcontextrestored",function(t){s.handleContextRestored(t)},!1),this.batchs=[];try{n.gl=this.gl=this.view.getContext("experimental-webgl",{alpha:this.transparent,antialias:!1,premultipliedAlpha:!1})}catch(a){throw Error(" This browser does not support webGL. Try using the canvas renderer"+this)}this.initShaders();var o=this.gl;n.WebGLRenderer.gl=o,this.batch=new n.WebGLBatch(o),o.disable(o.DEPTH_TEST),o.disable(o.CULL_FACE),o.enable(o.BLEND),o.colorMask(!0,!0,!0,this.transparent),this.projectionMatrix=n.mat4.create(),this.resize(this.width,this.height),this.contextLost=!1,this.stageRenderGroup=new n.WebGLRenderGroup(this.gl)},n.WebGLRenderer.constructor=n.WebGLRenderer,n.WebGLRenderer.getBatch=function(){return 0==n._batchs.length?new n.WebGLBatch(n.WebGLRenderer.gl):n._batchs.pop()},n.WebGLRenderer.returnBatch=function(t){t.clean(),n._batchs.push(t)},n.WebGLRenderer.prototype.initShaders=function(){var t=this.gl,e=n.CompileFragmentShader(t,n.shaderFragmentSrc),i=n.CompileVertexShader(t,n.shaderVertexSrc);n.shaderProgram=t.createProgram();var r=n.shaderProgram;t.attachShader(r,i),t.attachShader(r,e),t.linkProgram(r),t.getProgramParameter(r,t.LINK_STATUS)||alert("Could not initialise shaders"),t.useProgram(r),r.vertexPositionAttribute=t.getAttribLocation(r,"aVertexPosition"),t.enableVertexAttribArray(r.vertexPositionAttribute),r.textureCoordAttribute=t.getAttribLocation(r,"aTextureCoord"),t.enableVertexAttribArray(r.textureCoordAttribute),r.colorAttribute=t.getAttribLocation(r,"aColor"),t.enableVertexAttribArray(r.colorAttribute),r.mvMatrixUniform=t.getUniformLocation(r,"uMVMatrix"),r.samplerUniform=t.getUniformLocation(r,"uSampler")},n.WebGLRenderer.prototype.render=function(t){if(!this.contextLost){this.__stage!==t&&(this.__stage=t,this.stageRenderGroup.setRenderable(t)),n.WebGLRenderer.updateTextures(),t.updateTransform();var e=this.gl;if(e.colorMask(!0,!0,!0,this.transparent),e.viewport(0,0,this.width,this.height),e.bindFramebuffer(e.FRAMEBUFFER,null),e.clearColor(t.backgroundColorSplit[0],t.backgroundColorSplit[1],t.backgroundColorSplit[2],!this.transparent),e.clear(e.COLOR_BUFFER_BIT),this.stageRenderGroup.backgroundColor=t.backgroundColorSplit,this.stageRenderGroup.render(this.projectionMatrix),t.interactive&&(t._interactiveEventsAdded||(t._interactiveEventsAdded=!0,t.interactionManager.setTarget(this))),n.Texture.frameUpdates.length>0){for(var i=0;n.Texture.frameUpdates.length>i;i++)n.Texture.frameUpdates[i].updateFrame=!1;n.Texture.frameUpdates=[]}}},n.WebGLRenderer.updateTextures=function(){for(var t=0;n.texturesToUpdate.length>t;t++)this.updateTexture(n.texturesToUpdate[t]);for(var t=0;n.texturesToDestroy.length>t;t++)this.destroyTexture(n.texturesToDestroy[t]);n.texturesToUpdate=[],n.texturesToDestroy=[]},n.WebGLRenderer.updateTexture=function(t){var e=n.gl;t._glTexture||(t._glTexture=e.createTexture()),t.hasLoaded&&(e.bindTexture(e.TEXTURE_2D,t._glTexture),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,t.source),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),t._powerOf2?(e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.REPEAT),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.REPEAT)):(e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE)),e.bindTexture(e.TEXTURE_2D,null))},n.WebGLRenderer.prototype.destroyTexture=function(t){var e=this.gl;t._glTexture&&(t._glTexture=e.createTexture(),e.deleteTexture(e.TEXTURE_2D,t._glTexture))},n.WebGLRenderer.prototype.resize=function(t,e){this.width=t,this.height=e,this.view.width=t,this.view.height=e,this.gl.viewport(0,0,this.width,this.height);var i=this.projectionMatrix;i[0]=2/this.width,i[5]=-2/this.height,i[12]=-1,i[13]=1},n.WebGLRenderer.prototype.handleContextLost=function(t){t.preventDefault(),this.contextLost=!0},n.WebGLRenderer.prototype.handleContextRestored=function(){this.gl=this.view.getContext("experimental-webgl",{alpha:!0}),this.initShaders();for(var t=0;n.TextureCache.length>t;t++)this.updateTexture(n.TextureCache[t]);for(var t=0;this.batchs.length>t;t++)this.batchs[t].restoreLostContext(this.gl),this.batchs[t].dirty=!0;n._restoreBatchs(this.gl),this.contextLost=!1},n._batchs=[],n._getBatch=function(t){return 0==n._batchs.length?new n.WebGLBatch(t):n._batchs.pop()},n._returnBatch=function(t){t.clean(),n._batchs.push(t)},n._restoreBatchs=function(t){for(var e=0;n._batchs.length>e;e++)n._batchs[e].restoreLostContext(t)},n.WebGLBatch=function(t){this.gl=t,this.size=0,this.vertexBuffer=t.createBuffer(),this.indexBuffer=t.createBuffer(),this.uvBuffer=t.createBuffer(),this.colorBuffer=t.createBuffer(),this.blendMode=n.blendModes.NORMAL,this.dynamicSize=1},n.WebGLBatch.constructor=n.WebGLBatch,n.WebGLBatch.prototype.clean=function(){this.verticies=[],this.uvs=[],this.indices=[],this.colors=[],this.dynamicSize=1,this.texture=null,this.last=null,this.size=0,this.head,this.tail},n.WebGLBatch.prototype.restoreLostContext=function(t){this.gl=t,this.vertexBuffer=t.createBuffer(),this.indexBuffer=t.createBuffer(),this.uvBuffer=t.createBuffer(),this.colorBuffer=t.createBuffer()},n.WebGLBatch.prototype.init=function(t){t.batch=this,this.dirty=!0,this.blendMode=t.blendMode,this.texture=t.texture.baseTexture,this.head=t,this.tail=t,this.size=1,this.growBatch()},n.WebGLBatch.prototype.insertBefore=function(t,e){this.size++,t.batch=this,this.dirty=!0;var i=e.__prev;e.__prev=t,t.__next=e,i?(t.__prev=i,i.__next=t):this.head=t},n.WebGLBatch.prototype.insertAfter=function(t,e){this.size++,t.batch=this,this.dirty=!0;var i=e.__next;e.__next=t,t.__prev=e,i?(t.__next=i,i.__prev=t):this.tail=t},n.WebGLBatch.prototype.remove=function(t){return this.size--,0==this.size?(t.batch=null,t.__prev=null,t.__next=null,void 0):(t.__prev?t.__prev.__next=t.__next:(this.head=t.__next,this.head.__prev=null),t.__next?t.__next.__prev=t.__prev:(this.tail=t.__prev,this.tail.__next=null),t.batch=null,t.__next=null,t.__prev=null,this.dirty=!0,void 0)},n.WebGLBatch.prototype.split=function(t){this.dirty=!0;var e=new n.WebGLBatch(this.gl);e.init(t),e.texture=this.texture,e.tail=this.tail,this.tail=t.__prev,this.tail.__next=null,t.__prev=null;for(var i=0;t;)i++,t.batch=e,t=t.__next;return e.size=i,this.size-=i,e},n.WebGLBatch.prototype.merge=function(t){this.dirty=!0,this.tail.__next=t.head,t.head.__prev=this.tail,this.size+=t.size,this.tail=t.tail;for(var e=t.head;e;)e.batch=this,e=e.__next},n.WebGLBatch.prototype.growBatch=function(){var t=this.gl;this.dynamicSize=1==this.size?1:1.5*this.size,this.verticies=new Float32Array(8*this.dynamicSize),t.bindBuffer(t.ARRAY_BUFFER,this.vertexBuffer),t.bufferData(t.ARRAY_BUFFER,this.verticies,t.DYNAMIC_DRAW),this.uvs=new Float32Array(8*this.dynamicSize),t.bindBuffer(t.ARRAY_BUFFER,this.uvBuffer),t.bufferData(t.ARRAY_BUFFER,this.uvs,t.DYNAMIC_DRAW),this.dirtyUVS=!0,this.colors=new Float32Array(4*this.dynamicSize),t.bindBuffer(t.ARRAY_BUFFER,this.colorBuffer),t.bufferData(t.ARRAY_BUFFER,this.colors,t.DYNAMIC_DRAW),this.dirtyColors=!0,this.indices=new Uint16Array(6*this.dynamicSize);for(var e=this.indices.length/6,i=0;e>i;i++){var r=6*i,n=4*i;this.indices[r+0]=n+0,this.indices[r+1]=n+1,this.indices[r+2]=n+2,this.indices[r+3]=n+0,this.indices[r+4]=n+2,this.indices[r+5]=n+3}t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,this.indexBuffer),t.bufferData(t.ELEMENT_ARRAY_BUFFER,this.indices,t.STATIC_DRAW)},n.WebGLBatch.prototype.refresh=function(){this.gl,this.dynamicSizer;r++)i=this.batchs[r],i instanceof n.WebGLBatch?this.batchs[r].render():i instanceof n.TilingSprite?i.visible&&this.renderTilingSprite(i,t):i instanceof n.Strip&&i.visible&&this.renderStrip(i,t)},n.WebGLRenderGroup.prototype.renderSpecific=function(t,e){n.WebGLRenderer.updateTextures();var i=this.gl;this.checkVisibility(t,t.visible),i.uniformMatrix4fv(n.shaderProgram.mvMatrixUniform,!1,e);var r,s,a,o,h=t.renderable?t:this.getNextRenderable(t),l=h.batch;if(h instanceof n.Sprite){l=h.batch;var u=l.head;if(u==h)r=0;else for(r=1;u.__next!=h;)r++,u=u.__next}else l=h;for(var c,d=t,p=t;p.children.length>0;)p=p.children[p.children.length-1],p.renderable&&(d=p);if(d instanceof n.Sprite){c=d.batch;var u=c.head;if(u==d)a=0;else for(a=1;u.__next!=d;)a++,u=u.__next}else c=d;if(l==c)return l instanceof n.WebGLBatch?l.render(r,a+1):l instanceof n.TilingSprite?l.visible&&this.renderTilingSprite(l,e):l instanceof n.Strip?l.visible&&this.renderStrip(l,e):l instanceof n.CustomRenderable&&l.visible&&l.renderWebGL(this,e),void 0;s=this.batchs.indexOf(l),o=this.batchs.indexOf(c),l instanceof n.WebGLBatch?l.render(r):l instanceof n.TilingSprite?l.visible&&this.renderTilingSprite(l,e):l instanceof n.Strip?l.visible&&this.renderStrip(l,e):l instanceof n.CustomRenderable&&l.visible&&l.renderWebGL(this,e);for(var f=s+1;o>f;f++)renderable=this.batchs[f],renderable instanceof n.WebGLBatch?this.batchs[f].render():renderable instanceof n.TilingSprite?renderable.visible&&this.renderTilingSprite(renderable,e):renderable instanceof n.Strip?renderable.visible&&this.renderStrip(renderable,e):renderable instanceof n.CustomRenderable&&renderable.visible&&renderable.renderWebGL(this,e);c instanceof n.WebGLBatch?c.render(0,a+1):c instanceof n.TilingSprite?c.visible&&this.renderTilingSprite(c):c instanceof n.Strip?c.visible&&this.renderStrip(c):c instanceof n.CustomRenderable&&c.visible&&c.renderWebGL(this,e)},n.WebGLRenderGroup.prototype.checkVisibility=function(t,e){for(var i=t.children,r=0;i.length>r;r++){var n=i[r];n.worldVisible=n.visible&&e,n.textureChange&&(n.textureChange=!1,n.worldVisible&&(this.removeDisplayObject(n),this.addDisplayObject(n))),n.children.length>0&&this.checkVisibility(n,n.worldVisible)}},n.WebGLRenderGroup.prototype.updateTexture=function(t){if(1==t.batch.length)return t.batch.texture=t.texture.baseTexture,void 0;if(t.batch.texture!=t.texture.baseTexture)if(t.batch.head==t){var e=t.batch,i=this.batchs.indexOf(e),r=this.batchs[i-1];if(e.remove(t),r)if(r.texture==t.texture.baseTexture&&r.blendMode==t.blendMode)r.insertAfter(t,r.tail);else{var s=n.WebGLRenderer.getBatch();s.init(t),this.batchs.splice(i-1,0,s)}else{var s=n.WebGLRenderer.getBatch();s.init(t),this.batchs.splice(0,0,s)}}else if(t.batch.tail==t){var e=t.batch,i=this.batchs.indexOf(e),a=this.batchs[i+1];if(e.remove(t),a){if(a.texture==t.texture.baseTexture&&a.blendMode==t.blendMode)return a.insertBefore(t,a.head),void 0;var s=n.WebGLRenderer.getBatch();s.init(t),this.batchs.splice(i+1,0,s)}else{var s=n.WebGLRenderer.getBatch();s.init(t),this.batchs.push(s)}}else{var e=t.batch,o=e.split(t);o.remove(t);var s=n.WebGLRenderer.getBatch(),i=this.batchs.indexOf(e);s.init(t),this.batchs.splice(i+1,0,s,o)}},n.WebGLRenderGroup.prototype.addDisplayObject=function(t){if(t.__renderGroup&&t.__renderGroup.removeDisplayObjectAndChildren(t),t.__renderGroup=this,t.renderable){var e=this.getPreviousRenderable(t),i=this.getNextRenderable(t);if(t instanceof n.Sprite){var r,s;if(e instanceof n.Sprite){if(r=e.batch,r&&r.texture==t.texture.baseTexture&&r.blendMode==t.blendMode)return r.insertAfter(t,e),void 0}else r=e;if(i)if(i instanceof n.Sprite){if(s=i.batch){if(s.texture==t.texture.baseTexture&&s.blendMode==t.blendMode)return s.insertBefore(t,i),void 0;if(s==r){var a=r.split(i),o=n.WebGLRenderer.getBatch(),h=this.batchs.indexOf(r);return o.init(t),this.batchs.splice(h+1,0,o,a),void 0}}}else s=i;var o=n.WebGLRenderer.getBatch();if(o.init(t),r){var h=this.batchs.indexOf(r);this.batchs.splice(h+1,0,o)}else this.batchs.push(o)}else t instanceof n.TilingSprite?(this.initTilingSprite(t),this.batchs.push(t)):t instanceof n.Strip&&(this.initStrip(t),this.batchs.push(t));this.batchUpdate=!0}},n.WebGLRenderGroup.prototype.addDisplayObjectAndChildren=function(t){this.addDisplayObject(t);for(var e=t.children,i=0;e.length>i;i++)this.addDisplayObjectAndChildren(e[i])},n.WebGLRenderGroup.prototype.removeDisplayObject=function(t){if(t.__renderGroup=null,t.renderable){var e;if(t instanceof n.Sprite){var i=t.batch;if(!i)return;i.remove(t),0==i.size&&(e=i)}else e=t;if(e){var r=this.batchs.indexOf(e);if(-1==r)return;if(0==r||r==this.batchs.length-1)return this.batchs.splice(r,1),e instanceof n.WebGLBatch&&n.WebGLRenderer.returnBatch(e),void 0;if(this.batchs[r-1]instanceof n.WebGLBatch&&this.batchs[r+1]instanceof n.WebGLBatch&&this.batchs[r-1].texture==this.batchs[r+1].texture&&this.batchs[r-1].blendMode==this.batchs[r+1].blendMode)return this.batchs[r-1].merge(this.batchs[r+1]),e instanceof n.WebGLBatch&&n.WebGLRenderer.returnBatch(e),n.WebGLRenderer.returnBatch(this.batchs[r+1]),this.batchs.splice(r,2),void 0;this.batchs.splice(r,1),e instanceof n.WebGLBatch&&n.WebGLRenderer.returnBatch(e)}}},n.WebGLRenderGroup.prototype.removeDisplayObjectAndChildren=function(t){if(t.__renderGroup==this){this.removeDisplayObject(t);for(var e=t.children,i=0;e.length>i;i++)this.removeDisplayObjectAndChildren(e[i])}},n.WebGLRenderGroup.prototype.getNextRenderable=function(t){var e=t;do{if(0==e.children.length){if(!e.parent)return null;for(;e.childIndex==e.parent.children.length-1;)if(e=e.parent,e==this.root||!e.parent){e=null;break}e&&(e=e.parent.children[e.childIndex+1])}else e=e.children[0];if(!e)break}while(!e.renderable||!e.__renderGroup);return e},n.WebGLRenderGroup.prototype.getPreviousRenderable=function(t){var e=t;do{if(0==e.childIndex){if(e=e.parent,!e)return null}else for(e=e.parent.children[e.childIndex-1];0!=e.children.length;)e=e.children[e.children.length-1];if(e==this.root)break}while(!e.renderable||!e.__renderGroup);return e},n.WebGLRenderGroup.prototype.initTilingSprite=function(t){var e=this.gl;t.verticies=new Float32Array([0,0,t.width,0,t.width,t.height,0,t.height]),t.uvs=new Float32Array([0,0,1,0,1,1,0,1]),t.colors=new Float32Array([1,1,1,1]),t.indices=new Uint16Array([0,1,3,2]),t._vertexBuffer=e.createBuffer(),t._indexBuffer=e.createBuffer(),t._uvBuffer=e.createBuffer(),t._colorBuffer=e.createBuffer(),e.bindBuffer(e.ARRAY_BUFFER,t._vertexBuffer),e.bufferData(e.ARRAY_BUFFER,t.verticies,e.STATIC_DRAW),e.bindBuffer(e.ARRAY_BUFFER,t._uvBuffer),e.bufferData(e.ARRAY_BUFFER,t.uvs,e.DYNAMIC_DRAW),e.bindBuffer(e.ARRAY_BUFFER,t._colorBuffer),e.bufferData(e.ARRAY_BUFFER,t.colors,e.STATIC_DRAW),e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,t._indexBuffer),e.bufferData(e.ELEMENT_ARRAY_BUFFER,t.indices,e.STATIC_DRAW),t.texture.baseTexture._glTexture?(e.bindTexture(e.TEXTURE_2D,t.texture.baseTexture._glTexture),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.REPEAT),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.REPEAT),t.texture.baseTexture._powerOf2=!0):t.texture.baseTexture._powerOf2=!0},n.WebGLRenderGroup.prototype.renderStrip=function(t,e){var i=this.gl,r=n.shaderProgram,s=n.mat3.toMat4(t.worldTransform);n.mat4.transpose(s),n.mat4.multiply(e,s,s),i.uniformMatrix4fv(r.mvMatrixUniform,!1,s),t.blendMode==n.blendModes.NORMAL?i.blendFunc(i.ONE,i.ONE_MINUS_SRC_ALPHA):i.blendFunc(i.ONE,i.ONE_MINUS_SRC_COLOR),t.dirty?(t.dirty=!1,i.bindBuffer(i.ARRAY_BUFFER,t._vertexBuffer),i.bufferData(i.ARRAY_BUFFER,t.verticies,i.STATIC_DRAW),i.vertexAttribPointer(r.vertexPositionAttribute,2,i.FLOAT,!1,0,0),i.bindBuffer(i.ARRAY_BUFFER,t._uvBuffer),i.bufferData(i.ARRAY_BUFFER,t.uvs,i.STATIC_DRAW),i.vertexAttribPointer(r.textureCoordAttribute,2,i.FLOAT,!1,0,0),i.activeTexture(i.TEXTURE0),i.bindTexture(i.TEXTURE_2D,t.texture.baseTexture._glTexture),i.bindBuffer(i.ARRAY_BUFFER,t._colorBuffer),i.bufferData(i.ARRAY_BUFFER,t.colors,i.STATIC_DRAW),i.vertexAttribPointer(r.colorAttribute,1,i.FLOAT,!1,0,0),i.bindBuffer(i.ELEMENT_ARRAY_BUFFER,t._indexBuffer),i.bufferData(i.ELEMENT_ARRAY_BUFFER,t.indices,i.STATIC_DRAW)):(i.bindBuffer(i.ARRAY_BUFFER,t._vertexBuffer),i.bufferSubData(i.ARRAY_BUFFER,0,t.verticies),i.vertexAttribPointer(r.vertexPositionAttribute,2,i.FLOAT,!1,0,0),i.bindBuffer(i.ARRAY_BUFFER,t._uvBuffer),i.vertexAttribPointer(r.textureCoordAttribute,2,i.FLOAT,!1,0,0),i.activeTexture(i.TEXTURE0),i.bindTexture(i.TEXTURE_2D,t.texture.baseTexture._glTexture),i.bindBuffer(i.ARRAY_BUFFER,t._colorBuffer),i.vertexAttribPointer(r.colorAttribute,1,i.FLOAT,!1,0,0),i.bindBuffer(i.ELEMENT_ARRAY_BUFFER,t._indexBuffer)),i.drawElements(i.TRIANGLE_STRIP,t.indices.length,i.UNSIGNED_SHORT,0),i.uniformMatrix4fv(r.mvMatrixUniform,!1,e)},n.WebGLRenderGroup.prototype.renderTilingSprite=function(t,e){var i=this.gl;n.shaderProgram;var r=t.tilePosition,s=t.tileScale,a=r.x/t.texture.baseTexture.width,o=r.y/t.texture.baseTexture.height,h=t.width/t.texture.baseTexture.width/s.x,l=t.height/t.texture.baseTexture.height/s.y;t.uvs[0]=0-a,t.uvs[1]=0-o,t.uvs[2]=1*h-a,t.uvs[3]=0-o,t.uvs[4]=1*h-a,t.uvs[5]=1*l-o,t.uvs[6]=0-a,t.uvs[7]=1*l-o,i.bindBuffer(i.ARRAY_BUFFER,t._uvBuffer),i.bufferSubData(i.ARRAY_BUFFER,0,t.uvs),this.renderStrip(t,e)},n.WebGLRenderer.prototype.initStrip=function(t){var e=this.gl;this.shaderProgram,t._vertexBuffer=e.createBuffer(),t._indexBuffer=e.createBuffer(),t._uvBuffer=e.createBuffer(),t._colorBuffer=e.createBuffer(),e.bindBuffer(e.ARRAY_BUFFER,t._vertexBuffer),e.bufferData(e.ARRAY_BUFFER,t.verticies,e.DYNAMIC_DRAW),e.bindBuffer(e.ARRAY_BUFFER,t._uvBuffer),e.bufferData(e.ARRAY_BUFFER,t.uvs,e.STATIC_DRAW),e.bindBuffer(e.ARRAY_BUFFER,t._colorBuffer),e.bufferData(e.ARRAY_BUFFER,t.colors,e.STATIC_DRAW),e.bindBuffer(e.ELEMENT_ARRAY_BUFFER,t._indexBuffer),e.bufferData(e.ELEMENT_ARRAY_BUFFER,t.indices,e.STATIC_DRAW)},n.CanvasRenderer=function(t,e,i,r){this.transparent=r,this.width=t||800,this.height=e||600,this.refresh=!0,this.view=i||document.createElement("canvas"),this.view.width=this.width,this.view.height=this.height,this.count=0,this.context=this.view.getContext("2d")},n.CanvasRenderer.constructor=n.CanvasRenderer,n.CanvasRenderer.prototype.render=function(t){n.texturesToUpdate=[],n.texturesToDestroy=[],t.updateTransform(),this.view.style.backgroundColor==t.backgroundColorString||this.transparent||(this.view.style.backgroundColor=t.backgroundColorString),this.context.setTransform(1,0,0,1,0,0),this.context.clearRect(0,0,this.width,this.height),this.renderDisplayObject(t),t.interactive&&(t._interactiveEventsAdded||(t._interactiveEventsAdded=!0,t.interactionManager.setTarget(this))),n.Texture.frameUpdates.length>0&&(n.Texture.frameUpdates=[])},n.CanvasRenderer.prototype.resize=function(t,e){this.width=t,this.height=e,this.view.width=t,this.view.height=e},n.CanvasRenderer.prototype.renderDisplayObject=function(t){var e=t.worldTransform,i=this.context;if(t.visible){if(t instanceof n.Sprite){var r=t.texture.frame;r&&(i.globalAlpha=t.worldAlpha,i.setTransform(e[0],e[3],e[1],e[4],e[2],e[5]),i.drawImage(t.texture.baseTexture.source,r.x,r.y,r.width,r.height,t.anchor.x*-r.width,t.anchor.y*-r.height,r.width,r.height))}else t instanceof n.Strip?(i.setTransform(e[0],e[3],e[1],e[4],e[2],e[5]),this.renderStrip(t)):t instanceof n.TilingSprite?(i.setTransform(e[0],e[3],e[1],e[4],e[2],e[5]),this.renderTilingSprite(t)):t instanceof n.CustomRenderable&&t.renderCanvas(this);for(var s=0;t.children.length>s;s++)this.renderDisplayObject(t.children[s]);this.context.setTransform(1,0,0,1,0,0)}},n.CanvasRenderer.prototype.renderStripFlat=function(t){var e=this.context,i=t.verticies;t.uvs;var r=i.length/2;this.count++,e.beginPath();for(var n=1;r-2>n;n++){var s=2*n,a=i[s],o=i[s+2],h=i[s+4],l=i[s+1],u=i[s+3],c=i[s+5];e.moveTo(a,l),e.lineTo(o,u),e.lineTo(h,c)}e.fillStyle="#FF0000",e.fill(),e.closePath()},n.CanvasRenderer.prototype.renderTilingSprite=function(t){var e=this.context;t.__tilePattern||(t.__tilePattern=e.createPattern(t.texture.baseTexture.source,"repeat")),e.beginPath();var i=t.tilePosition,r=t.tileScale;e.scale(r.x,r.y),e.translate(i.x,i.y),e.fillStyle=t.__tilePattern,e.fillRect(-i.x,-i.y,t.width/r.x,t.height/r.y),e.scale(1/r.x,1/r.y),e.translate(-i.x,-i.y),e.closePath()},n.CanvasRenderer.prototype.renderStrip=function(t){var e=this.context,i=t.verticies,r=t.uvs,n=i.length/2;this.count++;for(var s=1;n-2>s;s++){var a=2*s,o=i[a],h=i[a+2],l=i[a+4],u=i[a+1],c=i[a+3],d=i[a+5],p=r[a]*t.texture.width,f=r[a+2]*t.texture.width,m=r[a+4]*t.texture.width,v=r[a+1]*t.texture.height,g=r[a+3]*t.texture.height,x=r[a+5]*t.texture.height;e.save(),e.beginPath(),e.moveTo(o,u),e.lineTo(h,c),e.lineTo(l,d),e.closePath(),e.clip();var b=p*g+v*m+f*x-g*m-v*f-p*x,T=o*g+v*l+h*x-g*l-v*h-o*x,y=p*h+o*m+f*l-h*m-o*f-p*l,_=p*g*l+v*h*m+o*f*x-o*g*m-v*f*l-p*h*x,w=u*g+v*d+c*x-g*d-v*c-u*x,R=p*c+u*m+f*d-c*m-u*f-p*d,A=p*g*d+v*c*m+u*f*x-u*g*m-v*f*d-p*c*x;e.transform(T/b,w/b,y/b,R/b,_/b,A/b),e.drawImage(t.texture.baseTexture.source,0,0),e.restore()}},n.Strip=function(t,e,i){n.DisplayObjectContainer.call(this),this.texture=t,this.blendMode=n.blendModes.NORMAL;try{this.uvs=new Float32Array([0,1,1,1,1,0,0,1]),this.verticies=new Float32Array([0,0,0,0,0,0,0,0,0]),this.colors=new Float32Array([1,1,1,1]),this.indices=new Uint16Array([0,1,2,3])}catch(r){this.uvs=[0,1,1,1,1,0,0,1],this.verticies=[0,0,0,0,0,0,0,0,0],this.colors=[1,1,1,1],this.indices=[0,1,2,3]}this.width=e,this.height=i,t.baseTexture.hasLoaded?(this.width=this.texture.frame.width,this.height=this.texture.frame.height,this.updateFrame=!0):(this.onTextureUpdateBind=this.onTextureUpdate.bind(this),this.texture.addEventListener("update",this.onTextureUpdateBind)),this.renderable=!0},n.Strip.constructor=n.Strip,n.Strip.prototype=Object.create(n.DisplayObjectContainer.prototype),n.Strip.prototype.setTexture=function(t){this.texture=t,this.width=t.frame.width,this.height=t.frame.height,this.updateFrame=!0},n.Strip.prototype.onTextureUpdate=function(){this.updateFrame=!0},n.Rope=function(t,e){n.Strip.call(this,t),this.points=e;try{this.verticies=new Float32Array(4*e.length),this.uvs=new Float32Array(4*e.length),this.colors=new Float32Array(2*e.length),this.indices=new Uint16Array(2*e.length)}catch(i){this.verticies=verticies,this.uvs=uvs,this.colors=colors,this.indices=indices}this.refresh()},n.Rope.constructor=n.Rope,n.Rope.prototype=Object.create(n.Strip.prototype),n.Rope.prototype.refresh=function(){var t=this.points;if(!(1>t.length)){var e=this.uvs,i=this.indices,r=this.colors,n=t[0],s=t[0];this.count-=.2,e[0]=0,e[1]=1,e[2]=0,e[3]=1,r[0]=1,r[1]=1,i[0]=0,i[1]=1;for(var a=t.length,o=1;a>o;o++){var s=t[o],h=4*o,l=o/(a-1);o%2?(e[h]=l,e[h+1]=0,e[h+2]=l,e[h+3]=1):(e[h]=l,e[h+1]=0,e[h+2]=l,e[h+3]=1),h=2*o,r[h]=1,r[h+1]=1,h=2*o,i[h]=h,i[h+1]=h+1,n=s}}},n.Rope.prototype.updateTransform=function(){var t=this.points;if(!(1>t.length)){var e,i=this.verticies,r=t[0],s={x:0,y:0},a=t[0];this.count-=.2,i[0]=a.x+s.x,i[1]=a.y+s.y,i[2]=a.x-s.x,i[3]=a.y-s.y;for(var o=t.length,h=1;o>h;h++){var a=t[h],l=4*h;e=t.length-1>h?t[h+1]:a,s.y=-(e.x-r.x),s.x=e.y-r.y;var u=10*(1-h/(o-1));u>1&&(u=1);var c=Math.sqrt(s.x*s.x+s.y*s.y),d=this.texture.height/2;s.x/=c,s.y/=c,s.x*=d,s.y*=d,i[l]=a.x+s.x,i[l+1]=a.y+s.y,i[l+2]=a.x-s.x,i[l+3]=a.y-s.y,r=a}n.DisplayObjectContainer.prototype.updateTransform.call(this)}},n.Rope.prototype.setTexture=function(t){this.texture=t,this.updateFrame=!0},n.TilingSprite=function(t,e,i){n.DisplayObjectContainer.call(this),this.texture=t,this.width=e,this.height=i,this.renderable=!0,this.tileScale=new n.Point(1,1),this.tilePosition=new n.Point(0,0),this.blendMode=n.blendModes.NORMAL},n.TilingSprite.constructor=n.TilingSprite,n.TilingSprite.prototype=Object.create(n.DisplayObjectContainer.prototype),n.TilingSprite.prototype.setTexture=function(t){this.texture=t,this.updateFrame=!0},n.TilingSprite.prototype.onTextureUpdate=function(){this.updateFrame=!0},n.Spine=function(t){if(n.DisplayObjectContainer.call(this),this.spineData=n.AnimCache[t],!this.spineData)throw Error("Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: "+t);this.count=0,this.sprites=[],this.skeleton=new o.Skeleton(this.spineData),this.skeleton.updateWorldTransform(),this.stateData=new o.AnimationStateData(this.spineData),this.state=new o.AnimationState(this.stateData);for(var e=0;this.skeleton.drawOrder.length>e;e++){var i=this.skeleton.drawOrder[e].data.attachmentName;n.TextureCache[i]||(i+=".png");var r=new n.Sprite(n.Texture.fromFrame(i));r.anchor.x=r.anchor.y=.5,this.addChild(r),this.sprites.push(r)}},n.Spine.constructor=n.Spine,n.Spine.prototype=Object.create(n.DisplayObjectContainer.prototype),n.Spine.prototype.updateTransform=function(){this.state.update(1/60),this.state.apply(this.skeleton),this.skeleton.updateWorldTransform();for(var t=0;this.skeleton.drawOrder.length>t;t++){var e=this.skeleton.drawOrder[t],i=e.bone.worldX+e.attachment.x*e.bone.m00+e.attachment.y*e.bone.m01+.5*e.attachment.width,r=e.bone.worldY+e.attachment.x*e.bone.m10+e.attachment.y*e.bone.m11+.5*e.attachment.height;if(e.cacheName!=e.attachment.name){var s=e.attachment.name;n.TextureCache[s]||(s+=".png"),this.sprites[t].setTexture(n.TextureCache[s]),e.cacheName=e.attachment.name}i+=-(e.attachment.width*(e.bone.worldScaleX+e.attachment.scaleX-1)>>1),r+=-(e.attachment.height*(e.bone.worldScaleY+e.attachment.scaleY-1)>>1),this.sprites[t].position.x=i,this.sprites[t].position.y=r,this.sprites[t].rotation=-(e.bone.worldRotation+e.attachment.rotation)*(Math.PI/180)}n.DisplayObjectContainer.prototype.updateTransform.call(this)};var o={};o.BoneData=function(t,e){this.name=t,this.parent=e},o.BoneData.prototype={length:0,x:0,y:0,rotation:0,scaleX:1,scaleY:1},o.SlotData=function(t,e){this.name=t,this.boneData=e},o.SlotData.prototype={r:1,g:1,b:1,a:1,attachmentName:null},o.Bone=function(t,e){this.data=t,this.parent=e,this.setToSetupPose()},o.Bone.yDown=!1,o.Bone.prototype={x:0,y:0,rotation:0,scaleX:1,scaleY:1,m00:0,m01:0,worldX:0,m10:0,m11:0,worldY:0,worldRotation:0,worldScaleX:1,worldScaleY:1,updateWorldTransform:function(t,e){var i=this.parent;null!=i?(this.worldX=this.x*i.m00+this.y*i.m01+i.worldX,this.worldY=this.x*i.m10+this.y*i.m11+i.worldY,this.worldScaleX=i.worldScaleX*this.scaleX,this.worldScaleY=i.worldScaleY*this.scaleY,this.worldRotation=i.worldRotation+this.rotation):(this.worldX=this.x,this.worldY=this.y,this.worldScaleX=this.scaleX,this.worldScaleY=this.scaleY,this.worldRotation=this.rotation);var r=this.worldRotation*Math.PI/180,n=Math.cos(r),s=Math.sin(r);this.m00=n*this.worldScaleX,this.m10=s*this.worldScaleX,this.m01=-s*this.worldScaleY,this.m11=n*this.worldScaleY,t&&(this.m00=-this.m00,this.m01=-this.m01),e&&(this.m10=-this.m10,this.m11=-this.m11),o.Bone.yDown&&(this.m10=-this.m10,this.m11=-this.m11)},setToSetupPose:function(){var t=this.data;this.x=t.x,this.y=t.y,this.rotation=t.rotation,this.scaleX=t.scaleX,this.scaleY=t.scaleY}},o.Slot=function(t,e,i){this.data=t,this.skeleton=e,this.bone=i,this.setToSetupPose()},o.Slot.prototype={r:1,g:1,b:1,a:1,_attachmentTime:0,attachment:null,setAttachment:function(t){this.attachment=t,this._attachmentTime=this.skeleton.time},setAttachmentTime:function(t){this._attachmentTime=this.skeleton.time-t},getAttachmentTime:function(){return this.skeleton.time-this._attachmentTime},setToSetupPose:function(){var t=this.data;this.r=t.r,this.g=t.g,this.b=t.b,this.a=t.a;for(var e=this.skeleton.data.slots,i=0,r=e.length;r>i;i++)if(e[i]==t){this.setAttachment(t.attachmentName?this.skeleton.getAttachmentBySlotIndex(i,t.attachmentName):null);break}}},o.Skin=function(t){this.name=t,this.attachments={}},o.Skin.prototype={addAttachment:function(t,e,i){this.attachments[t+":"+e]=i},getAttachment:function(t,e){return this.attachments[t+":"+e]},_attachAll:function(t,e){for(var i in e.attachments){var r=i.indexOf(":"),n=parseInt(i.substring(0,r)),s=i.substring(r+1),a=t.slots[n];if(a.attachment&&a.attachment.name==s){var o=this.getAttachment(n,s);o&&a.setAttachment(o)}}}},o.Animation=function(t,e,i){this.name=t,this.timelines=e,this.duration=i},o.Animation.prototype={apply:function(t,e,i){i&&0!=this.duration&&(e%=this.duration);for(var r=this.timelines,n=0,s=r.length;s>n;n++)r[n].apply(t,e,1)},mix:function(t,e,i,r){i&&0!=this.duration&&(e%=this.duration);for(var n=this.timelines,s=0,a=n.length;a>s;s++)n[s].apply(t,e,r)}},o.binarySearch=function(t,e,i){var r=0,n=Math.floor(t.length/i)-2;if(0==n)return i;for(var s=n>>>1;;){if(e>=t[(s+1)*i]?r=s+1:n=s,r==n)return(r+1)*i;s=r+n>>>1}},o.linearSearch=function(t,e,i){for(var r=0,n=t.length-i;n>=r;r+=i)if(t[r]>e)return r;return-1},o.Curves=function(t){this.curves=[],this.curves.length=6*(t-1)},o.Curves.prototype={setLinear:function(t){this.curves[6*t]=0},setStepped:function(t){this.curves[6*t]=-1},setCurve:function(t,e,i,r,n){var s=.1,a=s*s,o=a*s,h=3*s,l=3*a,u=6*a,c=6*o,d=2*-e+r,p=2*-i+n,f=3*(e-r)+1,m=3*(i-n)+1,v=6*t,g=this.curves;g[v]=e*h+d*l+f*o,g[v+1]=i*h+p*l+m*o,g[v+2]=d*u+f*c,g[v+3]=p*u+m*c,g[v+4]=f*c,g[v+5]=m*c},getCurvePercent:function(t,e){e=0>e?0:e>1?1:e;var i=6*t,r=this.curves,n=r[i];if(!n)return e;if(-1==n)return 0;for(var s=r[i+1],a=r[i+2],o=r[i+3],h=r[i+4],l=r[i+5],u=n,c=s,d=8;;){if(u>=e){var p=u-n,f=c-s;return f+(c-f)*(e-p)/(u-p)}if(0==d)break;d--,n+=a,s+=o,a+=h,o+=l,u+=n,c+=s}return c+(1-c)*(e-u)/(1-u)}},o.RotateTimeline=function(t){this.curves=new o.Curves(t),this.frames=[],this.frames.length=2*t},o.RotateTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/2},setFrame:function(t,e,i){t*=2,this.frames[t]=e,this.frames[t+1]=i},apply:function(t,e,i){var r=this.frames;if(!(r[0]>e)){var n=t.bones[this.boneIndex];if(e>=r[r.length-2]){for(var s=n.data.rotation+r[r.length-1]-n.rotation;s>180;)s-=360;for(;-180>s;)s+=360;return n.rotation+=s*i,void 0}var a=o.binarySearch(r,e,2),h=r[a-1],l=r[a],u=1-(e-l)/(r[a-2]-l);u=this.curves.getCurvePercent(a/2-1,u);for(var s=r[a+1]-h;s>180;)s-=360;for(;-180>s;)s+=360;for(s=n.data.rotation+(h+s*u)-n.rotation;s>180;)s-=360;for(;-180>s;)s+=360;n.rotation+=s*i}}},o.TranslateTimeline=function(t){this.curves=new o.Curves(t),this.frames=[],this.frames.length=3*t},o.TranslateTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/3},setFrame:function(t,e,i,r){t*=3,this.frames[t]=e,this.frames[t+1]=i,this.frames[t+2]=r},apply:function(t,e,i){var r=this.frames;if(!(r[0]>e)){var n=t.bones[this.boneIndex];if(e>=r[r.length-3])return n.x+=(n.data.x+r[r.length-2]-n.x)*i,n.y+=(n.data.y+r[r.length-1]-n.y)*i,void 0;var s=o.binarySearch(r,e,3),a=r[s-2],h=r[s-1],l=r[s],u=1-(e-l)/(r[s+-3]-l);u=this.curves.getCurvePercent(s/3-1,u),n.x+=(n.data.x+a+(r[s+1]-a)*u-n.x)*i,n.y+=(n.data.y+h+(r[s+2]-h)*u-n.y)*i}}},o.ScaleTimeline=function(t){this.curves=new o.Curves(t),this.frames=[],this.frames.length=3*t},o.ScaleTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/3},setFrame:function(t,e,i,r){t*=3,this.frames[t]=e,this.frames[t+1]=i,this.frames[t+2]=r},apply:function(t,e,i){var r=this.frames;if(!(r[0]>e)){var n=t.bones[this.boneIndex];if(e>=r[r.length-3])return n.scaleX+=(n.data.scaleX-1+r[r.length-2]-n.scaleX)*i,n.scaleY+=(n.data.scaleY-1+r[r.length-1]-n.scaleY)*i,void 0;var s=o.binarySearch(r,e,3),a=r[s-2],h=r[s-1],l=r[s],u=1-(e-l)/(r[s+-3]-l);u=this.curves.getCurvePercent(s/3-1,u),n.scaleX+=(n.data.scaleX-1+a+(r[s+1]-a)*u-n.scaleX)*i,n.scaleY+=(n.data.scaleY-1+h+(r[s+2]-h)*u-n.scaleY)*i}}},o.ColorTimeline=function(t){this.curves=new o.Curves(t),this.frames=[],this.frames.length=5*t},o.ColorTimeline.prototype={slotIndex:0,getFrameCount:function(){return this.frames.length/2},setFrame:function(t,e){t*=5,this.frames[t]=e,this.frames[t+1]=r,this.frames[t+2]=g,this.frames[t+3]=b,this.frames[t+4]=a},apply:function(t,e,i){var r=this.frames;if(!(r[0]>e)){var n=t.slots[this.slotIndex];if(e>=r[r.length-5]){var s=r.length-1;return n.r=r[s-3],n.g=r[s-2],n.b=r[s-1],n.a=r[s],void 0}var a=o.binarySearch(r,e,5),h=r[a-4],l=r[a-3],u=r[a-2],c=r[a-1],d=r[a],p=1-(e-d)/(r[a-5]-d);p=this.curves.getCurvePercent(a/5-1,p);var f=h+(r[a+1]-h)*p,m=l+(r[a+2]-l)*p,v=u+(r[a+3]-u)*p,g=c+(r[a+4]-c)*p;1>i?(n.r+=(f-n.r)*i,n.g+=(m-n.g)*i,n.b+=(v-n.b)*i,n.a+=(g-n.a)*i):(n.r=f,n.g=m,n.b=v,n.a=g)}}},o.AttachmentTimeline=function(t){this.curves=new o.Curves(t),this.frames=[],this.frames.length=t,this.attachmentNames=[],this.attachmentNames.length=t},o.AttachmentTimeline.prototype={slotIndex:0,getFrameCount:function(){return this.frames.length/2},setFrame:function(t,e,i){this.frames[t]=e,this.attachmentNames[t]=i},apply:function(t,e){var i=this.frames;if(!(i[0]>e)){var r;r=e>=i[i.length-1]?i.length-1:o.binarySearch(i,e,1)-1;var n=this.attachmentNames[r];t.slots[this.slotIndex].setAttachment(n?t.getAttachmentBySlotIndex(this.slotIndex,n):null)}}},o.SkeletonData=function(){this.bones=[],this.slots=[],this.skins=[],this.animations=[]},o.SkeletonData.prototype={defaultSkin:null,findBone:function(t){for(var e=this.bones,i=0,r=e.length;r>i;i++)if(e[i].name==t)return e[i];return null},findBoneIndex:function(t){for(var e=this.bones,i=0,r=e.length;r>i;i++)if(e[i].name==t)return i;return-1},findSlot:function(t){for(var e=this.slots,i=0,r=e.length;r>i;i++)if(e[i].name==t)return slot[i];return null},findSlotIndex:function(t){for(var e=this.slots,i=0,r=e.length;r>i;i++)if(e[i].name==t)return i;return-1},findSkin:function(t){for(var e=this.skins,i=0,r=e.length;r>i;i++)if(e[i].name==t)return e[i];return null},findAnimation:function(t){for(var e=this.animations,i=0,r=e.length;r>i;i++)if(e[i].name==t)return e[i];return null}},o.Skeleton=function(t){this.data=t,this.bones=[];for(var e=0,i=t.bones.length;i>e;e++){var r=t.bones[e],n=r.parent?this.bones[t.bones.indexOf(r.parent)]:null;this.bones.push(new o.Bone(r,n))}this.slots=[],this.drawOrder=[];for(var e=0,i=t.slots.length;i>e;e++){var s=t.slots[e],a=this.bones[t.bones.indexOf(s.boneData)],h=new o.Slot(s,this,a);this.slots.push(h),this.drawOrder.push(h)}},o.Skeleton.prototype={x:0,y:0,skin:null,r:1,g:1,b:1,a:1,time:0,flipX:!1,flipY:!1,updateWorldTransform:function(){for(var t=this.flipX,e=this.flipY,i=this.bones,r=0,n=i.length;n>r;r++)i[r].updateWorldTransform(t,e)},setToSetupPose:function(){this.setBonesToSetupPose(),this.setSlotsToSetupPose()},setBonesToSetupPose:function(){for(var t=this.bones,e=0,i=t.length;i>e;e++)t[e].setToSetupPose()},setSlotsToSetupPose:function(){for(var t=this.slots,e=0,i=t.length;i>e;e++)t[e].setToSetupPose(e)},getRootBone:function(){return 0==this.bones.length?null:this.bones[0]},findBone:function(t){for(var e=this.bones,i=0,r=e.length;r>i;i++)if(e[i].data.name==t)return e[i];return null},findBoneIndex:function(t){for(var e=this.bones,i=0,r=e.length;r>i;i++)if(e[i].data.name==t)return i;return-1},findSlot:function(t){for(var e=this.slots,i=0,r=e.length;r>i;i++)if(e[i].data.name==t)return e[i];return null},findSlotIndex:function(t){for(var e=this.slots,i=0,r=e.length;r>i;i++)if(e[i].data.name==t)return i;return-1},setSkinByName:function(t){var e=this.data.findSkin(t);if(!e)throw"Skin not found: "+t;this.setSkin(e)},setSkin:function(t){this.skin&&t&&t._attachAll(this,this.skin),this.skin=t},getAttachmentBySlotName:function(t,e){return this.getAttachmentBySlotIndex(this.data.findSlotIndex(t),e)},getAttachmentBySlotIndex:function(t,e){if(this.skin){var i=this.skin.getAttachment(t,e);if(i)return i}return this.data.defaultSkin?this.data.defaultSkin.getAttachment(t,e):null},setAttachment:function(t,e){for(var i=this.slots,r=0,n=i.size;n>r;r++){var s=i[r];if(s.data.name==t){var a=null;if(e&&(a=this.getAttachment(r,e),null==a))throw"Attachment not found: "+e+", for slot: "+t;return s.setAttachment(a),void 0}}throw"Slot not found: "+t},update:function(t){time+=t}},o.AttachmentType={region:0},o.RegionAttachment=function(){this.offset=[],this.offset.length=8,this.uvs=[],this.uvs.length=8},o.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(t,e,i,r,n){var s=this.uvs;n?(s[2]=t,s[3]=r,s[4]=t,s[5]=e,s[6]=i,s[7]=e,s[0]=i,s[1]=r):(s[0]=t,s[1]=r,s[2]=t,s[3]=e,s[4]=i,s[5]=e,s[6]=i,s[7]=r)},updateOffset:function(){var t=this.width/this.regionOriginalWidth*this.scaleX,e=this.height/this.regionOriginalHeight*this.scaleY,i=-this.width/2*this.scaleX+this.regionOffsetX*t,r=-this.height/2*this.scaleY+this.regionOffsetY*e,n=i+this.regionWidth*t,s=r+this.regionHeight*e,a=this.rotation*Math.PI/180,o=Math.cos(a),h=Math.sin(a),l=i*o+this.x,u=i*h,c=r*o+this.y,d=r*h,p=n*o+this.x,f=n*h,m=s*o+this.y,v=s*h,g=this.offset;g[0]=l-d,g[1]=c+u,g[2]=l-v,g[3]=m+u,g[4]=p-v,g[5]=m+f,g[6]=p-d,g[7]=c+f},computeVertices:function(t,e,i,r){t+=i.worldX,e+=i.worldY;var n=i.m00,s=i.m01,a=i.m10,o=i.m11,h=this.offset;r[0]=h[0]*n+h[1]*s+t,r[1]=h[0]*a+h[1]*o+e,r[2]=h[2]*n+h[3]*s+t,r[3]=h[2]*a+h[3]*o+e,r[4]=h[4]*n+h[5]*s+t,r[5]=h[4]*a+h[5]*o+e,r[6]=h[6]*n+h[7]*s+t,r[7]=h[6]*a+h[7]*o+e}},o.AnimationStateData=function(t){this.skeletonData=t,this.animationToMixTime={}},o.AnimationStateData.prototype={setMixByName:function(t,e,i){var r=this.skeletonData.findAnimation(t);if(!r)throw"Animation not found: "+t;var n=this.skeletonData.findAnimation(e);if(!n)throw"Animation not found: "+e;this.setMix(r,n,i)},setMix:function(t,e,i){this.animationToMixTime[t.name+":"+e.name]=i},getMix:function(t,e){var i=this.animationToMixTime[t.name+":"+e.name];return i?i:0}},o.AnimationState=function(t){this.data=t,this.queue=[]},o.AnimationState.prototype={current:null,previous:null,currentTime:0,previousTime:0,currentLoop:!1,previousLoop:!1,mixTime:0,mixDuration:0,update:function(t){if(this.currentTime+=t,this.previousTime+=t,this.mixTime+=t,this.queue.length>0){var e=this.queue[0];this.currentTime>=e.delay&&(this._setAnimation(e.animation,e.loop),this.queue.shift()) +}},apply:function(t){if(this.current)if(this.previous){this.previous.apply(t,this.previousTime,this.previousLoop);var e=this.mixTime/this.mixDuration;e>=1&&(e=1,this.previous=null),this.current.mix(t,this.currentTime,this.currentLoop,e)}else this.current.apply(t,this.currentTime,this.currentLoop)},clearAnimation:function(){this.previous=null,this.current=null,this.queue.length=0},_setAnimation:function(t,e){this.previous=null,t&&this.current&&(this.mixDuration=this.data.getMix(this.current,t),this.mixDuration>0&&(this.mixTime=0,this.previous=this.current,this.previousTime=this.currentTime,this.previousLoop=this.currentLoop)),this.current=t,this.currentLoop=e,this.currentTime=0},setAnimationByName:function(t,e){var i=this.data.skeletonData.findAnimation(t);if(!i)throw"Animation not found: "+t;this.setAnimation(i,e)},setAnimation:function(t,e){this.queue.length=0,this._setAnimation(t,e)},addAnimationByName:function(t,e,i){var r=this.data.skeletonData.findAnimation(t);if(!r)throw"Animation not found: "+t;this.addAnimation(r,e,i)},addAnimation:function(t,e,i){var r={};if(r.animation=t,r.loop=e,!i||0>=i){var n=0==this.queue.length?this.current:this.queue[this.queue.length-1].animation;i=null!=n?n.duration-this.data.getMix(n,t)+(i||0):0}r.delay=i,this.queue.push(r)},isComplete:function(){return!this.current||this.currentTime>=this.current.duration}},o.SkeletonJson=function(t){this.attachmentLoader=t},o.SkeletonJson.prototype={scale:1,readSkeletonData:function(t){for(var e=new o.SkeletonData,i=t.bones,r=0,n=i.length;n>r;r++){var s=i[r],a=null;if(s.parent&&(a=e.findBone(s.parent),!a))throw"Parent bone not found: "+s.parent;var h=new o.BoneData(s.name,a);h.length=(s.length||0)*this.scale,h.x=(s.x||0)*this.scale,h.y=(s.y||0)*this.scale,h.rotation=s.rotation||0,h.scaleX=s.scaleX||1,h.scaleY=s.scaleY||1,e.bones.push(h)}for(var l=t.slots,r=0,n=l.length;n>r;r++){var u=l[r],h=e.findBone(u.bone);if(!h)throw"Slot bone not found: "+u.bone;var c=new o.SlotData(u.name,h),d=u.color;d&&(c.r=o.SkeletonJson.toColor(d,0),c.g=o.SkeletonJson.toColor(d,1),c.b=o.SkeletonJson.toColor(d,2),c.a=o.SkeletonJson.toColor(d,3)),c.attachmentName=u.attachment,e.slots.push(c)}var p=t.skins;for(var f in p)if(p.hasOwnProperty(f)){var m=p[f],v=new o.Skin(f);for(var g in m)if(m.hasOwnProperty(g)){var x=e.findSlotIndex(g),b=m[g];for(var T in b)if(b.hasOwnProperty(T)){var y=this.readAttachment(v,T,b[T]);null!=y&&v.addAttachment(x,T,y)}}e.skins.push(v),"default"==v.name&&(e.defaultSkin=v)}var _=t.animations;for(var w in _)_.hasOwnProperty(w)&&this.readAnimation(w,_[w],e);return e},readAttachment:function(t,e,i){e=i.name||e;var r=o.AttachmentType[i.type||"region"],n=new o.RegionAttachment;return n.name=e,r==o.AttachmentType.region&&(n.x=(i.x||0)*this.scale,n.y=(i.y||0)*this.scale,n.scaleX=i.scaleX||1,n.scaleY=i.scaleY||1,n.rotation=i.rotation||0,n.width=(i.width||32)*this.scale,n.height=(i.height||32)*this.scale,n.updateOffset()),n},readAnimation:function(t,e,i){var r=[],n=0,s=e.bones;for(var a in s)if(s.hasOwnProperty(a)){var h=i.findBoneIndex(a);if(-1==h)throw"Bone not found: "+a;var l=s[a];for(var u in l)if(l.hasOwnProperty(u)){var c=l[u];if("rotate"==u){var d=new o.RotateTimeline(c.length);d.boneIndex=h;for(var p=0,f=0,m=c.length;m>f;f++){var v=c[f];d.setFrame(p,v.time,v.angle),o.SkeletonJson.readCurve(d,p,v),p++}r.push(d),n=Math.max(n,d.frames[2*d.getFrameCount()-2])}else{if("translate"!=u&&"scale"!=u)throw"Invalid timeline type for a bone: "+u+" ("+a+")";var d,g=1;"scale"==u?d=new o.ScaleTimeline(c.length):(d=new o.TranslateTimeline(c.length),g=this.scale),d.boneIndex=h;for(var p=0,f=0,m=c.length;m>f;f++){var v=c[f],x=(v.x||0)*g,b=(v.y||0)*g;d.setFrame(p,v.time,x,b),o.SkeletonJson.readCurve(d,p,v),p++}r.push(d),n=Math.max(n,d.frames[3*d.getFrameCount()-3])}}}var T=e.slots;for(var y in T)if(T.hasOwnProperty(y)){var _=T[y],w=i.findSlotIndex(y);for(var u in _)if(_.hasOwnProperty(u)){var c=_[u];if("color"==u){var d=new o.ColorTimeline(c.length);d.slotIndex=w;for(var p=0,f=0,m=c.length;m>f;f++){var v=c[f],R=v.color,A=o.SkeletonJson.toColor(R,0),S=o.SkeletonJson.toColor(R,1),L=o.SkeletonJson.toColor(R,2),C=o.SkeletonJson.toColor(R,3);d.setFrame(p,v.time,A,S,L,C),o.SkeletonJson.readCurve(d,p,v),p++}r.push(d),n=Math.max(n,d.frames[5*d.getFrameCount()-5])}else{if("attachment"!=u)throw"Invalid timeline type for a slot: "+u+" ("+y+")";var d=new o.AttachmentTimeline(c.length);d.slotIndex=w;for(var p=0,f=0,m=c.length;m>f;f++){var v=c[f];d.setFrame(p++,v.time,v.name)}r.push(d),n=Math.max(n,d.frames[Math.floor(d.getFrameCount())-1])}}}i.animations.push(new o.Animation(t,r,n))}},o.SkeletonJson.readCurve=function(t,e,i){var r=i.curve;r&&("stepped"==r?t.curves.setStepped(e):r instanceof Array&&t.curves.setCurve(e,r[0],r[1],r[2],r[3]))},o.SkeletonJson.toColor=function(t,e){if(8!=t.length)throw"Color hexidecimal length must be 8, recieved: "+t;return parseInt(t.substring(2*e,2),16)/255},o.Atlas=function(t,e){this.textureLoader=e,this.pages=[],this.regions=[];var i=new o.AtlasReader(t),r=[];r.length=4;for(var n=null;;){var s=i.readLine();if(null==s)break;if(s=i.trim(s),0==s.length)n=null;else if(n){var a=new o.AtlasRegion;a.name=s,a.page=n,a.rotate="true"==i.readValue(),i.readTuple(r);var h=parseInt(r[0]),l=parseInt(r[1]);i.readTuple(r);var u=parseInt(r[0]),c=parseInt(r[1]);a.u=h/n.width,a.v=l/n.height,a.rotate?(a.u2=(h+c)/n.width,a.v2=(l+u)/n.height):(a.u2=(h+u)/n.width,a.v2=(l+c)/n.height),a.x=h,a.y=l,a.width=Math.abs(u),a.height=Math.abs(c),4==i.readTuple(r)&&(a.splits=[parseInt(r[0]),parseInt(r[1]),parseInt(r[2]),parseInt(r[3])],4==i.readTuple(r)&&(a.pads=[parseInt(r[0]),parseInt(r[1]),parseInt(r[2]),parseInt(r[3])],i.readTuple(r))),a.originalWidth=parseInt(r[0]),a.originalHeight=parseInt(r[1]),i.readTuple(r),a.offsetX=parseInt(r[0]),a.offsetY=parseInt(r[1]),a.index=parseInt(i.readValue()),this.regions.push(a)}else{n=new o.AtlasPage,n.name=s,n.format=o.Atlas.Format[i.readValue()],i.readTuple(r),n.minFilter=o.Atlas.TextureFilter[r[0]],n.magFilter=o.Atlas.TextureFilter[r[1]];var d=i.readValue();n.uWrap=o.Atlas.TextureWrap.clampToEdge,n.vWrap=o.Atlas.TextureWrap.clampToEdge,"x"==d?n.uWrap=o.Atlas.TextureWrap.repeat:"y"==d?n.vWrap=o.Atlas.TextureWrap.repeat:"xy"==d&&(n.uWrap=n.vWrap=o.Atlas.TextureWrap.repeat),e.load(n,s),this.pages.push(n)}}},o.Atlas.prototype={findRegion:function(t){for(var e=this.regions,i=0,r=e.length;r>i;i++)if(e[i].name==t)return e[i];return null},dispose:function(){for(var t=this.pages,e=0,i=t.length;i>e;e++)this.textureLoader.unload(t[e].rendererObject)},updateUVs:function(t){for(var e=this.regions,i=0,r=e.length;r>i;i++){var n=e[i];n.page==t&&(n.u=n.x/t.width,n.v=n.y/t.height,n.rotate?(n.u2=(n.x+n.height)/t.width,n.v2=(n.y+n.width)/t.height):(n.u2=(n.x+n.width)/t.width,n.v2=(n.y+n.height)/t.height))}}},o.Atlas.Format={alpha:0,intensity:1,luminanceAlpha:2,rgb565:3,rgba4444:4,rgb888:5,rgba8888:6},o.Atlas.TextureFilter={nearest:0,linear:1,mipMap:2,mipMapNearestNearest:3,mipMapLinearNearest:4,mipMapNearestLinear:5,mipMapLinearLinear:6},o.Atlas.TextureWrap={mirroredRepeat:0,clampToEdge:1,repeat:2},o.AtlasPage=function(){},o.AtlasPage.prototype={name:null,format:null,minFilter:null,magFilter:null,uWrap:null,vWrap:null,rendererObject:null,width:0,height:0},o.AtlasRegion=function(){},o.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:!1,splits:null,pads:null},o.AtlasReader=function(t){this.lines=t.split(/\r\n|\r|\n/)},o.AtlasReader.prototype={index:0,trim:function(t){return t.replace(/^\s+|\s+$/g,"")},readLine:function(){return this.index>=this.lines.length?null:this.lines[this.index++]},readValue:function(){var t=this.readLine(),e=t.indexOf(":");if(-1==e)throw"Invalid line: "+t;return this.trim(t.substring(e+1))},readTuple:function(t){var e=this.readLine(),i=e.indexOf(":");if(-1==i)throw"Invalid line: "+e;for(var r=0,n=i+1;3>r;r++){var s=e.indexOf(",",n);if(-1==s){if(0==r)throw"Invalid line: "+e;break}t[r]=this.trim(e.substr(n,s-n)),n=s+1}return t[r]=this.trim(e.substring(n)),r+1}},o.AtlasAttachmentLoader=function(t){this.atlas=t},o.AtlasAttachmentLoader.prototype={newAttachment:function(t,e,i){switch(e){case o.AttachmentType.region:var r=this.atlas.findRegion(i);if(!r)throw"Region not found in atlas: "+i+" ("+e+")";var n=new o.RegionAttachment(i);return n.rendererObject=r,n.setUVs(r.u,r.v,r.u2,r.v2,r.rotate),n.regionOffsetX=r.offsetX,n.regionOffsetY=r.offsetY,n.regionWidth=r.width,n.regionHeight=r.height,n.regionOriginalWidth=r.originalWidth,n.regionOriginalHeight=r.originalHeight,n}throw"Unknown attachment type: "+e}},n.AnimCache={},o.Bone.yDown=!0,n.CustomRenderable=function(){n.DisplayObject.call(this)},n.CustomRenderable.constructor=n.CustomRenderable,n.CustomRenderable.prototype=Object.create(n.DisplayObject.prototype),n.CustomRenderable.prototype.renderCanvas=function(){},n.CustomRenderable.prototype.initWebGL=function(){},n.CustomRenderable.prototype.renderWebGL=function(){},n.BaseTextureCache={},n.texturesToUpdate=[],n.texturesToDestroy=[],n.BaseTexture=function(t){if(n.EventTarget.call(this),this.width=100,this.height=100,this.source=t,t){if(this.source instanceof Image)if(this.source.complete)this.hasLoaded=!0,this.width=this.source.width,this.height=this.source.height,n.texturesToUpdate.push(this);else{var e=this;this.source.onload=function(){e.hasLoaded=!0,e.width=e.source.width,e.height=e.source.height,n.texturesToUpdate.push(e),e.dispatchEvent({type:"loaded",content:e})}}else this.hasLoaded=!0,this.width=this.source.width,this.height=this.source.height,n.texturesToUpdate.push(this);this._powerOf2=!1}},n.BaseTexture.constructor=n.BaseTexture,n.BaseTexture.prototype.destroy=function(){this.source instanceof Image&&(this.source.src=null),this.source=null,n.texturesToDestroy.push(this)},n.BaseTexture.fromImage=function(t,e){var i=n.BaseTextureCache[t];if(!i){var r=new Image;e&&(r.crossOrigin=""),r.src=t,i=new n.BaseTexture(r),n.BaseTextureCache[t]=i}return i},n.TextureCache={},n.FrameCache={},n.Texture=function(t,e){if(n.EventTarget.call(this),e||(this.noFrame=!0,e=new n.Rectangle(0,0,1,1)),this.trim=new n.Point,t instanceof n.Texture&&(t=t.baseTexture),this.baseTexture=t,this.frame=e,this.scope=this,t.hasLoaded)this.noFrame&&(e=new n.Rectangle(0,0,t.width,t.height)),this.setFrame(e);else{var i=this;t.addEventListener("loaded",function(){i.onBaseTextureLoaded()})}},n.Texture.constructor=n.Texture,n.Texture.prototype.onBaseTextureLoaded=function(){var t=this.baseTexture;t.removeEventListener("loaded",this.onLoaded),this.noFrame&&(this.frame=new n.Rectangle(0,0,t.width,t.height)),this.noFrame=!1,this.width=this.frame.width,this.height=this.frame.height,this.scope.dispatchEvent({type:"update",content:this})},n.Texture.prototype.destroy=function(t){t&&this.baseTexture.destroy()},n.Texture.prototype.setFrame=function(t){if(this.frame=t,this.width=t.width,this.height=t.height,t.x+t.width>this.baseTexture.width||t.y+t.height>this.baseTexture.height)throw Error("Texture Error: frame does not fit inside the base Texture dimensions "+this);this.updateFrame=!0,n.Texture.frameUpdates.push(this)},n.Texture.fromImage=function(t,e){var i=n.TextureCache[t];return i||(i=new n.Texture(n.BaseTexture.fromImage(t,e)),n.TextureCache[t]=i),i},n.Texture.fromFrame=function(t){var e=n.TextureCache[t];if(!e)throw Error("The frameId '"+t+"' does not exist in the texture cache "+this);return e},n.Texture.fromCanvas=function(t){var e=new n.BaseTexture(t);return new n.Texture(e)},n.Texture.addTextureToCache=function(t,e){n.TextureCache[e]=t},n.Texture.removeTextureFromCache=function(t){var e=n.TextureCache[t];return n.TextureCache[t]=null,e},n.Texture.frameUpdates=[],n.RenderTexture=function(t,e){n.EventTarget.call(this),this.width=t||100,this.height=e||100,this.indetityMatrix=n.mat3.create(),this.frame=new n.Rectangle(0,0,this.width,this.height),n.gl?this.initWebGL():this.initCanvas()},n.RenderTexture.constructor=n.RenderTexture,n.RenderTexture.prototype=Object.create(n.Texture.prototype),n.RenderTexture.prototype.initWebGL=function(){var t=n.gl;this.glFramebuffer=t.createFramebuffer(),t.bindFramebuffer(t.FRAMEBUFFER,this.glFramebuffer),this.glFramebuffer.width=this.width,this.glFramebuffer.height=this.height,this.baseTexture=new n.BaseTexture,this.baseTexture.width=this.width,this.baseTexture.height=this.height,this.baseTexture._glTexture=t.createTexture(),t.bindTexture(t.TEXTURE_2D,this.baseTexture._glTexture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,this.width,this.height,0,t.RGBA,t.UNSIGNED_BYTE,null),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),this.baseTexture.isRender=!0,t.bindFramebuffer(t.FRAMEBUFFER,this.glFramebuffer),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,this.baseTexture._glTexture,0),this.projectionMatrix=n.mat4.create(),this.projectionMatrix[5]=2/this.height,this.projectionMatrix[13]=-1,this.projectionMatrix[0]=2/this.width,this.projectionMatrix[12]=-1,this.render=this.renderWebGL},n.RenderTexture.prototype.initCanvas=function(){this.renderer=new n.CanvasRenderer(this.width,this.height,null,0),this.baseTexture=new n.BaseTexture(this.renderer.view),this.frame=new n.Rectangle(0,0,this.width,this.height),this.render=this.renderCanvas},n.RenderTexture.prototype.renderWebGL=function(t,e){var i=n.gl;i.colorMask(!0,!0,!0,!0),i.viewport(0,0,this.width,this.height),i.bindFramebuffer(i.FRAMEBUFFER,this.glFramebuffer),e&&(i.clearColor(0,0,0,0),i.clear(i.COLOR_BUFFER_BIT));var r=t.children;t.worldTransform=n.mat3.create();for(var s=0,a=r.length;a>s;s++)r[s].updateTransform();var o=t.__renderGroup;o?t==o.root?o.render(this.projectionMatrix):o.renderSpecific(t,this.projectionMatrix):(this.renderGroup||(this.renderGroup=new n.WebGLRenderGroup(i)),this.renderGroup.setRenderable(t),this.renderGroup.render(this.projectionMatrix))},n.RenderTexture.prototype.renderCanvas=function(t,e){var i=t.children;t.worldTransform=n.mat3.create();for(var r=0,s=i.length;s>r;r++)i[r].updateTransform();e&&this.renderer.context.clearRect(0,0,this.width,this.height),this.renderer.renderDisplayObject(t),n.texturesToUpdate.push(this.baseTexture)},n.AssetLoader=function(t){n.EventTarget.call(this),this.assetURLs=t,this.crossorigin=!1,this.loadersByType={jpg:n.ImageLoader,jpeg:n.ImageLoader,png:n.ImageLoader,gif:n.ImageLoader,json:n.JsonLoader,anim:n.SpineLoader,xml:n.BitmapFontLoader,fnt:n.BitmapFontLoader}},n.AssetLoader.constructor=n.AssetLoader,n.AssetLoader.prototype.load=function(){var t=this;this.loadCount=this.assetURLs.length;for(var e=0;this.assetURLs.length>e;e++){var i=this.assetURLs[e],r=i.split(".").pop().toLowerCase(),n=this.loadersByType[r];if(!n)throw Error(r+" is an unsupported file type");var s=new n(i,this.crossorigin);s.addEventListener("loaded",function(){t.onAssetLoaded()}),s.load()}},n.AssetLoader.prototype.onAssetLoaded=function(){this.loadCount--,this.dispatchEvent({type:"onProgress",content:this}),this.onProgress&&this.onProgress(),0==this.loadCount&&(this.dispatchEvent({type:"onComplete",content:this}),this.onComplete&&this.onComplete())},n.JsonLoader=function(t,e){n.EventTarget.call(this),this.url=t,this.baseUrl=t.replace(/[^\/]*$/,""),this.crossorigin=e,this.loaded=!1},n.JsonLoader.constructor=n.JsonLoader,n.JsonLoader.prototype.load=function(){this.ajaxRequest=new s;var t=this;this.ajaxRequest.onreadystatechange=function(){t.onJSONLoaded()},this.ajaxRequest.open("GET",this.url,!0),this.ajaxRequest.overrideMimeType&&this.ajaxRequest.overrideMimeType("application/json"),this.ajaxRequest.send(null)},n.JsonLoader.prototype.onJSONLoaded=function(){if(4==this.ajaxRequest.readyState)if(200==this.ajaxRequest.status||-1==window.location.href.indexOf("http"))if(this.json=JSON.parse(this.ajaxRequest.responseText),this.json.frames){var t=this,e=this.baseUrl+this.json.meta.image,i=new n.ImageLoader(e,this.crossorigin),r=this.json.frames;this.texture=i.texture.baseTexture,i.addEventListener("loaded",function(){t.onLoaded()});for(var s in r){var a=r[s].frame;a&&(n.TextureCache[s]=new n.Texture(this.texture,{x:a.x,y:a.y,width:a.w,height:a.h}),r[s].trimmed&&(n.TextureCache[s].realSize=r[s].spriteSourceSize,n.TextureCache[s].trim.x=0))}i.load()}else if(this.json.bones){var h=new o.SkeletonJson,l=h.readSkeletonData(this.json);n.AnimCache[this.url]=l,this.onLoaded()}else this.onLoaded();else this.onError()},n.JsonLoader.prototype.onLoaded=function(){this.loaded=!0,this.dispatchEvent({type:"loaded",content:this})},n.JsonLoader.prototype.onError=function(){this.dispatchEvent({type:"error",content:this})},n.SpriteSheetLoader=function(t,e){n.EventTarget.call(this),this.url=t,this.baseUrl=t.replace(/[^\/]*$/,""),this.texture=null,this.frames={},this.crossorigin=e},n.SpriteSheetLoader.constructor=n.SpriteSheetLoader,n.SpriteSheetLoader.prototype.load=function(){var t=this,e=new n.JsonLoader(this.url,this.crossorigin);e.addEventListener("loaded",function(e){t.json=e.content.json,t.onJSONLoaded()}),e.load()},n.SpriteSheetLoader.prototype.onJSONLoaded=function(){var t=this,e=this.baseUrl+this.json.meta.image,i=new n.ImageLoader(e,this.crossorigin),r=this.json.frames;this.texture=i.texture.baseTexture,i.addEventListener("loaded",function(){t.onLoaded()});for(var s in r){var a=r[s].frame;a&&(n.TextureCache[s]=new n.Texture(this.texture,{x:a.x,y:a.y,width:a.w,height:a.h}),r[s].trimmed&&(n.TextureCache[s].realSize=r[s].spriteSourceSize,n.TextureCache[s].trim.x=0))}i.load()},n.SpriteSheetLoader.prototype.onLoaded=function(){this.dispatchEvent({type:"loaded",content:this})},n.ImageLoader=function(t,e){n.EventTarget.call(this),this.texture=n.Texture.fromImage(t,e)},n.ImageLoader.constructor=n.ImageLoader,n.ImageLoader.prototype.load=function(){if(this.texture.baseTexture.hasLoaded)this.onLoaded();else{var t=this;this.texture.baseTexture.addEventListener("loaded",function(){t.onLoaded()})}},n.ImageLoader.prototype.onLoaded=function(){this.dispatchEvent({type:"loaded",content:this})},n.BitmapFontLoader=function(t,e){n.EventTarget.call(this),this.url=t,this.baseUrl=t.replace(/[^\/]*$/,""),this.texture=null,this.crossorigin=e},n.BitmapFontLoader.constructor=n.BitmapFontLoader,n.BitmapFontLoader.prototype.load=function(){this.ajaxRequest=new XMLHttpRequest;var t=this;this.ajaxRequest.onreadystatechange=function(){t.onXMLLoaded()},this.ajaxRequest.open("GET",this.url,!0),this.ajaxRequest.overrideMimeType&&this.ajaxRequest.overrideMimeType("application/xml"),this.ajaxRequest.send(null)},n.BitmapFontLoader.prototype.onXMLLoaded=function(){if(4==this.ajaxRequest.readyState&&(200==this.ajaxRequest.status||-1==window.location.href.indexOf("http"))){var t=this.baseUrl+this.ajaxRequest.responseXML.getElementsByTagName("page")[0].attributes.getNamedItem("file").nodeValue,e=new n.ImageLoader(t,this.crossorigin);this.texture=e.texture.baseTexture;var i={},r=this.ajaxRequest.responseXML.getElementsByTagName("info")[0],s=this.ajaxRequest.responseXML.getElementsByTagName("common")[0];i.font=r.attributes.getNamedItem("face").nodeValue,i.size=parseInt(r.attributes.getNamedItem("size").nodeValue,10),i.lineHeight=parseInt(s.attributes.getNamedItem("lineHeight").nodeValue,10),i.chars={};for(var a=this.ajaxRequest.responseXML.getElementsByTagName("char"),o=0;a.length>o;o++){var h=parseInt(a[o].attributes.getNamedItem("id").nodeValue,10),l={x:parseInt(a[o].attributes.getNamedItem("x").nodeValue,10),y:parseInt(a[o].attributes.getNamedItem("y").nodeValue,10),width:parseInt(a[o].attributes.getNamedItem("width").nodeValue,10),height:parseInt(a[o].attributes.getNamedItem("height").nodeValue,10)};n.TextureCache[h]=new n.Texture(this.texture,l),i.chars[h]={xOffset:parseInt(a[o].attributes.getNamedItem("xoffset").nodeValue,10),yOffset:parseInt(a[o].attributes.getNamedItem("yoffset").nodeValue,10),xAdvance:parseInt(a[o].attributes.getNamedItem("xadvance").nodeValue,10),kerning:{},texture:new n.Texture(this.texture,l)}}var u=this.ajaxRequest.responseXML.getElementsByTagName("kerning");for(o=0;u.length>o;o++){var c=parseInt(u[o].attributes.getNamedItem("first").nodeValue,10),d=parseInt(u[o].attributes.getNamedItem("second").nodeValue,10),p=parseInt(u[o].attributes.getNamedItem("amount").nodeValue,10);i.chars[d].kerning[c]=p}n.BitmapText.fonts[i.font]=i;var f=this;e.addEventListener("loaded",function(){f.onLoaded()}),e.load()}},n.BitmapFontLoader.prototype.onLoaded=function(){this.dispatchEvent({type:"loaded",content:this})},n.SpineLoader=function(t,e){n.EventTarget.call(this),this.url=t,this.crossorigin=e,this.loaded=!1},n.SpineLoader.constructor=n.SpineLoader,n.SpineLoader.prototype.load=function(){new n.JsonLoader(this.url,this.crossorigin),jsonLoader.addEventListener("loaded",function(t){scope.json=t.content.json,scope.onJSONLoaded()}),jsonLoader.load()},n.SpineLoader.prototype.load=function(){var t=this,e=new n.JsonLoader(this.url,this.crossorigin);e.addEventListener("loaded",function(e){t.json=e.content.json,t.onJSONLoaded()}),e.load()},n.SpineLoader.prototype.onJSONLoaded=function(){var t=new o.SkeletonJson,e=t.readSkeletonData(this.json);n.AnimCache[this.url]=e,this.onLoaded()},n.SpineLoader.prototype.onLoaded=function(){this.loaded=!0,this.dispatchEvent({type:"loaded",content:this})},"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=n),exports.PIXI=n):i.PIXI=n}).call(this); \ No newline at end of file diff --git a/examples/example 1 - Basics/pixi.js b/examples/example 1 - Basics/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 1 - Basics/pixi.js +++ b/examples/example 1 - Basics/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 10 - Text/pixi.js b/examples/example 10 - Text/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 10 - Text/pixi.js +++ b/examples/example 10 - Text/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 11 - RenderTexture/pixi.js b/examples/example 11 - RenderTexture/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 11 - RenderTexture/pixi.js +++ b/examples/example 11 - RenderTexture/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 12 - Spine/data/dragonBonesData.json b/examples/example 12 - Spine/data/dragonBonesData.json new file mode 100644 index 0000000..ba7ae5f --- /dev/null +++ b/examples/example 12 - Spine/data/dragonBonesData.json @@ -0,0 +1,779 @@ +{ +"bones": [ + { "name": "root", "y": -176.12 }, + { "name": "COG", "parent": "root", "y": 176.12 }, + { "name": "back", "parent": "COG", "length": 115.37, "x": 16.03, "y": 27.94, "rotation": 151.83 }, + { "name": "chest", "parent": "COG", "length": 31.24, "x": 52.52, "y": 15.34, "rotation": 161.7 }, + { "name": "neck", "parent": "COG", "length": 41.36, "x": 64.75, "y": 11.98, "rotation": 39.05 }, + { "name": "L_front_thigh", "parent": "chest", "length": 67.42, "x": -45.58, "y": 7.92, "rotation": 138.94 }, + { "name": "L_wing", "parent": "chest", "length": 301.12, "x": -7.24, "y": -24.65, "rotation": -75.51 }, + { "name": "R_front_thigh", "parent": "chest", "length": 81.63, "x": -10.89, "y": 28.25, "rotation": 67.96 }, + { "name": "R_rear_thigh", "parent": "back", "length": 123.46, "x": 65.31, "y": 59.89, "rotation": 104.87 }, + { "name": "chin", "parent": "neck", "length": 153.15, "x": 64.62, "y": -6.99, "rotation": -69.07 }, + { "name": "head", "parent": "neck", "length": 188.83, "x": 69.96, "y": 2.49, "rotation": 8.06 }, + { "name": "tail1", "parent": "back", "length": 65.65, "x": 115.37, "y": -0.19, "rotation": 44.31 }, + { "name": "L_front_leg", "parent": "L_front_thigh", "length": 51.57, "x": 67.42, "y": 0.02, "rotation": 43.36 }, + { "name": "L_rear_thigh", "parent": "R_rear_thigh", "length": 88.05, "x": -8.59, "y": 30.18, "rotation": 28.35 }, + { "name": "R_front_leg", "parent": "R_front_thigh", "length": 66.52, "x": 83.04, "y": -0.3, "rotation": 92.7 }, + { "name": "R_rear_leg", "parent": "R_rear_thigh", "length": 91.06, "x": 123.46, "y": -0.26, "rotation": -129.04 }, + { "name": "R_wing", "parent": "head", "length": 359.5, "x": -74.68, "y": 20.9, "rotation": 83.21 }, + { "name": "tail2", "parent": "tail1", "length": 54.5, "x": 65.65, "y": 0.22, "rotation": 12 }, + { "name": "L_front_toe1", "parent": "L_front_leg", "length": 51.44, "x": 45.53, "y": 2.43, "rotation": -98 }, + { "name": "L_front_toe2", "parent": "L_front_leg", "length": 61.97, "x": 51.57, "y": -0.12, "rotation": -55.26 }, + { "name": "L_front_toe3", "parent": "L_front_leg", "length": 45.65, "x": 54.19, "y": 0.6, "scaleX": 1.134, "rotation": -11.13 }, + { "name": "L_front_toe4", "parent": "L_front_leg", "length": 53.47, "x": 50.6, "y": 7.08, "scaleX": 1.134, "rotation": 19.42 }, + { "name": "L_rear_leg", "parent": "L_rear_thigh", "length": 103.74, "x": 96.04, "y": -0.97, "rotation": -122.41 }, + { "name": "R_front_toe1", "parent": "R_front_leg", "length": 46.65, "x": 70.03, "y": 5.31, "rotation": 8.59 }, + { "name": "R_front_toe2", "parent": "R_front_leg", "length": 53.66, "x": 66.52, "y": 0.33, "rotation": -35.02 }, + { "name": "R_front_toe3", "parent": "R_front_leg", "length": 58.38, "x": 62.1, "y": -0.79, "rotation": -74.67 }, + { "name": "R_rear_toe1", "parent": "R_rear_leg", "length": 94.99, "x": 90.06, "y": 2.12, "rotation": 141.98 }, + { "name": "R_rear_toe2", "parent": "R_rear_leg", "length": 99.29, "x": 89.6, "y": 1.52, "rotation": 125.32 }, + { "name": "R_rear_toe3", "parent": "R_rear_leg", "length": 103.45, "x": 91.06, "y": -0.35, "rotation": 112.26 }, + { "name": "tail3", "parent": "tail2", "length": 41.78, "x": 54.5, "y": 0.37, "rotation": 1.8 }, + { "name": "tail4", "parent": "tail3", "length": 34.19, "x": 41.78, "y": 0.16, "rotation": -1.8 }, + { "name": "tail5", "parent": "tail4", "length": 32.32, "x": 34.19, "y": -0.19, "rotation": -3.15 }, + { "name": "tail6", "parent": "tail5", "length": 80.08, "x": 32.32, "y": -0.23, "rotation": -29.55 } +], +"slots": [ + { "name": "L_rear_leg", "bone": "L_rear_leg", "attachment": "L_rear_leg" }, + { "name": "L_rear_thigh", "bone": "L_rear_thigh", "attachment": "L_rear_thigh" }, + { "name": "L_wing", "bone": "L_wing", "attachment": "L_wing01" }, + { "name": "tail6", "bone": "tail6", "attachment": "tail06" }, + { "name": "tail5", "bone": "tail5", "attachment": "tail05" }, + { "name": "tail4", "bone": "tail4", "attachment": "tail04" }, + { "name": "tail3", "bone": "tail3", "attachment": "tail03" }, + { "name": "tail2", "bone": "tail2", "attachment": "tail02" }, + { "name": "tail1", "bone": "tail1", "attachment": "tail01" }, + { "name": "back", "bone": "back", "attachment": "back" }, + { "name": "L_front_thigh", "bone": "L_front_thigh", "attachment": "L_front_thigh" }, + { "name": "L_front_leg", "bone": "L_front_leg", "attachment": "L_front_leg" }, + { "name": "L_front_toe1", "bone": "L_front_toe1", "attachment": "front_toeA" }, + { "name": "L_front_toe4", "bone": "L_front_toe4", "attachment": "front_toeB" }, + { "name": "L_front_toe3", "bone": "L_front_toe3", "attachment": "front_toeB" }, + { "name": "L_front_toe2", "bone": "L_front_toe2", "attachment": "front_toeB" }, + { "name": "chest", "bone": "chest", "attachment": "chest" }, + { "name": "R_rear_toe1", "bone": "R_rear_toe1", "attachment": "rear-toe" }, + { "name": "R_rear_toe2", "bone": "R_rear_toe2", "attachment": "rear-toe" }, + { "name": "R_rear_toe3", "bone": "R_rear_toe3", "attachment": "rear-toe" }, + { "name": "R_rear_leg", "bone": "R_rear_leg", "attachment": "R_rear_leg" }, + { "name": "R_rear_thigh", "bone": "R_rear_thigh", "attachment": "R_rear_thigh" }, + { "name": "R_front_toe1", "bone": "R_front_toe1", "attachment": "front_toeB" }, + { "name": "R_front_thigh", "bone": "R_front_thigh", "attachment": "R_front_thigh" }, + { "name": "R_front_leg", "bone": "R_front_leg", "attachment": "R_front_leg" }, + { "name": "R_front_toe2", "bone": "R_front_toe2", "attachment": "front_toeB" }, + { "name": "R_front_toe3", "bone": "R_front_toe3", "attachment": "front_toeB" }, + { "name": "chin", "bone": "chin", "attachment": "chin" }, + { "name": "R_wing", "bone": "R_wing", "attachment": "R_wing01" }, + { "name": "head", "bone": "head", "attachment": "head" } +], +"skins": { + "default": { + "L_front_leg": { + "L_front_leg": { "x": 14.68, "y": 0.48, "rotation": 15.99, "width": 84, "height": 57 } + }, + "L_front_thigh": { + "L_front_thigh": { "x": 27.66, "y": -11.58, "rotation": 58.66, "width": 84, "height": 72 } + }, + "L_front_toe1": { + "front_toeA": { "x": 31.92, "y": 0.61, "rotation": 109.55, "width": 29, "height": 50 } + }, + "L_front_toe2": { + "front_toeB": { "x": 26.83, "y": -4.94, "rotation": 109.51, "width": 56, "height": 57 } + }, + "L_front_toe3": { + "front_toeB": { "x": 18.21, "y": -7.21, "scaleX": 0.881, "scaleY": 0.94, "rotation": 99.71, "width": 56, "height": 57 } + }, + "L_front_toe4": { + "front_toeB": { "x": 23.21, "y": -11.68, "scaleX": 0.881, "rotation": 79.89, "width": 56, "height": 57 } + }, + "L_rear_leg": { + "L_rear_leg": { "x": 67.29, "y": 12.62, "rotation": -162.65, "width": 206, "height": 177 } + }, + "L_rear_thigh": { + "L_rear_thigh": { "x": 56.03, "y": 27.38, "rotation": 74.93, "width": 91, "height": 149 } + }, + "L_wing": { + "L_wing01": { "x": 129.21, "y": -45.49, "rotation": -83.7, "width": 191, "height": 256 }, + "L_wing02": { "x": 126.37, "y": -31.69, "rotation": -86.18, "width": 179, "height": 269 }, + "L_wing03": { "x": 110.26, "y": -90.89, "rotation": -86.18, "width": 186, "height": 207 }, + "L_wing04": { "x": -61.61, "y": -83.26, "rotation": -86.18, "width": 188, "height": 135 }, + "L_wing05": { "x": -90.01, "y": -78.14, "rotation": -86.18, "width": 218, "height": 213 }, + "L_wing06": { "x": -143.76, "y": -83.71, "rotation": -86.18, "width": 192, "height": 331 }, + "L_wing07": { "x": -133.04, "y": -33.89, "rotation": -86.18, "width": 159, "height": 255 }, + "L_wing08": { "x": 50.15, "y": -15.71, "rotation": -86.18, "width": 164, "height": 181 }, + "L_wing09": { "x": 85.94, "y": -11.32, "rotation": -86.18, "width": 204, "height": 167 } + }, + "R_front_leg": { + "R_front_leg": { "x": 17.79, "y": 4.22, "rotation": 37.62, "width": 101, "height": 89 } + }, + "R_front_thigh": { + "R_front_thigh": { "x": 35.28, "y": 2.11, "rotation": 130.33, "width": 108, "height": 108 } + }, + "R_front_toe1": { + "front_toeB": { "x": 24.49, "y": -2.61, "rotation": 104.18, "width": 56, "height": 57 } + }, + "R_front_toe2": { + "front_toeB": { "x": 26.39, "y": 1.16, "rotation": 104.57, "width": 56, "height": 57 } + }, + "R_front_toe3": { + "front_toeB": { "x": 30.66, "y": -0.06, "rotation": 112.29, "width": 56, "height": 57 } + }, + "R_rear_leg": { + "R_rear_leg": { "x": 60.87, "y": -5.72, "rotation": -127.66, "width": 116, "height": 100 } + }, + "R_rear_thigh": { + "R_rear_thigh": { "x": 53.25, "y": 12.58, "rotation": 103.29, "width": 91, "height": 149 } + }, + "R_rear_toe1": { + "rear-toe": { "x": 54.75, "y": -5.72, "rotation": 134.79, "width": 109, "height": 77 } + }, + "R_rear_toe2": { + "rear-toe": { "x": 57.02, "y": -7.22, "rotation": 134.42, "width": 109, "height": 77 } + }, + "R_rear_toe3": { + "rear-toe": { "x": 47.46, "y": -7.64, "rotation": 134.34, "width": 109, "height": 77 } + }, + "R_wing": { + "R_wing01": { "x": 170.08, "y": -23.67, "rotation": -130.33, "width": 219, "height": 310 }, + "R_wing02": { "x": 171.14, "y": -19.33, "rotation": -130.33, "width": 203, "height": 305 }, + "R_wing03": { "x": 166.46, "y": 29.23, "rotation": -130.33, "width": 272, "height": 247 }, + "R_wing04": { "x": 42.94, "y": 134.05, "rotation": -130.33, "width": 279, "height": 144 }, + "R_wing05": { "x": -8.83, "y": 142.59, "rotation": -130.33, "width": 251, "height": 229 }, + "R_wing06": { "x": -123.33, "y": 111.22, "rotation": -130.33, "width": 200, "height": 366 }, + "R_wing07": { "x": -40.17, "y": 118.03, "rotation": -130.33, "width": 200, "height": 263 }, + "R_wing08": { "x": 48.01, "y": 28.76, "rotation": -130.33, "width": 234, "height": 254 }, + "R_wing09": { "x": 128.1, "y": 21.12, "rotation": -130.33, "width": 248, "height": 204 } + }, + "back": { + "back": { "x": 35.84, "y": 19.99, "rotation": -151.83, "width": 190, "height": 185 } + }, + "chest": { + "chest": { "x": -14.6, "y": 24.78, "rotation": -161.7, "width": 136, "height": 122 } + }, + "chin": { + "chin": { "x": 66.55, "y": 7.32, "rotation": 30.01, "width": 214, "height": 146 } + }, + "head": { + "head": { "x": 76.68, "y": 32.21, "rotation": -47.12, "width": 296, "height": 260 } + }, + "tail1": { + "tail01": { "x": 22.59, "y": -4.5, "rotation": 163.85, "width": 120, "height": 153 } + }, + "tail2": { + "tail02": { "x": 18.11, "y": -1.75, "rotation": 151.84, "width": 95, "height": 120 } + }, + "tail3": { + "tail03": { "x": 16.94, "y": -2, "rotation": 150.04, "width": 73, "height": 92 } + }, + "tail4": { + "tail04": { "x": 15.34, "y": -2.17, "rotation": 151.84, "width": 56, "height": 71 } + }, + "tail5": { + "tail05": { "x": 15.05, "y": -3.57, "rotation": 155, "width": 52, "height": 59 } + }, + "tail6": { + "tail06": { "x": 28.02, "y": -16.83, "rotation": -175.44, "width": 95, "height": 68 } + } + } +}, +"animations": { + "flying": { + "bones": { + "back": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 17.39 }, + { "time": 0.5, "angle": 0 }, + { "time": 0.8333, "angle": 7 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "neck": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": -8.18 }, + { "time": 0.3333, "angle": -23.16 }, + { "time": 0.5, "angle": -18.01 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "chest": { + "rotate": [ + { "time": 0, "angle": 0, "curve": "stepped" }, + { "time": 0.5, "angle": 0, "curve": "stepped" }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "tail1": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": -2.42 }, + { "time": 0.3333, "angle": -26.2 }, + { "time": 0.5, "angle": -29.65 }, + { "time": 0.6666, "angle": -23.15 }, + { "time": 0.8333, "angle": -55.46 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_rear_thigh": { + "rotate": [ + { "time": 0, "angle": 0, "curve": "stepped" }, + { "time": 0.5, "angle": 0, "curve": "stepped" }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "tail2": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": -1.12 }, + { "time": 0.3333, "angle": 10.48 }, + { "time": 0.5, "angle": 7.89 }, + { "time": 0.8333, "angle": -10.38 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "tail3": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 8.24 }, + { "time": 0.3333, "angle": 15.21 }, + { "time": 0.5, "angle": 14.84 }, + { "time": 0.8333, "angle": -18.9 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "tail4": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 17.46 }, + { "time": 0.3333, "angle": 22.15 }, + { "time": 0.5, "angle": 22.76 }, + { "time": 0.8333, "angle": -4.37 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "tail5": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 7.4 }, + { "time": 0.3333, "angle": 28.5 }, + { "time": 0.5, "angle": 21.33 }, + { "time": 0.8333, "angle": -1.27 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "tail6": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 45.99 }, + { "time": 0.4, "angle": 43.53 }, + { "time": 0.5, "angle": 61.79 }, + { "time": 0.8333, "angle": 13.28 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_rear_leg": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": -14.21 }, + { "time": 0.5, "angle": 47.17 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_rear_toe3": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.5, "angle": -36.06 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_rear_toe2": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.5, "angle": -20.32 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_rear_toe1": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.5, "angle": -18.71 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "head": { + "rotate": [ + { + "time": 0, + "angle": 0, + "curve": [ 0.408, 1.36, 0.675, 1.43 ] + }, + { "time": 0.5, "angle": 1.03 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "chin": { + "rotate": [ + { + "time": 0, + "angle": 0, + "curve": [ 0.416, 1.15, 0.494, 1.27 ] + }, + { "time": 0.3333, "angle": -5.15 }, + { "time": 0.5, "angle": 9.79 }, + { "time": 0.6666, "angle": 18.94 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "L_front_thigh": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": -19.18 }, + { "time": 0.3333, "angle": -32.02 }, + { "time": 0.5, "angle": -19.62 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_front_thigh": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": -12.96 }, + { "time": 0.5, "angle": 16.2 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "L_front_leg": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 37.77 }, + { "time": 0.5, "angle": 0, "curve": "stepped" }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "L_front_toe1": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": -16.08 }, + { "time": 0.5, "angle": 0, "curve": "stepped" }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "L_front_toe2": { + "rotate": [ + { "time": 0, "angle": 0, "curve": "stepped" }, + { "time": 0.5, "angle": 0, "curve": "stepped" }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1 }, + { "time": 0.5, "x": 1.33, "y": 1.029 }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "L_front_toe4": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.5, "angle": 26.51 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1 }, + { "time": 0.5, "x": 1.239, "y": 0.993 }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "L_front_toe3": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.5, "angle": 16.99 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1 }, + { "time": 0.5, "x": 1.402, "y": 1.007 }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_front_leg": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 26.07 }, + { "time": 0.5, "angle": -21.6 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.5, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_front_toe1": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 29.23 }, + { "time": 0.5, "angle": 34.83 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1 }, + { "time": 0.5, "x": 1.412, "y": 1 }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_front_toe2": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 24.89 }, + { "time": 0.5, "angle": 23.16 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1 }, + { "time": 0.5, "x": 1.407, "y": 1.057 }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "R_front_toe3": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.1666, "angle": 11.01 }, + { "time": 0.5, "angle": 0, "curve": "stepped" }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1 }, + { "time": 0.5, "x": 1.329, "y": 1.181 }, + { "time": 1, "x": 1, "y": 1 } + ] + }, + "L_rear_leg": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.3666, "angle": 25.19 }, + { "time": 0.6666, "angle": -15.65 }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1 } + ] + }, + "COG": { + "rotate": [ + { + "time": 0, + "angle": 0, + "curve": [ 0.456, 0.2, 0.422, 1.06 ] + }, + { "time": 0.3333, "angle": 23.93 }, + { + "time": 0.6666, + "angle": 337.8, + "curve": [ 0.41, 0, 0.887, 0.75 ] + }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { + "time": 0, + "x": 0, + "y": 0, + "curve": [ 0.33, 1, 0.816, 1.33 ] + }, + { + "time": 0.5, + "x": 0, + "y": 113.01, + "curve": [ 0.396, 0, 0.709, 2.03 ] + }, + { "time": 1, "x": 0, "y": 0 } + ] + } + }, + "slots": { + "R_wing": { + "attachment": [ + { "time": 0, "name": "R_wing01" }, + { "time": 0.0666, "name": "R_wing02" }, + { "time": 0.1333, "name": "R_wing03" }, + { "time": 0.2, "name": "R_wing04" }, + { "time": 0.2666, "name": "R_wing05" }, + { "time": 0.3333, "name": "R_wing06" }, + { "time": 0.4, "name": "R_wing07" }, + { "time": 0.4666, "name": "R_wing08" }, + { "time": 0.5333, "name": "R_wing09" }, + { "time": 0.6, "name": "R_wing01" }, + { "time": 0.7333, "name": "R_wing02" }, + { "time": 0.7666, "name": "R_wing02" }, + { "time": 0.8, "name": "R_wing03" }, + { "time": 0.8333, "name": "R_wing04" }, + { "time": 0.8666, "name": "R_wing05" }, + { "time": 0.9, "name": "R_wing06" }, + { "time": 0.9333, "name": "R_wing07" }, + { "time": 0.9666, "name": "R_wing08" }, + { "time": 1, "name": "R_wing09" } + ] + }, + "L_wing": { + "attachment": [ + { "time": 0, "name": "L_wing01" }, + { "time": 0.0666, "name": "L_wing02" }, + { "time": 0.1333, "name": "L_wing03" }, + { "time": 0.2, "name": "L_wing04" }, + { "time": 0.2666, "name": "L_wing05" }, + { "time": 0.3333, "name": "L_wing06" }, + { "time": 0.4, "name": "L_wing07" }, + { "time": 0.4666, "name": "L_wing08" }, + { "time": 0.5333, "name": "L_wing09" }, + { "time": 0.6, "name": "L_wing01" }, + { "time": 0.7333, "name": "L_wing02" }, + { "time": 0.8, "name": "L_wing03" }, + { "time": 0.8333, "name": "L_wing04" }, + { "time": 0.8666, "name": "L_wing05" }, + { "time": 0.9, "name": "L_wing06" }, + { "time": 0.9333, "name": "L_wing07" }, + { "time": 0.9666, "name": "L_wing08" }, + { "time": 1, "name": "L_wing09" } + ] + } + } + } +} +} \ No newline at end of file diff --git a/examples/example 12 - Spine/data/iP4_BGtile.jpg b/examples/example 12 - Spine/data/iP4_BGtile.jpg new file mode 100644 index 0000000000000000000000000000000000000000..767a4fd8213e7e0ffcb30d91541fb3d2b9e9fbc5 GIT binary patch literal 172812 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

OUYtcIx%XiLVFpvaTfK5Uqmf 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 U{;8vXAo355{O>cQ%F)>W0R!0IH~;_u literal 0 HcmV?d00001 diff --git a/examples/example 12 - Spine/data/iP4_ground.jpg b/examples/example 12 - Spine/data/iP4_ground.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3bde6d0e6b1f25914f5da2bb0235eb4a5797720e GIT binary patch literal 52005 zcmeFa2UJtbw>W%~kkEUPPJqxmLg+&1AWeExfh0hHP(l?OO$3x)LNuWzkyt<#8UO$o00065ARvT*gWyj%1_}UB5Qc&A=gDb5E8eb?+-}%UI2FX(z7PQHC*24zVyA6^AZJ(g^?tLBWSe1Rvipq$E}fsidKyjx@zb z2H?ZO=}3n0Jhsq&IPcMd5Z*@j8@V>qve$4LH7T;{$t`Vtp5y< zWxI{>eji0<5UzWF#r>7{uehL608sx3@+RZ2IIpt+&~gF*xTpS#Llpx6a{>S~Km4sd zTx5Om4GRm_R8WYFjFczfvGQbw{<{80gkPBdJ@8w7^5pt{@eXN-_x21A2t$&YiVX?~ z3MU~$gFUf$q})H1_5lB4b z6eJ5$04ae~L+T-IklT;}$S`CQG7njSyn}2&VNeDr2UGwm4po4vLov|3PKN5&ssgGis%ENgs!^(Wsy9?W z;EZs7xC~qqZU%RT`@kdNiSRsl1-uE~1AhcxhJQjJ5L^fegc`yW;ezl(97dc*6d|r6 zIuXN&MZ`yHYHA*88EPGBYidvGFzQ6=v(%TVJE%vfm#M$d(9;OfDAO3zxY7jD#L?u@ z)X?0b8KHSj^Np5;R*Y7Y)|wVadx$ojww(4l?L*pS+HZ8MbP{wrboO+9bg^{#bhUIn zbTf4C>FMc3=r!o~(G%!n==168==Ay0tF-S8QFt{^>Go&$8GITObGQ4MGWE5xA zV{~NkBq&HgPs1HXK_5TN&FOwncUr zyC}N>JC;43y_~&=eVGHnA;DqB;m2``;|j+EjyIfaoGP4-oKc+TIB#;!alyF6xy-l% zxYD^AxW>6Qb_wjl?85I#-c`G6WY=eIK5jj3Ja-EBRqjXJ8$3uJBOZUAOr92=Szan$ zY2JOjk-X=5dwAFQIQX>quzV?e4SZAlP=0BCTmHlRW&HQ}KMDv6mSDfPXTmCuQH*^u4<`zQngEU6Rn0Op=;4AYA7`iwL-PW z>Ky7e>ZjHFHQ*Zh8pkzmXnfT~YldkyXuj5x)AHA<(R!gRp^eur*Iv{S(eczN(V5$W z+~cvQc+Z@!u&$?WsqTWFs2)zQLhm_73gd^ljCrN6s87;w(*JCrX>in_(-3B8Vwht1 zz=+Mr!KlFKiLr>WkMSkrHzukkhfO+7;ih{{Gff|x37BEcs?FY*tC=4)@7~L>*KTjY z-USP33zEfkOQ@x}Wwzyvm6%naRf{!XZDyTqJ!>Oj6Jpc853$c?-?@FyY?W+}*!J16 z+a0v4vHN7NZ=Y^I?I7t8?$G7P?C9=T?fBWr&?(Dl&RN0vsPkPHUKfH(t1Gpuqicoh zM>iw4T(@O+Rrf^q@%<9}qxKIR;5`s<;O0T*gPsSkdB8oKJZe0?ds=&zdVcgW^D6Xu zgEhdO!M?=l;c{>*cpZEe{<*h~cb4}HA03};pA}zS-#p(nf&rm`@Yc`N?}FcFe{26r z|E&P0fU86rq6e`pkS)+ZuscXFC@N?;SSC0zcrHXUBsb&@X)mcF6cV~Wv^9(aTuj{y zmkd7A4)#-JlZ(=;$f=8*uz~%gpb4?nLmm-T5=3> z%si#mx`C>l_AO^%AS=wmiJv$zgS(tTXCvlvof%9 zuFAHmyIQTfx`w|d{Sx$2_@$N0?w5zI7+z_um8&he%60Wr9Z(luw_5K}Ki;snq3fFZ zwc19p#=<7{rj%w#^P%Q9Exs*ttuC#@ZRTxV*R`%Uwad3x-VnZV?k4BW^jmbd5^imE zM0b4b4DMXJ?Q?sv>tNUP9p^h^-TS&9^z7~F?=|kd(}(Hn=-27LF`zlnHmE+>au%ru(Y*n;xJaG(S{(*gB*!bbVNR_|}N-$n8j88mve>^vV zn|Lu9F!^>WV(Qy;%nW=cWtL?&?+O2t(x*~Sug;<8Zq6Id-&?R-m|ny#zFZ1j`o5g- zjPY6CbD`(eFO*;0STSB1T6J4pei{7o+gjo}>w4iU$ybf9^`#`ABH->JXnZ%S=m|FQSS%ocHLYik`~ zpzM?m8dw6*tycgOB?RDNz?A`HYZ^!Z7-(o{XlWQ|X&G4Q>F8NGnHd0z*O+!mZ5BX;z1c1VJ5?KHU6$A#Qg2Jg0 zG;kQhZZMG*MkUOq1jp?2WETmERYq`RmelKuqBw1Dd+k;s#SI%!i)GopnSYB7Rplye zh!;1^w#ONDm4%^qjW{ge-(5?%GaA15-dG~1{6u4J_t?^hi%mU`mOnarMoAQmx;K__g8H>zDa@lgfAN81v$0Y0%=3;9`rq9ApL{wK9lHZl9;Y328+TYIXPTtHIBzkTzHEtq;HfL9%?E)E6SbWbk%(!7? z`a{R%CKqFG)v%P#4a3%`bBOQ(knms2Bk1Zb}RntP7Ur$pN)ps2K7;!kKXz%pipitB5SE2rU#Lh*w{K#7Izg9lhJM8Z> zvdbYo<7v6fq0o5UmloH8+Bb!&f3VVh`k-br=;pRYlna#!TtK$3xV7Eto38jUdzdpm zAGHOjN#B~~mkyEbuYWY=+Sz+%68m7hDRm<-WaGfoCAH|#IQ~w|F3lLyEV)W~i{FAG#R#Xl1(BZoe+m6hI{bWTd0 zT=}ueA#4r~F3J3`#C zkxMOWP3sLe-L4cm3r;puDC*1j48E)i&Mf+FQ}Z)R61XVxkZ`QDB0n_dRrraDR~L?2_)k1Nwk{|UEI0M3t)unwqFP1$8q<|d z7pxqfz2X^l!_99UyPtME-2!Y1JcX~%WaX$OZ>Wzu;SI+=X|_`d)UxOWKbeFZ4_;v; zo+wwkPaQ_6KCHWAGNIL8bRi%-l}~T|Lxv}&OMbq=F9PK3-6b*>%ZT=lpeZt&~IaM3rPA= zy}4_}ZgW@q^O}w0Z|+K5JgzgQu?1ukwt(axo=-<~=eK}Yy5HH}HRia#8v5};_d~gh z#=)%PPqu)YM;SJ`Hklth|5AJ4>GSD_?@wMxe}4RFo``wx{*z}9p7Hz`jW{Y3we)uL zz|+sz&tngFZ3;a*xpei#79jI^3z&CO&q@d!=i5v-K9+jn{uZG6bS3oQ`{QpqHWVJ| z9e8m!%wg9=s(nLZpieqH8wwjj_tK7?8Sxq30ur}?;XS3WEub*6r^CpzJYDa`n)ZdQ$*pyM zk-v;D3qJ5BRTm`XrpaLtvP(sBHknzPOWL zKLCgPw|LvofG|qDO;muM?G83T7lFry1(6Ir!#v3?ph(lk$0l?~nq-)a3_i(Z0~UDi z?PC5>tvzT6|F>%GumRigHYBW`3%P`C>A}`1Aj?l%G<4@lk*qi+jfrxn+;n4p%AAoB zBrBjG1(WCRVc2_0n3i$}ZxD*6NRagd0F6=G+6;CCa$Xo{!UQiSeEena?`@K=Wg0>cBy;{kbWLU;v*2jW8i(Oa#N(Z{r|51H$Y*eg1~UgvEpT zK*xqzsHwfB#m>M%vlIO{WLn=KQZy!j;IqRi=IzFt?nHwvr^apDyb7@WvL@xLn$T>z*^|0R~eD<}+9 zsQ(gAPjTb?Bbw5yB*_(#eVG7M_YDQ!o~c+VFu0MVU~y1FWQB%M!eo4aeV!Z!f5qSr z)IrD`00?)2@jU=HxLy9^sQcrn`{StlKaRRTj=DdNx<8J(|8F?zwyl4{pnVSjUwj33@?>)#Gk^h2 z>p_4Q=$xp}hWsbhfksUwkIvk*e~Fa#%&Q5)!Q!X(}oqe_kSBZ6utxrmdmz&$_^nj>vCsl1J{A47;>S9 z0>eC`4-}9H6yG3{Tq5ZA3J=o}0V&;X0x{Uq@>jwC$}J+1+%5`sXc+ij z_kYF?C7Y%dZ1JJs%jQ@-h%Wk9W}^tO=Kmy^zh^dzzz2r?$_}#rKWE;l2TKb5cSJ1c zdnI|wkvt>iK!bP~_)>Z(MIClB2Jf74`Sz5wKbEI08;n-vEs^GSXKzFjQ7Vt1BsKDC;Y#t7xbg zV0L2+F=#b1k>oP|E$?n4MI$9c14Xook)o24k+M2kQAJ(Zz(`RU%&Le{-pOkg7#ijY zzLURGF9EDqQAr)lqo$&$wq36}25qRJYHVPjqNIp6G%%**)ifaCJwd;zJ?JytQSHB+ z@ZgD{zs>{whK~o`tau#}@&O?^MP)ff6?nu>})QNiwo1HHEYBQhv`O-uktF76N}I5>cS2RpSrDI70?{3Sn4il+pu93*bH z2oi_Y^bR5sJwe?h5IueH3Vy+OA9DBk$#6W05P`=TlY)pyivDX7{*8@_#)|r&$c$C~ zJMnGj#rxhC@F`2RnSoPbh!@(gt=fO;0TOEdU*nP*0F%k)M(OO7Q1?Bg26=}?dXn%MA5gFVRYiV3G`2$+*4H!8hb$ol5po+9$OF`N zqZGE8|5tk{X}=9-JLupDNd|un&3kr+&_B;V9Qeb5KOFeOfj=Dh!-4-Fap14b9X=3T zT10|dx8KfVfSbmj=P?jeR8(*(1RRc_MSwqg8U%udo`H^zo{o-zg@JPYd~65)d53@} z5E&U5xmj4a|8LG?q{0y}2zY?uuk#os;CYNvkWA{|k7EE-a2ON;0e+sxfI?xvr~dss z2KW&H@_7sdH4MB#Lm^NYD-{3-4`g8WA=o{^0~s93nI+Vm+vg$XcXN5YL6PF#iW#Wb zWtBEyL%UQBC9=z|p?BHi!tRVL(1^#=(sA=7 zzd4qnZtCnCm6TuA+CTB)i>P59u%@w7K4wN?(%H=jzGHhqKog{^Ru+FKy*q?T&j04nr>b6C&u2A z-W%3=k{4n(eZZ4UG$JU}LXHRFnSVgBv(!keKR}rf$#1>DJT4 zztL2kTOukJ5bOUJErE-vSDF#fxLI+=$+EOTKMSz`MPD1*^K~<{GV6KxzK#QTMBw#6E_DR9!|G161ziBNpzDm+ zrBd4m+KA^66&6b>OXcQDa&?X=i-eXC?!OaxCA?-VqKgjW(#Kda+Xpg}A50HvY6!=eFO1hLZ26py~~dgn;aEobBUmVF9Wq7#uK9^X_5erIWYPW{opXBduK~VS#D(Pi{q;KW1gn3AYz)NL|B2bz3a~M60ORJ>1 zXkk(9T;~KF&1`v^d5OWVt4Wz*hxcZ9{pDz;m@i9Jqauv1m8_Y~=pC)Ewwia0uU=Wt zN^0K6WlwdQ(X8r~@*raZd~L}l^>%+uHzMJdD6ci$ytH7&1#RE5gsZLsx2YkR{eAG# zv2s-v0qU0n2#gOudhoHaaFZ66r|58yhX9@id60g3_^Ht3>Uto)0P?qqDuMa(sJil4b zS2fFo5wEPi%STq}CI{-I{yg}MmGmnKPT$lwpseY4)V=BQ?db4LXT8Q)Kf^3POS9Km z-cO|09Qse+*W2S$*J+8&;GCctlU$9I5Mey$!FbMb|CkVetVcv3x$}dXe{Qg5-g@MS zvK|``V0r)$DGMga{u&G1M5U`GUI>0MUPij;N2j_zezBf6M4YE9e8^-_HE}vMu(zcm zp|Z6%yK5rX+V!+=Y05|GXqI4Gnu{NLxL&Et?exd-!Km60Km0*UqRHh=SN91ZG}8wHw`6{6aC-BBm0Q8`u$t0z>Q#L!$lm-+Jsh zpCSH<5jRw@P(@A?kD-1i}!!4y1&Ud=5% z2KHpWa(YQGT2&oV?=)FK?4kC!y-<*=k?bi=e;-4}ubr6qi?NXssQWh@CN2^9G>V0G zPk4xDn9|*&@{s<>mz~M?sx#<%>kMo$!-LYYRj(SN^yNZptLL3dF(_pq)BgpNQRa$~E0xi6I?ICn07 zC_XMjf5QtLYyaXB*yFD-Fq|+sxtNf81x55LB%!-@MnPjo)LVh;-``;hO&s=U$oTkY z`rdgO0B2hQJ4p|yMU#A9m!cN^gSLj>U*PW!mYWH2v(4>Y)r?fv!$HrRovU|z#FNTl zKL<9Ff0%U&(q3=@GK%z>87D)?H^R5 z=EF!g_Sb_GL{e1``Z0gjbn8O%FaWjc^eVwj)PGt7=SvTcagKJ*gZ%QFaO&ihOcX*~ zPXyay__X@1228~D^njn^)jOXT*jXQWtzo{sGl?(>PFl>JcKA7*PI1kDRbw3r|Ez&C z3Y>#&2X!vnrmC8&TM=Hv-X}s&ldGMr=2h#6R|P+g;YtH{)wuo5>-=JcVP9KpI;j;%Njr;R`Z9Eq?j_u83x{Ubuaes=ikI#=G6vjKG=pI)OxCuWM<1c=D)!71N~1B^ItIJl5kX zbBoWisgI7#g9c;zx|bVuu>mnn{Nzf3Y&~gf&lI@>TDFm)m^sZQhHv@&MT7H>suMO0 zC$e1EvPvJxnyr#N>jJ8oR!tHO>#{2OQ|rm|*LeS6irLGF!}HKVy@oiyEVI?<<@<~H zx8^oOuWt5z=pRIP_YV6xN=6gD7Eyw zGeEf$9gtMqa8^9<=Njh{*Q&|MxBXn#?jPsh7r2-*ZJ~d~dNyfRG)zkgYnKH*8*2rw zBWf}`U*D9}wleHTAUs5{v34PCZ*@r5L|pGByNKEkh4Dz1)A`Arn^-^HhI+;MWl64< z(tTLebd;JJ`5)eI{wC^~7mK+r68FgKij8k@WqAaJ!B*`#H*sR z$3EUIJYjkfH}UG0AP{`l$D*sSb&XWgJ8QWxFH$<2-7dH-)rC zNg1*wWnL(TBas`VE;Lc3N0gAO;%49DJ}tF}`vlD~4XprBQ$_>n^_kG894d@XRi`u> zPgG1TX1N{T8hPKs-_^3;&-UgX*2irFzyr~mRApjXg>*FoW(Ml3`TZSg$Wzn+=hw@z z&j?{}*_gFAsknM2*4~-TkA(F$@J{7mi-56YwmZH{%qZrd4z^BH=8*H@fuX$IM#KzcKa3yc_|ZVOHg2k9Oq>zsVq zpVejLmh`57nlF-dp1f>-1FkdE&T@%e2iM(_O*g=0eYcJMLi5SYySz;C6&7?vXr3Kv zV60~!`i%^eWgB&6K&`E4w*8XvZT9Xjc%oy|Dq_}(rPwu{J*)Yyn@u9~JR^K-W{%WY5YkzWXL3Q?~q zx8+dLs$QSZ%poS6D2=JAdU8{_;$l_@e0p8*EK*L1%O*jvORagX`Y;21=w^AM9jkAs)oH>-Y`hXxnh zeuYT_5ETiCN*qCepDr#%?Ul7%PS)NuocRVnUHMWrW(b!!kn%+5&9sc2%a|!J!>&&< z49MoAc_1N8@Qs0>hL|cjQip8YGA~Vp@%VD^l}gGkW;Hv4JEo~6Z{wN^`D?)}bTrfj z%4nwI4-(DxMk<*`@Cbh`W;*6}xs&4GkL0|wIAbDgT*WES3zbXe+GP$2H1}hk3#Nh% zU0xtr%yuu-f{Xzuqx?cu74Dg2gy)xKLp#G0(gHabATdbf%KnG$O*dykD3jfe5UOdu zB)#R~Az|gsZ-6uLlnRhNGN>bz@^9Nou3IL;oM z!nI)jh+};w{ZV$(>NI4mD&($Q19_BiasVI}C+fszOcKf4%R78-?xiQ6gIk@yi^Xw4 zQu6|(@SzU_&EOL7#o|&YyPhR&e>Nsrg>`@iV2)1EixuE zCJC-|=i?bG#!b{eLeoxq1!p!`L>Px?xS5Za#+em!r;fmjm$9NwEuM4AbCPPY19GyHZw=%pyx^@C5M*ImX&Ik**(e8HJ&t^Xnn`wpgz}(Zoo8Y2lt&5zP~r=&48QM zZ=E@|*|1)26x?n)fSq}IYxI5N)oz<`o!vC_;d8EnXR-d(DRPeL=u3>>7-F+ws?G~u zs~l|IF#uJTwTI&VdX(dfv!YRxSw@nBrg%?%nu$1OgA6|^R!^gvzUjIyp40+xzAjn5 zq)Lq$mE}YCc{FicLI_}LQ4?lpDgFmFH`1+&qn7ntDq?6=;&Z2rLA{B>7uy8`@Itc5 zV0&jf?I3pYo`(dEy+N@W$ebg!6B=6-NJS~Ni*dum*ohC_#bk)Al{H=MLhZ=Cw47EF zaBw0AJIExzv2^q&fd}9q!v=2L3KL(qZYcKK;FU0}eZs^Dvem&Tq;V?YpF0I8Fw zJKVN-V0ZH)GZknhxyQQe0V{ShOAPrut~YO3xwKXz>FR+3$jn?eEz6Ap=3{q6x}{ja zo}M`AW&y31XlA``{KC=pp4m)c9WN1)du7iqOk(pdkPs|Q;D%Z|z+ z%?e1IVeHjAZDH8-{`AEyuSvf@Tu9gY5IU)0R!$i~i)As`jd_ zm3+Ohk`24rptS0+D4d~|)Gu(#<@sLnf|hKZQKBm^6phexyU- zu8r^5jbxSX!Q7*|kj!R&(`yX%HPJ~n*AoxurCjxr8RNy#`Y%uwNL!grguPBc$t|W< zjCremF&WJgSX+Ejwz9NdRJP#Z#b7BZX?<5(G~z7d(C8peg->$jbvE>zh}7OR^GWiO zdS{e5l}cmuwsh8uU-sx<0JRF9gTs+ zQW354>1dnG#Fn6Go5w4Rl`51{`D_=nn<+B?t^8Y@o6m=W*{|^k83gD`E;Xb{h_M#rN?b= zdX-l9u^!G*V9#?SlGoo8u62SE5J|CZ^^^cjJiOt(<3sNr-efOcJwo!*S&!3sky|Di z*zE+aJ&R5q0mo#ryfo;fkHu0Zo>Vi`D=XItv|l{O)aBs3nbdY>{?lZWh22PHKx$p1 zeuh2o_a9iK>w_NutOx+~rZfdeWWioS{q6S!-gi{?T<8 zYI_WMe-o~OXkwM9>W!c0V?L5=rz|yN1&;Y3i(J>Lz{bxkz{{cbb2*I(0yStsjsdWj zOAZ-^46bi4as}QtwQP;OkG;fZZI>VKLlppaJR(oU%9&kfXsM(g_~_E8a0sG`V*0ZW zBMRFRFcKN~yz_dkRcetmr^b?Qdzsuj0b_;k4{KU1wq6S>;xlnPuVV9p)<4-)7FhL* zIQU7!H-<_3owa!PcW&ylAJ1vsv~{_hS2eb@#6l0z?+ky6^q_4aR&-)nP??SIS{zm0 z@EyA>g4EDQiUeE^lt^>SkYNBqNfGc=Kv2B(uE!lzx2XWGV{~x+MCs+QbM2GR&V(zz zBLX=`9&J z^JvWKNG4lcK|bFk3~hd)qXVZ{QQs!ttWzyVlC_$$uP*?cC_t-_t_(F7aPETOq>|t# zeSsI)fn?O!`L9aXd?y=g>O3bFBIzPI(&=qzUPKPM@*7c>G@Sa4_wGe)W=QJRI*VfJ z3?Ut=g~8J@!TP2!QivGUNMz~Pz%5`O!}w|mOU%2r;_5X?S`Eg#$rtRnLvhW=Fv-4; z&%htPGnU+wr&TB-@QUH%(mNrQ1C@l*7oQmL^c=Eu34GTI!UXmT9jSA$!KNHiHcx!M zdG~(BP1(Lte#eU3nbD@hI78TV?oPfc9x>uw_76)jkUD^!dANn^)6jk8;9UlV4q7>g zn9({7T7fAh8#CA!$LK?RZdNK6dK|?#&zdLe@wg><_ck&)6dFkTUxn zE=C+|FcY~+*k4cTEN;%f_|q)wWFnl_2xKp2iVPW5)H`q7+TVUN({DgTmN*g<{_U$L z0A1wI%`uEbxsxKEO)cBqg4*moVud~xAO+e!KbEZD0w?RYg502u>DO|HGM8VGxn+ia zq~vJ50abff?o@u_yts1wOc{&hvGuxwO6vEVIen#OeNrSx)BgC-j+mDT;Kwj+M$8}9 zO&*~>e%={iEJaF%fC~d7SKhdU4>R~s0oMkmsx%4Y*hTF)rG>uY0Z71tY{`H(EOQ3e z2{K7TwU5uT1`{}<2(a6NC*iXWGf>s#yimTYAnL~kS7Srb=~d05ZWJ+2ni*7MR-2@rjKVDO;0&!gLL^NY_lQyN+62zmad?&%RgcMU6~L@)`mS_8+5cjPiR#f{H(% z@3Ui0EmZBXpB(IM)U%)Va?s3HiX`9*BpqYg;Z$@*{Q^G@ z39^jq0lCL_Pu&i>e4n4T3&~LvXDJPj2Dhq++q^F9r`aOSt3p~t9-mP8*^MTqYZoCH zdpf2?cL{g{@&m>ysI0?ID8NTg+>S6h$(cAGHE6FT zhtXxJLKM9qHAIH=?vv{i6J|vLv=ScFw!yAZ+20Pz+((=Zsqq8uaO}(G&=EM>Y-_TX zQiwdL#^qX!6Bf8%UrIJoT7^<H}CcKFu|-RfFHSqW?cU; zR>g_hgo$GVPRmZ6bGMj%p^ca@)WacR#VIa@LtOU!g((G+Nc&9*+KGTHJ(@DXP3233 z=9C!e2<~_SVr()$O>}L%@$-NklDC56FHI_w<#N09Lar0`DBKk?QJ79c7}%G*$Lc+2 zdBpYmW2_1^kAA?bRjBO4t6yhR<5oyKSgQBs%_kY#xH{74v4IPqef;iRw1=cjSA1*# zl-xgtgT}iUS5G*`%|goRNC~bb`UB0J3c)Gd)5e~T<=FQ_t=9+g7g3>ZwJ+|M`P~ zqk|>qF*hNcz+qgm9#b~D%Hk{ZZ0~1_MkCoWFVksx6jJ~*&9g|4C!ymP&(Ez47Z}iP zeq^3H4qI!3Nf64qfd%cmTp!-J75OhTXSp~^jy0y~*=S~kAck;2!vn8jul<5LI@~2o zf|1aRQx}W?bt~mVXB@q)43w+=m+zycRgy+Bhcu=-kU%#U!hT=0PUReitpMf6rn*)d z!<;d&2E(YfJ$rw})(%}3D*U8Dk$Qrcxf73iwf>eaNzl~Q!~3Uk9Q0BS94Yf!_HM7d zvyb=u$JM7-CD`Wuo}n2IJI0od)nY{wOnXlfldCqP&g^Dwjb9qi4HD=r3!=)Ul}Yf@ z98N`d6*h>V(v~evtn#{6)|dH8Cfivj($5>N^$B8xJ()W_z=bW0&n>_{Y-K*C_*GFV z%trIbt_;(&4V;wn~Y| zSN9J4#*~Ux&!z{QnDnqbQ+4RTVV4hXk1o%!AyTEi7n450=ncV7@5B04Z#q7A>^OWjdBKcT0%sEeV1xF?v-Gh*53Vc{$Il%K z)hF87I8U$=c^I`)n+0Ous$-f{t&Y(=wSAzfAcr{`mNg(FYZ}YO-N^yFof^#T(&CVA z<3$4t_qSGf@hQi5?zx0d#yim&C(i^!#O{Y0p)yB$B;yU+L#}bq-1?p*RJCArH-wLK5#0yjiq7*Ud1E&2+!krNgH=IeN>65`&T7WW=e_sCr+ri(`hcyTI=B(8 z#x>YrSoSFv#rnRuKWowF;ryW5pc%oOjRu*Tq=UIl{6rQ{Bqa69rOEEmnH~VioIV~YIs?T6QYy4Mf@i|ZReLjqfbkGIct&X967eB%X&iXd`#28I+&C*li4cC@t)LE$7U~f$1A9G^@UdFH_S) z->a9sHY{C&;kc@#Bb1#?M7XR+^$~rDFEv=MXD1K13y{F{9QrtA*`10TTGR zwZSQ$t7n%nx{WRM_15O&Vkfi`IIgydl7jci3mQoAiQj#wt0=#DG;|aJx^$^QqiX(y z*7*k~upQ{#7V?KF?=etjrl94+;;oM|hH?{jsl4T5?h|9hm)R+b9C`e8YRa_9J~J=Q zv`6NL8FDFEfVDJVspLstV(%^PHbV2&a-Fj&IJ=K7!>($`k#cBPhj#L0@11<}+k1-7 zyy2F+HQNdL*7sTE+rKIxTo-s&<}dqc#--Hb3abispKDx1|FCx9k+TUj)HPg(_ntb$ z5ZXJezBZSB{U-Qz>f|_C&OPdK=wa75rN*fu&|m%QT>wWlk*Kuz&@6_;>FXysn;-Ec z!KypWY0F>gjm)>-U^P7+5u+W$%O+#aOt+-hu<*R!OExsx!nK^Gw;tum3OG1S0BC^W zOyYi?F0+P17q36gr{c=8ilEuU`4&6m$yzCj$m?fTXm36LYW|gk*E@}Bi8rtb&z7Tr z2*96nRGXOFpdjJvwfbehHIKhPiH6$afg13*uKNtV+l53GrgylhlX>g%^3QHuZ;c?( zil{*J1Ws5>k0M3!oqg3AP$bEI^m5p=8Y6>f#^$y5qt6-#-o%`~mXo-=_rd34-%BjI z91W7lD8Tw9H?7r4fu&n>?>BJ!?{2RlCPeL1*6X@o#mTCn9@vk1u0IAKdSu(Z4-wSw zz+%u?F075CuAlgx25U8Jp*f3pe%zCe$$(CEHj42ihqh_>$elYBFv}!qpThPG)bLwh z*$Bxhsd>HY#H_ilt|^Rt4jpKd8nb8YHBRzp=Q4VgxR~0|B$W(HT8;kRfXO*LrlVcd z?B=Ce0I`h**LeAdj_dEkqpW(hF2zW9I-Z(0Kb^rPaO}u9hCgm!E#XWwzB9EYxyLIn z=Za?hfkhd`inZPByB#(-=q}T_R(?&p zgPMO?>HI?K(CSRz)%Hw`v?y~iT=xm%9#UuuoYB38zI(Nl`llW4;HYw{>xfSlUDdzN=9kawlN@+_C-6QfiT> z%QHJc&~C9kEVy8eSf`gR)g2 zK^ugHb3Rf97*mJQh;)_S?SaPH&740Am91B5xG1_?C}}JzVgP_40lQ+*|M63-!x0r# z_8$fImg$2O~`_^o?^Ve zY@En|cY#oDSp(F?^l%;rD$FK2*1bVXb8YID+)305asEJQGk$06NVq zK00C}tidjXkL_ruhI>m$>Y(1C8@V;z^)p#!d!9?T97BWVFZ6G=#OBOvbw-@tNI)~o z66hVWq*ri#w*{O@tu3O0_#nlzdWw@KZiua$bj?dK6in1_u){=aCo8q8)lDWLXCWn|%q$*`^ zeUgx!4UATd85Sm_eJ-3G^l2}^cGQ3$=Y3;XnA)CC2Smc94`TzajD6}IpJB-YILZ47 z&>m5+|HU&#RWVzOxopj}LQ6^AiKTmzZWbi>s(0pbqeqhEmRc-m4zf#+Ww!EpAahvE z&+Xp*PUpBy7aXAn@EpwiXb_}59%x-DxfFMkCpz{$4|0ECJ2ML#e;4+-fK8FQo3L(gcT#4Z;1dZHMDEaTnsjt{rHd=! z(@QF7iki1BA)RD>q>(N>^z7M&Qy+UY=PUT-k}sty&>20~=pV^as?XZDZw;b*Rd|n; zW0q*j_k0unn%Uf+4tTPUm2Cd3*mH1{&*qVE0$p!qVMP&VET%qZ5!(b?P;4*k(>_X9n!%fBRPL1G}mZW`XcH(aN_sl{%Z z&&}ckBTo}wCpWk}M0oQN=a`ebAaXqKX_!^eS1b48kBhaB#rX3Ht{xk>V?TKknS-IR zk!m(7YOj#KWk$_Xzb4hzNp0KBxOPoJbg%HJSX#S@Fr798_;I6Ab2FG<@2fJRHP@19 zv9H2{nniC{(aSS(hpQiIsB$jku3f4dq@A6fhblp+GPLUKZSR5KMlZkMq3GtBtK1>~ zm4M8Ze?6pCjnE3!hX~L|71m)>R4K_{t{Kz_bFkVi5xy%%?Fqcrsq@n4v0T6C zGym_3Uk(u7ysRm$JNBV+G-k233VAZ`Yl^j#+YP@lT2pY3!4Ml+5aQg|rm&dW^1H2^ z-2aEapDg1$Dm&YM;YPc5ULzH9!fs+tY&atAnADJ#$>s?4``y;M_KoeZHI37w(Fo3` zKd2e@2H|ltJcPqI zi!;%oQc9{8wlR91-)Z{K1f5jRufMOb7&lF}z&8gi(HvYoBde(r12G?umk_kNcEBh- zR4!{!%1bJQZGdMr4aVI1nBA(S8#$qMK%*{qA7*p!62;gjiQP@LfmFCRL8Fh3Njgm6ToO zDOIMNBws}qGcSz9fXcfvrSVL98*^@@mqTp2ta&Eq9IKdMOAqd+&^dZQ_P{olb5_5k zhjPBpbY4z;gnA~4J6hd=nOP21y>_k~O)H!leQ1AMUID?C>WmfYe7$SxU7^N1oecR@ zjY|*p6PM4JgmYeoK6ykHf-J4t^yx0F!#plDHXdj=dAP*`qeIdICX`~@Bvgu5m4q!0 zj|i!_^YBtFyOK~*0x^RP0ZS`oj<$jt``gG4-JPC)#WR5uUUL{rpy)SQ(HkG?je!64 zz|p`|gulGf&UJ|SERWlBh`Md1R`tT#cY?cImsp|A(o!j%)G_|HU_QbPc2#Fd7slU2f!t#OOv^T0jK}b#%ivI;9(= zTSOTRN=r%&M5G19K77vioZs)f_TTf`Klk(8&-Gl_`+i5*-R#aB6XX==FdXzvP(nrJ zN*_t0Bo^?-r=kD*a!d$xx9x&b<#Gp=)TYK3#<%BWt#)WT9rW<-37^8xO05 zjmZjUPe-5jhj9Aw-;IzWP~$xS$A(BY%hs8TyCJeu>Y({>cp&ysjqg<87NfRuIKdUy z=F^koo2TCY53n4q(bJR)Q(_a_YY_4S_IL92A$}2g^iu!*mqwRndB-3l4dwjkdQWvM zs5Q5()*M~W^hHZ6tC4s(N%_S!mi+uqmT;WL&&BOS-as7{9bKcGU)JIQg0+-F&l!I0 zsn;2tGVd|3OY{Hx@IOqJ|CayCc3gqt&MoU2)++DaowPgR4)nMZaY1w@<^W1s+e&K;dP8GsE0?tOJoGVcxhe@+Dd;|jR-!_R-& zZ}8}tQLN1W2KK1N6@RCe>9k(4!LgsF)f(yGj}IXFUKsi=IDN89!roo7-$P+ZU+#wG znvwbqp-8JkO^*dd;3|!i$&N_M14nUD#HrjHzug1*pJjKw!R;?)>AolK7Av#FCV+UH z7gtT=l8dQK`U}dcUp@#(z(`%8B39FBU(Fp$?q2NaSw41@ciN^$BPfHNXd^Y=a`Qt0 z3M#a;4LP#^0G6|(6fpuNycZ>tWo>rK-QiaExraNI|2JFf`mj!J#`gxU3rl@->>lr@ z{fU62^*U%8B*(#C60O3?qyab+afFR=(!rUn#@5pHESo0pOA&&GEY1eK{b>5-I}%%P zCB2E-vivbd%}akN89mx3t@zbnwRwLwE;1CwM6?@T12q#u%y(0${8J(u`jebEXa~lh zkIS+Cpk{c@z5}F`OvAgkkOSdBodG(`1i-6qeM-p&unb{_aM|KfH9RsE(x3??O|$e4 ztu`VU7qT_Ng?zJ=^`Ix}YFCNoS+d_q`IMZ@plU(8`1B~hxAt(#rPQc=<_ncf9d5c3c}ISPdciu0(>G$9RghrfLV^FG;M9HW|*wbORT1Qkmbm^ksTY zlBSW)NeX;>T*a=6PlK&LoO2WO(;}hl9@dC@R>TT$92$qR zJI8*n*;h|)5$JfF+Y)ehOcqt;*U+hH2c={uPzZiP(KsNOHSYv5QV>o=GHY zey1ok`G3z%U3t-6GV6D#WG&GHVNpi&f&Mpz>#fKi9jqusu&gWkT^#IWTm?2e1~BY5>5<(z)O-Np}0E^A{qA@@@)VMZ@{rgJB8-Lu!4vdAZegS=c92Zl=B23XU$THuER$$Hm+B|`^WDX45pfP*tKZBKv#;_B>7k}w zgVijo3u4>hG9AA{b@5X7<1}^6N-}fb2)29Nu|P5zzOaSd*E%=!4kciyzL5$!5vt?z zLk7A(R9TaAkks&USzZyT*4I5H2Eq7g$4>E?m1ilz&spc2q+HtKC&biAi+Q0tzDl=; z$w=R6la6rt{r8C1!0CwY>(>D)fA*R8UdANm<2_6X9Rur^=_}XTsAy=QE9=FZTQ?KEPC4fu{!G=Q45}ZQwXScO(KZSn^9P_#F47w9@)O**%K>2OJchu) z1eeyJb?yQI3_5bcFyb%kCHJ$r>FM7c95TriuA?b4I>hE;;ap{B2&s-<;XBrfmjV&Z z8XeK)v+RW~GGD~TrwpP>H~SD7A9v&O|7~0T`#NvSw5?w2eg3=T zD@XZ$kg@F1*Ri>up-Ch6PQZLuEPuLvxSv{et(+))915rgl>y=@*#hR%yIH@DP9Be- zNCe&ZD-jbOja;_>NpU8BXPVFLswI~*6jIox`G2_#8x6};{VXI2(L0=F)*XHBrz;|? zj5rV(i_1C%kK0G?OYL?SLgH`WHCVyH*DOmHF;JwDNUf)`M^kg+qKuJ#xIVe~I_l2t ztVq(5iTp&Zi~yA30JG7wOh*JH)`KYNt)c7m*M+Vl0e}xFoA9l+`Qt1^a=?gGq4!dN z6x3sPJ=--X(q_NDQ{oC{`+r)t|JI)4kr(i)UmKEB#=k2#z!NtYO(SL9;y*Ik`u0c= zO+AeUP6N*;JSeRIK=H3qy=$Lg4Jessr(xu4bNd6DK{@f0(l?d3f)+3Mr1acpHmi^s zHR{|3(jVWInTWeN91k2Yv6siU-S_wGQiog-Q!Eah)JYwijx4o%<|@h)^|gL81Dn68 zQ;G=`{0@nZU7O}Mij`mW*_a3bwdF*}N?8b2nIM0|md+pNr9Tv_X5DBS^p#|oq!!Vqx>fPxDnM+hMn8(#@My(#hU6_bXvK+wDKx zI=%owwn-+zT&@v*$PtW&V4(wEyQD7wz%g*N2 zl?jz&MrPn~7(*4+l5G;VE0*-VeYcDk(8K^x%zDb9-Rsn3t8elTaIN53)fpYKVjP%` z6h%zZJjVQ|ZwL*FHR!;3b5Um^nya4v1Eg|33K;>9ebrGtcw3APE@Ah?q)6QW8eaTh ziNrg3y{IUS_rttk3^*Ng`4Jdee5;+Uuuju(P%>F;bSPSje}{;a9m|?SNOvS(a+!9I zGS4W9{m4X~AAN2>!6|2VAbPTU=af08N|Zszl&ry|iMD9h>Pb z-(>&!>*`|6bWG!gpno9zh6Gu%E5pMHTAzCJ7!Z^(G!iC%0a?8{^HNaR6*x*xNgZ68 zN87^{M=Hs{R6b0t#9;F>)mk44AbsU~Zu^lx(wsbuck>&BsE+_!vdgT=RLu!>zCtc7 z!o0X26>mHbACZQ1W0rKi-a~`l)gzdJ8p@1;pY_?Z)cL4rpU1k$+jjlhuEpB6H6WU} znol`I3F&$UBB7ygXUh$YnW?*ZFkf<^)5Qdh@q3LMh{r+N^$syTXfiT9aurvjQcwj= z6PN@jK_Hk>!iyFKkthj{sg@k*f@U5ta>T&_lUkgJR*h0%zc~cWEBJlg}8BJ8b1Znv%%%Kt8k~4 z;u?31@~hR}X~h>9dsahtEvi(fx5S9P0dn-J!2 zyJCK^%{6~olsTV^PVjTBb%iDm&@%?#x7f8QzNnzk?X|{WJKE-;U^Z1h0{o z{BM8^X7j|MGzWj)7aUZ>YB*UOHYXuvA~#xIqG=R2o4ik(-y*jG@8DQ&SOKFPKgbZM|5Zb0bxR?Yp`;_n)u<|xznUTd zrED&BM|s+{RhIh%hSW^b4UkIDnbbUgbAE!S5u-A21L~>_yM3dT>Z|6%1tO24c!R2_ z#P7h;;2`AyW2(muDO2;MTu<_>jkM6HA4k)!z8$7aY=)D`&+d6Szxjll@x#>GMp8i@ zlh+wqDMm$yX>h~o_3cX^IoDIS;HhyMpaYPSh$0so(@&natM@|Ri6?MJM@?hPBQ6Rc zGv1R_DfAa)@4il+%LuLE!|Jo7RlD1)gTX+S34(WkVJlX+(7h>2S~a~l z#*HEwajN7fzoV0IH+Prlh2&j_4QOHk70Dkj-%`VE(Sa7@*$|E3Hy#ph7YiM%lp7#d z$u>7M6-gX;j?6KHFLzQzu91?>a5KQtU$OQ|YB$e3QAqO81MpW7-T}o(z9OCY87|M( zp9h%jI3HVk0X&4+$sV+G(54ija9ve1+wkE$MU8wfCPuTwNKtJBSn&z74DSQjg7342 zY9q#9{9~qiW<0~Bt$iaB@sf3;q1jcQk3A6L+(=Q+Z!rwNbr~hQI1erPp?`9+guEpB z^%V!5wCD64gk!CK3~#440?D|am&)% z@8Z_Yr1&GpR?s!dfu294?1up;jcNet5^F82ooU5i)(TR(m~)?eDl{qbc+J|tP5lqh zWap5s@qqT}n0aQ((9wKQ$Ea(O2`eLg`9A?#iz`8-xS!tgV_uX3m`Ja~5KkWOK!7)6ZS40z!B%wqc0{TtsU zkmyC|owmW(qpqb=0}J;{-YONVz7O}iR4|uIWQY)(eP#9M%GIaybMxuQR0D&KZO3gx zP^S$>neEQBQ}lztg5|Ct?&+UJnL-(r(S2IKZ>@##g}X7UO`Z{%c|CZQXnfix;K_f& z75~JDoPhr-xl2trn6_W1rjBu*bgAs20z4w4d%NYt=>P{h04|xq8U#rj$!n&U;X-Xu z-esZEtTl?w{I0DrE~dtF;0P~f`%VZLZP8A^EDr#_+CLNv(ok5k=pwb#Cdsy6s1#YE z9iEtK3N+pf?D(GzQ+HjOS~)19(cdZAa@xQiAI#uLt{+|MV z{l%?Y_WW+yDdy`Jv-_}#2XIXIM#|QgONM|RzB_DVn>!*iW+Xh2ps(nAX6=fkZ?uWH z?Z#6D-@ygEM#Q?MqUaM%JU=jx@57lmBaQt1&!}BtfLnD_?OZon`}3B22)l?gz~M6- zwlg5r*_gcGuU$aCQFp@YbrVt}#<#Br1@6`!k!VGnyHhCcn8L|nY+s%vh?yU(zDuLU zB`^)KGZ|mAuE&OvA3sNXeYEOn&rzkGwdn=WDd-i&tpbCr+66Syj#x^kFc*Cpdxs}rY7wN{^B)_V-O!##Lg0Tnb`hDENhQtm6hDu z*0lw|dYsdy+B-`|lyAYl@X78z`JrD~W|`%DYGo~a^;w&U$kqvQ_BLnZ%YQRO&eWo8 zb4QYQ6jMvTxJNR8-+M&$UX5|Cbah$TBc0)bE8TF4&;3KTlJ+`o+Iqjm0et+qs=&c( zpSRjhAGn;#W+f#%&E%m0qEWU~{8=l@nN#JX#8fLv(+@!M>d5+RrbWyNu5{7aSmvDp zx%{_PAT6y~-y5-XL~k;w3FZ%hZwt%sPd!RA^%)y{vr>G?1PL%J#BkvQY+aS_<1W3c z=@!Ph?i#-y787J}Q%TErq-V2#B;dg-Ft)?i<4_QRNdx9Jh#5>Yx9e;5j`@km>wJ2(C=}0b?J(ntgYNK2D zs>@?`vX;8kG02nAxMNym0Wi-wW(}4j9b_i%5J>iK(p^TCxE$E>Y8#nUHK^;#oKwrq z8nlcAtmc<6y^-_-K#r+O#*&@$;H4Gh>0_b{Ry!{VM6-x(U`43AVuU1I^vW^i87S>| zDbg+xt7;2_15Ct6tpgs4-d4dtGkzn$0@II6tIds5fcn2?TT&zmkULCJ>iLMAU!x^# zF48qNL6j$~lhqh<@`h;!4xAn4ZsEYUYjCw+;G1b@;DpXvO2pY@c+MC91;xcJ<1|i2 z3DY@=5qb${^4eNvo%PW0CVxhcW=Qn-8aA?*ce^`_ym{;TrrHaqW61e&_RV%g++Z6W| zaY@&kijn?fot9lH`?ek`J6@1&%4!z-(UVBU%)xzOPWECDuucJ|5yE+Vw4zZStH!`f z7)jAaKeQfC=&03qg|y3q;->PoX${!X*NJPQ#ek>meuc7?2s>Zu#0urv9Icmm$(`3a zZ@}+_5-!F-q2$#0^~*emrT3nYTKyR0kCe0xoJ$&ia&(;ZSyH~IuU~m20>vL#<#*s| z=g`Q)3`o^dE1UTK!gfZovf-{)#QPs`z()MQd1#J55&Kn_6|F5VQaqZ?jW1n1S3ZN_ z9LmV&WFXWf(JcXKN5UVKm^~4I<1U*u*i3v?TzCRME{8kkMWK{Pw*HfnlyWRGS|E~e zVt`rO#T*{CDFJez)8*M(=R(Bf)`PpBwGbSL_iO<>Kc1b$lC6}^y5dpS*m;Zffv-6 z>u{v`i-C`FEy>pmOEwF6`2bLZ6VkK`P5yN3z!08IDU3oci@zVhOC&M*Xij~!NIkOo zISPEFkR{WErQi8okG(meTPJfd@!3(ILsJYa;!ICKf4Fj8?~*SH@z$Z$z8ts$|J^du z&^>yvqR?uoU5go!FWl5vTyp}g($h0``wMJjYj zTHw_?Mh$IA;=;BeN>Y1>J8GW!Hd!~k;Xfg8b)A9y!DcGWu=?>TL)nNBJ2Qa@RBdZc zoVgR+`=aer&EKe7$$)@e!J8f#{Y7dKRUJqIn+UsvYk!`0h3EFQ)g{y!&~qa@#q7u- zCr=;KRyK*1jk#H)K}o5;ju>yDZ|E}cDch}?R8HfZMwXkd`N5=Bsua6gX8Z?Bsyb{9 z8K_I^(=@y_*EX*k*R(ci9j|dJo_9Y;-714Zz>DvVX<8DaHenPuWzSaBEEdtEm-em(Kw3~ere zC*rE072A0)k-~)SP1_%J+b@lBr5`e0Zc=T0RISBc(o=sSaQHn31yXu>}y8h!XB9SzFbx!C>Rk^px z1?BFBQ1nAFESa?-=aX#{6d>K?>G{u&5>kOE@BEMmGAv^4`{3KPfSgQ-pxm;UwcG^1R}11g5J=qqK?_o0nv?S1k7oUw@B5su zN_0o`7c+g?&*bD%8dn&5K6g&xaejbj!2Wn|`lq(NwL7XA#6G7O8h- zL*-l*T@XyT)rAOvbt<;34Y>t{T6%qJYy)es||H+Ga2v$DM@|3 z=ZT$FrbL@XM^-O$vcy+P0Sn>y?|rH@WKqo&(P7CB1h)UpC=00gVQKN21`_|qXTJQL z1jt8>>UHeoy4lFMa`=i=`}|KM&PU6Oripu3R&xIPp|pMW-Rk**a_cL>r*g~>ja%tX z*QK{tk_@P)Gr=37w2+qM_vSML1hdqxm4c`;%HPAYWR{EvOey6gO7VvS;UcR(kY0!P zQ;vj^=eHg_ds+RSNtSuYoL91(Zvm6!w!(f@$`BArHy?uqHba=zL)a4^T@8U{HCE(= zb$F{HR$e?~MiJj>rEB7OcT5wgO!9-|$eyulp?qSQLrXY!-R@8Uo&=9Y7?hGd^rO1- zj4p_M0zc|QIB7G`_-@lHqow6;A#72Nrg~}mLE>TaUb3U)h0&vR3NE z;DGBJ;K(c-xe1OE=fMX5h4%Q!z4;p$n-@5O?xWFYM;I}p($ZGaTg-|# z^K6VNRs(T!{?XX|PMI4Z_QaxIQipse9GR()h|Xt9-WU&DhNvA*gLteqB+pJJKw01QJ?xSt@iy&R>O15TOo9v9ux&MevanhrG6K``FinA>+T4HL+F8lr-1s-U zn+ZMXw=|5!p1+MDM3amfFa+3nu=qsS5Tg1~{KdpaiVe4{tqr{VO|YzN{;Mqdo)O?K z^__mva{Y&AYA`anoE2p%*C6V$M)=`0ob=HijTNsT&)YaluwyV8@3Qet=pLdeCVlLq zO$NSWh#|0X=MDh{uWTcs#PdT&pE& zYmK3ZHfKs#k?&+w{j~+T!o5WPBeI*w&fu%64?4v=;Y|(Ks2BIznpnRl!RsCan$vuM;dg zUH2W&{DYAJ?r34ZBnN$$yqZ_n<_2b1;1Ajyu6FjN&=oFKZnRq$MUEMcdTT zy@fRBMu^CTP8oA(Iol;GQc^)x=PYAfZkjQYvX&e$#QM;_QvXivoNFG3x0smt2Pkg@ zg*dE!(TW7YQPi2&T2dJz+4PAIJCe;Lj6vM%XG7**$pfmeFffZ9Zbcd6`L|lBkOK&z z-oXf^eYBnezx*6&=7&6_w-?0*`N%*HV;spB-zFgtNBYq}n#X#EQgw4ps~f1)DFpzO ze)Vvwoq?KbEg_;=T`7E9Vr>;mZQ2RI7@2+$<5JAl&z_nA5$AHXm8{;Pcx9Dza_n56 zR|y|Bbwv*PL4yJ*E>>XxmsD};yQF9@h6g^f@1p^Uwvi0>sT8G59fu3#G0IGsM6XdX zne^#Gb7A-4wo*Qt*_>gtk+|oOlr^QNkP0#XM8^y9M zjWV|9CSI-u4VZw7h}0zBxbk=#TvvlZFpo_6DgSOp-idwZ7+^_Y@Wh9rq;VWT^MIVJ zo?JW>XS`N`WwXyxI(6asJdGDZaAH`s0bBqIeM;U0`IYsyF#fl2@qnnQkXM;?nn#Gp zC_Y-LEKxsanRg}g)7UVkHQo^*5ZhIlZN%>g2Yl#S$%U{mA)n;#__=u@_UVyHw4;=+ zcfB$ItR>*lWFH-=`D;GzW3H#m{M0_|1A+17j{1rHgydiz7BeQGBjTPp=$qV~SZa|{ zM$4P7p`_Y8s)n{q`VpEfXlBD5VJsTvxo9Heo*I3Ogg&_7uHQ-6pG3w)AzS`xJagSV zbuNv{qpvg3`+J*g-PTMz)Bwsuhl%@k-12SUV$pWhSAdu1(U z>9pZvQ79qVqzdzE6uU>ddNzu4#*%YU(Q-GuL>ExLJqk2wLvi1r|J(?- zj9J@Dqa2bdi5yvgy{clcl+74T!1Qs<-qzf9gGR2;$>Sul#!xqjqAm-?nkg3q)mCW;mI6h{eZqQMgd4&MrSz z$^8T{TWr&2g??Ld0`@rrOnS!Dw=(MH{DYLl)vTX9{jwOuN>t#QzI>xkO9>4sntBKU zden-K&7Kdj^!tvI9C7 zQFNcByJr&j--m-3z;#^opRiEVUHzAml#FB#>6T>xBs%Z50fR>rWxNB9iY}H0&$zu7 z$7j*3YTBZz{0t&H4?|2ROm*;M9&EaDLiUU0mrIN=GC!iObq+IdiNE4JILo;=Rfe>s zi7ZO+AAp1&V%4$qrIkJ13AY>)JkbH=3S-Y40tNAwksiJui#78d7^|I754>}1{<6^Z zDvbUrh9L1s?IObG&3>q*2ve`BGZAAuY-?mj9Sz~bO*{#%g6vS3)!t$hH~2-@Oj_Y@ zsYUBPs?*kvQ4NdT4~NMvQe2qu$$~0og*vnUthS^t$8P?i6k%U}_^3k|6tb8iVdE`- zxXOx^2+!ZT^Afnc=FW{496LB3sD9u@b(-86P;`v03Zh*rN(cvHEoQRx*U^(!FsfUz~ zk>R2V{Y7zA!YV&@S_;QzCOg4I_N*-JW7it*;GkeCSn{izl8u@dsM5+x+-&*@(Q5JJ z-`wuyQsUg8c*rHkjjb=Q?b0D;yN|DQ36WF z9oSx5_}e=xWj$4qgb^hetBT@XzE2k?GRVy9Rr5&Dv|Cc=uiR*aK{(hp@iUPhE_-r4 z4hX*GcMw~6##(_|`{h)jINp`}1RI)Kv?EC|bOZ8$Q*}0MiB0T{shRIz^Ml(AP(@b; zou1Mgs?FC7kn2zw?lIZ{=y3;%Qo8tX5J-0hw((W-lUgFxF-0r$jQAwTE*t5ef5?)& zd>{osO9xjgDq|EEJWUDoJ95f+7XiEauvodhTjKVfX%b|AmdUbs7-m+5;?%G-v^j@X zE+~N zOBsO)J{9WhrfPF&$gN2LXw*KB0r!#~k zw)t33=H6DJsH!n#p90Q_zkQ~vr(b%w{M5q52tYNdKOB<{j@08-vG}<%AO1JFiEf_m z^X*=v3un)!iNHXgi9YlgY5bt6lYX_Xw?`^WyX8c7Ju9ozDh`8|we1-wEfcof%b)?Ek1f4$ZQemSJn zPgrZ8c<41u9DWsCJfLYDlli5lr>QJ2wb-$el2NP=_VcXnLUu!7he;=sEjT%ne#EQ5 zYuXv4r5mgPlxy7igo$lUb3@6oY4IC=@*$z6;8LmcJVr6_Hz){_<$a~A7QET6ZS&K; z-V?{&+sn8-T!=bE7H^=ei zM{DW&SU{`>k6mY3i8{}@SrJLsMzsnawd7dtFXf%-Y?jCOoYpfZMJgCA5BX5Z=}ume zjaN2H8HuxT0*+zG#e7iHHPCQAG%p_BUnNdR&NrDeL9eCSm3mvF>8~j-x0wwh>&5~g^ zQ8+N3Hpn#Y z9=9)o&OBaDu;iMso(TJ*m-^~V$W7eNmk7XVsF-rWb9~K*C#AswJLaRJMTRCCZcLQxvyqCw!X4w7sywxPJ%sBPmj)4`Gb zao9+;JA>9QWcGZbfP#?rGZ&ZGLz3-1R?qB`VRI;B=EmnYVX^^M+WF=2tTDk{57oSn zaCLigZ;j-<@19#paHg+a5s}_S>bp<%_Mwi8nE=wS1d2ZYmaMU$I``G-2+a#(T`U|#VL2n+(#`{5W+kL{*CfaJ(lFCz-5iQQUKv(bJ?gL(m{C8wd}Tii(~>0 zFD@n|x(#ZTkc@D<&TR|U97;2mv({409yqrj9^Q zmOM$lJoG`Q7qR)!`i)}G>`wMJNwP9i7W=B0&eG*>ucp6w?=Cag22@()u~;HC0qj;O z=;ij1-vQt7VvC9QSAIN7OgSEuy!E(gIR+tc$ZkC1m8|9MUkdQ!Y;F+<#?nO`1SAKy zCrhphM=mOXD+40Aa>QaQ=K*Nla_&QC|7MJcGOTT{h{tx48+1=?9T5cF*>>*Dj5xAcxdV@p`% z%YH+~Cc8<+u2Q`thkdCkn@DY$VFi0tKa^RpqvzMiutc|VKkilrEAzQVvY)QC!xf=n zrs2$BtGzVZH(DM)bKGQ3Uc^(vG$Q;a-39S$Pn`MJf0rJkJ;&oYLG9mNINmT>Jf z{z{*wZDNDZML!~|L+^MKmZ5Z`#n)1Rb_tAEPSMj0aq zm~3275CK*c8r2ZGy?9OH0yckH1xJ>i{)`WU?&&p7nTZX+7RN=xg;Eq!zBTHI_$&C zTW>*BuGuE4gi!gc+x|wXWl&GD!D@&>A zYX7;=e@r$=Tm>+sUMOQJ`82j}>U9ZPr(5@18LwWTNTFuH{K|l+#;k167#zts4!9 zjh}92Izt%N7J}rvVp8L!E0%cun{yzJnQD~Van;}|*-q;d+SW03H{WZeXD+p-Ol@5r zW}`MvF0?P`R(B`w&}Yl6Z#}P)hBX$=OSm{WgOqB?BG)vBotmTPwE6XT3E#5Ba3SNk9CxM=rt@7LK>-|!&N!=oH7DQb1QCY)gM zuG(MsrrKAv7Sv1pORF$9b#3U&7(JMowX+bV_GkY9x-Qp7ibtIXBedTu3w6{>zJA(s z&subNvfv`FF>&i9$Dsw9x9qde;iKEzLoCF%6a%Qf*ML8g4SEcQa0BJYa&FF<&sU|I zkMP^?llyDw-pKuM+mq{ZQhZF~CkWH3Eju!;gj%84nD|V2F?xO!MsZ#~OaPOxz4+u? z?p3ubT9mW5DdA6HfBUfVw1!s50%iqgL)XP4_yaF*mOn0As@B26Iijn~bcfNduysW~ zkADE#THCCIe9@~%!yaS49bd9Wc&C~MokY3=T|*yHMD33o$=A>=66#%PLs>dV*UKVvW1H*Fi{F__Gk?150sRkgpVK2li2;=R9gre+bg)=ws7{2M zzvMG^%R`awIH7|zUuh`hKcI8V5w7`9K=?svADd&p6BK0Qt7T*3%|6v{SrEM=l-!Y*Bo(&QWGx zFVlusqx$>`#zEe85Rl6V(6aM>AdYF!=4BU5rck^nerVjX;Fjn}#FwAgpN_L}xD9}c za}I9*JPmE;eBskbl5&HO_K5+w!0Q9`Cd=GSX?mvtkalIAAkQdeL!dC7$pA^=yQcA= z5|{a+rFhs#73khvHZ_8xZOmWcnVkgP!yNt?)PcXh^zT=J2s;#aL2i&>IK=c3f(tBx z8jtJx5}OYw*VosZf8mqhE%?|?u0*)B(BG7V@hh*S=Z^h3qKO=fd`I};|C|;IP1HsO za&0zl2AvHEXaY$TG}{`>IgjkHfL4+Pmu?{|i?)-m18|TGTjvMBhep}F#{>da*Iycg z;Y_uerW;@tftD*Z0>Bh)UlQZ%St0|BsCRQnfNB8<>d&IuFB4Xo2}$CSn?5SeBQY*U zriOo-k%_$4qt4zPvP%L@_N4PZ*En9=WIdhAC3`uwW~BKW9*LM-HYS;LemU9VMydO5 zlVG}2$gNo9Y;iT@Dgr04G|IAIGUgKf_mdkLwjYo;{Zxrj)Rbd+FEQ(z8@_%)bDB2? z;LSed^ZE;oq-#kDk9OE5e;O6#Bi4%F%^yqKt(XzKq)_eHu^kE_>G_k1fs7JB)K1fA z{P98@+h?Asi(>$-V!G+7Pfk)UH-vu|dmBReaGn+_56CtWg~^#_@#1Bywj6_KX&yRWTrM>DoT6e&?Ty3~u3yEQg+m_hv-*W__HU}k{qn{cnAkls_z zs)y|wUvvbYbB{7DkCM9GuIyuxJWwC|Sj2uh(_U+U4n)J3PO>OGd0)FfwIUkN&xwsH zc#SF;)Ju)q6blB52QDSjVbB&&uFXl8wgl#rjHOv)vhuHu5UK^L#cfwllj**%Z2=)7 zS;m$Vp%Eo3R<9kLUGKb(>$$s-de^;^46n?|PzIy*tsg!z+?%HH9saB#t*0VWAb;-x zhig^1G?xf_Q~$?NK_$T>&a^5}JnasECcu_`SETtZIXD6pmz!i%;i;8aj&6DR9w&0S zN5S-K?AMB0w`7pjdotNCb1>vGGZ(~<@&uqhP*D9fV0X$vv@5T01bv;(7!Q@q#E#pi zF~LD+RjKeq`2iAn8r~oKCO`tP`$QkhHtO_h{0@p1_0VRn=ERPCWNIy^7tQQXQ5<^% z(7dhCpw={ZpMLcvtXj1o@f&+{V?6Fsj}G>BS$z7$DcQ#?+~JcTyRnqjqus{4;Sx8>=9O5*EnS}a~Y zkIxd>&_OP*{E^m9`RuqhfqQ*ACY3R@l(YT|9R-dN9(s6`aQXMQD~U}-iafdVijHQH z;vh9|OcGs)0J6C6E~f@0nwJa1;22E0KOl%?QgDOIl$gfmgAQuQgD8E-qiYx&=1Dgy zwIk?^^hi{kr~oB9_%yxA znkY{-B~rN2!>vfw{g$)L2N z+Hb{@AD_I)WqQpd{=tjWY>wcb z4Gs1qXZdvJRt@t3H82pZGp?v{7PORz6b$9eVHNR0iRspC0BT7ZQ_XGd)W3#HbGBS0DJp0RKIZ)vfO3>{ljI_tS z_z}u^V4L>c?8O;N?$3?mzmlVOY0aI*a^d3G_8kbsTowljfJkn+E2+J@rdvkk5+Et_jipx1*2ofpsJ%cKt6OqMOU%q+cXwv& zr{`r7BV6(}0UA>oEBm2A$M6bZrR!bC-Mfy2rxdsx(KWo$<)pOK8sSPyP6iZl3NY_N~BpnkG%hx5lP_K3S?ocL#l*330%Q5BwM=QcGe9@PLh!vk@cevi0hw3cVL z5{YD!z<4z_8m#i2Kn74i7wI?ymU~$6zyf$hb7vI`K6tp(rlqtMIeRYR3>P87?g!2F zA52N1YF5eRrtKPr%Q+*Z86&}X6RW6ZR#`+Go(q(&oS)U9MgD4b*BS5Y2HJNHQ8$g6y?A!joZ)M|Qs;2hMEY1pm`K0w zZ-NXQ)ub@h+)4!P8`A{V_t`zyPmz{xG}JMd(}D1o0jagW+JI@WJJm0D;vF)!#>(X6 z0ErAA5{v|xd$Y=Sjh0mscL&o80U&a|(jTEaX8n<%iK^NyQncywTy|J0xBI;*x*gW& z*JiUqb0@oPU2<2;q>t8^uN_Vq!v-Bwe2kTO3uYY}3J26P2%-VgEKNVS14#s)P7ahH zxn4?cBzJKX>w*KE8bEvW%~fIX2k_StII zmb53?w0%#v9eaV1?}RU4cxpNIs$wJMp|sa2B~2@<(OwbFNAcidlb2OTvAL3& zAQK&us4?jeQczA_yoX|+;nTk#!ascO9K(jR&#b5AqpZmR;>zw$WU)|YM&p$_CGoyB zW-2w{IKd{`myaUXocQTEr&|yNn{aQtc0W@w)FM;3JUwR_aKnHlo+2`*B})uy zqAtJ0K=+;CBjsjHma(!xW*l=&@k#tKaXn+A?+(L~;VOmq^#Hooql|eKFoxcnWDGhljF@xKEu!h`Y zbOclIQuU!Yk!{8|vZbty)Ag--jFKcME|rNSQxpKCO*cO*Su4qK(z2)&RR96ksV8ckv8~wF;l_Fb7y<}1#D+j_hL4ey5`FSSG9#c#tFRqDj3}+2|JLILsHBp2$U0R!hHCH9f|!O;3f5pqKs^NxB(cayN0p0nK}?Ob zf>ufC01EZ4<0KgZZ*>_PYT;%AiLEy4*9$PMYSbs(WJ?K!rO2o~L8e7OR*K=&5nZX% z5;s$ibr(LV3<;jUh^t~CbwW;QmA5JDGe&)MoJaOEKEkZU~ z6Df-AVpJwK6{ze+U&yv~MtWlL z2LOl#fslx@Lm-359&Wj+$E4fe}GcJnzJ z??}`WQ-wYnkt%^u2s9x4^C!a+6j+_!m*q`&M%3m@*d|yuB3R#Qnr@5^*{jW`G3d}^ zMVG;*yw;OTYC#CXLV)GPl070Leq5joInhwlC= zQo|p_PG(ZO(#DnEmNcoKnB(Vr0};~RFg}IM4G8p~>&XBm6Tk=toepOZq#$)Wc9+tL zM#R+Rc?HmF$}WO7-qB%w!7a&e6Y$i{+(s=RF6_ofV-k&}{BZDt~}mg+%DiXitn zRAWGvsK$k*6EHPq*i;Ot_pl?YP)$Ne+iDa2GPAjHvI{EGpHc-Qzgz?`;i8ti5h*F4 zkEd)mvm?rymRe8^v0?(O60l;92tEgHxYChJreIhQcN-t06S|RM%GCm!j0`JwrH}vs zJ6CgwWk_Q+4FSbGE_rq&oyevt1E6Y=Q`Z$CS>&i79mh;qfS`_gR+PfXghXBT2WlbH3Bc>@-+)#sF`(R?ZWITZ#dSXSRhSeHCTD8c4fenC zXZT#vbdh=b#%rH37^4T{nfD#OBZn9!5dNwpEQHR_L%9tDTBFS*WO7vm)}A!Wv&ONX za!CMgSp*(N^Nf+S@@Y2(Ni47Fl1L`Cr7M9{N6BV$XW(nF7_FW65Gg}ijS9#$B}x#iS~0#+iDhXsKI6I0TMDx3vu zlSEi$dS{b-$2F`b-%7c3bOl&M^dOG4r82-aYdV}`oT7@C^3TpmbsWE<kKT$MND%?X-`5~RP<#+zJX`|+iB zrHw1SENNZoV@mH!8dnIhrA@4({{WqO{CLS9@yr~*-^bk~bxKTIW_2a6ND)kFTlB^> zuJo~`StM1U_ul~uQ{8eUIu?webcx6OIn8#Ee)}i&kOwwCmj3{|Dt51Y*a5PEve@k7 zU6cT$@x%ro1=5hJLV!*r(n+L&YTAOjuO|;sDYZfGm|3?4#F5_AWOM4fN@~T8d}~~< zpmB0pJFJrMQ;0OK*eR$(dr?BN76)<#e*Bd-@X`kEswg^xy>Vv{hi0n^9^J8I0qv0nig5$pv3`D7`=IIQmGxMpHl)MTlUhy!N5W5tT}0Gi^olQizn zQg17>*0!N+(|wZ#Vyc)Dt`PHi?)kVYummR0mfw= zI$)*YUjb)g6cTm-4}3^cDNw@Ru!>MrChuK{9ZpIkec9s{e3Yi0LzVyv^aG$C7@f*a zlS$BTNWxtXR`};Ui>$wWvubT7kb%PDln|C>AQf_<|3};Ttm> zE_pbudy%#Pzzb83W?C@^i8QZj;ig6?sU1voCu6wz;yaKBML65vewY{#sL#X`-kX11 zA&?Y>c>vzH6a~JiVM2E6jV)5aC0nn2SrM`*NCJW5*A^6@?tMaQ^y0`3l_QjcLUAle zRP{IOfsH@^*WzlxR2uAQLy#fV5Y&dUC~vZ82_6LD0hTZkcJT<7Kw6|ZR6Z&XU#AR! z2+ROgS(Fpp4xseN4s8HY1!KwyuS$|>;gaUcthFKA6o9k>RFXFF$EzT?D=i4^-x3su zOuU#d@6=!cj0W_GHm$`-J2KQ9wov3;oRH*GD{VYWrGf{>Dnaj;h+JHuj2NZkOnZk| z4MAW?ueM|2g~*2=3@ngP_V%|LZlt1d@YDcLLNeeCPVPt?CFxd;mQ~HspRzpDF%m*NL4lCoUq^~XVq-#>&RYCx!z4K5PV|= z5Bw<^7f~H9rI49>)acv!=n02=8KP!BdZrr}4XFX{&jKl7z z7@}RHnDtHJ(yX-m>u;z)me7#PB1EA>P<%C@6{ZfZG_p!S**2S33tJ!aCzy4OEr7Do zC)7}inWJzN9^j}B`+uv8m8Zc8^O%F>Ps|DKF1+2T-CWz7S?8F_@Gu~ukOrZs2fkwD z8<(TQ0T=)e5CI~MYETnENEJP*X_b*}tdiZh-B5HX`i5ZUjmn!xC$0H^lwMteGqE5Z zC#?vt@yau4=-NSG+NG&Wr!gAGEqS4#6|aHF2!*yWanDSWd178F^bHW`8ofZqSj2|v zcY3eomzbXUeJL&iOA@aUAs`hZhNHbRORB;mZJ>%Qy2kJ<_j-FyC0I!?n$!?LGz9op zFuJzlotk7;wxdw;<>sE-iPA{Ldvj7N^b?u-Oq?Vi=E*sQ5MCppz|rb#j3~&GD+AOJ z2qQZ!X$KvS_^>HikA9h%O7BnG8drLL*wUt+V2BHgdJp$Rb^EgiLG;V|@<2+ARTQRY zQdIp4%ovV?Y#ckWt?gIS_QOn&T8+7!Bm-_nr1$}ln7x|v3!s4WD#vf7EGx4T0(^kn zD6YXhqLdw``CzR4T>&Hz{e z%?)@0OhDWbQBQnGra&OrkZ3l*$OTJTipT-pqY{aW3B)W(2D=e~#vxcAhkRHRHloDB z2sEL<8RHwJoY)alw_GjsjmC&O%0OH8UZuKh7hCMLGSU0tY1>4SSkp!T{`Cym-MBN2^S5qpFmb`lXG)w{H0R5UzXR3ChlqfE?diXJ$bTwCHsQx+`Y+WqsVxE)Jnp0~EW}reAf*S( zd^s`Ky*V+^^GYR?4U|);7y}R%ED}k*IvkJ|%nL(Z#A9H4AJ>42O(?tk zBk{#RRe+ATv5-`Bl>$mf?%tpuaoZ9NT6j%blQORV0EQ@~=`P^-;9^3sDkxN%p1u{u zOhpCEdSz`zaNZxXSJKxtNCAn*zS2kEXP({YHTa>xY*di2P+WN)(RWvVEk9BNAr zroKNcK%z?^K>#R*s3#^FQo&MBdg94&Qbk8h7Lpt*(-Orzher>gJ^Bi?GOD@%040I` zBO{JiB0Ix8Tar7%8&9!Wt|X1O1XLdi=3XcQNG?tsVAv`*wh}=hL%1FC6URlac(_Uq z`$f5d66FUtnuODOWaW&3VahO}Vd!6%66uoNS{s#=+zAv!A}ACe7pNqRhOpkAU)5~6 z5Gtvjd2`7=So0RMaXhv+d@ZDLEYC6s%MyMNs*2z6*^#CL!;ke{ezQ}P5a%0ueYTCI zY1RhX%GNj`1MHvxk~^{L#~oMqa`!rj<&^RRB1yD-bP*sP1NCI_eIwk{KdAfG$pyTO z8c31ZdNU{_e2E!i0V9!L@Y0m}J7R>hR?&3Jn@NjWL}n|rhBf2}HK=M=0UI_)B!<>$ z8)Tw8I1~9%N8BldFg(!uwD%V9eP-Ix?`Dg2w~dup*R2Z>HpQMW38HvI%)IUAEq4C^ zO`A@;^6Of|B`GDqc4Q#bGLWdDhA!tvq=aN7C&XTAVj$3p9}H>RyVLf@mENDWG_Lgh zv88vX?TstFKWu4T>HA|!ncA&1E79X5d4-gI{yyn1RI}6e%-UCae%R8x)Aq)d-k-KK zuJpI9F{MrQWv-K@4+x+BBy$5wKYNq<$O$x&kh>1MW_bxbnlLF^8h{4$G{H+j1mQ~+ zK}gUaq~r@x=BKAwfprtfN*3fte;mimDg^m3t*wgc5~01iBkY1ok8)|100{Us5DF9F z<`pV-+@7@~d`R7a7HOd=jhv0B0Kj%ABX!rM8WAefupVA`_Hh5j;bXc$TGU%cW_JCA5|!KqN-iw;;44gknOFsT?z0$dN=jJJ11)Vo9K}fhK}`d*$_l-<*VfUfL=m z3YK|TZcqConKH#gen2@=!T{fp40>@SYSB2PiB3bfuJk#KMQmI`DDJx0);gZ20|GZ* z%JMY_%Nk`$bK0`RTAPo@2^7kZiqr?aI$%K!MAd40;*t@5l(K>&6(}l4urZ_{Sn8lHo-1!SvjnWoqjw48!8S|JuBM?KwjTx3iU+6!|JbJ5lkIJV5NebO?qUC z3ZC^8`Z6P%7<1V_vl?l(t@}EP9wHS}_?8Fo%&Bs7<#f(;CH%2IUWiWmlHYI0#IZ8c zz7EU!^2~fx&Kz~O3KHfo%fo+b!sk*90jCHp%MoANtiTVWG4S7sFM9#Sds2^>V*IxH zuf=n!df)E4!Ee{-n=+@%c3C`1{su?nKlxVw08mk?84TTx$Z@yIDzD>{jnj#r^FPDM z>RLbeC(>Whe^KN6JUu>LynZ|`r!S9wt|XJl-iP>$ST_~@E9yN*`>aRHekc8EcF7)2 zZod2|SJ6!Ce2b@c_^Y?YROF{LuUhSkB~9^S?eSO3{{ZulKUe<%m4k8VSJe8nA6T#R z@ih89J{gUdo6>e|_&*n%53sohJ(K9P`t`Tge~@yojXoT}$@L$5WO+Se)1TF&r`h-M zpv;`LOhXaX>FPfk*009BDPQZD2b(a*H9te@C+izYQ}4b+-Kpe^@@Lk*v^@{x+vS?g zT~kI!tLP>DH1$M${wk7BE>!TR+RaH$4q=>md!MFh_IKr9nWkiE+w<~yHRsg-0LFTs z)k!gWv(loQ)bgjdD%1R&>~de_qkR1z=~}umlft>QWbcC&cfpFLdi_0ndj9~+Jb$Y& zbU#V{ypR&lgENxv-PaX&!HTILOV>5~RNLi)s~)Ul&NcPnt$1-fd@E60#?)PhA5nG~ zv~{lj9}mj8j@8jxAMRwY*{{>n2;`B;3n%P+jzOd;&t5{kX|@(&4%PJydiTXEt+?|) z&48jw)vu@N8rRg@kA*j`J2V=vHD~8fhLph86o9sA5MMJ#Y*>1)5{Vowel99m0o%o~ z6??LelK%iTuarijKf@1r);^l^7s!g2Ap`c zNdld>-xQu{>V0#a$JzBiNBn%h-+YC5*Y>(%R3rSg$J0O0wLVnIMTGe%YJQjLDE_zp z^ZZ?U_sMlwU2MiDIN_wZG>o*@A{&^ggY%*1{ zUnu&YQf^e?%VIj_P7Y2+4oW_A`rp*YpcBxj`oUBC%5TP_`7mlOw&c0)wqk2eOG8on z1nhcurE?ItXR04I#Qe0t#C0ldpm0O$qyv-bboBq88VMGRpYfk zvbhGrvG~+e2x~0U%e?;p!g`>Vg4(I-vE7HpDL`po_$;a)-v&_r@FTpMKQF&YP5M=@ zOng2Q5~wx#5_4sW?`Ja~73_=GBK)!;xZLWfwfJiVvmbBwa^<-yw@ywjybI!s^4s)q z^)7WnoPHzqdtg5avO|+(cBCm{&oBOn{U83~_@7M|6)X3}4IYNHr+VVa ziFu>`Rqxf`^taW0N`Jbk>(|@U_^b29&eMKk#|Ibt*N*Eyq5l9#Kj@JlvD?!1OX&>On3sGQ ztG*0X-v%o0gB49RPCuhG-}uJg@hrj9e|wYOfz2yEo@XUWf9{)yTm=OMAE)UWb+5;j zwJm>Fi+x_6y*>)`>6K@$18il~QOPYgM(f+A73;st07zD~rvdo?0B$FmCCxI_e@=ddvGptZ6V;RIzh<7d`?HlkE?yk}0N7zX znRH28`_o#F>47~F_T2uVB|o_O#Cx~%d-myt0Wu!1_=4l>TzyYabM*ejXemr5nv}=P z9;eig4xCR;l*yh=aOT2ajYk61rsaoz^vD+>QreW4mxtrq2;@>Z6iht0ZPz2!tg^q$ z{{X7}a_{Sh_di#cm3%lHt5N;m`O$JP)f-~Nht`|Z-YT9PwluC}j%bgPwb#}7{{WXv zoS8{q%Y6R;NvEemC~M1MOmK5tH*CXDGJQ?`0U4kD)#{orKHUNMtM+p;cV2E}xjfmu zUUGV+y>3U=4fFBq<6PIDwbvegXscd3a_LR51_PQZ4j!PVOpxZxb5OYTxXWL$Q^yJ9 zS(K<(dU$1RYh&eHQc=BxFk0~NN!BGsWu2#S(aq0 zjLP(O&YZH_SMHB*pE8nch%Fo0KI?hrnUOT3v(GpC-D|CPy=yIkmYwu=`K1>V%d*M0 zzxzGaI*o??$y>KA@FM+6m#bg@-RHmm)06G`Nb6U0a@(d0*IQCXvn|VUQt5O+2$|Aa zPk#Hm-_tMUb9v`mx8_o=vsY-lQ^^3HSAE%MzWk%#o=n%sTet7YXIEdbm3q}`XIFR6 zdA)NN+^DtcJk+=U2M6{)c{d?+5o0vkc){6YnM}4!8SDSnzuzriYG)Kh`nFtGx-*^5 zVE>U}2t_^wVB5)bjjVMmpM17^&t84znd@%6?5bDYao$Cn7GJda(%I|ITEC&Ydq&oE z-SIhd=PhyE#G-?J`=#%DJ!h<49qrn)xBOB%qjhT+-?e1noc#LJ7vYJ6!v=q&&~ zda_(6ZQaHvr}fUAUsfW5j3S6IMBsZUls$+rvZN3slS;B}Teoif+?Ve9+gHEtO;?YN zj?6BXO6OjC^<^*m2c}Z$wi%syESx(F0LSVjy~w5SVUPZTtPd90o}DX)BlS+qGx!i<0no(;GHr z32@iBXDnx{moG-88lWu!jEoLr|KL>i%rln!>Y+#W?QeOmmOZ6?{Ntax`#-<)uXlz) z0LO7aDFGz}gi;VfK`8|#8dDVyp#)y{n%8c*=2h3N86FyN0BnBIdMzoh>ugKe%NEXt z0!5@KKp0jn>_B^(BNF=dR#@1w-)TR;=Z9Z;_`%;ik_x$;u$ z#dIDwTzBzl*_5?<#r$qi${?f$Ap`)02F0>DDRiVLLaA?QfvaUtYn*c$hCu`Xlv03@ z7*xiK)g=Jh++@Xyk5#9xIQ=!(-}v%swc;qHPJ1z(hmi6ui)Llf+m(iAjs;q4FiH_f zg6^z?rL!DB%UfIEYT45oA+#c*$Oq!EFa}l)S+%dt)0;pkiK@`pvhB*PD^{+aTP#e@ zyZVZ)FADE<K8d+J{f zr9q`quGTzP9ZYx%-5MeAsugtQQmM1gyRFD*iqG77NCJzVXIUPuw>baWs8<9)1_iTU46yY_80Ow z-28@%7Uom*yxtB6N)r#2aJB4MyXMMEiRXE2>9Xak6SkGE(axk17#dg+UH4qIzb7#Y7cX6L zQ6`g3Ng;Y(sONC&n>Lb^t==@Nor5#usSLI#D6>JC4W%WN6tEo^ln@97bfzuL%&Oiu zT(@aU3tTOmW>FNSlF8JpbuT-6EdVI3raP<}W2gZQjia?r0zf*GS>h&=Gb15hxQcl# zFY8LHo_v}?gMu<{axH2D%mR1X*fvLRJ44b|x3|F6vT0T+m*~9t z3s$Z;b>+OHBR}$VJ+6en;ueZi6^xCJ%uFQQ8NOHL*Iu>Fc_E&I%ha1^W(i!|0tf}; zE*MXOa2J|b0O3%YKobkZPQgm#z^o)RC_2&vU1@Rob+6kps|Bu>)y?<)^r@$venxj! zSL~H^Gf9Z2%DxP9M)BkmdoVOSf^#=+dvzw8JI||D*Syfq;LUHkxF?^Km*>+Qlv*In z0(UZ?b`pddibrb=DI)+HN`#=yg5_qRi3OVz%t#AZT;0?HSIZhtY&Ws&^i^w40|2RX zy-;k5#z#Xd)*TXn62J>X_!3C8bz$?%ubsc?(wARRsg!13eZ|)73+W7ODsSpeO1Kt* z1Yo%ta3=#oS^SO_qzIs-03j4wDf2a@(1d|<7u^|#oc)JUY_!02lG@B!v)3>gaaW!uZTduA=KR?4$qNN139qMO@O=D{L` z0-+X|WrI@FvZj@Qh&)4bPzI$nl#3p{<_VVw7}IeY&dIm{*=W_ z&Y3l9Hk3Yo5ZJ8NpiRK4_#uQ;=;>(3obC>&Aj0f9^O8%JuUO?(tEm^t8QgTk#`Cg{ zT#!zfaF8$#DJ8U&AdH(ZPzq?RK`CoeYfuwHYYn9oEY8uB1zb!1aSL261LJ(f@)f5p zb8HJr{T@A!1W-ytYYmGtbmlVXozVry<_Mz@w#Csqcfou@Y38bzU-E*`8ZyY+91d8P z1Iuz?J4sN+5Cm1NM5q7_t%Pa6iQ91~F}-et7?HEm04POwmZCi&x7~Qd#-0|qUcz?C zrt|I2&aTzVSDZEraUM0DXg?0vG(f3g)Y7hlgkysMkkWK513-7r%nrwKa!SgR-h172 z!>0DE6TZ^52`G1A*)A+QVe%>sC`AZ}1x+|WEfD5_FdLLvpp1eL21+>y2$Uj7xg5Q1 z1WB%*SMSvtP;-)5xm@Z#?etZrp0ap}tB>!bpzEJW9c+XUFiN4chExh#>-tX!so*9O zZI0vSrBZ1Cg=czwx81mLGl19IoF|pmN=iYM(o{nefG`43KoPAq)fz-=LbZ8B5DieR zH6a>=P(nGQgc3rOQbfjo%_tEmTg;i6qgF1Hf@Rxa+y=7}pgaL;XNaN>KyC9uGzd?C z+G#MQz@q>%iom!HDMCo0Knca{JVzzMd*5;E#*0D;Nr_efWHq8uAZVn4P-}#M_5oC( zQ6@x}2|=K>RsaOFj+EAdXdMDNP=HUh_LWv;30el_ic?nn#y5VrzXh(7%&xv-EA2mW zc-Hb2E9WE=E))Q*RTG42OondMy$ERnlA)%Xb%ulz$Vfm+Nf_r+g<SpU17@dTCxywiA|FzQ&{{!DiCmIm zQ8#GPQRjG}h6aIDKqx7^$Z$rC0+a%rQrH%S%?Jq5=HCdwIfcy$ENT=Yn#8OmbD)$) z#Sbu5_SB!gW#gSv;ms{@okSKz;jBzHJ9o|6b@L4yrk^6kBC+C0Pm2*)xA(h5_> zLL~@-anAYVGrhi)OO_{H2lHm+vFwz2m@}gjnphAL&{{wVA6ira8A7QLQbbTPf|kOl zNR))q8UTaRfK)PO_XI+}3O^KacQL>i17{4DlZ2hhgE<*6D+R(FfH@%40wEllNoZ<; za2K3A0AVIvB&H7L>jD%M2vO+FD5M+$UjSM&Fh*fp9HAue1px>HXBHfrn-7b_;tUpN za4ZVfvEVu`>_i%rCrnYB*-$b>SRR51tJrgBoIbF-|Hgr${&EXkCy`BzPqeQ&W8Km< zYu28M*lRUFGMz0n&)%3hB)aCdjzJh91R4Y*!-FHfSM9s!=f8QzQ=tgq-xi9!ePFD( zhS;M>W%5`!w;O?^P{cAo79Lbkf{d!r!iQ4A)StA1QW^>rN>eB;%@nK9LlpTSb`>|t;UK_qaf1+J4PB+MT?*q7h5`I0jMm`>Yc0z%Rh84_rs4I@Sfh;C$H z8V#&b%1|nmaQNVXeOl`y&*mEc*Ju8#^zPfQSno|#{-qiod3g{?Fv~^n>^Z20fF>M- z<)GapV22ZHN!38Ih6XS^IPlQD zzt~=Qw%7T$fB#({z!iUV>(-A?m8RYm231^q?gsSEo{vx}gi6O!B^$5^s8&!af)+ls zjN%-~hn69BQagx}Tmv8Ahbg-}c)$hD+ zi{m(1d7NB}H04@`%rL};*5)s25n(+t)C81KR6P%U`}e{3sv{@znm_xc@BPW!ZoF{! z&c`47vZxH>tTk(};FMG0CUOX+N%TQD2xXu=30NtZDNV|FnaJd4N`{ z-7JJA(BL54jaBdrjM<>vh6X^vr#*29A<)z^hoc0via?nK+s#5t0VP9d&Y^_`K$;F_ z#*H_$)1X!w#7cp17ob+X36l~)h86fzLx`#qkYNQuc?eNu93ztz?C6`slYnUAp=FId}i?XNOPJb${+3@BQC@eA9VP?mam0tDB|(x!Ysp#n%obFJ(=<1 z=6j$5KnrLsVugkTQ3{|8%+7*v+vKihpbaQ7#E52&X}qLySF@5JDg;!7s4|Z7WFP#} zC`4F+@FwAVCG?GWcbzNUc;A<%)+r z13q>SL@4_7AMX6xPu})a=hE_Ae_)%Oob0{rt+&5&;lf38wLV($ek`!iL~}GkL0!+c zYQPF3fvIu@+)W^vZNt39%hz>0aNmLDtIxcxd)Ay8i~s`%_dl++{!7N#kH7b$pN~J& z_wdOt-`oF=TQ5ATC=y>6e=ASqfkxo5YE!mLYT$ z#-9Vor~(yLp`$8fSVmBtM5Q=_YPkTR1Q;P8%E!oL8IK(a(Kn8Ar0A`8e(fjUc)D|G zEn@fs+C}G|wQ$z#-fi#svk%=aFZVyt zr|)~<(EnYxW+9bQUobT_iJ&|NBC80z5~`IVqObxP`H(V#RV=;+`%L4FU?nC@8l;Ef>Ug4NwUM(Hal}It-w^GGuieqCAAC zG=!*ZKEm=as-r7hU z`UBZDS6Y1Su=acal7H1pj`X-KK+!dhWX{PZbJ$KPkq|<4nTo;`kd2BdueJ1cjvi<&FOsqR&O>y_0z0ZnKyzhZS z_pV)Y$`ewvYpPy_gkA}*1waXS)hPtt6oTp$LcbKtlZ=XjGH!g32%w_?5W;vZP!JWn z^x7Fv(#Ve*?%gP5Nj$)YXh13GAcU@#psGbkZvxUAgQ$!`R7Mf`MTnpbzfwS@Jb_AS z64go(LEu3v0gFob)e;^(SjIy~CGBQl z|KNw;!a28;QpaHko5!e`T2_%ib@KFDepzrX&vtGY*{{sgfJMFZ^cT2F#gp?Ub4+)zX zHG`5+B7_hDMC3z?08;pnQ3OgExa}B{f*R5SN&-p)DkgZeu5+jLjTA=}*r62=Mo@kQ z$}2)fCF2=I6)LJi2@g^P5K#c%ufq4H5cpMup@%360IlG1g~?(W+xG|9HDV1$iofcv zul@MT|DAJaf$I-o+b+3iZW#D0|NQ+QdfWU33+70De2Fe?3J06Z$+de-X$2`wSg7K> zOKFWrN(4~^sR&A?5)STr5>6tCxr>&0rvN)nJKxa1PdO=wD<>j{wH7Zb0Q(J$R6j4<#ZLf;Iw;S?4XI5=Fw zul5NXDcav+lwNwrSAVqQzk3EPLgRT42e;V=`}WO#^=q!b?CkX$*2J{mQ+ljAu$sR| zn;=rD2C!5;Bvgomgp`IwEEFa%Rh&TYf+a{KlCT&>!nI*@4$h21K>=8O##!fd_sqOE z2!bSl(usJlU%K{YK!Z{>;1Pfn&|1czGR>2w^`;Rzhk*~TT0-bo5QSAJ8Gmj=$~Z(=1+;>Ud_#TL7*UN_ z*%5(2gK!E^4QSc;251mQW4b*q1lHI@t>W)BtvFHW!}rQ{U87P0fWlO%ga`K(@ZdqU z+n4<8X9KJjxSnUb^71V+D&_L5*T4RamvnV@W!15)`?SwB_tosz6twwJ3bH1KRShB| z5)wgV)a9j831dS8=$tVNZS5VfC_%!pz!?RrZK@;$i~>;*h9Zi5LdkOy&HV4Xe*CdW z>Z_g{*8b0SSMl)v0>-912oXTZ03!4e1{DY?poj%UY{LeUdJGIk8HjBIJg$3b5mV}_ zPR&!s0M6_6EFA-o5JUY-r4h!gr4SLIOpD6sWCf2Ms^GvlzCtKnb;m#b=-1EoJ-5L1 zJlVBZZDT`&12fleIB(rGuXxpECht~90qfXs@E9qE29(s(Kve^lkP1O05JUnJz)=4| z1ipuva~8m{EhKCX+u~r9#x3!rxrRoFkvu$S^9?_L$36e9LC!4tY~PbZA$IqdF<$fm z;Xy|pq!6YEPAt<18W)iv0uU`h5($9EvL~gWf&eP?4Z|3RmY@{WGW9a!v9tjxwE$|F z8HbwIM__BMFkYyxo8DQA3E{?Ex}SL11tg% zts#Q|N=PUTLtyx2Xc^~al!4l=QD(%UA&C|6T5BV#;tnXc5k?YYla)i&kSzb$7k_ov ziF~gua6LcaV8_tVAiw^NH(qht>8s8;ZZJ4~vga{g%cK^%PPt2cY;7wP#%pQ9Fgnza z@>Bt{=PyFib>LbIwq?bO%bUis#U&w1sU!qa1JC&Yo6p~QPX(HMQb}cMQ9ABUi#-Vt z*Q!JSh%#6r0oaMyZ38m!p;ct0Pn1CsX2iKc*>pdHnr2GdHO-Y;8!tv^?9Ei%6Xrk> zhCos4FF$u*--&*oEpR=@cJ&oots{pIr?0r`+Lzz@=C{2I07R45s(nF@lY31cOVeNj zrBZb=Lp9Owei$KA8etgX$bl!2%I473Jrg!1a2*T0?y*Rdtjd5bib5|80>LPIUdl5H zK~X%$5|_w%{fbGymk)^RH`x>jh?m1O2I0tJiFK&!4^jL$z?QITSnw zSTz!($!j%eb~Hd$r{EQ&G|ZqM3J}UM+!?9 zDwT4Db1t5bbD@yivPpujEC+}c1Iq@p)3EG>VfVNV=41h_L1kp5OJM~X!Z@JXV@KBa=#ia??sroiuDW4N4dQ3ZBRT z)PWKj$}2;86VTxlbXWyZK8W;k^4l0JWryQ^oAeIZQ0ozW1a~n_! zlu|IuK{lO+#jUMxxp8Bv1+Eu>ZP|D(VT>*RllT7lUoTv^sHdi0Z(h54G_dO7V6)eP z$)}XoP*NF>r8IQ>cTyRK&<`VkaSR^rLveBpa~CZ|Hj{=EgNxBdIM@v0ny1|L_>RM4 zqr(-?tDbk^d1r3C^773yo~yHo6y0cB99``hIEf6n<$~I2IEgk`mIX}!p2&h(uDRC8 zhYTxFIs#D+b1l^% zHbNm0gb@^BC{In{z@8mwYwyC$*>mA=3fJafj2_drQge%zZKLRg*neO@d)YbXzyFoj zz3H=OZ#e(YtJUhIS6sF!{al=n*4UUyFyzv%>7=F%+|DAAY&V39<$&3ybJ=t)SDSu9uR4+Mv!yM34y-+wN}hNAMQ?f6pT6&QN(dU^AbHAA@EFaz)+Y4R8cG5} zNeHFlsY3=_LMr${go+EmjRH-q+MKc`Bm$8JhXi^fW0U3 zd6n=Ra z9T^L4DVCn{#3cZ}-2&H1W>;RmB|9+C-*xkw-+J>Yr!1aT3kRPHSmY>ARpT#ew0lh} zD3ol{5*j6WBqY2rLKrCsO|W<8!_XR7xa@Q!T^p{&>$%pn%_vQ|RwO0LRUe&QUFc}b zqAQ<6==%Vn=;-X`>1=N9=8M)_KfLEx(Fp?=^u~08p)=<~6Atd=kjZv{I|;Bv2N-uv z4J)cZhGj%S8P)Ox{A$q*p3!F0iHgdxP}i`ppa8R~u#$OLuIV-wgu<~cSXx363TkIe z$Fh@y^dfM}LVMR73{?wA*%4-B84is_SDz?wwQ{Z#WY=D`jSmeDbgbWS-UY9D?dxCn z-vQRq35n^+YV>Ly-LoZPajx%20JAV~_yC5F^kLDdYti1(37Y~**9L1=Vl z1=qH)pm!F!^KE8AuGZk3p;Ve0EKe1ymSyKpY?7}wb!IGNQweD1BAx4jao0xHEpREk3oUJ;R3G;Jc4QACvyL|y?=R7Dh-mXV-5hU(-IRHsHELQ@a( zDno!|0&vg=6p-`C5EsNat zmOr|#Mx7r`UXa@KqT}RO0O|^Ntqm(^+A?ZATaEBA2nDJ^2myjhwSp%edl31a*_gBN z6fh_x9jji~A|Q1N{%D4uGlq21$b+O*ac)RpF<`L&@a|%l7=DCDa2!)~D z{Xjr-8wYmpKAY>=wJfEE1_H%KzV8efmenSzR?J3g{V3K zuUbOM4^j0(M2haUZReD(oQt+h!i-7_fk;LWVHLtn!FJP7GJuu`kc0wH_CSMy+G%J% zfbAra%FO^o88fpQ2gmfQP6W7GVd1m1%P-y7Gc+{7-~Ntw-*(m2uXtzOYaInFa@+_| z*WHM&F@s8(QE5^_NL{Dk>jXv^!3!g3#xXiPh{OAz!2G2vk!|mQMKrRh1UNTCTqw{K z7S^_c)V^*ka%glmo9mGPq?8FtX<%A;PZ*hTvFlK@WztBd+u|uDKg5hHL;hR77^Tm4xgVn#-U4HQ4{RNaO zB{;+bl^#@7Mc@_TmB-^DITI+49f0u0!HDVa0&~IaEE35K(wR2Q&bx5AyyE)nw(yqc z`oj_!`ThI$I=9^V*4JKt!;OD>Twpy#e$@;tg(g9+(ujmYBw~Om2`N3PYG0KM1c}FvDx&l@nA)5J3rra)9AU z@6jNVx4q}n58ej=AN}0V9|3U2o36j;{e9z=53QaXVacovY)c~$8WI{>3P^52hCXEE zBip?gR;mM%GEh4K%T7SpY0R0?iOx(le|SP&4B!VX&-DketFPFaJ9Mxw`HEM)_VU}` z{;rQaC9ufT&s|kBqAvh358Hdgi)gjQfgrTo}KV36)ar73Y>E!ZH8>h zHLd$-G(u9CZevP_QH#fQE=@3BFl`;mBwS{p0@MzWw9dSJi;kYr`XvmG7O;P)^tq|3Jng-o-hR*T z`+DE}m!EvD;IZXDe9Zg5-@0!EI}Z#4Q5hD(c(j0o2&*S-FRHv=H5F2-$SSWguR);tzsVAbe#11tloP#SL~ zghV7wJBg4AnsAJdj^K&M9zr_Tj^0H}!3dB|*l5cnU~w~4lTl(;r^Yp|=ESdgrK^sI zwWwO=759cE9fDup^TY2B969tz@7(z(BrJT}O&262=y~}}8rfVPiYCko9~nYa$50$Q zjM7AZtiL3Pf+={OkC7>HaH68#^4?GX_8BpEU;oCB4gq-Ot6#nGRYxXDf46LAwRiRW z6lUZTkWq*b8r)8yIyC|Uf@HP}R;mlx)Ffu)ZS)tsYXRKzOy76QbNx5$!gJ5;`Orr` z_SNfNf5UA6h}OqW$i?pf*0gToxLqWSMEoHUh30c8;@nGVfN@MtPT=uJwu4(X7A!j* zuHztKGqh(@NH{h)rC==nh&6+x>7&+OV!}}s&)JMev}r&{iLUN;?0K?p@Av=nTR&>c zcgS!3``y8bIJZdg^^~$qK9@FQ)szOL2Si0oO&msXqCd{BLIjm@j7}7=f7JWRSXr)o zrofu!#lHFDe*v>sJkpQPfA@hY+_z@}rE(eY#}QV?0j&^v1%zJF)ZS7#%+4o};OeSY zSol2H`5V@CedMn{{*4PRym(V{ct?%`*71t9x?UVU+G7a>VT2%x5YzxBBqAYVk*)$^ z9ECy=k3IAoXr(cK=}IJ1X*isqGnYa-Vc0=N2{>a0STVS!Ux#kUs$z{|(|Q8T=JLia{69Z_YdNwn{?(J>k?%cH!p_4blqUNjs>9Gq zAo5F~jKfKHVn#lVwiKQHy4P+ww-pvXx4`Q7>yLfv8|SRwaM3gx{r3P?P0rM4(>JxN zND4$T0~kaxks+n2Yf0l-D7R54Ok&4__d`Sx<}F=`R5k}rHQF;N&yfgEE_o>f)^USl( z-t=_9YW8CFYLY&Bu#BoJFTy5Zg;9+eH2D_*Fw4Q@y@My}{BC;VC9^oi$LDpYuyV;fBvNfCPfeg)9EPZlV|?Tg_VpLW4~&yr-uIc` z{^h;f2mI&b_xk9L{r9d~b;`HK%2+xu?k}i%6(lVnX;V0E5*Fu}m>k34czNan5BGhl z1+Eip7i?IU`{ZXn|F3J;uKS(9YD%uAeQBt6Ri7&;Vn)!91cJzXZ9G;?Tmay&z47QO? zf?qRh!vI@E5iH9__pDybnmG%iTFmU-^~ec-*fN4kT#KS3n*>oF{HX#)hYn%qzW%8L zqtXBPn=fs@^AG*IzU!+$`s^F8-}0XaN4$?0ssq;!j+Jrh{5B*kjhXo*MoQJ!0QhAK zTqnRbUT{wSQ-A-3e_46j=^LLOSU3h)s)@L$wTVPcwW?6WtY91lntm;F^V_zAa;b#h z-uE+jUKPvMoDCazQ?0qdf^sj16JuzJBcL8~#qAqW<_3U-|XnljV1Q z=U0Ew2jCTNy5W-7?mSfd#8fq$(VMp+q(;Ui&uH913tUIp=8HD8fBLgu{O0l%r*1rc zDA)w7X`ZT_dDjvB1XQ-6RSibfgBr;iuFhn|$ zKzDlv`AiBn=ds6%xxS-W!B{6Kg|36Db{(|oXQ<85mPsK(fXT68b#U+Qhb`LeyP`-euozZ#p0Uh8tz=Wy~@ zEpR+TPrtu?*Wm+CZqMY}2T%05+Duy8(zg@mAxRuqrd$XCdWsyZ0$Kn=kf@BA4%87jP@*Mc4U!E7ZQo=^%+E&RjBK6uB2_x<$Ges3{k z$t$h>4}Izf)tAzJ^^*2nueka$Yj9v-&g8^I-={wNh4-yJW8E8?LqYu%PoL>wpvi57 zR55uV8y&<=Az_Ub6fyA3`Y4XF=VRxCzs6Ky0?XE%ZGaW{NF@xgdh%H$^%tv?UYh$!F2kmZ{Iepp?}1X*aG*H+L>eL+^)CT{;za zz{KI8i5+Vr*$A^RJ~4@3{P6$XTbY`)ueoa5I<3@$-~R6RR4bM_G4_%S3pZYHPW$-y z7`yW7SFC^cd*1hvR4RR1ZIoGa4l^BOwE}Eo_pqvis*!6o`mkhu!()wZug!M{<(R6J z@#Kz&5k)@cFF6fPG6N|>v}cl-)!By5Y}!n3Vof!yS_iRi#HAwkjzJ{gg%N^KKuXzY z*>7qysr8J3S*Qj9e)9jmz3=DW`_>;TrN%NQJuB zq8yXOBKGcl2mr8P*(z9W5?Ts$wWTq)Cy$P78jfX|mXUbKOig`JZ(xpzi%>}TL4;}$ znJp*6h<(i}9#UJ%SfA7@K`}8oiHClBKL!Vmc&pFbdiL3yu3W34;P9b+yQ-FDx9@p! z|G^eCTHyM4{pcA_!#CbUz6=!v}{KAj9E+0 zI)BsF>Qtd`&tne`tzEmiw0qCqatj`?}icqqRJ##%EoJNy8ZUSL;EZHpLqN*fT0#VTAu5;cI{Q$+{KILE#9|p zuU@ohar%>={)fN6Wb3xu0dO-fLLURGdRquEeUnJj(zzOOQS)A+se`y#gd0g|_WCj# z#i=58J@ji#3=d%T!lmfyorfrjU@?l>U2T}#okun`9az&y6f}Sp3A5y`8ieqJ(1e~+ znvfHXjghsmk;I!95A5HI#~$7eMJ>!+xB{tc9+YUbC0*oFNf0eDXU+^O3_`K}S3ljp zcFn3IyPw#nTM%h^uDV@w<)wrWvS4U%u)SO^?Y!>wZ#?Vm?|jc)wr$O8_EtxO3Qad~ zt>wK|JF02)Y9xk^)$Yx8tT3K)5yl<_ltF1=;7}h9?0OW8F`RPR8AxW^5d}M3G?x{aP3(gc%sxv}jA!Swz#a;s+si?|cON_UuBs zqX#|n7Q=E}P)M|=Q)o*k;n*A&XON-9DNB|uYRh-_Mq!u*Fxi4g3tY7ju&Z1yEf^de z99gmA)ZK5p{T*-KaPIkk3&3jRRt;Eb-N<7a9;*pVwOv?*K!E}T;FblcHKHhjCKQTj zXiz8+vknIg)Z*}i5PNq&hW`C~knfs>xl2w3YWh0L-k0V;I1i zr)pAIOl!XfDC5vtqc~MU-+_HNbYL&0iUm|E6?oMu7-vXlvalT&LI_mKB}7pKCy_+9 zy|dn@wP5*bev zKfo;Xp0WaMoih-HK9tsQZ5x@SQy+mQrGln$9jb{Ily!wilWa6?zwr#$2+sikvT)A=+fR0+u)ExfRau|J7 z-KP-(>iPojNCAfq9l)=DaWD4m*@b*(7uKGA9u_P-1-Z61xULJwwm~U{RHG6z64K%fAEdMH{G-qwhcnntnN zyMh3WaWKkIE=^(Ja32Pb9E2A}NO$xg+13p(%giBkEOh45=x)y-lW>ifqNZhq)uux= zwXPgKc%V;+h!8?r`PNGXTtkBc$(|WAmt1(!rZs1tbD!A^sRSpJN9VkS;BFd9DNq2Jq=TNe47%DgNIG^r#D>)Kt7i2Z zr33=Nlk`js#D8kU!*N30}@NNtZ9LDOiFTm=Ttw+jr5CuL6W3V}g%~?b1 zN2a&>E2SYtgaz{#VA+b5xbGJ~MR8&b>5d-L9o+Q#W>$utHtDurH;Xlh4R2+>hcX7n zIHEAb_+UT!5AMU{#5i~&gN}L2U?nrqgqg-mPLaoS+TvuOtDJ@J+pj4V7bLPxny?(=auek8yP1mkky>_w18|0Oc zHSOHA=&$j06XT|vXmweaE15N@%(5XAuye-_{Pg?(fr-%}oW1cftXz9G2sE?^;J7v{ z&f!=rUfg}m#_(7bVUjtEGt8Jd3xp6u(YP8ZNo0e8c8Tw!LyrZ*N-- zFg5Y%bWlwf+%^NNwm(=lfvT3j5X!*a6voEKasMyw#r;41A)pn`+j=#Yo_+=(BRCF6 z!nKXZY64cxV;!qQBp_NtL;{Ia2HbWaq%hYa)wFGqN;P{i*%%^I>-7RaIR~W-LEvL* zXaGY;`YYgv|L?;d$qT1vK$*Q;HL3RI*5DG|WTfZzZGK*T{DV1U8o)ZNoLR9CJq zy|910ud1sDq-d|aAVAeSrvaQsPjz+AyWcO}-@WUQ$Pa>;4us&4VF=qa(3eSJs4t6R zHWkm$bSkRBwc5B?6Xm-aKX1JL>YI&5z13IjpXfp3Jr1t-U3;Y&MGrwOYBTiG!;(A&W}8t4lQ%E0U-D%BcJzWg#? ze)b7WUO0o{u?=|NNAJPLZ973kgoJG%<=U`116EC>UF*3;R@YFJfra702g(ku$L513P!_{0IRo09qei>IndK5G*V%B8(!W^Tkd>g%I6z@s-}U;!?)7p9Uf| zOn?wH>QyYyO<{3n67^aY)U=W68%1_t1a>M1uD!DW07=KfKrW5pzAOqEEnY*(YCYA_ zivJFOA1bC;S(XKhVEj+N{QEzB?##)tv2`1!d$4$ygXMLKm6ogXnye6Ok$1;&mCotNd$3QGx(=D;q zUQ_8_`n+7zkOCkEgPU}4_{i(uJov!hR11Z^Z4VweG}D8{yAWLW+;y8>t5%Y}?;F1F z4R6`Heea&VS6_eCzH9egv2*v8Lj(P}?ynaL;9RVMkopeK*lyq6_NscD))aBMx=Jr3 z6k$+=qS^9LDlOyEg|m3;^%pU5<^&wa#m=j)#ikv5Q5@L-Cz*!D2-22TvUP1sM=M5Q zGKR}7!rPZqcCFU8NxMN9K~fWwQcc-gFy(103zN**6-2tTBt0| zqclH*TDb(lBe>}-2DfZPGTR5!P3UMB1Ukx?2J)!{hWoM@$fuD>IAD~nNmaW=YY2!& zQTw43H!#|m^Fog4@j0BkIQjEq8#WD=mlhxR^u2c$9z1Ypr3a070k}SO&mE**tC;nA zJz1~UHtg7W<-UE_T=&6!*IakQ&MWuq$|Mt=HlJwEqb`e1-wjONDVUP0bF22n4k7_B z67ac1$R&I~M73PP{OlCwrp7UK@eCGcF2QjV*nRE$v0>{j6b46Oxk(tNfrLeoPr67a zY}l6Bl?JZ$w(58W18bTOVMlx>Lc|pU5#aRkH$a$)fpwc91dlnj6p}DVMG%G&>eVHb zm*!AennTcP!LS{q`v#H97vZFHV1@-QIDFrSVK8Kq4st0M#cT?F*%UIa1Jht=FPW?w zo3B0$;x0QnUM#UtjgS&v5TVj&VWr+ewN}I6&Dr!hY@frY7Yl$RI4qX@}N7CZOdh>bh0M6Ngp zPy-+owq>G_P9T?bkZ^2R27?htEJ|qnWLIn6kL(iqY}1jr1Ol$WC4)35CN7-9+|&d% z?z|e=;xIzL36t0mJi_wQ0%|KusFjxBw;CXnB9Y0VZ*()a*P0MgqL@u#Xm9|Q?PN2# z0$W^|?dfNHKf!g!$8JeaPfZrLZ{K;#Cq8-KPu+Uk?H?NKFRs>3TpJ6#&1I30?;WMO z+!uBGU3bwWfRIbnybz1!D&}WrFgHDkh3N?_%}k`C}u3tqE^snbW{B-0?&#Pa+!n$;2rrLf%;a)YB9P?if7&zUFz9FrpHCXh|J zD5Uhila5YR+XYmoLDAWY)c@*e>vYM&wSNc&bWF?{rAWK_fkL3Igfh7Tj=XgE!lm(Z zTg@69fe)vZ}|G7dg%K(7KGoIC=`7$7pt~UB= zzS700P#uM$w)>%wtKh480cIA^@^QrSGV?!Fd%gCocl2a(F; zVOTb#k_ZI{B_G zPfem$Spkni_ER0dsODM*<2Aj8HHd`vp@5Gj3SW&OliIqobx2a(EGk9}0LON2ON7!u%}e zr^c~3I|aX4hwZq?l6mgVSR&egrF)YkZAzkQ4-{?k2E+9n& z$1;&{Or%{K*`$lK>mU&ithLJ1YII_2>{l;~`|2_kxUL0DEo8GgOz$|Jh*dSJFp5wd z9>d|MzV+~%uO2?pR~%?Oc;HZv`t(kM>*fz!OLK+7JwN`FU-;2ae(KY==d;ZgvRlShbuk?jpkh#oF0Nf8kBctoEZqrtrJ$@7u z=T2h9aWK4jCkQc+uo(KY>CTFMJ1r$KqB>d{-Re)>zge=XwA=0iwxd<|+^?JU&o7hP zjyMOi94ss>l)rW0s|QWfbeUni)`P@52(Fvne~q(c+xCC?uYdV}`Qnwkb}|46A`U5K z$H@9SPor!DicJITwP1{f$7(~)_VNwE%k(hJ1D%`06 zNJ2n4hm}oX;6oolv9EwPkGupok-)&Z%^*aBaP`lrPV1btCDka=zFXatrOS8HI^9zl zGg)++DBA)$G5xD!B@};7Ysf3uZVLPV?05h0)X`VY53k!;d+@-as0WF62wWff;C1Q# zfx(~nrC<49e|z^8SCB|3NGW2+jJFR~-(G8}F7cPQ#kKDO$yHLTClpQ|djlt4ehO|X zjjem%hrtb7Kuib8bQT*&2Qga6z_BezrO;@4s5NxNId-w89 zIJyB|6rr(Pfr6GB17+}iA4{{BFnjR~7N^G1tXE*#Hu^_5VrmRYqbXzb~50@zn#=wODr3%}3?8MBa3CxV2MIuu`uCJ)oq12iQ6$mAu6xuKx zq~p8VPNUmj-eLE5NxpUOdv$n=a@Av5beWt-LLrs!$LlXY|H9#KJ^X6EuRr&}mTDA#H=_xaqgrHKbj%%!ZuBe6|-2MPr$V(h~f}ZA!#$%W(NJmK5X5- z6ML`!APztE7*4-=1j}<%xaOvhVbiXC5X{#0q0Jh~i?f(McO3JR=fR@@`GFB^zvcrN zTE7MTgTqKAl1Mow3TYc@*MV&?(5}I4JAyDMFhx256GT&qu5}N$I{w~Tpy0T_Ts}| z{r&&=8&X8OQt3i(uJulV>(lq%=}wGaDEyP3`Dgck=?kY& zU0#4?I~d)(10$QSz`)2lEnNV^uYNe8ZFfl~4|`2d~nYOawY3M}630#%(> zsFD)JViCz?8ueOL&zlV0t#hP;B(cPRQXo=6k<|wfx+JvQHIoJuI{&TB8OR!cuFe*K z&}zUu9wj7lMVvWt?CHP!o!|Ib-)r7pC=N|Nu>X;%9u(doa4jy(7k2Nt`j#*J+|U0M z00g1md0MwSS4}U#VzrJ+D@4e3+7U$&q9_C*1dJJQT^A{MXYPA}|z(a9p9hl`H;vB_+3^okpk#?Q6>1fQUGXYRyK?s3}`^Y6N+<4Q6Ft~0b zo_%ybjz0T1a>X}5m;uL4V(vC33N903nnDn2lsMi}NRso&2l+=Qsal;5Bb66o>c&`yY9#2ZMJAT=(4d z(ZWipH1;$9^yfdHa4e_Z@;gs#8mE3M^#-PwDyRhl^;!jsGnY_VUO=O|g249x1=xus zMz`+5=;kX>X@wZch3L%jzb39NA(@DUeUxerTaBT~oAI7yC1*Z~}>xKp) zbXB0V%P?GZjVnUV;RO+DEgwxU&`c8s5Gj$)q~W?QOviy`TB_CZnoEld-udUgee#)y zzxs#G>dFoI;-Dv`_<9cl?;yAq=jSsYyX&4?KXmggA8Cbr%{)aag~du8<8vi6B7ySK zJTAU<1XCAIt7>VnCIv4MLINp7QYo1`_vR~!&3kSzwqJKMTEPIg5Ev?CwE(4F2I)!* zwN6F5VCMqxnoTIBU?q}}Qi2fz*D*okPG2W2yKEtZ#PuHHj+|vMAd0YQY!vr==ErgH ztAB#p$|5Ka;5sH4BS^SOBy9_hWx_BRpnymS5J^@oQe)!lYWRKo!lvw+*>xK==+%A- z8MndxC_=*vV(mZ;DAH-Wlg==^)2L8tjWTc;EAMh4&Zt<}=?&EzOL( z*WU5N)G#-}u}l;)DZTQrc9I-37i332FbsW2ApoMK$QatuQv-8#y+SuY))qqm1<}+Y zLSQlq0f~+4$FO7P9=!g_Va!~*h&@+d3&)_aP1cb-(?HPgiN^J1=i;}wk-Ivks;iXj z7=Fj8oYrwCXf#@=Rx2Qe0Vx%NP=m`hjgG6yDvQ0V!`dZT(Ead`3Z1!K(sfX6v>-qr z3>USghnW|jmFM1g@h_L>XO0<$X}ihf>*-wnB&BTpf&D%It#=$;E2X8xtslGV{nuQz z=cZ~a=o(Tg1OdnNQW<40M6FWB=_AiJPal18K8pCOd#=0j>tpLSEI5f&$V@8|puvSR zCx$PaIC`hW%uh_8d2^j2%+B2ZGi)G}Kt^zwh7n)TcGT)}@sI*skPcv^;1H5`iu54l znxPR(gi%nC__f+8TRTakgu+P(-ui1J2=vA%rw^Glr~2NGNjIEO70^ zhya?z3m|D*5IlluWRb}iF?sT};Ox=Ef9y9K`}>E-#sSPebnwwu@4A1Vz;*8@KAtTt zFXnH$^<%eE06dCT=|K`$skg9H_fT&%apBl2-r1uso%MbH_|N={U;f`dcmJo)E2Wa{ z5~|lOihAX7VKmWg6sFJltRSYYETqZst&iX{S+XC ztR2S3>4a#jNrAxU5)-FiJ$34(Cth8iI{zA>^luM6dAR4V^=<&yr|-Sfn3< zgM&lGE9#YcTvBVigb_!>kFc_|h)buAE-cMVR5xC+=dsUx_D5d+rGNE54FI63T^#7k zr65Aum$K6L|JYAXR+i=;c=g%GKiH~QHqB3sBiy+ImSbpjT%x~|ZZmiJPzj09_aMRm zi<4(iE6u}6Wl)~tU}m7ok$Gc!1O>~;99 z2Kv@*1+#1j!9i_P?^s0~QqtABORjPX6#ydFvR1U{jzYi%TFn-!m1Q()HJp9r$@%%S zZ+tz|H~8FvN1o`djl5gH#W_#Ai6p!3#+yIY-uIJ3X5@>opmS!igICb8OggSHA zz4!0mI5KDfuvB-o9b?Qi446^CM*1_UkKTFj%O{V%@@Au6-MYLmiy-ikb~8{)ba=lV zyPTL&AOr`Y6qGU4mS#|%9fwj17bpWWEM)V2*nGtv?7HSgtlzv13=&O0gkzboO$IY= zaWhKeL9B#f+E^*gqg+}>t~dZYkwVK4(e#2&DyZ7bSVDETPDI*>oKg@%0V2Q%fx(0Z zaeKis{y_{%U@)DcE-%jG^s%F;*Q)3n+l)l62*D#LLcrpsOra!*Smj?*N;*EsM2lT( z%hx!4^?V<#RtuHV5~|B{@S6>!iz7(o2Us$l<2_)#%fQua)aa(IJFdQB?=^Qq_ghahs!SeKYR9>8KY~ORu6CeJ_ZF2yMz<1u*r-Z_>Eu{c==Z+nvb(=PyIdP8Hw<5tiqtu`)YJ31e^GeB0g6 zjTW;G0J^rKS=(AS^h!F(q*E%B$u+|;h>nsZ{#rt)7UpESEDgODyhbhN_em(B-=7o$BxZ0RR&)X( zP)fp1q|rCF33j@G$ZtXkuK%BiP>p;vD1i|-V1y9hoW~y#coZV^J@6>dvfE05P!rTJ zVWtaki!Mw%iDW8?jKf}QE>8a~q3rztTmU_Ay<5OV2ubJr1~!3Ea4tY8fh4O#+Lq^` zyf}yQ@?3Mnw%t!(bHk1E3K+oKg=g2oOWRKo!(ha+EKhLZJ8puz$yADS0VU+Bk%3wr zwsI*u0H=llLMb9HkTXq;^`+ql0ZP>n>B0cEUiTqni$l0{`Y4`1^Z@qV@^K7}tV1&h z!G(y0Jt+()fs5l4ID6t4oNNJ!d_Q;?BGoqtW|%N62gEQy2m_%Egc&i=;#4;Vk`NKX zRvlio1iw**3IiCXty4b1;aAHLJODLJ5JFL3nnitSwkxFTm3_h(m|+5OdZ@Pz+MPfx zAWA3!bJ8$OTfZLD5zjCT*rtI@!p6pRV`wf;{ng_1_?+n^O#s#2wSJd?>rCrdIgD8=ER=8SIy*XB-;Z1-)$Bn zrGk=*NTP^M2}vXpb;B@%OeQzj-jvm|oK>n2muo$#>eUs^`ep`nPl3WQ4dhZT9Lq#D z>0)lBifY40t~iJ*Z@L|)Uj8;tzV^I!b?h5}ZQEF0F5&F4S1~_x2?JYp!%pWB`7H=3 zAOV<$iCipPj3p>+?>ih(jFP%kxbTd-LVel}7 zTx60CGRXuou8l;(1tR~+GsoXp zvTWzapw!oTSNdH9E+wTjP17+gyH7I{WQRD~UYGEib%cHkh5mub?OQgt2;eHE-nQU> zxpj|%qU}$GK_FA~U~#VZuP9kfA9<_;7(^Tr1kGj><>f^fmJJ#=3@p>sI_L^; zZ4;yYIV2nlv&$7MSDVNdin#LqAH~Fp*Kz!%r;zO%gyAMpEiEC4A{4jmL1uUZIOp2H zKmn#{U|oL>JJt=NkV%110@3!yiuDycYE)7o;F{A{ZFpEH*RWh`f+jO?w(doJVG5;* zQ^)}IB2KU@`8v$j>h{usj$&RWd7Xpo11%e9< z5091x`}-mQDRgsJ-6rw9%-$12?E!)uwkfxy_?4{TFilf=thd_R!<^p?1_Nf|RCX1IaV{Rg&45k^+ z>e}N9iH>uNn@FJ*s#m}M*MGL3^XOL$!~Euh2fitLSNh%7(4f?nQW}vEkW#`Rde$PP z(kmHpah*t}7y;$boLE3=vKP~0o45BqG&do*RS8S70GlKQMU$1Y)}wJ z0zninUP57UehxhFVGU-X2nAydu4BRI+ACAgjD~E|fl^rzLQrjZNM6IL<{2?E(N$Q`;~w zkk25UbWm^lh@?azc@^sAC9KSjqquPwe9qDE1Js%x61ItiQlN44YHJZvsv8<@WRW#B znT}dY>L$_9KmlhbXCM9QUwnBsSLpjWLdcgMJaDMqyUy>fLjiz?L2YSiVWHCW;RPW$ zmmRkd&LgDKS@abL;CtRj&!0QJ0YEigE+=|RP|@y1K`9lXlu$w_WrS2tojiV*LFrI& zXcW{m!8z|ZJ@UBD@&gXf4-qM#vQk2Ac^-Bm4Z}%7DZq6Ta7?2^cGX6Bt2G;ew$v%4 z6Ue4rIEKQyu`%q~cLS8)fL|-Yuq+f(32Yx5z)&HBh;uC08d$E@QL3=3Mc)7bAOJ~3 zK~yzRuD4KadT4k7>RtdZj1Yz#yuH!e)!=9ogjGDRv*Sif#>p(P4(P6|aItIM}a5lR3d6TgV@FBtDrp%;%)qDZ!Fb2auD#>A z3U&f1BJ>XoB3~T9;?gXPWEPSGz}HN^j%F?(=RU z1ojh@V*Hza{WRi)H5@G^`DX8`~6$zqg}Ovu}s_-!d~U0+E^0ETHH;vE0= zw|@O6zVb)^ciAwEU+n82m{1B|eCXh#rQS7u7lUip72EnF-^=F4w(Yc1xr`D~Y*!kS zO9e;&;1C#PsH`k!E}lQV>BvhjEG4pq+VI%Ax=9HKA+!}nMl%qqrOe8)x8AA_6w<#i zdG6$IUU|)pxcQ@Z>JEqyor@L4WYmTiqUnc-B*D~$lUSHI17=W-MSfCdqaaS zmCoYL*IzyTH-GY_mBEqG+mosEex=kO^cEoB3*g#yh4O1FeauPI-1;3?^PmORSwif% z|6+J-JuK5gvtAvWotfJ5(&4AAQzuW*<#LVtLN%$CC@q!BmBUXzQGDXy*M7BDF8zny z`>w}_Zodb~bQ*!z>g*>8A+;uZ7@_F};Gn3M%NRfYDnj1_r3{f*N7$^wFm0srMM$aO z`963|o1(GqL}wV)js{B0H%kDmRtp!-okFM#xcR|&hf2T?A~gL_cPHceCyWQgk&y8+ z4xOgNwv2!a5F5bVC@23U@RROW@Nr^e51I(_1;V`opFd}Cy6{qe!k^{wT_ z`Q6J4bN7ynZP@vNTW`nCJ^PSMXV7XkVh){t;z#ji^a3r-7Abjv6Y`UlS!*rdezaQeQ=v@CWUO967&|?R_b|9V3{2YM&4;_58#~*yJ zfC~UjH+jLYEM7l-{Dp@%fAHR&2N{m2*VJZbEL94 zTzTz{*y^WbnJgT~g;IJKNpP`dr6dx%KJkJGEkA@}CMqi{ID7O(1c8rK z{}`-vAHWP4rVTe!fC^hEPoBcc^m!Og3i*)@kgr&+o_zY=yS4}QmD`eo>-)Ce{!_oiD9t39 zz-Td#VlEBGGCD1A60fowZE+Z?Qz;JfB(jiFfH5N`6>C5#DWL?{z!O66kns>Q3Lt_Of<_r(z2Xx9 zZeh@|Qh89@1z~!?ZKv{JrUeoM%c3YPpMT@Y^DjMi#;-2t68ZjulOGy$vi(WsrWiHt zPzP5MDkUeWOMzh<~$m+=aC-U zj>6bB*c7k~0@tySb{(XW37D3RndwV-{rM;0c|MBUuYsA&LyAb(NCu6$jA~VzLMqU< zb!)dHvXJ1d8p@YWMN4OnJV_~?G@Qf+LfLhM8b!lR!^#x6nauiTB5e~9T`XTb_Lb83 zNyD)08!R{V4Z*_~MCk1$APEpk2q6rB4T6M{vPPIuV5YrIi}T19lxj)I&nY2ZG;HVW zfk&R`F)w~lzy$!Gy65)yg|*U$^INVOTX)l^zhGLnV+s%XR04%;3b~{ojeHiRVgD?A$^rU1g2LHKEp6HxYU*oOtr@URyZ#<}W|`%&XtN_pXnOfsnfhp&wFG z*h(qR44d(=RhOKPnQqD=jD6{$1BbpAKd>m2$|kAWq+_!n}%!XU;|4l_`zgJn*^;|BGD>tpf zw$UO|j*XRi6UWDAFRhoy_s3K7uL2lC?Fe?H0 zH4J2v*f7+GLOP+fHDWi%c9)RG?eVsGfgf@-{Q!-YkGdB^*(n@<;aewPi6Xl@tSn8ZM!93cxcD9elJWZPo+VcR%-i@{Ubmz5E#?k={0R z^{uyMH(j-f8YYN{U>iWnHsM$nEOT`zs-PV>1lN{AoKQ$&!f(`2nmj$Zc<#;l`r@T3 zq*yX6+m|AGE7`w(U2gN$Uq}wD%Q8C&*PE^Q9Ut%q^qRiV%7Yw3en2 z)Jm}ru+&Y7(AUaPBb#8@?!4nT$FA6O^{suGw8_1?7SD7nWIJXHCJd_OuG_xOZL1wW z3{h=rK43sBOq@M&=J415;7Vj@%wzEEg!g+?e*^FlNO4lhz@aljWqt-Ku0oOt=osn@>!#{8*Qrv2*D zCBw8HH*EJW38l|a!V2EXT(&-Yt{GI9k_0fERMt{f0#U@Fl+?UF(*U4!2a+(bQ~_ox zrw6pjEE2_aNc4|^84RWKM@w_Zo_loZ!m-2k<++BDNR2v$VW-*h&}cPdmWS4Q*96?P zma%Plw;BO}o!Yk53Tc1Ar zdUIjozC>~Cqlv!JJ~NdAGi^}D>LAnup$?R^m6THQsNt387V9(T&w4BK7YStz(@icA zYP|Z;!9z#7zQqNdVtwp}`pmgcHD)i|R60A7>)U#Dad^kRVGss_Ci=1|j23e+7{gqt zf^x%yvR%M*JA_-uv60%a39doKnfXgpDs}DYBhPvlk01H=&JWzKcHeUM%}N;qOO-nO zAVL@kClhjKEF}`k2n;i3B$AyD%qnP_0B&$E&Ptnj=OKOB#(B3;Cc|Y z2f#3tRD@Cx!%RXUp`?K1;e_PT1U1Z(VJD6Own_ocSL^kvx-N^mFFLR?3tsztJ6FG;K+6P+5gBBA%LUt2R?Q8$7~Qbs+1a1oTmW@LWsvq zr)=1Xng3p@&i-$F>(Tq}z4J}QqhBjuJmx5+CN_NV-i<-Ly}Wg70P6@IsD|xft5CeR$^Fv8_s}KmW#K&m6q>?%V$J_-oJJWF<07o3FdIUo+i3gj^u$ z1+YyMmdW7SCT!ExCVa8?uw*8xi?efnZKZ13&SLM{^v?e;;CegQ9uELqj6eQ|oQDn^ zn!E4bJO8bn&i``t(y4GRm6vkgM>k*O9Hz;zZEOHZ$Bwy*+J!O_0#Ybgh5}_2#i7x( zX}iNx@XY|`m|^~V!?J#R=G2kV{w;fu%oY$tAvhOk`Jv_t8U|d)LejBd84O%X01TW9 z%%6YjjNoDAk;k8#>0O)N`3E;NzMtpxne)-kojYEplogtblP+QAI+h(WLoSfAOgNT_ zAkx5ScmY~o1fNH-);opgwVcT_ZhuebzT_lQ6KBp{>@_ob=fCODck`Qj=)fV5n%3`9 zX6!GYJ9547+MKRNCc7IP;U4E>Yk5!%L8?@n7(-C+pj(PH-E<%HJM?(-n%lr^PZ1f zT@j3*IY0Tv_Kl-UVWWJbnH|c)O2X0%*UmO^5OGAY&V^xGs8?3R**9Lk$a(PExeJr0 z+po8M>jqo$a93gd_D#&xc0T&7ZZShCB(epxn)RtC|MI{7VSQz3S2EXk@WBJ$JlDH2 zz4HSRxz@Z<(>fTe%>7+s?m|>;`Is%$P_DPo^7UH27ew#_y&o0`g-W?p^BR@1VVLFa z*D-C^Z!FKgSzVZ#G%OcDtRU51xTFkrwunZfp`QNgm!2!lUb;G6=zrwFoV6$U*`(mn zRBdkJ4445h6A}u6g6#c?%c82=lW2ZfX{odkTZId}58M;?iS=81!czWuGs(oApnt{vC5 z%8LfG6ExX3Izmkcexv->^wFn&W$N{(XC_{IrtH;Lo=If#fB4YBN00TcN$>oBImUbG z&F+8XiRF7f_QA)zxpTLqN45<4)PVHE*r*#|h6!mpIQz^)Un~KC|L6XPzx7)0YV^)O^pN-BoBq_@x2;bOZ~2znzivk|S46Tn4Bo7r znS1N`|5&#`{W%AfC(UY=-{KOcO`n~A2qlD;Qr74&}eFS z)9)FkbE}N}$@1jM-}TCim+W+2P-Z;+;DPVWW9*&Y>4EF-IiLNJ`%`|kM4*(*Y$tl? zz@c*QueEo2r+0d%cY3FHdgmQa3qSvbU)=<#q*7=RLVN%|fa+Ji{6*3G_4ZB=T>t%m zx=BRg5BXl}IIQZ~aFn>S5kni|lPW?flvDr-Kxi3+Gy!l05&%Si5Dr4? zAf%x|Ng@CqRxM6Mm+grce(1`5yzW&wt@p|8s3+`CmK940hb`L2SM1dVgfy7eAsKpi}7m!gX$ZV;Fx%rZ8wtQM^MTTsxfJHS>$w4Fs5dqLDfyxksSs+sA&&58a zMCqVLqyR)LR;!?`Q$c{3odGBi?ZMn-C=?qSf5)@x6508BEfE~Qwpz7kRZ;HJXyyb;A8BjT$2Wxf+y(im-7Ix(FbM z)9D!i<6rsm7c0GM`Huu#Kk>7_Mg`}Gt1HVluG_X7AN|aaw8plLu*iqW{g5gtp(Mjl zri@%Nz;ojRI5ux0<4B}!0znug1L_el)c{tSKo|icwxWW7RIQ3YP*b2xLMc!gQb1A- zEJYxNQlZD;Rg~ZX)jRl{C>aqU1c;0XQIb%_DQP={gFq4@6`^gbX4ysw078@`pcIsu z3d|&g8AP*~6@^li$cR(kqEvE=F$0895Xr%!CM2N%iODUoc?01A2=_oF2PB7z;s=Cb zYjcP8N{bXgSYsU+;`O2DL)pp-x=0>|JOE6gKfHPD|-B4OlV5D6j`5(WoT z5;g$CBp6HpEzVF2DXj&7#|0CVnv$8Ch)W2h;edc>w`IhRkygf1?f@hYpp*oZfQ&-$ zRs|wx0wC)0D5aF53X0;g0M&_7%mlMtMVUzmvp^_S(7N|TDNrT3Aj}Aum57aoq@*I? zAdD))7$H!E8U)M)fr4wuR??)LfD|m%q6SeQh695LfG|jA!yrO3zXDS>}iZ-9P+EZYVfj!|(V2Q>wrvL&^?t#n45#hL6(=oAJ`6K7=X)0l?*| zrB5gTE)_ya&uCTl}62An-XsE7a>DL~%e#NGTBn0h)~lYK;J) zl&fz^$rwml%bm1gqGj74WdsNThzLRmKnZlrk=v2!ZQ<%W{P0R@ATblnPJvJcr39pu zV9W$F?Nv8*qCrI{UG?;$hIV-|47iR7*D{cBZ2Z6My?M0d*Hz#7+2@?!9o|r{CaFqN zX_hR>vNc$vjU_zb5rYFZxNVSW8#4tA4NV$M8j>_Lgzj`FO*frDyt;$ESdb8K2Lf?p z3?9Ko9%RcJtf{K3n&15Hd-wi^bI$HR&iVcBEkV-%r7CN^yH>4N^`?94y|d5W`@6sU z`&}G3<_AL0Nix~X1{KZ-R=^s;ndBBDMpzmejx1S*{q*mm2XlSsnJvzqUDYpq=>%W+ z)L+Z;u_syBvxj0Z{1c7wJ9e|!#rybMS@G}w!Jk{&SU&fKYi@m6{~bU0o_gNUk5vsO z8Q6gkR4@q66fE{Vd*^o0=xB5=KJ?gWB0+^JK+se^mR^|D%6945t|OBsB=^O#q-j$B zp&B6uGzX8W&t;&3PAz{l)M)VOeHv33i8&vvkpKAo(fLQ@fPo0vK(7ThjO$^X%il&Y z$=42>1I!qkvXFCFSD>1lA?F83DaNA`XAC~1(yZ@<0Z>>&-@?KmqBs!2M~9EXLfe1K+Sijo~_wGUoz|oA@m8cLiGekmM-TzzUK{Ge&jMf{%?MZ z(b~#Ad-fmvBjC-uNBO^i%=NwB|1&?by*2vbSH10f!X5WqC)=x~H5u(HtTNXN>>D=h zpKn+klq?RnneUA;q8MZGp`dJBwl5V#gu+NVSXpt&iqf}{zJtCM#2BI&R4m3?jIlWj zN%s9Rc_A`ojQ0DAafmGt2yH>Gk>)&f%QF8cV-$soyd}VFjB5tFQ)3`u`7IWKW?i z^pv?GruP$=@0IKsY%?gz9LyFGgAYnm2SQbF<@Hx`*>$(_+>=kTy}o?oeXo4u=2yP> z>Cf#R=Zm=KdiM|f+~W4e+RCNZ-qib^_y0sSXXwYKqTf^I`jN#!q;D&nG(baWJW2hz zEt4E|955>TOd3N_*scv{N5ZI5HYTt(QO1p;Ay9iy?LFINWOEW|T6{i6qHoZqCin)8 z9!+3%YP&Qhgd~RS5E)SoNYpMXB@iN`DY>ueW>`6pBzv@qfT*JC5slbln6sCq7|vnc zAP1Yl6b@@F&LwcendE6~jnH?nIB+ZtEsK4_pip{F=vxEo(Gc@x6B#;X=u-A_CSnkJ zwq#+jji{%p1I8E%2d*cKMq@m4XzwuF<$$?;J)VAMt@`bM_N#qg?l8Y+?~xDv=Kr`W zSbDLRg)xSo_fdPd-T$CpS`=GtOxQCY*gMw{n=#dBhu)&a_J!yLBNmN@CQ43%7EEBV zn6T7OE|cRR44mcEmQZ`cq=DMQM1?vU#*Lwlig6AnLd^4^SoEMj#D+kO9zrS=HAal4 zU`jTt(~pUOLC`icp()5(AS8Go=B#C~)}~7#+t7-nEM|*Bi{2n*ELTtzy<}0Fvru7@ zi)P`_p0zS?Mh1mpzAr2dEen0i&bhZ4DoyND)#*|)#(_g}u73@9+wPIR2s771@B6vM z@o3}h{wuB+kDDpgr=`QZaxe%WEH=>vjBLsUUy#D~~{ z62=Jqet}n^^srS5XGg-xEz8Odtc_uPqHLFe%6lrW)LscuJD;!EVuUIkNTI4kjag%G zsx_#cHkx#RM3?PyS=h2Jj0naWj3giDY}u;4!cthl7*PR?AcX~IP%9$V2u?^qgcAl% z7#1RPg<-A`hCRbv4~7NwEjXJp*T6#G1qv52LKkQz8*w^G$g8R*FKLpebku-vlu4y* zY)6(?Cp57~Z@7T#4Ji6U;+*L3{mb8$$Nus!*ncgKHaq|TAOJ~3K~(8wcm39H{?e{+ z>BX11;v1T}8D4+e-FoEm!p_AJ^>~YCpL>Sr$GR{#&-GVd$1h z!-<+){b}dFEcvdG98jr$N>OD*G6XJIo1k!KQd{S8z}Vau_H7CxYlNN=dJZY9NZ+A@ z+#~i27#707k@`lL5Wl|j6iz9OVvJ%*uR&CCCL)H^o8}8c^DB-DF`A5C+Wg*t5$F}* zoTcbltefZDTE%E<3o+X)F70J;VUOJXnm5IxkA20|b@j`@8+VWOMHXC5RX#Yk|1j6z zdTX;-gko!Di)SAD64lNociwgr*IjcpQ5M+PNhqI>VB6a_QT*sx!8w!q5Ti61ypVh| z_eHtHDSHOWp+&=1Wmz8!%Ohn}6>K&G)^`G%Wn^3j$|h2WWDWqJKwrNeqoTR&YxAuU zgE8r!X>ODP-5K)R6eLDwKb`=Vo=w5jvqI0Jeb<(7s&j=hC{+4ZaTai!%zdCBc_a{3@ImlV@IeVPBe=Hw6wz+>m41dXgEvgotPEJ=iR!UNx%5Dv`Go;z zmiwGOy~@sLgd5CrzwwJ-**)49QE=6h@yjo}@h%Qs zeNb1hMpcjP8g^;WeUIfxZ*^wnZY-zk4>l zE|&w{il#CwA7(2NXL7k_6lWtn>vdpN`!3RVQF>O@N|5u%&(ncfGH6Jz#Rq7DP&Y8C zzSN1y)UXT(?4e+PWe?$N$Tdaj4w_j5OlH`kuH>pQ;Rz4CQ;`*V+c-s#p+ z?z{hGEFL(*iRFgI3-f)$ph%6qNDBZ=4}>aRpe58V4V`01T|_!asfq8oNY4cZg<>uF z#?K7fXz*$Xno7?Q1s@GT4Iv7G>B_wHztAp|7&Hf91c|xFQjA1IB1TeQ(@wY2fI3rz z6$ufV0F4)#C{zv9O}aR|PXXm)%2Xky;dpBC60^c(mrG|-37UJjG)^GRMOeRkcgpFYhEH{8IEJ;dn~6@6z}nzIxx^ZPnobxl;L zb|@=(6+{wJCH%hdi|W9dgN)=w;Rg-Wo(TN^L@kI zz#^mput+Lr)JWnP#?BaIipI5BD&NrBLEky6Aw(jLhe@qWe56T4_e7c16d*m9zK1cA z&^H%xM!PafbD3rGp^d79pegHkXksD?`&?4SocU7FO*4*meNgV-@^kiz92h3Q&Ck>R zz55!I0?wv^8A>W0W6owGK#;EIiaE}VF&$G&725S{D<<`Ik|2B|V4Y!pXbIG8mw}B* z$zW($TI{K44IZw&`DT6ilYcIu@vqqpu8SbJ9(vyg+}g^@OOIT-#GwW3nYkq{z5W(@ zgFb_v!4=72Q6Ldk#iU()pR$5#>MNqAvJM1GOwD+{k?97&l0S!J@LdSTC@vW45W9?4 zH<7Xt*2gJGQUJGyGQ;eWK@LqTDP5+TCWusyS;0vQv}C441~(G1U1s{S{IxmT%u;{5 znT1Xv|Jas;+SPSvv?-u49Sf^NFwr!uCjZQGrV2rcnn0JHO_xwYHFGu;Fsei&d3~&; zX`*bbmuA#huH4tBsUw$MeuM*uFXP;a<99stz7LpPxr7U!xfVraFz7q#28yMFTsmB& zKP>RJ4>4df?Z_Z#fF`svEhVNY9<%0?DM>1hkkoaAvd34873gUM=;D+I=6%0VLf5qoC4G%nT7j z)bzG1?J z0**)w+GUs!B0fffk3@|*bIoQAGAFPtsdB$IGZSu?^)*e5)1lQi8%hqkeES>0wIx*h z<tlKQ#l+{)^5LA zBWVFz(J=LHZZD?x#T%2eYvv<5XXrUk(>R<7sL4BLk`}h+guApup`UX2U#1%FjjMOmTb1+ptQu{#gAq7^9DU)bS zo5OM1itK+;-9(A<}Zq9z^4s3EVKv#nkeH|%xOoONU*b9GJnnEk3wXCzD?LDDH(k0`w{)gtBwI&1_AMife z97)dB=|_;tzedt!Fbg&E8b@c{{{SXbn7DIywCB2A1`RiySF@AK`eO2`@zXqz!~N{G$P zytG8w7X}!iE3YCZtz(F!(ks8fAdaBvK&!UsWZMgA7fkI;NoTsGl%(3{Ou;53Qm^QF zGB({FSAuC;^iwl!Drdy*d8K2Bbc#3{yPhiPV#E|sb+VITe6l)@0 z1R7BFDWh3S;S}GC^flsaK@ezg1lvQ!6T_I*%`xj+OR{JGL52%^h{~nA$NNGDmx!~@ z8fz_0qo^9hxI8{e41^FPbrYx>PwgY2bG%O<>z0JOb*kw*(&|Y_B^f3i(Hwj+jb%H; zjxM;S86>LE#8j5GThLJ@vG0N={hIdR%5452&ABx9J~|D^>6li}d&}%=X9|wY#?$zT^vT9U~h*(36ip}Lu2r0N~ zAMqg&LhOJHoyhAAbVtj! z0WvEG(3W0ZPc#!F`T_*7iyWQFdR>a_p0-63wJSpt2U7D4&xc5Ce?B2yKHXGHTQojh z2-0T8yek*{^n0{BY-!Y?6q=ng1JqIJ7m6A~9W5S9R6|uaoLk#wZhnDcFaTt*d%Q1r zS*ZI^H4#G-qN|bUgEvMf^6rP5fKo>|LxkOC} zrq3Wnh)T>CkLr|((JF(a|N0ApSR)#t4heZpb1l>LZ$S)EA;c!3iz-AlIM<*Un%Ku< zsk~=8^FyP-PrMmMR zXFnr$^PYB?+Ms(N zT7n8ageWe3)Hr-l##KYv_*PJLw=BFE&_ZKc4xfrx+fr21Vb8!tdsAzCVHubYtIhxj z<^>>IK26&8F>SdSneq_Z;EHi3fk;%rG;F$G~~zY0KL)=cTFHz*jPvy^~=} z%!>B^GR?Tm49HUknaIMq;P~Wm$G;ozZjvb!NY}?Uz~=T$xE*t%6db%1Qc|Ov!T7ph`D&M7#OeG+<}z zM``hmE`x|Uf9@o4Hh2;{f0~?hX7hOF^JEqR?4(D#?9?W@j#d>@#tI~cf4e2!m331g zs`CU$Wp-GtQ!pe~2PxxagC)sCNzD{0$yqoJ(aki2Mo^P_Qj?#zW4w^Y7piH8E=hRD z`SW_p-Nyw8E@V@qr<{~1qNW>C=?r{5)4I&gh+arwC2OL0Tltx}5hEclJ+`G~%q3EX zd}k_>=y@GcF0CRlw)%qS3#4`PsdivI6Hqdf)!J!q8wj(3-SQLLek#ViE|Z(Bn0q2f zgWfiqocFzunVFa7*WFE+GsI3;P8yQpR6_M>jKZYPQ@7U-h(k>X8Nd^GnrxN84y9}Rwa-m5ZVF}+A283kRjxj z8#n_Kv>@ILy4O9AoDVR}%3CL{pXstfcdp|Mz%rwuF|EyJGE7I+$~{v{KF-;+3pVm= zPCDBdNs3l&pheQmE#E0qr74+?2HHN&$Cw0y^Q9CsS5xvVuv?Op>8wy=+DTKfxgSgO zFqg|lV+7mkakv7}2KAo2`n+pCc;Pb_o2b%M6(Ju`+wC5;v!b^2lCFVnm6qD(JE-Cn zVhoA-ErPf?j1*{$*btEz@b#GJn<>zd5x7pRsSTP|_Yeg&Go*+-2$or>Je5om+iZJ& zyQN*(r(M|>b1=1(s+h~b_H)_*R3&Q~w*5~+0aYb~J9#}+W2fa|rn6;2%>AWTe3X2d zm=u7{CGABOY&(0)5(4QCvzbPvsU*Z=Fa!*mFSHJZaRu5ms3{QFOO%z}#|6(^1qyXl zW%zeG;8d0Ks6Sb=9GO(lQJ3ibjkH zDXYC=dvnY!EOPGHS2+6Rk8$?-CpmrmG0N?Add{VIEatfM>X&fSy>H?0O%LFD1x>k~ zRd!{%x{*^!j3M(9r-UWinO0ELSZ1+ACY;%Tn9@*3{C{SNND<34VD(TmyI+T`h{pWv@P`)7Re!yn+*2j0o8 zum4es;Ub~jPQ&X?2^Z98g6DI9Au|K+wrY(rtz+nPUKVrHADP1bvCUX*KNVvJQ)bR# zCK$JwFp2N9eOg+(5pDU7iCCL}gSK`|rL5-hMQCSWEt|hsAZkIgb&6)~uhWlyxF1{0 zVTK1Wilb8m$ zji^GrR;7cA(jr z?w#d6)Tj{h@@g08?f*-agcBN>j`-oB_kBPBKK#L7+3myrGY8kUh@q(~v}uq*FCk)C zMYjo1M+j=uFEV&S2)N;15XYl`{;&A#AN&)>J6rtFk9>fK9{OQ=gN1ZZ{e)(`NsJ*O zTSbpO`!46FfBNSck9YXOfA~%2_a5e!*Zd&O&Pv+dVmkM+&Jlx8J5YvBT{ms!=J{Dj z&0t9VM!LN-q1MOPCKXIRh_MAwWQu@xdt935PR4ZKLudLg6$9D2sI@g}S8PJoR!jrm zGpXh@?&E?5*QyvpNTvrdf~|t&m35QK zAxZ1^!6&AITR2ElRea&!{W72b=x=h>)i?4pKl{tP@lD?iKsDJ)G$!@gr`w_w_@=@8 zEsk7$E5G>j{{uhq_ukK6e&l~;@8KJ{@};k3XX6wq5mD2rNEz$!v6*rLV=|_TVl&Y< zb{Z6|K+Wt%w9W9m54mq?eYU#UTo$a!&6hZv6@K$hS+}`tAvS2!>X=4b)I>vaz|M<6Pd_Zi{u`onBT|d+F3sTZ-0p=AO2$=eAS!zZ{Ghi9J%IJf-kAc9SAkC84>+B z^%TXPbj-8!$Yk>jhpxVipZ>8Q;~rcUdm*&I!md?n54!Ovh-9d zZLN6^tcAxzlti0qDO364j+#X}HL=ajv=+?Z#B?BRjSzh#64G|EF`xz!o1_ftsw)Sz z)dJC&18WMPWHld}06R~Al(Wx$kvF~hZT#>Ly&o`)x3^M56a%65%rETWm9PF5UU~0b z96frJuRZlNfA#s#^2N{oPc)~$#Ns#oEU~u_@#7?nO4dP?>FmyBjc#u&7|RRT882W| z?>_!>1y>1D#;9}N2W|D(RSZ@XtBOZJJ;oK(A@HR?{3ks2SAWQ>U;mx_gAe>ehD(>Q zv-UjEk5OMjtWm8~AEk-TI9*VVDjj_)WN;!u z^%055CMXindO{37JE-JYnNMM;s>CQ=52m7Mu}Hc66sI2ff4KRkm+<}{_(9OfXta*C zE(eK46?P`uFqvRt%@v3DaqZQw=1s4DHNX4s{)i8K=+|+>z4Y$?``Jp#pjZ@>hSRwp zvj#M&b1C9>PxeLdFhH%UjWGsmowQuFRqI5Sx-%ZWDdoS;_|A;2Td9^Bz|x(h~_W;De#58aBT65n{E$_r2>O z_Uzls*6IpY@@7GcsT2#i!G3Ii0g8FHz2V&28k5Nm-~H`x;hy_n&H7jWHR1VBp#6QR z#1@m3f3@9lO9gQHU#1HIwATU|yN?SOT-%C{#k9V@X^h&sFN(3rc`Mrjt(|(B7$VnS zyPq>h|2=2E{Ck*s6F)jZv-Ld9=mhoJGlcDP$(XL$LF*B*-XYdosGp$C80!X%Cjnbn ze(XK(=J2Hl`O?S!Z^oOeh#TUY1e~=@=V6;mOC zKF!(3KgJtg|5m=~u9vcY`Y3+q6rtS8>=J`$M6AOU3yA3<#^QR5^yl|dMy0psAP>Cq z^@yL)ocvsZ_f0_%gV$vA5Tc|YYpH315nF&V4-DI}{4P>>0fOtpAN-XX)srSQ!^GL4 zDy^tx$_k085SbqqEG;@d``F`r>eIhZbM}jb(epH$$7r^W6YC8$`b6y!M`*_Qa)WC7 z6xHYyp&6sCAOjZKM`n~Cz&UrM*PDe$9h zzWM(9`1mJ2!PB4n1NL71GKPDvpsqI)U@=pAwT(O)i8Fl}lIo-&wp&1KH7<-DWCB%4 zN>VXkG*GqsVXcvP=i%C-l6kAR4NxVmcC_pcotins5Hr2WF?sqwFgp7TZ-4juxcc%- zSy@}fSc9EEh`1iH9-*-&)FZUsNxhouWBQATIE*%I9Q`Pt{EOe`Ts_aey$hThoq(M) z_~A{tUo$gI`@}dg-D}b$drI4l&e-v5d=-%MM+_{rB9**6MMdf8jDHbyOKJ`kz@ibMP2e<*;9GE`U=q&ZtS*q;~>T*Je22YP5 zLxK#lnRiLk)cB^MsdI1VJ-%tE^WTH_dB&C5gDnHZhx9SGNOSfP&V22Ux&4m2dCO}b zWIP$CfqQQO+g}8Ygldy!=QN>OC;BR9y)FFs4541bkDg28zY;wl1uwAW-F;ku%q7OGho&afwY2bbBEn0zm(|jIdi^1d zrcLEx5%T(nMXWzMb*Hi%g-Qc=7f~)d%NnMr(lbIMXmlV0p_k z+9_FIImzYMUV^v71Qo|7vj?;TG_31kY%i5-Vu-%NPgW7z%R^jYb5wHEbyx7Z2Or>H z{qFCw|Jdg^c>CLkBpK+m+CQ_Y+rEuTY&txYDQ#~6=yIQz8?<`xgK_lg&@_oeTk*mHztyqYOfGm_JtmT5Lvm>E2`!Np)d z)pH+Z^W;}}@Atl&yKj9Ft81%>225`;m8P*FG$V+0X1EKg4bUVI#n;5~TFQcUfyKgd zrZJpe9bxxgi{1Ysj30vyBs+}ayj7e;g9@TeMSg-^d9@22T*lhkR~2bKr`b*>~uAp8UpB+h3c~unr6OZ znu(>$Xma%9gz+-p`nuP#Z_fhb$pnohYKydjQjZDaRYJ9ubn%-pVX{taCWLxIC`Z&~ zh1vnl_8GQTjo?-t6x2z6gQ~IE5;q zuBNN{@dY}9BsN1e8FA>w``CBnrF{9>bL=!Q8LcrHZ%_>Prrpbs4rJ=Rd}7>Jn?yfO z{Zdn=k}=fyax;By^eBLt58frqbO6};(NUQbom zjGy~|X|_&Gnfx-91?ZM9Vaj;d2*o^M^gOFa|C+n*xQhqwzKzlL7>!lh^D=#6JtkCJ z)Z^3n=P{<)IY+2>2u+DrBYZu^BUn7^XP#kF)|}dc(WJp%`>n8eHPURPEU(keXD|>o zA+#;yIhD{AFy1}l7d&&xC-v$hS4q*|!!>ujp3_^xqeoW>%@(7r zHEc0NiiIu!+MY;5O{lhLMkjN>;V{xm^rJelImB2r)J&>CF+a!K-uxz{Tw(p0zd-CT zJCU>qW*Qe*o7^1j{6WeyU*p+N{0sISIKM1trKF^)rBrINw zUj85wCMkfjmL#1$F3AUN`l1pi5;hUmcaQjm&Ril58VOC^`B}B|la?GCJ24nTRc>+M z>N~mQ+I#r?}z_Ac_xuXqJsEzRl^sNV)tbZ(f^Dq1n? zGpUGMB&h!10a4XqR-N3lg?h=e4TY3i2+j#x#rF{Gm3lp!V{<2SUJwd^0V}c9+)29Pw>@gQj zZXShVz9Rrnl`-1zQq($BJv+xf!9p+c=GVNMJqz7DZ5#J+$(QIb!kUi1|Y|arsT(#N)>|dG^FA=Z-zh z&f2rMqL2ED7$#X4!_sa*rbd1ZBZ^p;8yTM){g_d~$i~(dH{E;#-}!BShqF(8iml_H zM|+D|S3=W1SFym_Gk-GeUOn6{F$ znymBkeJ9*^=k4@+j;h?w6e^d>;*c_RY&nJMY3CHJQ&?2N7>D=~=gyuWh~vy= z%|(!NC=`tI*y=+Ck7+-H!v{kXl?Qms7Au}}R5y-^gUVlLAUCQGF8i^pkXv#5yY#r|oQM_r9`@ zyt9_k+Hp3vHmPF4H_nami`Sz|H(+B)t7w{@KY}Tee_4KCt9jBc3#E6uU9#>)5?o$H z@@jrmOF$$khFVdqHR*OW7Bxj$cL>Q$U|fUSe;pfP4UEHs;(J#`$#u{?^L9f7&@F6`U^geLclS;bdCP1ZSyIy2kK z^4*e^Tz9n#FPh-mQdO*TASPKDw(I0AK4u-59s$Rsj%-)T*gKjKDE1s74ln1__K*;i z6VEEMbbp3`R^%9a@9$7#d=;ll+7;Eh?0o`0cZ4pGu*rOGL-qdh_*YwaHU| z{?B>*PkxnZ^E7|&{XfndUiETDo2w8eh(>IGkr?W5b)Lr`II?Bi&;j@sc=1JTYAR-T|-euCG(_6^*1+l$%R8K?OZ zh8R3X4BCtd7{^(y&+5CxlOgzO!tv9%W^93uAgbzfv-Gm&1XVVkLI&nH``a z7HlyiyFE1oM2zC=NN9+4IoUnl7e{byqKYlj!XE1LevowPNrm3i|8j9*Gnj%pP=<7e&=F7Tc_`dgd5vETaZa zt=7omm6-WM7@9m=i&Lpm<4{wa_p_ANPfT(JkTU0)-Q#^>A1!NoOGFyqG)0K2Ht&_R zMY~s}48nG;OoBoB3mETF-(ZVANWt*nb(}l#1x{=SZdu&q_|p$_=*pY1#on~Hr2Yjq zsV%w{(O6;_V2maBidawZ)jD>tm~3&XF@w1wue#@Y?z#CO74r<{mRMdsnb;Dl*xsIW zDfkMlHwfdEjL29p39!&=lHl>Y4@MqF8w`HDj2-M{5(2%sA%FMX@8H<;-{ANYALWwa zrI<_ZhTbCn8y~}NALq?)e=kQ4?Pq0W6>9}Yz=R5MmU4TYSZ`%x2*I>oN}ZuzKuy3| z$9VlDs~hVK21`71CbCu**sE{B_J%pQX69=dTtZx@#TYPYxSUAQLJWo&z?j_`*F}=K zMk2D^lw%t2rz;f-rB}waGN~2Cq@60`klsAv28b;XBlPxNO@Gg2Jhxgh=;y($YK<>9@YM)$LsSD*Q?a(O#ZK9v2$S&`Uu_YaF~$wj z_wr*xxk|l#CL6;ECPoOJSZ$$xoaNp^e$Lij)e`G5&DKdEFj`;bz*Sf9qwo2z5o0*} z#ouD`#BVb`{s}go`!uh5`F*_Mm3PxjHtD;_un2eZU7edbT3WT?zYd!D`(JpSB_%UHbPrL2_$j&IK6T)~OsPta_i#JIUM zznaCb^5P|!gtBP@QIb-WPc)&ObNN*|)cdS6QQ+%_(3Bt+*Wa7;73&dUvP#%Kn-@39 zs9cjobD9ZJJt_?%Ax9 z%r7>^wB~MXI=Hq-EAs}7H4HAlhyJO@`TE(%II_40U0-JTNDMC0iEXU|qc z$!X4m>1WR`i^iH*ZPD01wwMR4F}+1x5%EoyzP59zp|7`z^>#Ygq~%G?`WRl4{s%4O zubOtOWY)wXsV4<`=+^Vt!4j1{fT?$Q`|I!L)i1k}XP$WqwBfp|uV(L}Wqo}au;CIkwix?V2Bmok6_v?j^!WC?u`8u1Mt4OhzCr$+#x1Zkr7bnJrNH@li_SYm9 zvAs?)IA$lw=+nNNW(nb}zPq}6yf1>x_2CcxidtvKzM8PMIcBSrq@47(nAyz^K`|Dw zMb^R)?82q&z3!E))$=^Hn$}NFKL2&%WI6Rjl0b{Ud67$^L`lfWvBYPFdL#yL5;?EGk62#SltX~pSNPvgr8 zCpQXCuGSO>UW6`QgV8D>oU<%|L<(%MFn_BXpd*F{nmB4SSYx~44a zEHV?%Q?d;&u-@tTeKrM{aRWqadY9eK;*mS}`m*C(xtG!UIhId9LBB8jFYOd}+s)=?yh5045X%kx&MM7#6<=*9=)T?|RO_hvPM@&do^DOWyVNn$^K=7Q z^(ZyTk0PSRQ8y*k=JP3dRM_6B*_jlSdWgy%f?_FCxh5&_n@K_&>+OWpWw(}&oAQDH zb48!=<_VThpGH+Uz8kAdN}J8@Q(;$ zl=G8E8*FigjvKpPNKu+<2h0iuX=q#5n^-r5r7u|%>*ctLrnB$t!Wq()v+S*T5!xOK5kEQ;u}@XZ(rJDIQ7il)bzw{c2# zjV<*k=AEe6$)e3b4AZ}BB>IYayp^`4$IHZeE6v}^MB8c)vHCI%t(%>0*7P5vMi^0C z(Py%Gl9Q*uL4Vli%+8SYvX43NV(i?0oJp^_HFEF976Rh>*&{3gkuIA`xNMpy*@Y#q zOmFNS?~5mMZ3bWB$6JCJRa8QrrSybM-%+ea>d@rDX5Pka9YV4zN${TGmG?6|_jR6J zd6t8N0}PISjip03vvBAR#+wtOA0uXJ$Er!#ooEsWKs)%2HAI_MGE|5f5R&U+r_t9X zqlEmmu9%$}Lx= z@-+TWl?k>-C|5Xn`Xs%g&$-DQXGR6?@GaPd%WzsF(IM7?$reY(qQ)lINs)xkT9bI6 zCUF+A5pNA@4L*7_G;88|yJ+FXQWhd|4wP6=q}3ga*^0**L(eMCM64m19+3Ee#+VLB zK$FC@aRvAtdY9bF-YZ|qiLD;T%PUzqbBt3*A7(JZ%f2L@??x%FKIhp$#kG4 z)`-?gttr;&HODahIsE_9WQBg=SUr1;m5mXDexIZ3J;t&RIdChkFc>2kjY+E8BqI#T zZ?Q%GWu@mVEa{I#`l1xhP*@X8Y(QE2@CX0#X!m$uEWrh<3dC{;qpHqi(rzxVtUyl` zmt8@vH5sW&NSJNWKM|}eFecr;gDdW#Kexo!SLa!4mN@m?!&F<()9Wwf!Lnw*t(nZz zmWe1)plKq$-iDa=w304-ND^f_{hCy&I>ZebEbL?M|7-7DV(iMU^Zc!~_c`Z2>ct{i z>?XTgZb_E)XvvZ#za$t&;DM2eJg^faF>DVa1Rex%0wi$kX%N#uCJ7P*NWw`(WD+15 z1V$bRY{QZx*_Pa`ev-{*H=o@ki)67Lx9;Pdv-iqi?{jXkB}*}qAe3Dfs3wa|vWivv zKYOiz{p_Ml5h5;Ovz-5{ z)c^N@mMfsymkFrj zr&xRLM|p7N54d@FiW9{OXCMg6`y;haD-eiKuuE50Z3?2i~ai$mz1=1 zZWoG_Lx$sZ>it`M>&nYKm>ligpdZb=?m2M zi=yPct}YAAj`nBY17KxQ@#wwR*?w@J(QuVl-mN&`6xEp@!5x;GR^fB`7PI561B>pzB*F8a>W!;VOBDI7yAYD`wv4~~!alT{WD)K^j;JfuF@ z(XuM@l73pDQ0YsLoW0vM++A;=$=@PJqo&d+nYiEwtepEaGrz^Py><2`Gj3mdnNl0H zT2IOii{I9xbe@4^5BhG1-TmI>&5CXTz|C5?QP-F`~-XT32r|e z^6uUV4(7t;%P%o`aEYocvlo0gLgg<#p85o@&>Xp(YzeTVjOCy4DvFBzotxa>zJo8v zync7UVfiea`5cAIPF6X>t_w$3UFM^SdW6K-R%%ol@tTPq44=OT7J|gZ3_*_jttSjz zo}9m>7IR4wb0lb_a;b^--X&5)lAf?{y0day`(QWAjzkJ!L2=@F)}HcMu@+IhQ#5bS4qRa1(l>VCp(-7kJvBrfs_6;ul-^5FHO*&IY2$Et z3wU_?W3<&--rn0_@37&$8<+6X;09}mud+t&v8hvze~_dC@AV?ffpAhQ6ySzfGhyf5 zS9$Yp!Onp)SU-!L{v4DW#9{-^{1n4yKhO3=dG*05?(8;Pef_IwoZyS$`%1j5r_79H z0(Fx&)5UTX$D!S3ZQC_$Wo3=SZa3OTvy0p%dU-B9Jui zZ})^p0-59+9rd*HL&AK5s5@Sie8RwW7v#2?p(^H88G5LEw*@PBg@J>jaJg(rLRptf z!X*7krD8V>buKwag=UJ3PO*OOoSx_pbb9t`o-D*nu8lOtL?fp7@z1U>a5tUUF@+UugpV##nOHVjXHlsI~dt9vKdJ#4t~<{u&LKCW2F zi;=c;_vPr1(x-aFo;r85n##d?_X@AS{t|zDYs91$2IqbZS$P&|XZiC}poK8SJ^ckn zCx3v~-;2C+c#dn=-sJY%-()lx=8`l%4l2nruU@-nd~X&5)KxUo2kdNL<-OS!mmaL( zPkb0#`2YnA)TjQ`tqoXt6aVGb@sNePz~2O%9B_pRJNsGyydWRzH7@R zR7Js~`|t2z=YT7Fr008gbrdOK;2Nv-TEgJ&}PX2C-M7(C?s$Kpj&iavNhZ z$7vcYhGUQTClXv`Xs#=I*)~# z!Rb%n$6H+88FCoL+<5crEOy^PU6K5zzQcB*k$vJVg`!6av{+?WM7FPejaRNuxwbo` z7;hm@e<6Y2V!sZf~8gbYsMX)@S51;%oW(E&%)XM@jpv{>%9 zW(-&aB?KdcNDI;~CJ@_4Aji7GCk~O`DYhrd-T5X5hX>rfbB)Tg)avCft6OIZy^6y?99oVU4hB5B z`NzC@+ zO(B2H=m*Kt7sef)Llo*Dw9&dSbc@X#%y!{;QS!+Hm)Q&vn(3sUmqzPs&{DXy&^6e( zd=YZlB3W^mKqQvCzj!r9pMXaERKJFuBR~%U|KfBgeJf5e!aI zoc>JKD+WXz&N;mEN$Yq736vS zj2d(0ac|0%oi!%ykij!wfZ=IqXZ?-ooKGLGBVV#4OI%VtARz4=cltBv=nOBuJ7gkf zxO?|5*Dw7M!^$JBJaX8-_m@+gC+=V6)~$E=lLsGSKdhmfpFmuh#LnI?6)mbqh@lKL zb?MuZo7m+xPFe^RNnVRFrnzFw4p6aeuyz5WH;#|>6WMd=zNpf+jkaxa+--Be6QcAK zxpo8NTsAhra-5B({VYvAmdm$?Iz?T9`U=fANeF>PMpRp$U}rwy_Cdv^n|HW<_W`Rb zt5h2wpctRQm20>vy%jF9|L_K{U3!h{cXqk-aD#i3lHs#IiJts85@yM-LLJ_v;k9-v z{(0K1eZLsfVU9KC#|+Q@DEoEEE8FW>xxuS%UgFO67a0~ptS4D=C4c6yxC3Be6DIdw z<@ax%=Emd%#l{DSgL4$pq#Im17SJU|HTPYhm}}xyq8X4E3DY z%n{M|j*s<|@j5EprEM1bB0^i&u5Q{83-D2hqNIsTJQ6LGn5{N%Y*ap}wz0G{m{@B| z<(RqjzLd0P)FBe0LTqp+UtoU!HQwC5%5Hne!7DGau`=fLQy->XStT@cLNh~~ZSLQG zncsiuH4bXWtls3^eMfcXGq|nKA)!thNcD+G@h+z=DmY2jWBuTH2@`dSg+thUf$^Ce zymRN9oLM==2gZSyUwfHVx5sd0okpI)mE&x_`F>_2Ddxo@?(j|iSniuhABaazLFrr!a+RJokd>4_7DKwp7E`RA@1XAH@v(ja z!4;e4JrSX)7t~FVHaNVHrA&(E&1B8Ado&>!c&B)^o>i2-F;602v!Z7bTjyy_5ntdf z68t*lQ=jGFt(#Oc$7r(0SO56;*gEwZtD_Yv7tuIF7rWfp-sR!E;?Z=-{XK^}^>O^U zFQmFDHi-AQqQn=z@3BN&GQ*LG(55H7TDn0=>8WagNI;+daop}Zyn1JslOH<4Anx-& zU%tfWUpU99=Re9cjtKQ+sVU4xr#5>41XlbZSFgX#@4R)I)(ZTIj}gi>RNGt*=A5%m zlPSGaH-F8PC`#-YLeWejafB&Ngocv@t`J+=V6HxGctZM0DQP^;x8!PB85TWQmGQfD4e3&`7GtLVoMKufb3`!$l2+C&Yc4{Ap7}I!_K=6S zzJjw2CiQ~LcW+U;$WU8K?`YeSNi*X9fwC7i@lSmWfA%N9RVkCpdt6bZ-6)?}LFdZM zcv>b5QaPApMWI)h7=ng5Hhzl1Ge5!MTmOY`-Cg5_Gc|krf$594`F!(rp83Eh(Q1`8 zx)k4o!|ELlcVB^K#$KH9A75SN&f%Ex=^vmOKaZ0J&Gf%!aEWSK$;qjkW=REan!a9X z+ToUhsElJ3!67)8W`;3>HFe6bo*qD3A5aW-j*s<|2ri5FMA&Yd`nlP3N?ffd9F#r~ zOeqjdj76IkCM%zq!;mv=&&EbD8VjD{9n@5^lW8fOa+l-Ii0-Z^9?0#IoT7RsQ7-4nA97N z^q?zU3S*E^6Pr0fij=n;!y$4!Kz<^@b@AW+llm8b|Rg8 z*%$~hc^4Lgb*j-8I(`a~5{q*n^)kLJDT=BGmh($C(2%rn8G#rv&DPKA(r6(D)CV-Q zv>_nX3Vi72D5n>=dw&;`XF0d>2#Si=AMW$k{VR-$$jZR8>eFp_ryX#0{|C9Yy-Kli zn&$KuFh52KxMG;8_uOl_1S}PodNmQmX-cH*J)PWA7Ly5>LWR=9&_n4Jb71Y9(AG3T zVmsYau)T{H&d1|p{RDz5k!pIgUd(v-aM$*>HrZO$G;2~YQ&a*~0plPvQK$p7(P-OF z(GdF^Omais#8mP)O(EIhmSljIB|!yiYltnn^>M1Lk7uv<>@X#1uZtM4Fo#$pMFB4L zR%Mwi&$RHkuKY^s;lyHV+5zjB0Sgf$qFtzZG+Ye$7zt)91|Ow);cw$EUF6=~JM51t z*2bq86dRP(?A0~eG`Ik>wq!eOu{$5(RyJr(e;zB>@m8mqR;LI#;)S8k_cJV!oHda-M5SSnCTReD(bMAP(|49ZH;%-W)nI7!x{;Z*H z3!C}F&Tztz(uo^2OV0Px}FL(Fm4f-?moO-x2$>7ZnQ^-t%lu!^bR zRB=St<}Ks4HyX`5ANk{V1_B^?K%FedvOw6mG)K~<4}G*m#-vJ1mZexs0PD;}6)+!( z3x`QV85?ZvDdPGsBJ)Ss{!MJLN30KN=ew8`1RW-=iVO%kKqaIN;bMgICE|--dX-cv znK6`ZeIU)YOw!!T$OMPFblsB3qk)IY3xh%^z2LoI!LWADyb0DG?n%+k8*~23@sWO# z!G(NFEy8q{y~*4jPP{y|IiPYzOX7c3k_+!3-(CPADj_c~21)56A*K~T)rgYVLdm=N z9aPf5Gy%!w&^?R3w>*1HbBF79p8DlXJtB5h_n4%}jtqDc^^!a>ICGBSepB z-)&&hqEZkfq?-hnN=A)fKGXWS!pn3u*+!u2OVSuXd#?%2B!=Z1Pf5bxZY+=p4*D(X zsL8Y2g3;6w3jx=y1c)T-fzTkKMsvao5>XfNK$H?Id~(~(q=l->{vug=Omsr`-D}`4E9&pNHF=cmu!fer4Ss#jE_YLkGAL%Fh<^p{A z-*mqB!gOyZJXjg}yAKQ2SB8uSf_Fv;X{Rd|P!E(vNF`u2Xd9);*$6J1TbheW<&k@g zh$OSkNZMD*8{5Wz^*a?Ue67%j;Hs|bFw)A_W(Ss^<{_?#;@Y3P8 z&O%|$>oatvqPjH5jWLrthN>bRBb&ad{K;7+&7tSBY20iKEGAaH;8D{_BaOb^p zd-YU%;=zGGxw%GJ7L1EXp+XGlfQEt4M(x$>(E`ylZOB?c)o?-4j?S0#Bv0;pbz%c? zn8cj!k&k9Ji0E6c>@1bcSY&C?r2esaP|W3ES2kMAV}d-5?bc+{I!mfgn_#qoTw+?6 z;?VDssb+GaH$b&awkV3nV(bCbX?&$CL^IYc?`t7Kv`z@Dm=rnWA>CXiN~wu7Dinr= zFf5gEA*>Xj&auCD$fF1Mse?g1Ro_f8ahHyd^ON<`@AQioFZ|XwMD%w@2iIk?yA$v3 zwcOsG@Ms!XG|6?SETAle)e1I8%E_^Ea-?hwm2s8I7{t?<(vlo{qd_YWrGR1x#Rw~g z1ocGo`Jkl@Tr zbc{=3Z2(mv9L(YV!$;KfDJ|Z#p5Ve96ZhuvasGZN3xVj$e>!OPex|wiN_l_y6JdRI z=tiRndTN9SjH^V)3&Kj3`x^l#4zGbdfvLc}^#qGVk{(V>XzBW}5vB&Ng%}8-K|<4m zMI@Q2sp}ZWBhP{y7O|WOWp~={Fz4k>6U(Nb9Pz|R7Y? znusOo7iga48IRi@F3m2aTOSc6cDq{QF!c#o9qpd`EH6+wV_Yihqk`3;uwKEa6lTG( zvpZqBzen8~b%b*E5REOtmDi7t^Y;n3E?zkQ-+%c_zy9j#HCnikrat>BQfQQb3|S*m=Bbl4B)Vmd^?j>9>%*cMMBmW#CvJ zIZlloC&$Xhz*wzPY*W2haCkW7(f*tV2XpT4&Ui43Hr#njM~eqUSN)rd7ta6Q@p1k> z0vEumFMj2yG=5UYgy0TexqA`m1N>zHice&@X>~omdy-qFBZO*Eu z`ByrqS*@OXpk$qvy&<}pQq11)-O^wOn(nFJ`KxD5UdjvWR$j8E<4Ce=jL>#{79E49 zeQ-p#a!>yJHJ5iwtAISXejoVKn?C}q1TLq(&npv6R^i?`O79qyj&Y@|mcr&pI5|>I z43*V^G4d%jvT0hT^T6)3=E44)dynS4yVpdw^G-am_m-L~rilLhD=+@x;`lg!pMfji zV3$7i*`NLf&i}1ZyQlSZBKPWHoV7>{t*8oB;c$fq6i1RqsV=*ju0ak)QelQ~x){ z?S2s#hw`8u$AbkjU(^yJh!dRG{G^v`AZjVBTvM)Fp+ez=A|E1`U3+_3#}SjG_&(Rb zW%7w0@lli`zKTcs#r%6??$n|wO@KO(DS2Ird&TAKVd+T+>6GdsOU+-_NLmLfS?U72 zQqMcZ7mm^?WuXiUO>?SBSt*sZfv`SIfLa>}<0=h@E1!B+pKtgk1P*Ise+IkL$nAIE z<=R_s*y7%m=EUS?Sz-iNoxgbDxBkWPF@7>%^5^{nzx1Ww_^e?6)rHyVi95lgjTdaN zaZ1i@R{X%z8~ni8b+%3n8COLL1}EJVbx04IDPctclDTXhLEa ze9-ifvw)^V}dy;FxQ zKcDm||`cRdO{-{iwy&r`cQ|$+M@1Ja=Z5 zvs-Iyu2l?6hZF7nHbR8fV9^NE2KE;)sf~lVaahA)6KTSdCJ(W@!It;Z(z{;zL+X}7 z^FF7!gIBcl_jM#j?5xTO@Ow|-uF#Qy{Kv1nx3@nM=PzD3 z|IOp$`xg*g8DIkPuUwq}YBXWLdWzZTY+Gz>#nYQ>#o5h)JhQdV)2CM0SScxs95j&o zqY#ZnD@%bL~y<4=$LLYmJG?zyJJ>a^Zv!uqmnO2C)_p?cG zA2fdSs%yUQXga;aN67#1+7BrP)*-7S?sRpjb34VsNM81n#&#+WyeMH6Z2 zn#FuZGdmzOHS_t*_II|~yLU_K-E9V;p%C}`ruxsmeBu0mK0da8!NIiz*>Cw9O&`YI?|Qb{OmFzQ+Cg zgoB4@J*N=9CF=gAiG2Cuh4anvk^M^qu6&C<4g98B_*D(HuT7Z_x90QJ=d@ToS)5uc z<=p8N&YfE4)QJ(Rql&6Zy1ZyznFzB+IIN9nW7I)d1Yyyp;d9JF+b#*jj{FOH^JiJ! zNL{tp<{4VFrGX~z17>#%YDTV*MhfX;S?=kUeO9-2Kk&w&OeJ8coda&#+HRY`Y~FA< ztJy!CayVTuU)0ofO|xid+J?FbH1&c|Pl>aAWWI-=@8R1yUfpZr+`lu?e|_=7`Qwb^ zzpUUo0kmOm!)C1kkZY~e9jF!s*i{yPr?{ensL`_#s136#N-@~>^>WO*7Y&K^$n=_f!OlJ*?x(;m!);6^@iv?}HD6F01+bO<2 zM8X^&+sh^G>#FYSrtWLNOBXMkpC2F5V{pA6WPb(tdur_$B{VE#z`R&*>uNLD__P({ zv8)YUd19!tv0AaQGGsiisHzI@ecpo-j81LtiDWnOGf7v+Y;YRwg8@@?cZwEMc2%%EkcJo&Luc8HCS(;z43_NQ+61P$si_wYP2DhGESN7E7IjPA zL_*sVW3ZS$P{2Y^3sGaM5*pEVF0P$K0fm&M{=Q){0U3k2~rQqf9j!Bvaa4JOf50be8zwgi}%-YlRNIkKgnBYiXg*Fo->$Qy>d1iS%Uy?EjLadgk|>&XDu`+znAeiHa` z;3L3E!Jfx#ONax^#%Vjs9^>G<%c&dr9!-Hf`g4xhZ1-M>y(gk#A`P$rd1SDS=z+lu zX#O@KK_1I+UK zn*W>tEpR{z<(>mgewzb_$MpB{>n{bkz601!0wZ7rcq-pNowENS@Bo+sbp}!01rz+z rmwvyKamuV4QQOIGzLk%?tp00000NkvXXu0mjf-IU)r diff --git a/examples/example 12 - Spine/data/spineboy.anim b/examples/example 12 - Spine/data/spineboySpineData.json similarity index 100% rename from examples/example 12 - Spine/data/spineboy.anim rename to examples/example 12 - Spine/data/spineboySpineData.json diff --git a/examples/example 12 - Spine/index.html b/examples/example 12 - Spine/index.html index bae38d5..8b4dc17 100644 --- a/examples/example 12 - Spine/index.html +++ b/examples/example 12 - Spine/index.html @@ -21,7 +21,7 @@ // create an array of assets to load - var assetsToLoader = ["data/spineboy.json", "data/spineboy.anim"]; + var assetsToLoader = ["data/spineboy.json", "data/spineboySpineData.json"]; // create a new loader loader = new PIXI.AssetLoader(assetsToLoader); @@ -47,16 +47,20 @@ function onAssetsLoaded() { - var spineBoy = new PIXI.Spine("data/spineboy.anim"); + // create a spine boy + var spineBoy = new PIXI.Spine("data/spineboySpineData.json"); + // set the position spineBoy.position.x = window.innerWidth/2; spineBoy.position.y = window.innerHeight; spineBoy.scale.x = spineBoy.scale.y = window.innerHeight / 400; + // set up the mixes! spineBoy.stateData.setMixByName("walk", "jump", 0.2); spineBoy.stateData.setMixByName("jump", "walk", 0.4); + // play animation spineBoy.state.setAnimationByName("walk", true); diff --git a/examples/example 12 - Spine/index_pixie.html b/examples/example 12 - Spine/index_pixie.html index 0dd475a..8a32112 100644 --- a/examples/example 12 - Spine/index_pixie.html +++ b/examples/example 12 - Spine/index_pixie.html @@ -61,31 +61,31 @@ stage.addChild(foreground2); foreground.position.y = foreground2.position.y = 640 - foreground2.height; - var dragon = new PIXI.Spine("data/PixieSpineData.json"); + var pixie = new PIXI.Spine("data/PixieSpineData.json"); var scale = 0.3;//window.innerHeight / 700; - dragon.position.x = 1024/3; - dragon.position.y = 500 + pixie.position.x = 1024/3; + pixie.position.y = 500 - dragon.scale.x = dragon.scale.y = scale + pixie.scale.x = pixie.scale.y = scale //dragon.state.setAnimationByName("running", true); - stage.addChild(dragon); + stage.addChild(pixie); - dragon.stateData.setMixByName("running", "jump", 0.2); - dragon.stateData.setMixByName("jump", "running", 0.4); + pixie.stateData.setMixByName("running", "jump", 0.2); + pixie.stateData.setMixByName("jump", "running", 0.4); - dragon.state.setAnimationByName("running", true); + pixie.state.setAnimationByName("running", true); stage.click = function() { - dragon.state.setAnimationByName("jump", false); - dragon.state.addAnimationByName("running", true); + pixie.state.setAnimationByName("jump", false); + pixie.state.addAnimationByName("running", true); } diff --git a/examples/example 12 - Spine/pixi.js b/examples/example 12 - Spine/pixi.js index 68e2e69..c7005f9 100644 --- a/examples/example 12 - Spine/pixi.js +++ b/examples/example 12 - Spine/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-11 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -929,6 +929,8 @@ PIXI.MovieClip.prototype.updateTransform = function() * @param {String} [style.align="left"] An alignment of the multiline text ("left", "center" or "right") * @param {String} [style.stroke] A canvas fillstyle that will be used on the text stroke eg "blue", "#FCFF00" * @param {Number} [style.strokeThickness=0] A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param {Boolean} [style.wordWrap=false] Indicates if word wrap should be used + * @param {Number} [style.wordWrapWidth=100] The width at which text will wrap */ PIXI.Text = function(text, style) { @@ -956,6 +958,8 @@ PIXI.Text.prototype = Object.create(PIXI.Sprite.prototype); * @param {String} [style.align="left"] An alignment of the multiline text ("left", "center" or "right") * @param {String} [style.stroke] A canvas fillstyle that will be used on the text stroke eg "blue", "#FCFF00" * @param {Number} [style.strokeThickness=0] A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param {Boolean} [style.wordWrap=false] Indicates if word wrap should be used + * @param {Number} [style.wordWrapWidth=100] The width at which text will wrap */ PIXI.Text.prototype.setStyle = function(style) { @@ -964,6 +968,8 @@ PIXI.Text.prototype.setStyle = function(style) style.fill = style.fill || "black"; style.align = style.align || "left"; style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; this.style = style; this.dirty = true; }; @@ -976,7 +982,6 @@ PIXI.Text.prototype.setStyle = function(style) PIXI.Sprite.prototype.setText = function(text) { this.text = text.toString() || " "; - this.dirty = true; }; @@ -987,9 +992,15 @@ PIXI.Sprite.prototype.setText = function(text) PIXI.Text.prototype.updateText = function() { this.context.font = this.style.font; + + var outputText = this.text; + + // word wrap + // preserve original text + if(this.style.wordWrap)outputText = this.wordWrap(this.text); //split text into lines - var lines = this.text.split(/(?:\r\n|\r|\n)/); + var lines = outputText.split(/(?:\r\n|\r|\n)/); //calculate text width var lineWidths = []; @@ -1103,6 +1114,57 @@ PIXI.Text.prototype.determineFontHeight = function(fontStyle) return result; }; +/** + * A Text Object will apply wordwrap + * @private + */ +PIXI.Text.prototype.wordWrap = function(text) +{ + // search good wrap position + var searchWrapPos = function(ctx, text, start, end, wrapWidth) + { + var p = Math.floor((end-start) / 2) + start; + if(p == start) { + return 1; + } + + if(ctx.measureText(text.substring(0,p)).width <= wrapWidth) + { + if(ctx.measureText(text.substring(0,p+1)).width > wrapWidth) + { + return p; + } + else + { + return arguments.callee(ctx, text, p, end, wrapWidth); + } + } + else + { + return arguments.callee(ctx, text, start, p, wrapWidth); + } + }; + + var lineWrap = function(ctx, text, wrapWidth) + { + if(ctx.measureText(text).width <= wrapWidth || text.length < 1) + { + return text; + } + var pos = searchWrapPos(ctx, text, 0, text.length, wrapWidth); + return text.substring(0, pos) + "\n" + arguments.callee(ctx, text.substring(pos), wrapWidth); + }; + + var result = ""; + var lines = text.split("\n"); + for (var i = 0; i < lines.length; i++) + { + result += lineWrap(this.context, lines[i], this.style.wordWrapWidth) + "\n"; + } + + return result; +}; + PIXI.Text.prototype.destroy = function(destroyTexture) { if(destroyTexture) @@ -2437,7 +2499,7 @@ PIXI.WebGLRenderer = function(width, height, view, transparent) PIXI.gl = this.gl = this.view.getContext("experimental-webgl", { alpha: this.transparent, antialias:false, // SPEED UP?? - premultipliedAlpha:true + premultipliedAlpha:false }); } catch (e) @@ -2580,7 +2642,7 @@ PIXI.WebGLRenderer.prototype.render = function(stage) gl.bindFramebuffer(gl.FRAMEBUFFER, null); - gl.clearColor(stage.backgroundColorSplit[0],stage.backgroundColorSplit[1],stage.backgroundColorSplit[2], this.transparent); + gl.clearColor(stage.backgroundColorSplit[0],stage.backgroundColorSplit[1],stage.backgroundColorSplit[2], !this.transparent); gl.clear(gl.COLOR_BUFFER_BIT); @@ -3592,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - //this.addDisplayObject(child); - this.updateTexture(child); + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } diff --git a/examples/example 2 - SpriteSheet/pixi.js b/examples/example 2 - SpriteSheet/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 2 - SpriteSheet/pixi.js +++ b/examples/example 2 - SpriteSheet/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 3 - MovieClip/pixi.js b/examples/example 3 - MovieClip/pixi.js index 2028846..c7005f9 100755 --- a/examples/example 3 - MovieClip/pixi.js +++ b/examples/example 3 - MovieClip/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 4 - Balls/pixi.js b/examples/example 4 - Balls/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 4 - Balls/pixi.js +++ b/examples/example 4 - Balls/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 5 - Morph/pixi.js b/examples/example 5 - Morph/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 5 - Morph/pixi.js +++ b/examples/example 5 - Morph/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 6 - Interactivity/pixi.js b/examples/example 6 - Interactivity/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 6 - Interactivity/pixi.js +++ b/examples/example 6 - Interactivity/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 7 - Transparent Background/pixi.js b/examples/example 7 - Transparent Background/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 7 - Transparent Background/pixi.js +++ b/examples/example 7 - Transparent Background/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 8 - Dragging/pixi.js b/examples/example 8 - Dragging/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 8 - Dragging/pixi.js +++ b/examples/example 8 - Dragging/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/examples/example 9 - Tiling Texture/pixi.js b/examples/example 9 - Tiling Texture/pixi.js index 2028846..c7005f9 100644 --- a/examples/example 9 - Tiling Texture/pixi.js +++ b/examples/example 9 - Tiling Texture/pixi.js @@ -4,7 +4,7 @@ * Copyright (c) 2012, Mat Groves * http://goodboydigital.com/ * - * Compiled: 2013-06-07 + * Compiled: 2013-06-12 * * Pixi.JS is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php @@ -1916,6 +1916,8 @@ PIXI.Stage = function(backgroundColor, interactive) this.setBackgroundColor(backgroundColor); this.worldVisible = true; + + this.stage.dirty = true; } // constructor @@ -3233,7 +3235,6 @@ PIXI.WebGLBatch.prototype.update = function() tx = worldTransform[2]; ty = worldTransform[5]; - this.verticies[index + 0 ] = a * w1 + c * h1 + tx; this.verticies[index + 1 ] = d * h1 + b * w1 + ty; @@ -3319,6 +3320,7 @@ PIXI.WebGLBatch.prototype.render = function(start, end) { this.refresh(); this.dirty = false; + } if (this.size == 0)return; @@ -3652,8 +3654,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - this.removeDisplayObject(child) - this.addDisplayObject(child) + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! } @@ -3665,6 +3668,106 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global }; } +PIXI.WebGLRenderGroup.prototype.updateTexture = function(displayObject) +{ + // we know this exists.. + // is it in a batch.. + // check batch length + if(displayObject.batch.length == 1) + { + // just one! this guy! so simply swap the texture + displayObject.batch.texture = displayObject.texture.baseTexture; + return; + } + + // early out! + if(displayObject.batch.texture == displayObject.texture.baseTexture)return; + + + if(displayObject.batch.head == displayObject) + { + //console.log("HEAD") + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var previousBatch = this.batchs[index-1]; + currentBatch.remove(displayObject); + + if(previousBatch) + { + if(previousBatch.texture == displayObject.texture.baseTexture && previousBatch.blendMode == displayObject.blendMode) + { + previousBatch.insertAfter(displayObject, previousBatch.tail); + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index-1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(0, 0, batch); + } + + } + else if(displayObject.batch.tail == displayObject) + { + var currentBatch = displayObject.batch; + + var index = this.batchs.indexOf( currentBatch ); + var nextBatch = this.batchs[index+1]; + currentBatch.remove(displayObject); + + if(nextBatch) + { + if(nextBatch.texture == displayObject.texture.baseTexture && nextBatch.blendMode == displayObject.blendMode) + { + nextBatch.insertBefore(displayObject, nextBatch.head); + return; + } + else + { + // add it before.. + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch); + } + + } + else + { + // we are 0! + var batch = PIXI.WebGLRenderer.getBatch(); + batch.init(displayObject); + this.batchs.push(batch); + } + } + else + { + // console.log("MIDDLE") + var currentBatch = displayObject.batch; + + // split the batch into 2 + // AH! dont split on the current display object as the texture is wrong! + var splitBatch = currentBatch.split(displayObject); + + // now remove the display object + splitBatch.remove(displayObject); + + var batch = PIXI.WebGLRenderer.getBatch(); + var index = this.batchs.indexOf( currentBatch ); + batch.init(displayObject); + this.batchs.splice(index+1, 0, batch, splitBatch); + } +} + PIXI.WebGLRenderGroup.prototype.addDisplayObject = function(displayObject) { // add a child to the render group.. @@ -4850,6 +4953,1453 @@ PIXI.TilingSprite.prototype.onTextureUpdate = function(event) } +/** + * @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 + * @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; + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ @@ -5400,10 +6950,13 @@ PIXI.AssetLoader = function(assetURLs) "jpeg": PIXI.ImageLoader, "png": PIXI.ImageLoader, "gif": PIXI.ImageLoader, - "json": PIXI.SpriteSheetLoader, + "json": PIXI.JsonLoader, + "anim": PIXI.SpineLoader, "xml": PIXI.BitmapFontLoader, "fnt": PIXI.BitmapFontLoader }; + + }; /** @@ -5485,6 +7038,8 @@ PIXI.JsonLoader = function (url, crossorigin) { this.url = url; this.baseUrl = url.replace(/[^\/]*$/, ""); this.crossorigin = crossorigin; + this.loaded = false; + }; // constructor @@ -5513,7 +7068,57 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { if (this.ajaxRequest.readyState == 4) { if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { this.json = JSON.parse(this.ajaxRequest.responseText); - this.onLoaded(); + + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; + + this.texture = image.texture.baseTexture; + image.addEventListener("loaded", function (event) { + scope.onLoaded(); + }); + + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } + + image.load(); + + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + + + + } else { this.onError(); } @@ -5525,6 +7130,7 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { + this.loaded = true; this.dispatchEvent({ type: "loaded", content: this @@ -5815,6 +7421,83 @@ PIXI.BitmapFontLoader.prototype.onLoaded = function() this.dispatchEvent({type: "loaded", content: this}); }; +/** + * @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 + * + */ + +/** + * The Spine loader is used to load in JSON spine data + * To generate the data you need to use http://esotericsoftware.com/ and export the "JSON" format + * 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 + * You will need to generate a sprite sheet to accompany the spine data + * When loaded this class will dispatch a "loaded" event + * @class Spine + * @constructor + * @extends + * @param {String} url the url of the sprite sheet JSON file + * @param {Boolean} crossorigin + */ +PIXI.SpineLoader = function(url, crossorigin) +{ + PIXI.EventTarget.call(this); + this.url = url; + this.crossorigin = crossorigin; + this.loaded = false; +} + +PIXI.SpineLoader.constructor = PIXI.SpineLoader; + +PIXI.SpineLoader.prototype.load = function() +{ + new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +PIXI.SpineLoader.prototype.load = function () { + + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { + + var spineJsonParser = new spine.SkeletonJson(); + + var skeletonData = spineJsonParser.readSkeletonData(this.json); + + PIXI.AnimCache[this.url] = skeletonData; + + this.onLoaded(); +}; + + + +PIXI.SpineLoader.prototype.onLoaded = function() +{ + this.loaded = true; + this.dispatchEvent({type: "loaded", content: this}); +}; + + /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ diff --git a/src/pixi/renderers/WebGLRenderGroup.js b/src/pixi/renderers/WebGLRenderGroup.js index 40ca640..ec022dc 100644 --- a/src/pixi/renderers/WebGLRenderGroup.js +++ b/src/pixi/renderers/WebGLRenderGroup.js @@ -275,8 +275,9 @@ PIXI.WebGLRenderGroup.prototype.checkVisibility = function(displayObject, global child.textureChange = false; if(child.worldVisible) { - //this.addDisplayObject(child); - this.updateTexture(child); + this.removeDisplayObject(child); + this.addDisplayObject(child); + //this.updateTexture(child); } // update texture!! }