text: /******************************************************************************
   * Spine Runtimes Software License
   * Version 2
   *
   * Copyright (c) 2013, Esoteric Software
   * All rights reserved.
   *
   * You are granted a perpetual, non-exclusive, non-sublicensable and
   * non-transferable license to install, execute and perform the Spine Runtimes
   * Software (the "Software") solely for internal use. Without the written
   * permission of Esoteric Software, you may not (a) modify, translate, adapt or
   * otherwise create derivative works, improvements of the Software or develop
   * new applications using the Software or (b) remove, delete, alter or obscure
   * any trademarks or any copyright, trademark, patent or other intellectual
   * property or proprietary rights notices on or in the Software, including
   * any copy thereof. Redistributions in binary or source form must include
   * this license and terms. THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *****************************************************************************/
  
  var spine = spine || {
      radDeg: 180 / Math.PI,
      degRad: Math.PI / 180,
      temp: [],
      Float32Array: (typeof(Float32Array) === 'undefined') ? Array : Float32Array,
      Uint16Array: (typeof(Uint16Array) === 'undefined') ? Array : Uint16Array
  };
  
  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,
      inheritScale: true,
      inheritRotation: true
  };
  
  spine.SlotData = function (name, boneData) {
      this.name = name;
      this.boneData = boneData;
      this.r = this.g = this.b = this.a = 1;                  //FOR google compiler Advance mode
  };
  spine.SlotData.prototype = {
      r: 1, g: 1, b: 1, a: 1,
      attachmentName: null,
      additiveBlending: false
  };
  
  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;
              if (this.data.inheritScale) {
                  this.worldScaleX = parent.worldScaleX * this.scaleX;
                  this.worldScaleY = parent.worldScaleY * this.scaleY;
              } else {
                  this.worldScaleX = this.scaleX;
                  this.worldScaleY = this.scaleY;
              }
              this.worldRotation = this.data.inheritRotation ? parent.worldRotation + this.rotation : this.rotation;
          } else {
              this.worldX = flipX ? -this.x : this.x;
              this.worldY = flipY != spine.Bone.yDown ? -this.y : 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 != 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, lastTime, time, loop, events) {
          if (loop && this.duration != 0) {
              time %= this.duration;
              lastTime %= this.duration;
          }
          var timelines = this.timelines;
          for (var i = 0, n = timelines.length; i < n; i++)
              timelines[i].apply(skeleton, lastTime, time, events, 1);
      },
      mix: function (skeleton, lastTime, time, loop, events, alpha) {
          if (loop && this.duration != 0) {
              time %= this.duration;
              lastTime %= this.duration;
          }
          var timelines = this.timelines;
          for (var i = 0, n = timelines.length; i < n; i++)
              timelines[i].apply(skeleton, lastTime, time, events, 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, lastTime, time, firedEvents, 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, lastTime, time, firedEvents, 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, lastTime, time, firedEvents, 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 / 5;
      },
      setFrame: function (frameIndex, time, r, g, b, a) {
          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, lastTime, time, firedEvents, 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 = [];
      this.attachmentNames.length = frameCount;
  };
  spine.AttachmentTimeline.prototype = {
      slotIndex: 0,
      getFrameCount: function () {
          return this.frames.length;
      },
      setFrame: function (frameIndex, time, attachmentName) {
          this.frames[frameIndex] = time;
          this.attachmentNames[frameIndex] = attachmentName;
      },
      apply: function (skeleton, lastTime, time, firedEvents, 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];
          skeleton.slots[this.slotIndex].setAttachment(!attachmentName ? null : skeleton.getAttachmentBySlotIndex(this.slotIndex, attachmentName));
      }
  };
  
  spine.EventTimeline = function (frameCount) {
      this.frames = []; // time, ...
      this.frames.length = frameCount;
      this.events = [];
      this.events.length = frameCount;
  };
  spine.EventTimeline.prototype = {
      getFrameCount: function () {
          return this.frames.length;
      },
      setFrame: function (frameIndex, time, event) {
          this.frames[frameIndex] = time;
          this.events[frameIndex] = event;
      },
      /** Fires events for frames > lastTime and <= time. */
      apply: function (skeleton, lastTime, time, firedEvents, alpha) {
          if (!firedEvents) return;
  
          var frames = this.frames;
          var frameCount = frames.length;
  
          if (lastTime > time) { // Fire events after last time for looped animations.
              this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha);
              lastTime = -1;
          } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
              return;
          if (time < frames[0]) return; // Time is before first frame.
  
          var frameIndex;
          if (lastTime < frames[0])
              frameIndex = 0;
          else {
              frameIndex = spine.binarySearch(frames, lastTime, 1);
              var frame = frames[frameIndex];
              while (frameIndex > 0) { // Fire multiple events with the same frame.
                  if (frames[frameIndex - 1] != frame) break;
                  frameIndex--;
              }
          }
          var events = this.events;
          for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++)
              firedEvents.push(events[frameIndex]);
      }
  };
  
  spine.DrawOrderTimeline = function (frameCount) {
      this.frames = []; // time, ...
      this.frames.length = frameCount;
      this.drawOrders = [];
      this.drawOrders.length = frameCount;
  };
  spine.DrawOrderTimeline.prototype = {
      getFrameCount: function () {
          return this.frames.length;
      },
      setFrame: function (frameIndex, time, drawOrder) {
          this.frames[frameIndex] = time;
          this.drawOrders[frameIndex] = drawOrder;
      },
      apply: function (skeleton, lastTime, time, firedEvents, 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 drawOrder = skeleton.drawOrder;
          var slots = skeleton.slots;
          var drawOrderToSetupIndex = this.drawOrders[frameIndex];
          if (!drawOrderToSetupIndex) {
              for (var i = 0, n = slots.length; i < n; i++)
                  drawOrder[i] = slots[i];
          } else {
              for (var i = 0, n = drawOrderToSetupIndex.length; i < n; i++)
                  drawOrder[i] = skeleton.slots[drawOrderToSetupIndex[i]];
          }
  
      }
  };
  
  spine.SkeletonData = function () {
      this.bones = [];
      this.slots = [];
      this.skins = [];
      this.events = [];
      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 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].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. */
      findEvent: function (eventName) {
          var events = this.events;
          for (var i = 0, n = events.length; i < n; i++)
              if (events[i].name == eventName) return events[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.length; i < n; i++) {
              var slot = slots[i];
              if (slot.data.name == slotName) {
                  var attachment = null;
                  if (attachmentName) {
                      attachment = this.getAttachment(i, attachmentName);
                      if (!attachment) throw "Attachment not found: " + attachmentName + ", for slot: " + slotName;
                  }
                  slot.setAttachment(attachment);
                  return;
              }
          }
          throw "Slot not found: " + slotName;
      },
      update: function (delta) {
          this.time += delta;
      }
  };
  
  spine.EventData = function (name) {
      this.name = name;
  };
  
  spine.EventData.prototype = {
      intValue: 0,
      floatValue: 0,
      stringValue: null
  };
  
  spine.Event = function (data) {
      this.data = data;
  };
  
  spine.Event.prototype = {
      intValue: 0,
      floatValue: 0,
      stringValue: null
  };
  
  spine.AttachmentType = {
      region: 0,
      boundingbox: 1,
      mesh: 2,
      skinnedmesh: 3
  };
  
  spine.RegionAttachment = function (name) {
      this.name = name;
      this.offset = [];
      this.offset.length = 8;
      this.uvs = [];
      this.uvs.length = 8;
      this["type"] = spine.AttachmentType.region;                //FOR advance mode
  };
  
  spine.RegionAttachment.prototype = {
      type: spine.AttachmentType.region,
      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, slot, vertices) {
          var bone = slot.bone;
          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.MeshAttachment = function (name) {
      this.name = name;
  };
  spine.MeshAttachment.prototype = {
      type: spine.AttachmentType.mesh,
      vertices: null,
      uvs: null,
      regionUVs: null,
      triangles: null,
      hullLength: 0,
      r: 1, g: 1, b: 1, a: 1,
      path: null,
      rendererObject: null,
      regionU: 0, regionV: 0, regionU2: 0, regionV2: 0, regionRotate: false,
      regionOffsetX: 0, regionOffsetY: 0,
      regionWidth: 0, regionHeight: 0,
      regionOriginalWidth: 0, regionOriginalHeight: 0,
      edges: null,
      width: 0, height: 0,
      updateUVs: function () {
          var width = this.regionU2 - this.regionU, height = this.regionV2 - this.regionV;
          var n = this.regionUVs.length;
          if (!this.uvs || this.uvs.length != n) {
              this.uvs = new spine.Float32Array(n);
          }
          if (this.regionRotate) {
              for (var i = 0; i < n; i += 2) {
                  this.uvs[i] = this.regionU + this.regionUVs[i + 1] * width;
                  this.uvs[i + 1] = this.regionV + height - this.regionUVs[i] * height;
              }
          } else {
              for (var i = 0; i < n; i += 2) {
                  this.uvs[i] = this.regionU + this.regionUVs[i] * width;
                  this.uvs[i + 1] = this.regionV + this.regionUVs[i + 1] * height;
              }
          }
      },
      computeVertices: function (x, y, slot, vertices) {
          var bone = slot.bone;
          x += bone.worldX;
          y += bone.worldY;
          var m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11;
          var verticesCount = this.vertices.length;
          if (slot.attachment.length == verticesCount) this.vertices = slot.attachment;
          for (var i = 0; i < verticesCount; i += 2) {
              var vx = this.vertices[i];
              var vy = this.vertices[i + 1];
              vertices[i] = vx * m00 + vy * m01 + x;
              vertices[i + 1] = vx * m10 + vy * m11 + y;
          }
      }
  };
  
  spine.SkinnedMeshAttachment = function (name) {
      this.name = name;
  };
  spine.SkinnedMeshAttachment.prototype = {
      type: spine.AttachmentType.skinnedmesh,
      bones: null,
      weights: null,
      uvs: null,
      regionUVs: null,
      triangles: null,
      hullLength: 0,
      r: 1, g: 1, b: 1, a: 1,
      path: null,
      rendererObject: null,
      regionU: 0, regionV: 0, regionU2: 0, regionV2: 0, regionRotate: false,
      regionOffsetX: 0, regionOffsetY: 0,
      regionWidth: 0, regionHeight: 0,
      regionOriginalWidth: 0, regionOriginalHeight: 0,
      edges: null,
      width: 0, height: 0,
      updateUVs: function (u, v, u2, v2, rotate) {
          var width = this.regionU2 - this.regionU, height = this.regionV2 - this.regionV;
          var n = this.regionUVs.length;
          if (!this.uvs || this.uvs.length != n) {
              this.uvs = new spine.Float32Array(n);
          }
          if (this.regionRotate) {
              for (var i = 0; i < n; i += 2) {
                  this.uvs[i] = this.regionU + this.regionUVs[i + 1] * width;
                  this.uvs[i + 1] = this.regionV + height - this.regionUVs[i] * height;
              }
          } else {
              for (var i = 0; i < n; i += 2) {
                  this.uvs[i] = this.regionU + this.regionUVs[i] * width;
                  this.uvs[i + 1] = this.regionV + this.regionUVs[i + 1] * height;
              }
          }
      },
      computeWorldVertices: function (x, y, slot, worldVertices) {
          var skeletonBones = slot.bone.skeleton.bones;
          var weights = this.weights;
          var bones = this.bones;
  
          var w = 0, v = 0, b = 0, f = 0, n = bones.length, nn;
          var wx, wy, bone, vx, vy, weight;
          if (!slot.attachmentVertices.length) {
              for (; v < n; w += 2) {
                  wx = 0;
                  wy = 0;
                  nn = bones[v++] + v;
                  for (; v < nn; v++, b += 3) {
                      bone = skeletonBones[bones[v]];
                      vx = weights[b];
                      vy = weights[b + 1];
                      weight = weights[b + 2];
                      wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight;
                      wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight;
                  }
                  worldVertices[w] = wx + x;
                  worldVertices[w + 1] = wy + y;
              }
          } else {
              var ffd = slot.attachmentVertices;
              for (; v < n; w += 2) {
                  wx = 0;
                  wy = 0;
                  nn = bones[v++] + v;
                  for (; v < nn; v++, b += 3, f += 2) {
                      bone = skeletonBones[bones[v]];
                      vx = weights[b] + ffd[f];
                      vy = weights[b + 1] + ffd[f + 1];
                      weight = weights[b + 2];
                      wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight;
                      wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight;
                  }
                  worldVertices[w] = wx + x;
                  worldVertices[w + 1] = wy + y;
              }
          }
      }
  };
  
  spine.BoundingBoxAttachment = function (name) {
      this.name = name;
      this.vertices = [];
      this["type"] = spine.AttachmentType.boundingBox;     //FOR advance mode
  };
  
  spine.BoundingBoxAttachment.prototype = {
      type: spine.AttachmentType.boundingBox,
      computeWorldVertices: function (x, y, bone, worldVertices) {
          x += bone.worldX;
          y += bone.worldY;
          var m00 = bone.m00;
          var m01 = bone.m01;
          var m10 = bone.m10;
          var m11 = bone.m11;
          var vertices = this.vertices;
          for (var i = 0, n = vertices.length; i < n; i += 2) {
              var px = vertices[i];
              var py = vertices[i + 1];
              worldVertices[i] = px * m00 + py * m01 + x;
              worldVertices[i + 1] = px * m10 + py * m11 + y;
          }
      }
  };
  
  spine.AnimationStateData = function (skeletonData) {
      this.skeletonData = skeletonData;
      this.animationToMixTime = {};
  };
  spine.AnimationStateData.prototype = {
      defaultMix: 0,
      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 : this.defaultMix;
      }
  };
  
  spine.TrackEntry = function () {
  };
  spine.TrackEntry.prototype = {
      next: null, previous: null,
      animation: null,
      loop: false,
      delay: 0, time: 0, lastTime: -1, endTime: 0,
      timeScale: 1,
      mixTime: 0, mixDuration: 0,
      onStart: null, onEnd: null, onComplete: null, onEvent: null
  };
  
  spine.AnimationState = function (stateData) {
      this.data = stateData;
      this.tracks = [];
      this.events = [];
  };
  
  spine.AnimationState.prototype = {
      onStart: null,
      onEnd: null,
      onComplete: null,
      onEvent: null,
      timeScale: 1,
      update: function (delta) {
          delta *= this.timeScale;
          for (var i = 0; i < this.tracks.length; i++) {
              var current = this.tracks[i];
              if (!current) continue;
  
              var trackDelta = delta * current.timeScale;
              current.time += trackDelta;
              if (current.previous) {
                  current.previous.time += trackDelta;
                  current.mixTime += trackDelta;
              }
  
              var next = current.next;
              if (next) {
                  if (current.lastTime >= next.delay) this.setCurrent(i, next);
              } else {
                  // End non-looping animation when it reaches its end time and there is no next entry.
                  if (!current.loop && current.lastTime >= current.endTime) this.clearTrack(i);
              }
          }
      },
      apply: function (skeleton) {
          for (var i = 0; i < this.tracks.length; i++) {
              var current = this.tracks[i];
              if (!current) continue;
  
              this.events.length = 0;
  
              var time = current.time;
              var lastTime = current.lastTime;
              var endTime = current.endTime;
              var loop = current.loop;
              if (!loop && time > endTime) time = endTime;
  
              var previous = current.previous;
              if (!previous)
                  current.animation.apply(skeleton, current.lastTime, time, loop, this.events);
              else {
                  var previousTime = previous.time;
                  if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime;
                  previous.animation.apply(skeleton, previousTime, previousTime, previous.loop, null);
  
                  var alpha = current.mixTime / current.mixDuration;
                  if (alpha >= 1) {
                      alpha = 1;
                      current.previous = null;
                  }
                  current.animation.mix(skeleton, current.lastTime, time, loop, this.events, alpha);
              }
  
              for (var ii = 0, nn = this.events.length; ii < nn; ii++) {
                  var event = this.events[ii];
                  if (current.onEvent != null) current.onEvent(i, event);
                  if (this.onEvent != null) this.onEvent(i, event);
              }
  
              // Check if completed the animation or a loop iteration.
              if (loop ? (lastTime % endTime > time % endTime) : (lastTime < endTime && time >= endTime)) {
                  var count = Math.floor(time / endTime);
                  if (current.onComplete) current.onComplete(i, count);
                  if (this.onComplete) this.onComplete(i, count);
              }
  
              current.lastTime = current.time;
          }
      },
      clearTracks: function () {
          for (var i = 0, n = this.tracks.length; i < n; i++)
              this.clearTrack(i);
          this.tracks.length = 0;
      },
      clearTrack: function (trackIndex) {
          if (trackIndex >= this.tracks.length) return;
          var current = this.tracks[trackIndex];
          if (!current) return;
  
          if (current.onEnd != null) current.onEnd(trackIndex);
          if (this.onEnd != null) this.onEnd(trackIndex);
  
          this.tracks[trackIndex] = null;
      },
      _expandToIndex: function (index) {
          if (index < this.tracks.length) return this.tracks[index];
          while (index >= this.tracks.length)
              this.tracks.push(null);
          return null;
      },
      setCurrent: function (index, entry) {
          var current = this._expandToIndex(index);
          if (current) {
              var previous = current.previous;
              current.previous = null;
  
              if (current.onEnd != null) current.onEnd(index);
              if (this.onEnd != null) this.onEnd(index);
  
              entry.mixDuration = this.data.getMix(current.animation, entry.animation);
              if (entry.mixDuration > 0) {
                  entry.mixTime = 0;
                  // If a mix is in progress, mix from the closest animation.
                  if (previous && current.mixTime / current.mixDuration < 0.5)
                      entry.previous = previous;
                  else
                      entry.previous = current;
              }
          }
  
          this.tracks[index] = entry;
  
          if (entry.onStart != null) entry.onStart(index);
          if (this.onStart != null) this.onStart(index);
      },
      setAnimationByName: function (trackIndex, animationName, loop) {
          var animation = this.data.skeletonData.findAnimation(animationName);
          if (!animation) throw "Animation not found: " + animationName;
          return this.setAnimation(trackIndex, animation, loop);
      },
      /** Set the current animation. Any queued animations are cleared. */
      setAnimation: function (trackIndex, animation, loop) {
          var entry = new spine.TrackEntry();
          entry.animation = animation;
          entry.loop = loop;
          entry.endTime = animation.duration;
          this.setCurrent(trackIndex, entry);
          return entry;
      },
      addAnimationByName: function (trackIndex, animationName, loop, delay) {
          var animation = this.data.skeletonData.findAnimation(animationName);
          if (!animation) throw "Animation not found: " + animationName;
          return this.addAnimation(trackIndex, 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 (trackIndex, animation, loop, delay) {
          var entry = new spine.TrackEntry();
          entry.animation = animation;
          entry.loop = loop;
          entry.endTime = animation.duration;
  
          var last = this._expandToIndex(trackIndex);
          if (last) {
              while (last.next)
                  last = last.next;
              last.next = entry;
          } else
              this.tracks[trackIndex] = entry;
  
          if (delay <= 0) {
              if (last)
                  delay += last.endTime - this.data.getMix(last.animation, animation);
              else
                  delay = 0;
          }
          entry.delay = delay;
  
          return entry;
      },
      /** May be null. */
      getCurrent: function (trackIndex) {
          if (trackIndex >= this.tracks.length) return null;
          return this.tracks[trackIndex];
      }
  };
  
  spine.SkeletonJson = function (attachmentLoader) {
      this.attachmentLoader = attachmentLoader;
  };
  spine.SkeletonJson.prototype = {
      scale: 1,
      readSkeletonData: function (root, name) {
          var skeletonData = new spine.SkeletonData();
          skeletonData.name = name;
  
          // Skeleton.
          var skeletonMap = root["skeleton"];
          if (skeletonMap) {
              skeletonData.hash = skeletonMap["hash"];
              skeletonData.version = skeletonMap["spine"];
              skeletonData.width = skeletonMap["width"] || 0;
              skeletonData.height = skeletonMap["height"] || 0;
          }
  
          // 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.hasOwnProperty("scaleX") ? boneMap["scaleX"] : 1;
              boneData.scaleY = boneMap.hasOwnProperty("scaleY") ? boneMap["scaleY"] : 1;
              boneData.inheritScale = boneMap.hasOwnProperty("inheritScale") ? boneMap["inheritScale"] : true;
              boneData.inheritRotation = boneMap.hasOwnProperty("inheritRotation") ? boneMap["inheritRotation"] : true;
              skeletonData.bones.push(boneData);
          }
  
          // IK constraints.
          var ik = root["ik"];
          if (ik) {
              for (var i = 0, n = ik.length; i < n; i++) {
                  var ikMap = ik[i];
                  var ikConstraintData = new spine.IkConstraintData(ikMap["name"]);
  
                  var bones = ikMap["bones"];
                  for (var ii = 0, nn = bones.length; ii < nn; ii++) {
                      var bone = skeletonData.findBone(bones[ii]);
                      if (!bone) throw "IK bone not found: " + bones[ii];
                      ikConstraintData.bones.push(bone);
                  }
  
                  ikConstraintData.target = skeletonData.findBone(ikMap["target"]);
                  if (!ikConstraintData.target) throw "Target bone not found: " + ikMap["target"];
  
                  ikConstraintData.bendDirection = (!ikMap.hasOwnProperty("bendPositive") || ikMap["bendPositive"]) ? 1 : -1;
                  ikConstraintData.mix = ikMap.hasOwnProperty("mix") ? ikMap["mix"] : 1;
  
                  skeletonData.ikConstraints.push(ikConstraintData);
              }
          }
  
          // 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 = this.toColor(color, 0);
                  slotData.g = this.toColor(color, 1);
                  slotData.b = this.toColor(color, 2);
                  slotData.a = this.toColor(color, 3);
              }
  
              slotData.attachmentName = slotMap["attachment"];
              slotData.additiveBlending = slotMap["additive"] && slotMap["additive"] == "true";
  
              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) skin.addAttachment(slotIndex, attachmentName, attachment);
                  }
              }
              skeletonData.skins.push(skin);
              if (skin.name == "default") skeletonData.defaultSkin = skin;
          }
  
          // Events.
          var events = root["events"];
          for (var eventName in events) {
              if (!events.hasOwnProperty(eventName)) continue;
              var eventMap = events[eventName];
              var eventData = new spine.EventData(eventName);
              eventData.intValue = eventMap["int"] || 0;
              eventData.floatValue = eventMap["float"] || 0;
              eventData.stringValue = eventMap["string"] || null;
              skeletonData.events.push(eventData);
          }
  
          // 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"];
          var path = map["path"] || name;
          
          var scale = this.scale;
          if (type == spine.AttachmentType.region) {
              var region = this.attachmentLoader.newRegionAttachment(skin, name, path);
              if (!region) return null;
              region.path = path;
              region.x = (map["x"] || 0) * scale;
              region.y = (map["y"] || 0) * scale;
              region.scaleX = map.hasOwnProperty("scaleX") ? map["scaleX"] : 1;
              region.scaleY = map.hasOwnProperty("scaleY") ? map["scaleY"] : 1;
              region.rotation = map["rotation"] || 0;
              region.width = (map["width"] || 0) * scale;
              region.height = (map["height"] || 0) * scale;
  
              var color = map["color"];
              if (color) {
                  region.r = this.toColor(color, 0);
                  region.g = this.toColor(color, 1);
                  region.b = this.toColor(color, 2);
                  region.a = this.toColor(color, 3);
              }
  
              region.updateOffset();
              return region;
          } else if (type == spine.AttachmentType.mesh) {
              var mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
              if (!mesh) return null;
              mesh.path = path; 
              mesh.vertices = this.getFloatArray(map, "vertices", scale);
              mesh.triangles = this.getIntArray(map, "triangles");
              mesh.regionUVs = this.getFloatArray(map, "uvs", 1);
              mesh.updateUVs();
  
              color = map["color"];
              if (color) {
                  mesh.r = this.toColor(color, 0);
                  mesh.g = this.toColor(color, 1);
                  mesh.b = this.toColor(color, 2);
                  mesh.a = this.toColor(color, 3);
              }
  
              mesh.hullLength = (map["hull"] || 0) * 2;
              if (map["edges"]) mesh.edges = this.getIntArray(map, "edges");
              mesh.width = (map["width"] || 0) * scale;
              mesh.height = (map["height"] || 0) * scale;
              return mesh;
          } else if (type == spine.AttachmentType.skinnedmesh) {
              var mesh = this.attachmentLoader.newSkinnedMeshAttachment(skin, name, path);
              if (!mesh) return null;
              mesh.path = path;
  
              var uvs = this.getFloatArray(map, "uvs", 1);
              var vertices = this.getFloatArray(map, "vertices", 1);
              var weights = [];
              var bones = [];
              for (var i = 0, n = vertices.length; i < n; ) {
                  var boneCount = vertices[i++] | 0;
                  bones[bones.length] = boneCount;
                  for (var nn = i + boneCount * 4; i < nn; ) {
                      bones[bones.length] = vertices[i];
                      weights[weights.length] = vertices[i + 1] * scale;
                      weights[weights.length] = vertices[i + 2] * scale;
                      weights[weights.length] = vertices[i + 3];
                      i += 4;
                  }
              }
              mesh.bones = bones;
              mesh.weights = weights;
              mesh.triangles = this.getIntArray(map, "triangles");
              mesh.regionUVs = uvs;
              mesh.updateUVs();
              
              color = map["color"];
              if (color) {
                  mesh.r = this.toColor(color, 0);
                  mesh.g = this.toColor(color, 1);
                  mesh.b = this.toColor(color, 2);
                  mesh.a = this.toColor(color, 3);
              }
              
              mesh.hullLength = (map["hull"] || 0) * 2;
              if (map["edges"]) mesh.edges = this.getIntArray(map, "edges");
              mesh.width = (map["width"] || 0) * scale;
              mesh.height = (map["height"] || 0) * scale;
              return mesh;
          } else if (type == spine.AttachmentType.boundingbox) {
              var attachment = this.attachmentLoader.newBoundingBoxAttachment(skin, name);
              var vertices = map["vertices"];
              for (var i = 0, n = vertices.length; i < n; i++)
                  attachment.vertices.push(vertices[i] * scale);
              return attachment;
          }
          throw "Unknown attachment type: " + type;
      },
      readAnimation: function (name, map, skeletonData) {
          var timelines = [];
          var duration = 0;
  
          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 = this.toColor(color, 0);
                          var g = this.toColor(color, 1);
                          var b = this.toColor(color, 2);
                          var a = this.toColor(color, 3);
                          timeline.setFrame(frameIndex, valueMap["time"], r, g, b, a);
                          this.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);
                      duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);
  
                  } else
                      throw "Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")";
              }
          }
  
          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"]);
                          this.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);
                          this.readCurve(timeline, frameIndex, valueMap);
                          frameIndex++;
                      }
                      timelines.push(timeline);
                      duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]);
  
                  } else if (timelineName == "flipX" || timelineName == "flipY") {
                      var x = timelineName == "flipX";
                      var timeline = x ? new spine.FlipXTimeline(values.length) : new spine.FlipYTimeline(values.length);
                      timeline.boneIndex = boneIndex;
  
                      var field = x ? "x" : "y";
                      var frameIndex = 0;
                      for (var i = 0, n = values.length; i < n; i++) {
                          var valueMap = values[i];
                          timeline.setFrame(frameIndex, valueMap["time"], valueMap[field] || false);
                          frameIndex++;
                      }
                      timelines.push(timeline);
                      duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]);
                  } else
                      throw "Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")";
              }
          }
  
          var ikMap = map["ik"];
          for (var ikConstraintName in ikMap) {
              if (!ikMap.hasOwnProperty(ikConstraintName)) continue;
              var ikConstraint = skeletonData.findIkConstraint(ikConstraintName);
              var values = ikMap[ikConstraintName];
              var timeline = new spine.IkConstraintTimeline(values.length);
              timeline.ikConstraintIndex = skeletonData.ikConstraints.indexOf(ikConstraint);
              var frameIndex = 0;
              for (var i = 0, n = values.length; i < n; i++) {
                  var valueMap = values[i];
                  var mix = valueMap.hasOwnProperty("mix") ? valueMap["mix"] : 1;
                  var bendDirection = (!valueMap.hasOwnProperty("bendPositive") || valueMap["bendPositive"]) ? 1 : -1;
                  timeline.setFrame(frameIndex, valueMap["time"], mix, bendDirection);
                  this.readCurve(timeline, frameIndex, valueMap);
                  frameIndex++;
              }
              timelines.push(timeline);
              duration = Math.max(duration, timeline.frames[timeline.frameCount * 3 - 3]);
          }
  
          var ffd = map["ffd"];
          for (var skinName in ffd) {
              var skin = skeletonData.findSkin(skinName);
              var slotMap = ffd[skinName];
              for (slotName in slotMap) {
                  var slotIndex = skeletonData.findSlotIndex(slotName);
                  var meshMap = slotMap[slotName];
                  for (var meshName in meshMap) {
                      var values = meshMap[meshName];
                      var timeline = new spine.FfdTimeline(values.length);
                      var attachment = skin.getAttachment(slotIndex, meshName);
                      if (!attachment) throw "FFD attachment not found: " + meshName;
                      timeline.slotIndex = slotIndex;
                      timeline.attachment = attachment;
                      
                      var isMesh = attachment.type == spine.AttachmentType.mesh;
                      var vertexCount;
                      if (isMesh)
                          vertexCount = attachment.vertices.length;
                      else
                          vertexCount = attachment.weights.length / 3 * 2;
  
                      var frameIndex = 0;
                      for (var i = 0, n = values.length; i < n; i++) {
                          var valueMap = values[i];
                          var vertices;
                          if (!valueMap["vertices"]) {
                              if (isMesh)
                                  vertices = attachment.vertices;
                              else {
                                  vertices = [];
                                  vertices.length = vertexCount;
                              }
                          } else {
                              var verticesValue = valueMap["vertices"];
                              var vertices = [];
                              vertices.length = vertexCount;
                              var start = valueMap["offset"] || 0;
                              var nn = verticesValue.length;
                              if (this.scale == 1) {
                                  for (var ii = 0; ii < nn; ii++)
                                      vertices[ii + start] = verticesValue[ii];
                              } else {
                                  for (var ii = 0; ii < nn; ii++)
                                      vertices[ii + start] = verticesValue[ii] * this.scale;
                              }
                              if (isMesh) {
                                  var meshVertices = attachment.vertices;
                                  for (var ii = 0, nn = vertices.length; ii < nn; ii++)
                                      vertices[ii] += meshVertices[ii];
                              }
                          }
                          
                          timeline.setFrame(frameIndex, valueMap["time"], vertices);
                          this.readCurve(timeline, frameIndex, valueMap);
                          frameIndex++;
                      }
                      timelines[timelines.length] = timeline;
                      duration = Math.max(duration, timeline.frames[timeline.frameCount - 1]);
                  }
              }
          }
  
          var drawOrderValues = map["drawOrder"];
          if (!drawOrderValues) drawOrderValues = map["draworder"];
          if (drawOrderValues) {
              var timeline = new spine.DrawOrderTimeline(drawOrderValues.length);
              var slotCount = skeletonData.slots.length;
              var frameIndex = 0;
              for (var i = 0, n = drawOrderValues.length; i < n; i++) {
                  var drawOrderMap = drawOrderValues[i];
                  var drawOrder = null;
                  if (drawOrderMap["offsets"]) {
                      drawOrder = [];
                      drawOrder.length = slotCount;
                      for (var ii = slotCount - 1; ii >= 0; ii--)
                          drawOrder[ii] = -1;
                      var offsets = drawOrderMap["offsets"];
                      var unchanged = [];
                      unchanged.length = slotCount - offsets.length;
                      var originalIndex = 0, unchangedIndex = 0;
                      for (var ii = 0, nn = offsets.length; ii < nn; ii++) {
                          var offsetMap = offsets[ii];
                          var slotIndex = skeletonData.findSlotIndex(offsetMap["slot"]);
                          if (slotIndex == -1) throw "Slot not found: " + offsetMap["slot"];
                          // Collect unchanged items.
                          while (originalIndex != slotIndex)
                              unchanged[unchangedIndex++] = originalIndex++;
                          // Set changed items.
                          drawOrder[originalIndex + offsetMap["offset"]] = originalIndex++;
                      }
                      // Collect remaining unchanged items.
                      while (originalIndex < slotCount)
                          unchanged[unchangedIndex++] = originalIndex++;
                      // Fill in unchanged items.
                      for (var ii = slotCount - 1; ii >= 0; ii--)
                          if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
                  }
                  timeline.setFrame(frameIndex++, drawOrderMap["time"], drawOrder);
              }
              timelines.push(timeline);
              duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);
          }
  
          var events = map["events"];
          if (events) {
              var timeline = new spine.EventTimeline(events.length);
              var frameIndex = 0;
              for (var i = 0, n = events.length; i < n; i++) {
                  var eventMap = events[i];
                  var eventData = skeletonData.findEvent(eventMap["name"]);
                  if (!eventData) throw "Event not found: " + eventMap["name"];
                  var event = new spine.Event(eventData);
                  event.intValue = eventMap.hasOwnProperty("int") ? eventMap["int"] : eventData.intValue;
                  event.floatValue = eventMap.hasOwnProperty("float") ? eventMap["float"] : eventData.floatValue;
                  event.stringValue = eventMap.hasOwnProperty("string") ? eventMap["string"] : eventData.stringValue;
                  timeline.setFrame(frameIndex++, eventMap["time"], event);
              }
              timelines.push(timeline);
              duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);
          }
  
          skeletonData.animations.push(new spine.Animation(name, timelines, duration));
      },
      readCurve: function (timeline, frameIndex, valueMap) {
          var curve = valueMap["curve"];
          if (!curve) 
              timeline.curves.setLinear(frameIndex);
          else if (curve == "stepped")
              timeline.curves.setStepped(frameIndex);
          else if (curve instanceof Array)
              timeline.curves.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]);
      },
      toColor: function (hexString, colorIndex) {
          if (hexString.length != 8) throw "Color hexidecimal length must be 8, recieved: " + hexString;
          return parseInt(hexString.substring(colorIndex * 2, (colorIndex * 2) + 2), 16) / 255;
      },
      getFloatArray: function (map, name, scale) {
          var list = map[name];
          var values = new spine.Float32Array(list.length);
          var i = 0, n = list.length;
          if (scale == 1) {
              for (; i < n; i++)
                  values[i] = list[i];
          } else {
              for (; i < n; i++)
                  values[i] = list[i] * scale;
          }
          return values;
      },
      getIntArray: function (map, name) {
          var list = map[name];
          var values = new spine.Uint16Array(list.length);
          for (var i = 0, n = list.length; i < n; i++)
              values[i] = list[i] | 0;
          return values;
      }
  };
  
  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, (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)
              page = null;
          else if (!page) {
              page = new spine.AtlasPage();
              page.name = line;
  
              if (reader.readTuple(tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker.
                  page.width = parseInt(tuple[0]);
                  page.height = parseInt(tuple[1]);
                  reader.readTuple(tuple);
              }
              page.format = spine.Atlas.Format[tuple[0]];
  
              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);
  
              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 (1, 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) 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 = {
      newRegionAttachment: function (skin, name, path) {
          var region = this.atlas.findRegion(name);
          if (!region) throw "Region not found in atlas: " + path + " (region attachment: " + name + ")";
          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;
      },
      newMeshAttachment: function (skin, name, path) {
          var region = this.atlas.findRegion(name);
          if (!region) throw "Region not found in atlas: " + path + " (mesh attachment: " + name + ")";
          var attachment = new spine.MeshAttachment(name);
          attachment.rendererObject = region;
          attachment.regionU = region.u;
          attachment.regionV = region.v;
          attachment.regionU2 = region.u2;
          attachment.regionV2 = region.v2;
          attachment.regionRotate = 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;
      },
      newSkinnedMeshAttachment: function (skin, name, path) {
          var region = this.atlas.findRegion(name);
          if (!region) throw "Region not found in atlas: " + path + " (skinned mesh attachment: " + name + ")";
          var attachment = new spine.SkinnedMeshAttachment(name);
          attachment.rendererObject = region;
          attachment.regionU = region.u;
          attachment.regionV = region.v;
          attachment.regionU2 = region.u2;
          attachment.regionV2 = region.v2;
          attachment.regionRotate = 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;
      },
      newBoundingBoxAttachment: function (skin, name) {
          return new spine.BoundingBoxAttachment(name);
      }
  };
  
  spine.SkeletonBounds = function () {
      this.polygonPool = [];
      this.polygons = [];
      this.boundingBoxes = [];
  };
  spine.SkeletonBounds.prototype = {
      minX: 0, minY: 0, maxX: 0, maxY: 0,
      update: function (skeleton, updateAabb) {
          var slots = skeleton.slots;
          var slotCount = slots.length;
          var x = skeleton.x, y = skeleton.y;
          var boundingBoxes = this.boundingBoxes;
          var polygonPool = this.polygonPool;
          var polygons = this.polygons;
  
          boundingBoxes.length = 0;
          for (var i = 0, n = polygons.length; i < n; i++)
              polygonPool.push(polygons[i]);
          polygons.length = 0;
  
          for (var i = 0; i < slotCount; i++) {
              var slot = slots[i];
              var boundingBox = slot.attachment;
              if (boundingBox.type != spine.AttachmentType.boundingBox) continue;
              boundingBoxes.push(boundingBox);
  
              var poolCount = polygonPool.length, polygon;
              if (poolCount > 0) {
                  polygon = polygonPool[poolCount - 1];
                  polygonPool.splice(poolCount - 1, 1);
              } else
                  polygon = [];
              polygons.push(polygon);
  
              polygon.length = boundingBox.vertices.length;
              boundingBox.computeWorldVertices(x, y, slot.bone, polygon);
          }
  
          if (updateAabb) this.aabbCompute();
      },
      aabbCompute: function () {
          var polygons = this.polygons;
          var minX = Number.MAX_VALUE, minY = Number.MAX_VALUE, maxX = Number.MIN_VALUE, maxY = Number.MIN_VALUE;
          for (var i = 0, n = polygons.length; i < n; i++) {
              var vertices = polygons[i];
              for (var ii = 0, nn = vertices.length; ii < nn; ii += 2) {
                  var x = vertices[ii];
                  var y = vertices[ii + 1];
                  minX = Math.min(minX, x);
                  minY = Math.min(minY, y);
                  maxX = Math.max(maxX, x);
                  maxY = Math.max(maxY, y);
              }
          }
          this.minX = minX;
          this.minY = minY;
          this.maxX = maxX;
          this.maxY = maxY;
      },
      /** Returns true if the axis aligned bounding box contains the point. */
      aabbContainsPoint: function (x, y) {
          return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY;
      },
      /** Returns true if the axis aligned bounding box intersects the line segment. */
      aabbIntersectsSegment: function (x1, y1, x2, y2) {
          var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY;
          if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
              return false;
          var m = (y2 - y1) / (x2 - x1);
          var y = m * (minX - x1) + y1;
          if (y > minY && y < maxY) return true;
          y = m * (maxX - x1) + y1;
          if (y > minY && y < maxY) return true;
          var x = (minY - y1) / m + x1;
          if (x > minX && x < maxX) return true;
          x = (maxY - y1) / m + x1;
          if (x > minX && x < maxX) return true;
          return false;
      },
      /** Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. */
      aabbIntersectsSkeleton: function (bounds) {
          return this.minX < bounds.maxX && this.maxX > bounds.minX && this.minY < bounds.maxY && this.maxY > bounds.minY;
      },
      /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
       * efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */
      containsPoint: function (x, y) {
          var polygons = this.polygons;
          for (var i = 0, n = polygons.length; i < n; i++)
              if (this.polygonContainsPoint(polygons[i], x, y)) return this.boundingBoxes[i];
          return null;
      },
      /** Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually
       * more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns true. */
      intersectsSegment: function (x1, y1, x2, y2) {
          var polygons = this.polygons;
          for (var i = 0, n = polygons.length; i < n; i++)
              if (polygons[i].intersectsSegment(x1, y1, x2, y2)) return this.boundingBoxes[i];
          return null;
      },
      /** Returns true if the polygon contains the point. */
      polygonContainsPoint: function (polygon, x, y) {
          var nn = polygon.length;
          var prevIndex = nn - 2;
          var inside = false;
          for (var ii = 0; ii < nn; ii += 2) {
              var vertexY = polygon[ii + 1];
              var prevY = polygon[prevIndex + 1];
              if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
                  var vertexX = polygon[ii];
                  if (vertexX + (y - vertexY) / (prevY - vertexY) * (polygon[prevIndex] - vertexX) < x) inside = !inside;
              }
              prevIndex = ii;
          }
          return inside;
      },
      /** Returns true if the polygon contains the line segment. */
      intersectsSegment: function (polygon, x1, y1, x2, y2) {
          var nn = polygon.length;
          var width12 = x1 - x2, height12 = y1 - y2;
          var det1 = x1 * y2 - y1 * x2;
          var x3 = polygon[nn - 2], y3 = polygon[nn - 1];
          for (var ii = 0; ii < nn; ii += 2) {
              var x4 = polygon[ii], y4 = polygon[ii + 1];
              var det2 = x3 * y4 - y3 * x4;
              var width34 = x3 - x4, height34 = y3 - y4;
              var det3 = width12 * height34 - height12 * width34;
              var x = (det1 * width34 - width12 * det2) / det3;
              if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
                  var y = (det1 * height34 - height12 * det2) / det3;
                  if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true;
              }
              x3 = x4;
              y3 = y4;
          }
          return false;
      },
      getPolygon: function (attachment) {
          var index = this.boundingBoxes.indexOf(attachment);
          return index == -1 ? null : this.polygons[index];
      },
      getWidth: function () {
          return this.maxX - this.minX;
      },
      getHeight: function () {
          return this.maxY - this.minY;
      }
  };