浏览代码

Apply tsfmt to TypeScript sources, formatting pass on .ts files.

Mario Zechner 4 年之前
父节点
当前提交
61f63549d4
共有 67 个文件被更改,包括 13922 次插入14014 次删除
  1. 2 1
      formatters/README.md
  2. 5 0
      formatters/build.gradle
  3. 24 0
      formatters/tsfmt.json
  4. 38 38
      spine-ts/canvas/src/AssetManager.ts
  5. 42 42
      spine-ts/canvas/src/CanvasTexture.ts
  6. 309 309
      spine-ts/canvas/src/SkeletonRenderer.ts
  7. 2145 2145
      spine-ts/core/src/Animation.ts
  8. 1171 1171
      spine-ts/core/src/AnimationState.ts
  9. 76 76
      spine-ts/core/src/AnimationStateData.ts
  10. 284 284
      spine-ts/core/src/AssetManager.ts
  11. 4 4
      spine-ts/core/src/AtlasAttachmentLoader.ts
  12. 378 378
      spine-ts/core/src/Bone.ts
  13. 90 90
      spine-ts/core/src/BoneData.ts
  14. 1 1
      spine-ts/core/src/ConstraintData.ts
  15. 52 52
      spine-ts/core/src/Event.ts
  16. 47 47
      spine-ts/core/src/EventData.ts
  17. 294 294
      spine-ts/core/src/IkConstraint.ts
  18. 66 66
      spine-ts/core/src/IkConstraintData.ts
  19. 483 483
      spine-ts/core/src/PathConstraint.ts
  20. 84 84
      spine-ts/core/src/PathConstraintData.ts
  21. 638 638
      spine-ts/core/src/Skeleton.ts
  22. 299 299
      spine-ts/core/src/SkeletonBinary.ts
  23. 231 231
      spine-ts/core/src/SkeletonBounds.ts
  24. 1 1
      spine-ts/core/src/SkeletonClipping.ts
  25. 216 216
      spine-ts/core/src/SkeletonData.ts
  26. 965 965
      spine-ts/core/src/SkeletonJson.ts
  27. 202 202
      spine-ts/core/src/Skin.ts
  28. 118 118
      spine-ts/core/src/Slot.ts
  29. 69 69
      spine-ts/core/src/SlotData.ts
  30. 78 78
      spine-ts/core/src/Texture.ts
  31. 264 264
      spine-ts/core/src/TextureAtlas.ts
  32. 279 279
      spine-ts/core/src/TransformConstraint.ts
  33. 75 75
      spine-ts/core/src/TransformConstraintData.ts
  34. 1 1
      spine-ts/core/src/Triangulator.ts
  35. 42 42
      spine-ts/core/src/Updatable.ts
  36. 462 462
      spine-ts/core/src/Utils.ts
  37. 3 3
      spine-ts/core/src/VertexEffect.ts
  38. 159 159
      spine-ts/core/src/attachments/Attachment.ts
  39. 55 55
      spine-ts/core/src/attachments/AttachmentLoader.ts
  40. 50 50
      spine-ts/core/src/attachments/BoundingBoxAttachment.ts
  41. 198 198
      spine-ts/core/src/attachments/MeshAttachment.ts
  42. 66 66
      spine-ts/core/src/attachments/PathAttachment.ts
  43. 226 226
      spine-ts/core/src/attachments/RegionAttachment.ts
  44. 1 1
      spine-ts/core/src/polyfills.ts
  45. 3 3
      spine-ts/core/src/vertexeffects/JitterEffect.ts
  46. 3 3
      spine-ts/core/src/vertexeffects/SwirlEffect.ts
  47. 8 8
      spine-ts/player/src/Player.ts
  48. 7 7
      spine-ts/player/src/PlayerEditor.ts
  49. 38 38
      spine-ts/threejs/src/AssetManager.ts
  50. 125 125
      spine-ts/threejs/src/MeshBatcher.ts
  51. 322 322
      spine-ts/threejs/src/SkeletonMesh.ts
  52. 72 72
      spine-ts/threejs/src/ThreeJsTexture.ts
  53. 38 38
      spine-ts/webgl/src/AssetManager.ts
  54. 88 88
      spine-ts/webgl/src/Camera.ts
  55. 111 111
      spine-ts/webgl/src/GLTexture.ts
  56. 233 233
      spine-ts/webgl/src/Input.ts
  57. 0 122
      spine-ts/webgl/src/LoadingScreen.ts
  58. 340 340
      spine-ts/webgl/src/Matrix4.ts
  59. 208 208
      spine-ts/webgl/src/Mesh.ts
  60. 133 133
      spine-ts/webgl/src/PolygonBatcher.ts
  61. 506 506
      spine-ts/webgl/src/SceneRenderer.ts
  62. 293 293
      spine-ts/webgl/src/Shader.ts
  63. 349 349
      spine-ts/webgl/src/ShapeRenderer.ts
  64. 225 225
      spine-ts/webgl/src/SkeletonDebugRenderer.ts
  65. 299 299
      spine-ts/webgl/src/SkeletonRenderer.ts
  66. 121 121
      spine-ts/webgl/src/Vector3.ts
  67. 107 107
      spine-ts/webgl/src/WebGL.ts

+ 2 - 1
formatters/README.md

@@ -1,10 +1,11 @@
 # Formatters
 This folder contains formatter configuration files to be used with IDEs as well as the [spotless](https://github.com/diffplug/spotless/blob/main/plugin-gradle/README.md) formatter expressed in the Gradle project in this directory.
 
-You will need the following in your `PATH`:
+You will need the following on your `PATH`:
 
 - JDK 10+
 - clang-format 12 (i.e. `brew install clang-format`)
 - dotnet format (i.e. `dotnet tool install -g dotnet-format`, comes with dotnet 6 out of the box)
+- tsfmt, (i.e. `npm install -g typescript-formatter`)
 
 To run the formatter, invoke the `format.sh` script. This will shuffle around the Gradle config files, invoke spotless, then undo the config file shuffling. Invoking `./gradlew spotlessApply` from the `formatters/` directory will not work.

+ 5 - 0
formatters/build.gradle

@@ -28,4 +28,9 @@ spotless {
                'spine-ue4/**/*.h'
         clangFormat('12.0.1').style('file')
     }
+
+    typescript {
+        target 'spine-ts/**/*.ts'
+        tsfmt('7.2.2').tsfmtFile('formatters/tsfmt.json')
+    }
 }

+ 24 - 0
formatters/tsfmt.json

@@ -0,0 +1,24 @@
+{
+	"baseIndentSize": 0,
+	"indentSize": 4,
+	"tabSize": 4,
+	"indentStyle": 2,
+	"newLineCharacter": "\r\n",
+	"convertTabsToSpaces": false,
+	"insertSpaceAfterCommaDelimiter": true,
+	"insertSpaceAfterSemicolonInForStatements": true,
+	"insertSpaceBeforeAndAfterBinaryOperators": true,
+	"insertSpaceAfterConstructor": true,
+	"insertSpaceAfterKeywordsInControlFlowStatements": true,
+	"insertSpaceAfterFunctionKeywordForAnonymousFunctions": true,
+	"insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false,
+	"insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false,
+	"insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true,
+	"insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false,
+	"insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false,
+	"insertSpaceAfterTypeAssertion": false,
+	"insertSpaceBeforeFunctionParenthesis": true,
+	"insertSpaceBeforeTypeAnnotation": false,
+	"placeOpenBraceOnNewLineForFunctions": false,
+	"placeOpenBraceOnNewLineForControlBlocks": false
+}

+ 38 - 38
spine-ts/canvas/src/AssetManager.ts

@@ -1,38 +1,38 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-/// <reference path="../../core/src/AssetManager.ts"/>
-
-module spine.canvas {
-	export class AssetManager extends spine.AssetManager {
-		constructor (pathPrefix: string = "", downloader: Downloader = null) {
-			super((image: HTMLImageElement) => { return new spine.canvas.CanvasTexture(image); }, pathPrefix, downloader);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+/// <reference path="../../core/src/AssetManager.ts"/>
+
+module spine.canvas {
+	export class AssetManager extends spine.AssetManager {
+		constructor (pathPrefix: string = "", downloader: Downloader = null) {
+			super((image: HTMLImageElement) => { return new spine.canvas.CanvasTexture(image); }, pathPrefix, downloader);
+		}
+	}
+}

+ 42 - 42
spine-ts/canvas/src/CanvasTexture.ts

@@ -1,42 +1,42 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-/// <reference path="../../core/src/Texture.ts"/>
-
-module spine.canvas {
-	export class CanvasTexture extends Texture {
-		constructor (image: HTMLImageElement) {
-			super(image);
-		}
-
-		setFilters (minFilter: TextureFilter, magFilter: TextureFilter) { }
-		setWraps (uWrap: TextureWrap, vWrap: TextureWrap) { }
-		dispose () { }
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+/// <reference path="../../core/src/Texture.ts"/>
+
+module spine.canvas {
+	export class CanvasTexture extends Texture {
+		constructor (image: HTMLImageElement) {
+			super(image);
+		}
+
+		setFilters (minFilter: TextureFilter, magFilter: TextureFilter) { }
+		setWraps (uWrap: TextureWrap, vWrap: TextureWrap) { }
+		dispose () { }
+	}
+}

+ 309 - 309
spine-ts/canvas/src/SkeletonRenderer.ts

@@ -1,309 +1,309 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.canvas {
-	export class SkeletonRenderer {
-		static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
-		static VERTEX_SIZE = 2 + 2 + 4;
-
-		private ctx: CanvasRenderingContext2D;
-
-		public triangleRendering = false;
-		public debugRendering = false;
-		private vertices = Utils.newFloatArray(8 * 1024);
-		private tempColor = new Color();
-
-		constructor (context: CanvasRenderingContext2D) {
-			this.ctx = context;
-		}
-
-		draw (skeleton: Skeleton) {
-			if (this.triangleRendering) this.drawTriangles(skeleton);
-			else this.drawImages(skeleton);
-		}
-
-		private drawImages (skeleton: Skeleton) {
-			let ctx = this.ctx;
-			let color = this.tempColor;
-			let skeletonColor = skeleton.color;
-			let drawOrder = skeleton.drawOrder;
-
-			if (this.debugRendering) ctx.strokeStyle = "green";
-
-			for (let i = 0, n = drawOrder.length; i < n; i++) {
-				let slot = drawOrder[i];
-				let bone = slot.bone;
-				if (!bone.active) continue;
-
-				let attachment = slot.getAttachment();
-				if (!(attachment instanceof RegionAttachment)) continue;
-				let region: TextureAtlasRegion = <TextureAtlasRegion>attachment.region;
-				let image: HTMLImageElement = (<CanvasTexture>region.page.texture).getImage() as HTMLImageElement;
-
-				let slotColor = slot.color;
-				let regionColor = attachment.color;
-				color.set(skeletonColor.r * slotColor.r * regionColor.r,
-					skeletonColor.g * slotColor.g * regionColor.g,
-					skeletonColor.b * slotColor.b * regionColor.b,
-					skeletonColor.a * slotColor.a * regionColor.a);
-
-				ctx.save();
-				ctx.transform(bone.a, bone.c, bone.b, bone.d, bone.worldX, bone.worldY);
-				ctx.translate(attachment.offset[0], attachment.offset[1]);
-				ctx.rotate(attachment.rotation * Math.PI / 180);
-
-				let atlasScale = attachment.width / region.originalWidth;
-				ctx.scale(atlasScale * attachment.scaleX, atlasScale * attachment.scaleY);
-
-				let w = region.width, h = region.height;
-				ctx.translate(w / 2, h / 2);
-				if (attachment.region.degrees == 90) {
-					let t = w;
-					w = h;
-					h = t;
-					ctx.rotate(-Math.PI / 2);
-				}
-				ctx.scale(1, -1);
-				ctx.translate(-w / 2, -h / 2);
-
-				if (color.r != 1 || color.g != 1 || color.b != 1 || color.a != 1) {
-					ctx.globalAlpha = color.a;
-					// experimental tinting via compositing, doesn't work
-					// ctx.globalCompositeOperation = "source-atop";
-					// ctx.fillStyle = "rgba(" + (color.r * 255 | 0) + ", " + (color.g * 255 | 0)  + ", " + (color.b * 255 | 0) + ", " + color.a + ")";
-					// ctx.fillRect(0, 0, w, h);
-				}
-				ctx.drawImage(image, region.x, region.y, w, h, 0, 0, w, h);
-				if (this.debugRendering) ctx.strokeRect(0, 0, w, h);
-				ctx.restore();
-			}
-		}
-
-		private drawTriangles (skeleton: Skeleton) {
-			let ctx = this.ctx;
-			let color = this.tempColor;
-			let skeletonColor = skeleton.color;
-			let drawOrder = skeleton.drawOrder;
-
-			let blendMode: BlendMode = null;
-			let vertices: ArrayLike<number> = this.vertices;
-			let triangles: Array<number> = null;
-
-			for (let i = 0, n = drawOrder.length; i < n; i++) {
-				let slot = drawOrder[i];
-				let attachment = slot.getAttachment();
-
-				let texture: HTMLImageElement;
-				let region: TextureAtlasRegion;
-				if (attachment instanceof RegionAttachment) {
-					let regionAttachment = <RegionAttachment>attachment;
-					vertices = this.computeRegionVertices(slot, regionAttachment, false);
-					triangles = SkeletonRenderer.QUAD_TRIANGLES;
-					region = <TextureAtlasRegion>regionAttachment.region;
-					texture = (<CanvasTexture>region.page.texture).getImage() as HTMLImageElement;
-				} else if (attachment instanceof MeshAttachment) {
-					let mesh = <MeshAttachment>attachment;
-					vertices = this.computeMeshVertices(slot, mesh, false);
-					triangles = mesh.triangles;
-					texture = (<TextureAtlasRegion>mesh.region.renderObject).page.texture.getImage() as HTMLImageElement;
-				} else
-					continue;
-
-				if (texture) {
-					if (slot.data.blendMode != blendMode) blendMode = slot.data.blendMode;
-
-					let slotColor = slot.color;
-					let attachmentColor = attachment.color;
-					color.set(skeletonColor.r * slotColor.r * attachmentColor.r,
-						skeletonColor.g * slotColor.g * attachmentColor.g,
-						skeletonColor.b * slotColor.b * attachmentColor.b,
-						skeletonColor.a * slotColor.a * attachmentColor.a);
-
-					if (color.r != 1 || color.g != 1 || color.b != 1 || color.a != 1) {
-						ctx.globalAlpha = color.a;
-						// experimental tinting via compositing, doesn't work
-						// ctx.globalCompositeOperation = "source-atop";
-						// ctx.fillStyle = "rgba(" + (color.r * 255 | 0) + ", " + (color.g * 255 | 0) + ", " + (color.b * 255 | 0) + ", " + color.a + ")";
-						// ctx.fillRect(0, 0, w, h);
-					}
-
-					for (var j = 0; j < triangles.length; j += 3) {
-						let t1 = triangles[j] * 8, t2 = triangles[j + 1] * 8, t3 = triangles[j + 2] * 8;
-
-						let x0 = vertices[t1], y0 = vertices[t1 + 1], u0 = vertices[t1 + 6], v0 = vertices[t1 + 7];
-						let x1 = vertices[t2], y1 = vertices[t2 + 1], u1 = vertices[t2 + 6], v1 = vertices[t2 + 7];
-						let x2 = vertices[t3], y2 = vertices[t3 + 1], u2 = vertices[t3 + 6], v2 = vertices[t3 + 7];
-
-						this.drawTriangle(texture, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2);
-
-						if (this.debugRendering) {
-							ctx.strokeStyle = "green";
-							ctx.beginPath();
-							ctx.moveTo(x0, y0);
-							ctx.lineTo(x1, y1);
-							ctx.lineTo(x2, y2);
-							ctx.lineTo(x0, y0);
-							ctx.stroke();
-						}
-					}
-				}
-			}
-
-			this.ctx.globalAlpha = 1;
-		}
-
-		// Adapted from http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
-		// Apache 2 licensed
-		private drawTriangle(img: HTMLImageElement, x0: number, y0: number, u0: number, v0: number,
-						x1: number, y1: number, u1: number, v1: number,
-						x2: number, y2: number, u2: number, v2: number) {
-			let ctx = this.ctx;
-
-			u0 *= img.width;
-			v0 *= img.height;
-			u1 *= img.width;
-			v1 *= img.height;
-			u2 *= img.width;
-			v2 *= img.height;
-
-			ctx.beginPath();
-			ctx.moveTo(x0, y0);
-			ctx.lineTo(x1, y1);
-			ctx.lineTo(x2, y2);
-			ctx.closePath();
-
-			x1 -= x0;
-			y1 -= y0;
-			x2 -= x0;
-			y2 -= y0;
-
-			u1 -= u0;
-			v1 -= v0;
-			u2 -= u0;
-			v2 -= v0;
-
-			var det = 1 / (u1 * v2 - u2 * v1),
-
-			// linear transformation
-			a = (v2 * x1 - v1 * x2) * det,
-			b = (v2 * y1 - v1 * y2) * det,
-			c = (u1 * x2 - u2 * x1) * det,
-			d = (u1 * y2 - u2 * y1) * det,
-
-			// translation
-			e = x0 - a * u0 - c * v0,
-			f = y0 - b * u0 - d * v0;
-
-			ctx.save();
-			ctx.transform(a, b, c, d, e, f);
-			ctx.clip();
-			ctx.drawImage(img, 0, 0);
-			ctx.restore();
-		}
-
-		private computeRegionVertices(slot: Slot, region: RegionAttachment, pma: boolean) {
-			let skeletonColor = slot.bone.skeleton.color;
-			let slotColor = slot.color;
-			let regionColor = region.color;
-			let alpha = skeletonColor.a * slotColor.a * regionColor.a;
-			let multiplier = pma ? alpha : 1;
-			let color = this.tempColor;
-			color.set(skeletonColor.r * slotColor.r * regionColor.r * multiplier,
-				skeletonColor.g * slotColor.g * regionColor.g * multiplier,
-				skeletonColor.b * slotColor.b * regionColor.b * multiplier,
-				alpha);
-
-			region.computeWorldVertices(slot.bone, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE);
-
-			let vertices = this.vertices;
-			let uvs = region.uvs;
-
-			vertices[RegionAttachment.C1R] = color.r;
-			vertices[RegionAttachment.C1G] = color.g;
-			vertices[RegionAttachment.C1B] = color.b;
-			vertices[RegionAttachment.C1A] = color.a;
-			vertices[RegionAttachment.U1] = uvs[0];
-			vertices[RegionAttachment.V1] = uvs[1];
-
-			vertices[RegionAttachment.C2R] = color.r;
-			vertices[RegionAttachment.C2G] = color.g;
-			vertices[RegionAttachment.C2B] = color.b;
-			vertices[RegionAttachment.C2A] = color.a;
-			vertices[RegionAttachment.U2] = uvs[2];
-			vertices[RegionAttachment.V2] = uvs[3];
-
-			vertices[RegionAttachment.C3R] = color.r;
-			vertices[RegionAttachment.C3G] = color.g;
-			vertices[RegionAttachment.C3B] = color.b;
-			vertices[RegionAttachment.C3A] = color.a;
-			vertices[RegionAttachment.U3] = uvs[4];
-			vertices[RegionAttachment.V3] = uvs[5];
-
-			vertices[RegionAttachment.C4R] = color.r;
-			vertices[RegionAttachment.C4G] = color.g;
-			vertices[RegionAttachment.C4B] = color.b;
-			vertices[RegionAttachment.C4A] = color.a;
-			vertices[RegionAttachment.U4] = uvs[6];
-			vertices[RegionAttachment.V4] = uvs[7];
-
-			return vertices;
-		}
-
-		private computeMeshVertices(slot: Slot, mesh: MeshAttachment, pma: boolean) {
-			let skeletonColor = slot.bone.skeleton.color;
-			let slotColor = slot.color;
-			let regionColor = mesh.color;
-			let alpha = skeletonColor.a * slotColor.a * regionColor.a;
-			let multiplier = pma ? alpha : 1;
-			let color = this.tempColor;
-			color.set(skeletonColor.r * slotColor.r * regionColor.r * multiplier,
-				skeletonColor.g * slotColor.g * regionColor.g * multiplier,
-				skeletonColor.b * slotColor.b * regionColor.b * multiplier,
-				alpha);
-
-			let vertexCount = mesh.worldVerticesLength / 2;
-			let vertices = this.vertices;
-			if (vertices.length < mesh.worldVerticesLength) this.vertices = vertices = Utils.newFloatArray(mesh.worldVerticesLength);
-			mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, SkeletonRenderer.VERTEX_SIZE);
-
-			let uvs = mesh.uvs;
-			for (let i = 0, u = 0, v = 2; i < vertexCount; i++) {
-				vertices[v++] = color.r;
-				vertices[v++] = color.g;
-				vertices[v++] = color.b;
-				vertices[v++] = color.a;
-				vertices[v++] = uvs[u++];
-				vertices[v++] = uvs[u++];
-				v += 2;
-			}
-
-			return vertices;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.canvas {
+	export class SkeletonRenderer {
+		static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
+		static VERTEX_SIZE = 2 + 2 + 4;
+
+		private ctx: CanvasRenderingContext2D;
+
+		public triangleRendering = false;
+		public debugRendering = false;
+		private vertices = Utils.newFloatArray(8 * 1024);
+		private tempColor = new Color();
+
+		constructor (context: CanvasRenderingContext2D) {
+			this.ctx = context;
+		}
+
+		draw (skeleton: Skeleton) {
+			if (this.triangleRendering) this.drawTriangles(skeleton);
+			else this.drawImages(skeleton);
+		}
+
+		private drawImages (skeleton: Skeleton) {
+			let ctx = this.ctx;
+			let color = this.tempColor;
+			let skeletonColor = skeleton.color;
+			let drawOrder = skeleton.drawOrder;
+
+			if (this.debugRendering) ctx.strokeStyle = "green";
+
+			for (let i = 0, n = drawOrder.length; i < n; i++) {
+				let slot = drawOrder[i];
+				let bone = slot.bone;
+				if (!bone.active) continue;
+
+				let attachment = slot.getAttachment();
+				if (!(attachment instanceof RegionAttachment)) continue;
+				let region: TextureAtlasRegion = <TextureAtlasRegion>attachment.region;
+				let image: HTMLImageElement = (<CanvasTexture>region.page.texture).getImage() as HTMLImageElement;
+
+				let slotColor = slot.color;
+				let regionColor = attachment.color;
+				color.set(skeletonColor.r * slotColor.r * regionColor.r,
+					skeletonColor.g * slotColor.g * regionColor.g,
+					skeletonColor.b * slotColor.b * regionColor.b,
+					skeletonColor.a * slotColor.a * regionColor.a);
+
+				ctx.save();
+				ctx.transform(bone.a, bone.c, bone.b, bone.d, bone.worldX, bone.worldY);
+				ctx.translate(attachment.offset[0], attachment.offset[1]);
+				ctx.rotate(attachment.rotation * Math.PI / 180);
+
+				let atlasScale = attachment.width / region.originalWidth;
+				ctx.scale(atlasScale * attachment.scaleX, atlasScale * attachment.scaleY);
+
+				let w = region.width, h = region.height;
+				ctx.translate(w / 2, h / 2);
+				if (attachment.region.degrees == 90) {
+					let t = w;
+					w = h;
+					h = t;
+					ctx.rotate(-Math.PI / 2);
+				}
+				ctx.scale(1, -1);
+				ctx.translate(-w / 2, -h / 2);
+
+				if (color.r != 1 || color.g != 1 || color.b != 1 || color.a != 1) {
+					ctx.globalAlpha = color.a;
+					// experimental tinting via compositing, doesn't work
+					// ctx.globalCompositeOperation = "source-atop";
+					// ctx.fillStyle = "rgba(" + (color.r * 255 | 0) + ", " + (color.g * 255 | 0)  + ", " + (color.b * 255 | 0) + ", " + color.a + ")";
+					// ctx.fillRect(0, 0, w, h);
+				}
+				ctx.drawImage(image, region.x, region.y, w, h, 0, 0, w, h);
+				if (this.debugRendering) ctx.strokeRect(0, 0, w, h);
+				ctx.restore();
+			}
+		}
+
+		private drawTriangles (skeleton: Skeleton) {
+			let ctx = this.ctx;
+			let color = this.tempColor;
+			let skeletonColor = skeleton.color;
+			let drawOrder = skeleton.drawOrder;
+
+			let blendMode: BlendMode = null;
+			let vertices: ArrayLike<number> = this.vertices;
+			let triangles: Array<number> = null;
+
+			for (let i = 0, n = drawOrder.length; i < n; i++) {
+				let slot = drawOrder[i];
+				let attachment = slot.getAttachment();
+
+				let texture: HTMLImageElement;
+				let region: TextureAtlasRegion;
+				if (attachment instanceof RegionAttachment) {
+					let regionAttachment = <RegionAttachment>attachment;
+					vertices = this.computeRegionVertices(slot, regionAttachment, false);
+					triangles = SkeletonRenderer.QUAD_TRIANGLES;
+					region = <TextureAtlasRegion>regionAttachment.region;
+					texture = (<CanvasTexture>region.page.texture).getImage() as HTMLImageElement;
+				} else if (attachment instanceof MeshAttachment) {
+					let mesh = <MeshAttachment>attachment;
+					vertices = this.computeMeshVertices(slot, mesh, false);
+					triangles = mesh.triangles;
+					texture = (<TextureAtlasRegion>mesh.region.renderObject).page.texture.getImage() as HTMLImageElement;
+				} else
+					continue;
+
+				if (texture) {
+					if (slot.data.blendMode != blendMode) blendMode = slot.data.blendMode;
+
+					let slotColor = slot.color;
+					let attachmentColor = attachment.color;
+					color.set(skeletonColor.r * slotColor.r * attachmentColor.r,
+						skeletonColor.g * slotColor.g * attachmentColor.g,
+						skeletonColor.b * slotColor.b * attachmentColor.b,
+						skeletonColor.a * slotColor.a * attachmentColor.a);
+
+					if (color.r != 1 || color.g != 1 || color.b != 1 || color.a != 1) {
+						ctx.globalAlpha = color.a;
+						// experimental tinting via compositing, doesn't work
+						// ctx.globalCompositeOperation = "source-atop";
+						// ctx.fillStyle = "rgba(" + (color.r * 255 | 0) + ", " + (color.g * 255 | 0) + ", " + (color.b * 255 | 0) + ", " + color.a + ")";
+						// ctx.fillRect(0, 0, w, h);
+					}
+
+					for (var j = 0; j < triangles.length; j += 3) {
+						let t1 = triangles[j] * 8, t2 = triangles[j + 1] * 8, t3 = triangles[j + 2] * 8;
+
+						let x0 = vertices[t1], y0 = vertices[t1 + 1], u0 = vertices[t1 + 6], v0 = vertices[t1 + 7];
+						let x1 = vertices[t2], y1 = vertices[t2 + 1], u1 = vertices[t2 + 6], v1 = vertices[t2 + 7];
+						let x2 = vertices[t3], y2 = vertices[t3 + 1], u2 = vertices[t3 + 6], v2 = vertices[t3 + 7];
+
+						this.drawTriangle(texture, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2);
+
+						if (this.debugRendering) {
+							ctx.strokeStyle = "green";
+							ctx.beginPath();
+							ctx.moveTo(x0, y0);
+							ctx.lineTo(x1, y1);
+							ctx.lineTo(x2, y2);
+							ctx.lineTo(x0, y0);
+							ctx.stroke();
+						}
+					}
+				}
+			}
+
+			this.ctx.globalAlpha = 1;
+		}
+
+		// Adapted from http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
+		// Apache 2 licensed
+		private drawTriangle (img: HTMLImageElement, x0: number, y0: number, u0: number, v0: number,
+			x1: number, y1: number, u1: number, v1: number,
+			x2: number, y2: number, u2: number, v2: number) {
+			let ctx = this.ctx;
+
+			u0 *= img.width;
+			v0 *= img.height;
+			u1 *= img.width;
+			v1 *= img.height;
+			u2 *= img.width;
+			v2 *= img.height;
+
+			ctx.beginPath();
+			ctx.moveTo(x0, y0);
+			ctx.lineTo(x1, y1);
+			ctx.lineTo(x2, y2);
+			ctx.closePath();
+
+			x1 -= x0;
+			y1 -= y0;
+			x2 -= x0;
+			y2 -= y0;
+
+			u1 -= u0;
+			v1 -= v0;
+			u2 -= u0;
+			v2 -= v0;
+
+			var det = 1 / (u1 * v2 - u2 * v1),
+
+				// linear transformation
+				a = (v2 * x1 - v1 * x2) * det,
+				b = (v2 * y1 - v1 * y2) * det,
+				c = (u1 * x2 - u2 * x1) * det,
+				d = (u1 * y2 - u2 * y1) * det,
+
+				// translation
+				e = x0 - a * u0 - c * v0,
+				f = y0 - b * u0 - d * v0;
+
+			ctx.save();
+			ctx.transform(a, b, c, d, e, f);
+			ctx.clip();
+			ctx.drawImage(img, 0, 0);
+			ctx.restore();
+		}
+
+		private computeRegionVertices (slot: Slot, region: RegionAttachment, pma: boolean) {
+			let skeletonColor = slot.bone.skeleton.color;
+			let slotColor = slot.color;
+			let regionColor = region.color;
+			let alpha = skeletonColor.a * slotColor.a * regionColor.a;
+			let multiplier = pma ? alpha : 1;
+			let color = this.tempColor;
+			color.set(skeletonColor.r * slotColor.r * regionColor.r * multiplier,
+				skeletonColor.g * slotColor.g * regionColor.g * multiplier,
+				skeletonColor.b * slotColor.b * regionColor.b * multiplier,
+				alpha);
+
+			region.computeWorldVertices(slot.bone, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE);
+
+			let vertices = this.vertices;
+			let uvs = region.uvs;
+
+			vertices[RegionAttachment.C1R] = color.r;
+			vertices[RegionAttachment.C1G] = color.g;
+			vertices[RegionAttachment.C1B] = color.b;
+			vertices[RegionAttachment.C1A] = color.a;
+			vertices[RegionAttachment.U1] = uvs[0];
+			vertices[RegionAttachment.V1] = uvs[1];
+
+			vertices[RegionAttachment.C2R] = color.r;
+			vertices[RegionAttachment.C2G] = color.g;
+			vertices[RegionAttachment.C2B] = color.b;
+			vertices[RegionAttachment.C2A] = color.a;
+			vertices[RegionAttachment.U2] = uvs[2];
+			vertices[RegionAttachment.V2] = uvs[3];
+
+			vertices[RegionAttachment.C3R] = color.r;
+			vertices[RegionAttachment.C3G] = color.g;
+			vertices[RegionAttachment.C3B] = color.b;
+			vertices[RegionAttachment.C3A] = color.a;
+			vertices[RegionAttachment.U3] = uvs[4];
+			vertices[RegionAttachment.V3] = uvs[5];
+
+			vertices[RegionAttachment.C4R] = color.r;
+			vertices[RegionAttachment.C4G] = color.g;
+			vertices[RegionAttachment.C4B] = color.b;
+			vertices[RegionAttachment.C4A] = color.a;
+			vertices[RegionAttachment.U4] = uvs[6];
+			vertices[RegionAttachment.V4] = uvs[7];
+
+			return vertices;
+		}
+
+		private computeMeshVertices (slot: Slot, mesh: MeshAttachment, pma: boolean) {
+			let skeletonColor = slot.bone.skeleton.color;
+			let slotColor = slot.color;
+			let regionColor = mesh.color;
+			let alpha = skeletonColor.a * slotColor.a * regionColor.a;
+			let multiplier = pma ? alpha : 1;
+			let color = this.tempColor;
+			color.set(skeletonColor.r * slotColor.r * regionColor.r * multiplier,
+				skeletonColor.g * slotColor.g * regionColor.g * multiplier,
+				skeletonColor.b * slotColor.b * regionColor.b * multiplier,
+				alpha);
+
+			let vertexCount = mesh.worldVerticesLength / 2;
+			let vertices = this.vertices;
+			if (vertices.length < mesh.worldVerticesLength) this.vertices = vertices = Utils.newFloatArray(mesh.worldVerticesLength);
+			mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, SkeletonRenderer.VERTEX_SIZE);
+
+			let uvs = mesh.uvs;
+			for (let i = 0, u = 0, v = 2; i < vertexCount; i++) {
+				vertices[v++] = color.r;
+				vertices[v++] = color.g;
+				vertices[v++] = color.b;
+				vertices[v++] = color.a;
+				vertices[v++] = uvs[u++];
+				vertices[v++] = uvs[u++];
+				v += 2;
+			}
+
+			return vertices;
+		}
+	}
+}

+ 2145 - 2145
spine-ts/core/src/Animation.ts

@@ -1,2145 +1,2145 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** A simple container for a list of timelines and a name. */
-	export class Animation {
-		/** The animation's name, which is unique across all animations in the skeleton. */
-		name: string;
-		timelines: Array<Timeline>;
-		timelineIds: StringSet;
-
-		/** The duration of the animation in seconds, which is the highest time of all keys in the timeline. */
-		duration: number;
-
-		constructor (name: string, timelines: Array<Timeline>, duration: number) {
-			if (!name) throw new Error("name cannot be null.");
-			this.name = name;
-			this.setTimelines(timelines);
-			this.duration = duration;
-		}
-
-		setTimelines(timelines: Array<Timeline>) {
-			if (!timelines) throw new Error("timelines cannot be null.");
-			this.timelines = timelines;
-			this.timelineIds = new StringSet();
-			for (var i = 0; i < timelines.length; i++)
-				this.timelineIds.addAll(timelines[i].getPropertyIds());
-		}
-
-		hasTimeline(ids: string[]) : boolean {
-			for (let i = 0; i < ids.length; i++)
-				if (this.timelineIds.contains(ids[i])) return true;
-			return false;
-		}
-
-		/** Applies all the animation's timelines to the specified skeleton.
-		 *
-		 * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}.
-		 * @param loop If true, the animation repeats after {@link #getDuration()}.
-		 * @param events May be null to ignore fired events. */
-		apply (skeleton: Skeleton, lastTime: number, time: number, loop: boolean, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			if (!skeleton) throw new Error("skeleton cannot be null.");
-
-			if (loop && this.duration != 0) {
-				time %= this.duration;
-				if (lastTime > 0) lastTime %= this.duration;
-			}
-
-			let timelines = this.timelines;
-			for (let i = 0, n = timelines.length; i < n; i++)
-				timelines[i].apply(skeleton, lastTime, time, events, alpha, blend, direction);
-		}
-	}
-
-	/** Controls how a timeline value is mixed with the setup pose value or current pose value when a timeline's `alpha`
-	 * < 1.
-	 *
-	 * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */
-	export enum MixBlend {
-		/** Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup
-		 * value is set. */
-		setup,
-		/** Transitions from the current value to the timeline value. Before the first key, transitions from the current value to
-		 * the setup value. Timelines which perform instant transitions, such as {@link DrawOrderTimeline} or
-		 * {@link AttachmentTimeline}, use the setup value before the first key.
-		 *
-		 * `first` is intended for the first animations applied, not for animations layered on top of those. */
-		first,
-		/** Transitions from the current value to the timeline value. No change is made before the first key (the current value is
-		 * kept until the first key).
-		 *
-		 * `replace` is intended for animations layered on top of others, not for the first animations applied. */
-		replace,
-		/** Transitions from the current value to the current value plus the timeline value. No change is made before the first key
-		 * (the current value is kept until the first key).
-		 *
-		 * `add` is intended for animations layered on top of others, not for the first animations applied. Properties
-		 * keyed by additive animations must be set manually or by another animation before applying the additive animations, else
-		 * the property values will increase continually. */
-		add
-	}
-
-	/** Indicates whether a timeline's `alpha` is mixing out over time toward 0 (the setup or current pose value) or
-	 * mixing in toward 1 (the timeline's value).
-	 *
-	 * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */
-	export enum MixDirection {
-		mixIn, mixOut
-	}
-
-	const Property = {
-		rotate: 0,
-		x: 1,
-		y: 2,
-		scaleX: 3,
-		scaleY: 4,
-		shearX: 5,
-		shearY: 6,
-
-		rgb: 7,
-		alpha: 8,
-		rgb2: 9,
-
-		attachment: 10,
-		deform: 11,
-
-		event: 12,
-		drawOrder: 13,
-
-		ikConstraint: 14,
-		transformConstraint: 15,
-
-		pathConstraintPosition: 16,
-		pathConstraintSpacing: 17,
-		pathConstraintMix: 18
-	}
-
-	/** The interface for all timelines. */
-	export abstract class Timeline {
-		propertyIds: string[];
-		frames: ArrayLike<number>;
-
-		constructor(frameCount: number, propertyIds: string[]) {
-			this.propertyIds = propertyIds;
-			this.frames = Utils.newFloatArray(frameCount * this.getFrameEntries());
-		}
-
-		getPropertyIds () {
-			return this.propertyIds;
-		}
-
-		getFrameEntries (): number {
-			return 1;
-		}
-
-		getFrameCount () {
-			return this.frames.length / this.getFrameEntries();
-		}
-
-		getDuration (): number {
-			return this.frames[this.frames.length - this.getFrameEntries()];
-		}
-
-		abstract apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;
-
-		static search1 (frames: ArrayLike<number>, time: number) {
-			let n = frames.length;
-			for (let i = 1; i < n; i++)
-				if (frames[i] > time) return i - 1;
-			return n - 1;
-		}
-
-		static search (frames: ArrayLike<number>, time: number, step: number) {
-			let n = frames.length;
-			for (let i = step; i < n; i += step)
-				if (frames[i] > time) return i - step;
-			return n - step;
-		}
-	}
-
-	export interface BoneTimeline {
-		/** The index of the bone in {@link Skeleton#bones} that will be changed. */
-		boneIndex: number;
-	}
-
-	export interface SlotTimeline {
-		/** The index of the slot in {@link Skeleton#slots} that will be changed. */
-		slotIndex: number;
-	}
-
-	/** The base class for timelines that use interpolation between key frame values. */
-	export abstract class CurveTimeline extends Timeline {
-		protected curves: ArrayLike<number>; // type, x, y, ...
-
-		constructor (frameCount: number, bezierCount: number, propertyIds: string[]) {
-			super(frameCount, propertyIds);
-			this.curves = Utils.newFloatArray(frameCount + bezierCount * 18/*BEZIER_SIZE*/);
-			this.curves[frameCount - 1] = 1/*STEPPED*/;
-		}
-
-		/** Sets the specified key frame to linear interpolation. */
-		setLinear (frame: number) {
-			this.curves[frame] = 0/*LINEAR*/;
-		}
-
-		/** Sets the specified key frame to stepped interpolation. */
-		setStepped (frame: number) {
-			this.curves[frame] = 1/*STEPPED*/;
-		}
-
-		/** Shrinks the storage for Bezier curves, for use when <code>bezierCount</code> (specified in the constructor) was larger
-		 * than the actual number of Bezier curves. */
-		shrink (bezierCount: number) {
-			let size = this.getFrameCount() + bezierCount * 18/*BEZIER_SIZE*/;
-			if (this.curves.length > size) {
-				let newCurves = Utils.newFloatArray(size);
-				Utils.arrayCopy(this.curves, 0, newCurves, 0, size);
-				this.curves = newCurves;
-			}
-		}
-
-		/** Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than
-		 * one curve per frame.
-		 * @param bezier The ordinal of this Bezier curve for this timeline, between 0 and <code>bezierCount - 1</code> (specified
-		 *           in the constructor), inclusive.
-		 * @param frame Between 0 and <code>frameCount - 1</code>, inclusive.
-		 * @param value The index of the value for this frame that this curve is used for.
-		 * @param time1 The time for the first key.
-		 * @param value1 The value for the first key.
-		 * @param cx1 The time for the first Bezier handle.
-		 * @param cy1 The value for the first Bezier handle.
-		 * @param cx2 The time of the second Bezier handle.
-		 * @param cy2 The value for the second Bezier handle.
-		 * @param time2 The time for the second key.
-		 * @param value2 The value for the second key. */
-		setBezier (bezier: number, frame: number, value: number, time1: number, value1: number, cx1: number, cy1: number, cx2: number,
-			cy2: number, time2: number, value2: number) {
-			let curves = this.curves;
-			let i = this.getFrameCount() + bezier * 18/*BEZIER_SIZE*/;
-			if (value == 0) curves[frame] = 2/*BEZIER*/ + i;
-			let tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = (value1 - cy1 * 2 + cy2) * 0.03;
-			let dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006;
-			let ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy;
-			let dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = (cy1 - value1) * 0.3 + tmpy + dddy * 0.16666667;
-			let x = time1 + dx, y = value1 + dy;
-			for (let n = i + 18/*BEZIER_SIZE*/; i < n; i += 2) {
-				curves[i] = x;
-				curves[i + 1] = y;
-				dx += ddx;
-				dy += ddy;
-				ddx += dddx;
-				ddy += dddy;
-				x += dx;
-				y += dy;
-			}
-		}
-
-		/** Returns the Bezier interpolated value for the specified time.
-		 * @param frameIndex The index into {@link #getFrames()} for the values of the frame before <code>time</code>.
-		 * @param valueOffset The offset from <code>frameIndex</code> to the value this curve is used for.
-		 * @param i The index of the Bezier segments. See {@link #getCurveType(int)}. */
-		getBezierValue (time: number, frameIndex: number, valueOffset: number, i: number) {
-			let curves = this.curves;
-			if (curves[i] > time) {
-				let x = this.frames[frameIndex], y = this.frames[frameIndex + valueOffset];
-				return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
-			}
-			let n = i + 18/*BEZIER_SIZE*/;
-			for (i += 2; i < n; i += 2) {
-				if (curves[i] >= time) {
-					let x = curves[i - 2], y = curves[i - 1];
-					return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
-				}
-			}
-			frameIndex += this.getFrameEntries();
-			let x = curves[n - 2], y = curves[n - 1];
-			return y + (time - x) / (this.frames[frameIndex] - x) * (this.frames[frameIndex + valueOffset] - y);
-		}
-	}
-
-	export abstract class CurveTimeline1 extends CurveTimeline {
-		constructor(frameCount: number, bezierCount: number, propertyId: string) {
-			super(frameCount, bezierCount, [ propertyId ]);
-		}
-
-		getFrameEntries () {
-			return 2/*ENTRIES*/;
-		}
-
-		/** Sets the time and value for the specified frame.
-		 * @param frame Between 0 and <code>frameCount</code>, inclusive.
-		 * @param time The frame time in seconds. */
-		setFrame (frame: number, time: number, value: number) {
-			frame <<= 1;
-			this.frames[frame] = time;
-			this.frames[frame + 1/*VALUE*/] = value;
-		}
-
-		/** Returns the interpolated value for the specified time. */
-		getCurveValue (time: number) {
-			let frames = this.frames;
-			let i = frames.length - 2;
-			for (let ii = 2; ii <= i; ii += 2) {
-				if (frames[ii] > time) {
-					i = ii - 2;
-					break;
-				}
-			}
-
-			let curveType = this.curves[i >> 1];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i], value = frames[i + 1/*VALUE*/];
-				return value + (time - before) / (frames[i + 2/*ENTRIES*/] - before) * (frames[i + 2/*ENTRIES*/ + 1/*VALUE*/] - value);
-			case 1/*STEPPED*/:
-				return frames[i + 1/*VALUE*/];
-			}
-			return this.getBezierValue(time, i, 1/*VALUE*/, curveType - 2/*BEZIER*/);
-		}
-	}
-
-	/** The base class for a {@link CurveTimeline} which sets two properties. */
-	export abstract class CurveTimeline2 extends CurveTimeline {
-		/** @param bezierCount The maximum number of Bezier curves. See {@link #shrink(int)}.
-		 * @param propertyIds Unique identifiers for the properties the timeline modifies. */
-		constructor (frameCount: number, bezierCount: number, propertyId1: string, propertyId2: string) {
-			super(frameCount, bezierCount, [ propertyId1, propertyId2 ]);
-		}
-
-		getFrameEntries () {
-			return 3/*ENTRIES*/;
-		}
-
-		/** Sets the time and values for the specified frame.
-		 * @param frame Between 0 and <code>frameCount</code>, inclusive.
-		 * @param time The frame time in seconds. */
-		setFrame (frame: number, time: number, value1: number, value2: number) {
-			frame *= 3/*ENTRIES*/;
-			this.frames[frame] = time;
-			this.frames[frame + 1/*VALUE1*/] = value1;
-			this.frames[frame + 2/*VALUE2*/] = value2;
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#rotation}. */
-	export class RotateTimeline extends CurveTimeline1 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount, Property.rotate + "|" + boneIndex);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.rotation = bone.data.rotation;
-					return;
-				case MixBlend.first:
-					bone.rotation += (bone.data.rotation - bone.rotation) * alpha;
-				}
-				return;
-			}
-
-			let r = this.getCurveValue(time);
-			switch (blend) {
-			case MixBlend.setup:
-				bone.rotation = bone.data.rotation + r * alpha;
-				break;
-			case MixBlend.first:
-			case MixBlend.replace:
-				r += bone.data.rotation - bone.rotation;
-			case MixBlend.add:
-				bone.rotation += r * alpha;
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#x} and {@link Bone#y}. */
-	export class TranslateTimeline extends CurveTimeline2 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount,
-				Property.x + "|" + boneIndex,
-				Property.y + "|" + boneIndex,
-			);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.x = bone.data.x;
-					bone.y = bone.data.y;
-					return;
-				case MixBlend.first:
-					bone.x += (bone.data.x - bone.x) * alpha;
-					bone.y += (bone.data.y - bone.y) * alpha;
-				}
-				return;
-			}
-
-			let x = 0, y = 0;
-			let i = Timeline.search(frames, time, 3/*ENTRIES*/);
-			let curveType = this.curves[i / 3/*ENTRIES*/];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				x = frames[i + 1/*VALUE1*/];
-				y = frames[i + 2/*VALUE2*/];
-				let t = (time - before) / (frames[i + 3/*ENTRIES*/] - before);
-				x += (frames[i + 3/*ENTRIES*/ + 1/*VALUE1*/] - x) * t;
-				y += (frames[i + 3/*ENTRIES*/ + 2/*VALUE2*/] - y) * t;
-				break;
-			case 1/*STEPPED*/:
-				x = frames[i + 1/*VALUE1*/];
-				y = frames[i + 2/*VALUE2*/];
-				break;
-			default:
-				x = this.getBezierValue(time, i, 1/*VALUE1*/, curveType - 2/*BEZIER*/);
-				y = this.getBezierValue(time, i, 2/*VALUE2*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-			}
-
-			switch (blend) {
-			case MixBlend.setup:
-				bone.x = bone.data.x + x * alpha;
-				bone.y = bone.data.y + y * alpha;
-				break;
-			case MixBlend.first:
-			case MixBlend.replace:
-				bone.x += (bone.data.x + x - bone.x) * alpha;
-				bone.y += (bone.data.y + y - bone.y) * alpha;
-				break;
-			case MixBlend.add:
-				bone.x += x * alpha;
-				bone.y += y * alpha;
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#x}. */
-	export class TranslateXTimeline extends CurveTimeline1 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount, Property.x + "|" + boneIndex);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.x = bone.data.x;
-					return;
-				case MixBlend.first:
-					bone.x += (bone.data.x - bone.x) * alpha;
-				}
-				return;
-			}
-
-			let x = this.getCurveValue(time);
-			switch (blend) {
-			case MixBlend.setup:
-				bone.x = bone.data.x + x * alpha;
-				break;
-			case MixBlend.first:
-			case MixBlend.replace:
-				bone.x += (bone.data.x + x - bone.x) * alpha;
-				break;
-			case MixBlend.add:
-				bone.x += x * alpha;
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#x}. */
-	export class TranslateYTimeline extends CurveTimeline1 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount, Property.y + "|" + boneIndex);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.y = bone.data.y;
-					return;
-				case MixBlend.first:
-					bone.y += (bone.data.y - bone.y) * alpha;
-				}
-				return;
-			}
-
-			let y = this.getCurveValue(time);
-			switch (blend) {
-			case MixBlend.setup:
-				bone.y = bone.data.y + y * alpha;
-				break;
-			case MixBlend.first:
-			case MixBlend.replace:
-				bone.y += (bone.data.y + y - bone.y) * alpha;
-				break;
-			case MixBlend.add:
-				bone.y += y * alpha;
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */
-	export class ScaleTimeline extends CurveTimeline2 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount,
-				Property.scaleX + "|" + boneIndex,
-				Property.scaleY + "|" + boneIndex
-			);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.scaleX = bone.data.scaleX;
-					bone.scaleY = bone.data.scaleY;
-					return;
-				case MixBlend.first:
-					bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
-					bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
-				}
-				return;
-			}
-
-			let x, y;
-			let i = Timeline.search(frames, time, 3/*ENTRIES*/);
-			let curveType = this.curves[i / 3/*ENTRIES*/];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				x = frames[i + 1/*VALUE1*/];
-				y = frames[i + 2/*VALUE2*/];
-				let t = (time - before) / (frames[i + 3/*ENTRIES*/] - before);
-				x += (frames[i + 3/*ENTRIES*/ + 1/*VALUE1*/] - x) * t;
-				y += (frames[i + 3/*ENTRIES*/ + 2/*VALUE2*/] - y) * t;
-				break;
-			case 1/*STEPPED*/:
-				x = frames[i + 1/*VALUE1*/];
-				y = frames[i + 2/*VALUE2*/];
-				break;
-			default:
-				x = this.getBezierValue(time, i, 1/*VALUE1*/, curveType - 2/*BEZIER*/);
-				y = this.getBezierValue(time, i, 2/*VALUE2*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-			}
-			x *= bone.data.scaleX;
-			y *= bone.data.scaleY;
-
-			if (alpha == 1) {
-				if (blend == MixBlend.add) {
-					bone.scaleX += x - bone.data.scaleX;
-					bone.scaleY += y - bone.data.scaleY;
-				} else {
-					bone.scaleX = x;
-					bone.scaleY = y;
-				}
-			} else {
-				let bx = 0, by = 0;
-				if (direction == MixDirection.mixOut) {
-					switch (blend) {
-					case MixBlend.setup:
-						bx = bone.data.scaleX;
-						by = bone.data.scaleY;
-						bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
-						bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
-						break;
-					case MixBlend.first:
-					case MixBlend.replace:
-						bx = bone.scaleX;
-						by = bone.scaleY;
-						bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
-						bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
-						break;
-					case MixBlend.add:
-						bx = bone.scaleX;
-						by = bone.scaleY;
-						bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bone.data.scaleX) * alpha;
-						bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - bone.data.scaleY) * alpha;
-					}
-				} else {
-					switch (blend) {
-					case MixBlend.setup:
-						bx = Math.abs(bone.data.scaleX) * MathUtils.signum(x);
-						by = Math.abs(bone.data.scaleY) * MathUtils.signum(y);
-						bone.scaleX = bx + (x - bx) * alpha;
-						bone.scaleY = by + (y - by) * alpha;
-						break;
-					case MixBlend.first:
-					case MixBlend.replace:
-						bx = Math.abs(bone.scaleX) * MathUtils.signum(x);
-						by = Math.abs(bone.scaleY) * MathUtils.signum(y);
-						bone.scaleX = bx + (x - bx) * alpha;
-						bone.scaleY = by + (y - by) * alpha;
-						break;
-					case MixBlend.add:
-						bx = MathUtils.signum(x);
-						by = MathUtils.signum(y);
-						bone.scaleX = Math.abs(bone.scaleX) * bx + (x - Math.abs(bone.data.scaleX) * bx) * alpha;
-						bone.scaleY = Math.abs(bone.scaleY) * by + (y - Math.abs(bone.data.scaleY) * by) * alpha;
-					}
-				}
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */
-	export class ScaleXTimeline extends CurveTimeline1 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount, Property.scaleX + "|" + boneIndex);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.scaleX = bone.data.scaleX;
-					return;
-				case MixBlend.first:
-					bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
-				}
-				return;
-			}
-
-			let x = this.getCurveValue(time) * bone.data.scaleX;
-			if (alpha == 1) {
-				if (blend == MixBlend.add)
-					bone.scaleX += x - bone.data.scaleX;
-				else
-					bone.scaleX = x;
-			} else {
-				// Mixing out uses sign of setup or current pose, else use sign of key.
-				let bx = 0;
-				if (direction == MixDirection.mixOut) {
-					switch (blend) {
-					case MixBlend.setup:
-						bx = bone.data.scaleX;
-						bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
-						break;
-					case MixBlend.first:
-					case MixBlend.replace:
-						bx = bone.scaleX;
-						bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
-						break;
-					case MixBlend.add:
-						bx = bone.scaleX;
-						bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bone.data.scaleX) * alpha;
-					}
-				} else {
-					switch (blend) {
-					case MixBlend.setup:
-						bx = Math.abs(bone.data.scaleX) * MathUtils.signum(x);
-						bone.scaleX = bx + (x - bx) * alpha;
-						break;
-					case MixBlend.first:
-					case MixBlend.replace:
-						bx = Math.abs(bone.scaleX) * MathUtils.signum(x);
-						bone.scaleX = bx + (x - bx) * alpha;
-						break;
-					case MixBlend.add:
-						bx = MathUtils.signum(x);
-						bone.scaleX = Math.abs(bone.scaleX) * bx + (x - Math.abs(bone.data.scaleX) * bx) * alpha;
-					}
-				}
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */
-	export class ScaleYTimeline extends CurveTimeline1 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount, Property.scaleY + "|" + boneIndex);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.scaleY = bone.data.scaleY;
-					return;
-				case MixBlend.first:
-					bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
-				}
-				return;
-			}
-
-			let y = this.getCurveValue(time) * bone.data.scaleY;
-			if (alpha == 1) {
-				if (blend == MixBlend.add)
-					bone.scaleY += y - bone.data.scaleY;
-				else
-					bone.scaleY = y;
-			} else {
-				// Mixing out uses sign of setup or current pose, else use sign of key.
-				let by = 0;
-				if (direction == MixDirection.mixOut) {
-					switch (blend) {
-					case MixBlend.setup:
-						by = bone.data.scaleY;
-						bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
-						break;
-					case MixBlend.first:
-					case MixBlend.replace:
-						by = bone.scaleY;
-						bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
-						break;
-					case MixBlend.add:
-						by = bone.scaleY;
-						bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - bone.data.scaleY) * alpha;
-					}
-				} else {
-					switch (blend) {
-					case MixBlend.setup:
-						by = Math.abs(bone.data.scaleY) * MathUtils.signum(y);
-						bone.scaleY = by + (y - by) * alpha;
-						break;
-					case MixBlend.first:
-					case MixBlend.replace:
-						by = Math.abs(bone.scaleY) * MathUtils.signum(y);
-						bone.scaleY = by + (y - by) * alpha;
-						break;
-					case MixBlend.add:
-						by = MathUtils.signum(y);
-						bone.scaleY = Math.abs(bone.scaleY) * by + (y - Math.abs(bone.data.scaleY) * by) * alpha;
-					}
-				}
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
-	export class ShearTimeline extends CurveTimeline2 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount,
-				Property.shearX + "|" + boneIndex,
-				Property.shearY + "|" + boneIndex
-			);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.shearX = bone.data.shearX;
-					bone.shearY = bone.data.shearY;
-					return;
-				case MixBlend.first:
-					bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
-					bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
-				}
-				return;
-			}
-
-			let x = 0, y = 0;
-			let i = Timeline.search(frames, time, 3/*ENTRIES*/);
-			let curveType = this.curves[i / 3/*ENTRIES*/];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				x = frames[i + 1/*VALUE1*/];
-				y = frames[i + 2/*VALUE2*/];
-				let t = (time - before) / (frames[i + 3/*ENTRIES*/] - before);
-				x += (frames[i + 3/*ENTRIES*/ + 1/*VALUE1*/] - x) * t;
-				y += (frames[i + 3/*ENTRIES*/ + 2/*VALUE2*/] - y) * t;
-				break;
-			case 1/*STEPPED*/:
-				x = frames[i + 1/*VALUE1*/];
-				y = frames[i + 2/*VALUE2*/];
-				break;
-			default:
-				x = this.getBezierValue(time, i, 1/*VALUE1*/, curveType - 2/*BEZIER*/);
-				y = this.getBezierValue(time, i, 2/*VALUE2*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-			}
-
-			switch (blend) {
-			case MixBlend.setup:
-				bone.shearX = bone.data.shearX + x * alpha;
-				bone.shearY = bone.data.shearY + y * alpha;
-				break;
-			case MixBlend.first:
-			case MixBlend.replace:
-				bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
-				bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
-				break;
-			case MixBlend.add:
-				bone.shearX += x * alpha;
-				bone.shearY += y * alpha;
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
-	export class ShearXTimeline extends CurveTimeline1 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount, Property.shearX + "|" + boneIndex);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.shearX = bone.data.shearX;
-					return;
-				case MixBlend.first:
-					bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
-				}
-				return;
-			}
-
-			let x = this.getCurveValue(time);
-			switch (blend) {
-			case MixBlend.setup:
-				bone.shearX = bone.data.shearX + x * alpha;
-				break;
-			case MixBlend.first:
-			case MixBlend.replace:
-				bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
-				break;
-			case MixBlend.add:
-				bone.shearX += x * alpha;
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
-	export class ShearYTimeline extends CurveTimeline1 implements BoneTimeline {
-		boneIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
-			super(frameCount, bezierCount, Property.shearY + "|" + boneIndex);
-			this.boneIndex = boneIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let bone = skeleton.bones[this.boneIndex];
-			if (!bone.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					bone.shearY = bone.data.shearY;
-					return;
-				case MixBlend.first:
-					bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
-				}
-				return;
-			}
-
-			let y = this.getCurveValue(time);
-			switch (blend) {
-			case MixBlend.setup:
-				bone.shearY = bone.data.shearY + y * alpha;
-				break;
-			case MixBlend.first:
-			case MixBlend.replace:
-				bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
-				break;
-			case MixBlend.add:
-				bone.shearY += y * alpha;
-			}
-		}
-	}
-
-	/** Changes a slot's {@link Slot#color}. */
-	export class RGBATimeline extends CurveTimeline implements SlotTimeline {
-		slotIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
-			super(frameCount, bezierCount, [
-				Property.rgb + "|" + slotIndex,
-				Property.alpha + "|" + slotIndex
-			]);
-			this.slotIndex = slotIndex;
-		}
-
-		getFrameEntries () {
-			return 5/*ENTRIES*/;
-		}
-
-		/** Sets the time in seconds, red, green, blue, and alpha for the specified key frame. */
-		setFrame (frame: number, time: number, r: number, g: number, b: number, a: number) {
-			frame *= 5/*ENTRIES*/;
-			this.frames[frame] = time;
-			this.frames[frame + 1/*R*/] = r;
-			this.frames[frame + 2/*G*/] = g;
-			this.frames[frame + 3/*B*/] = b;
-			this.frames[frame + 4/*A*/] = a;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let slot = skeleton.slots[this.slotIndex];
-			if (!slot.bone.active) return;
-
-			let frames = this.frames;
-			let color = slot.color;
-			if (time < frames[0]) {
-				let setup = slot.data.color;
-				switch (blend) {
-				case MixBlend.setup:
-					color.setFromColor(setup);
-					return;
-				case MixBlend.first:
-					color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha,
-						(setup.a - color.a) * alpha);
-				}
-				return;
-			}
-
-			let r = 0, g = 0, b = 0, a = 0;
-			let i = Timeline.search(frames, time, 5/*ENTRIES*/);
-			let curveType = this.curves[i / 5/*ENTRIES*/];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				r = frames[i + 1/*R*/];
-				g = frames[i + 2/*G*/];
-				b = frames[i + 3/*B*/];
-				a = frames[i + 4/*A*/];
-				let t = (time - before) / (frames[i + 5/*ENTRIES*/] - before);
-				r += (frames[i + 5/*ENTRIES*/ + 1/*R*/] - r) * t;
-				g += (frames[i + 5/*ENTRIES*/ + 2/*G*/] - g) * t;
-				b += (frames[i + 5/*ENTRIES*/ + 3/*B*/] - b) * t;
-				a += (frames[i + 5/*ENTRIES*/ + 4/*A*/] - a) * t;
-				break;
-			case 1/*STEPPED*/:
-				r = frames[i + 1/*R*/];
-				g = frames[i + 2/*G*/];
-				b = frames[i + 3/*B*/];
-				a = frames[i + 4/*A*/];
-				break;
-			default:
-				r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
-				g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-				b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
-				a = this.getBezierValue(time, i, 4/*A*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
-			}
-			if (alpha == 1)
-				color.set(r, g, b, a);
-			else {
-				if (blend == MixBlend.setup) color.setFromColor(slot.data.color);
-				color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
-			}
-		}
-	}
-
-	/** Changes a slot's {@link Slot#color}. */
-	export class RGBTimeline extends CurveTimeline implements SlotTimeline {
-		slotIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
-			super(frameCount, bezierCount, [
-				Property.rgb + "|" + slotIndex
-			]);
-			this.slotIndex = slotIndex;
-		}
-
-		getFrameEntries () {
-			return 4/*ENTRIES*/;
-		}
-
-		/** Sets the time in seconds, red, green, blue, and alpha for the specified key frame. */
-		setFrame (frame: number, time: number, r: number, g: number, b: number) {
-			frame <<= 2;
-			this.frames[frame] = time;
-			this.frames[frame + 1/*R*/] = r;
-			this.frames[frame + 2/*G*/] = g;
-			this.frames[frame + 3/*B*/] = b;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let slot = skeleton.slots[this.slotIndex];
-			if (!slot.bone.active) return;
-
-			let frames = this.frames;
-			let color = slot.color;
-			if (time < frames[0]) {
-				let setup = slot.data.color;
-				switch (blend) {
-				case MixBlend.setup:
-					color.r = setup.r;
-					color.g = setup.g;
-					color.b = setup.b;
-					return;
-				case MixBlend.first:
-					color.r += (setup.r - color.r) * alpha;
-					color.g += (setup.g - color.g) * alpha;
-					color.b += (setup.b - color.b) * alpha;
-				}
-				return;
-			}
-
-			let r = 0, g = 0, b = 0;
-			let i = Timeline.search(frames, time, 4/*ENTRIES*/);
-			let curveType = this.curves[i >> 2];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				r = frames[i + 1/*R*/];
-				g = frames[i + 2/*G*/];
-				b = frames[i + 3/*B*/];
-				let t = (time - before) / (frames[i + 4/*ENTRIES*/] - before);
-				r += (frames[i + 4/*ENTRIES*/ + 1/*R*/] - r) * t;
-				g += (frames[i + 4/*ENTRIES*/ + 2/*G*/] - g) * t;
-				b += (frames[i + 4/*ENTRIES*/ + 3/*B*/] - b) * t;
-				break;
-			case 1/*STEPPED*/:
-				r = frames[i + 1/*R*/];
-				g = frames[i + 2/*G*/];
-				b = frames[i + 3/*B*/];
-				break;
-			default:
-				r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
-				g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-				b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
-			}
-			if (alpha == 1) {
-				color.r = r;
-				color.g = g;
-				color.b = b;
-			} else {
-				if (blend == MixBlend.setup) {
-					let setup = slot.data.color;
-					color.r = setup.r;
-					color.g = setup.g;
-					color.b = setup.b;
-				}
-				color.r += (r - color.r) * alpha;
-				color.g += (g - color.g) * alpha;
-				color.b += (b - color.b) * alpha;
-			}
-		}
-	}
-
-	/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
-	export class AlphaTimeline extends CurveTimeline1 implements SlotTimeline {
-		slotIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
-			super(frameCount, bezierCount, Property.alpha + "|" + slotIndex);
-			this.slotIndex = slotIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let slot = skeleton.slots[this.slotIndex];
-			if (!slot.bone.active) return;
-
-			let color = slot.color;
-			if (time < this.frames[0]) { // Time is before first frame.
-				let setup = slot.data.color;
-				switch (blend) {
-				case MixBlend.setup:
-					color.a = setup.a;
-					return;
-				case MixBlend.first:
-					color.a += (setup.a - color.a) * alpha;
-				}
-				return;
-			}
-
-			let a = this.getCurveValue(time);
-			if (alpha == 1)
-				color.a = a;
-			else {
-				if (blend == MixBlend.setup) color.a = slot.data.color.a;
-				color.a += (a - color.a) * alpha;
-			}
-		}
-	}
-
-	/** Changes a slot's {@link Slot#color} and {@link Slot#darkColor} for two color tinting. */
-	export class RGBA2Timeline extends CurveTimeline implements SlotTimeline{
-		slotIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
-			super(frameCount, bezierCount, [
-				Property.rgb + "|" + slotIndex,
-				Property.alpha + "|" + slotIndex,
-				Property.rgb2 + "|" + slotIndex
-			]);
-			this.slotIndex = slotIndex;
-		}
-
-		getFrameEntries () {
-			return 8/*ENTRIES*/;
-		}
-
-		/** Sets the time in seconds, light, and dark colors for the specified key frame. */
-		setFrame (frame: number, time: number, r: number, g: number, b: number, a: number, r2: number, g2: number, b2: number) {
-			frame <<= 3;
-			this.frames[frame] = time;
-			this.frames[frame + 1/*R*/] = r;
-			this.frames[frame + 2/*G*/] = g;
-			this.frames[frame + 3/*B*/] = b;
-			this.frames[frame + 4/*A*/] = a;
-			this.frames[frame + 5/*R2*/] = r2;
-			this.frames[frame + 6/*G2*/] = g2;
-			this.frames[frame + 7/*B2*/] = b2;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let slot = skeleton.slots[this.slotIndex];
-			if (!slot.bone.active) return;
-
-			let frames = this.frames;
-			let light = slot.color, dark = slot.darkColor;
-			if (time < frames[0]) {
-				let setupLight = slot.data.color, setupDark = slot.data.darkColor;
-				switch (blend) {
-				case MixBlend.setup:
-					light.setFromColor(setupLight);
-					dark.r = setupDark.r;
-					dark.g = setupDark.g;
-					dark.b = setupDark.b;
-					return;
-				case MixBlend.first:
-					light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
-						(setupLight.a - light.a) * alpha);
-					dark.r += (setupDark.r - dark.r) * alpha;
-					dark.g += (setupDark.g - dark.g) * alpha;
-					dark.b += (setupDark.b - dark.b) * alpha;
-				}
-				return;
-			}
-
-			let r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0;
-			let i = Timeline.search(frames, time, 8/*ENTRIES*/);
-			let curveType = this.curves[i >> 3];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				r = frames[i + 1/*R*/];
-				g = frames[i + 2/*G*/];
-				b = frames[i + 3/*B*/];
-				a = frames[i + 4/*A*/];
-				r2 = frames[i + 5/*R2*/];
-				g2 = frames[i + 6/*G2*/];
-				b2 = frames[i + 7/*B2*/];
-				let t = (time - before) / (frames[i + 8/*ENTRIES*/] - before);
-				r += (frames[i + 8/*ENTRIES*/ + 1/*R*/] - r) * t;
-				g += (frames[i + 8/*ENTRIES*/ + 2/*G*/] - g) * t;
-				b += (frames[i + 8/*ENTRIES*/ + 3/*B*/] - b) * t;
-				a += (frames[i + 8/*ENTRIES*/ + 4/*A*/] - a) * t;
-				r2 += (frames[i + 8/*ENTRIES*/ + 5/*R2*/] - r2) * t;
-				g2 += (frames[i + 8/*ENTRIES*/ + 6/*G2*/] - g2) * t;
-				b2 += (frames[i + 8/*ENTRIES*/ + 7/*B2*/] - b2) * t;
-				break;
-			case 1/*STEPPED*/:
-				r = frames[i + 1/*R*/];
-				g = frames[i + 2/*G*/];
-				b = frames[i + 3/*B*/];
-				a = frames[i + 4/*A*/];
-				r2 = frames[i + 5/*R2*/];
-				g2 = frames[i + 6/*G2*/];
-				b2 = frames[i + 7/*B2*/];
-				break;
-			default:
-				r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
-				g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-				b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
-				a = this.getBezierValue(time, i, 4/*A*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
-				r2 = this.getBezierValue(time, i, 5/*R2*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
-				g2 = this.getBezierValue(time, i, 6/*G2*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
-				b2 = this.getBezierValue(time, i, 7/*B2*/, curveType + 18/*BEZIER_SIZE*/ * 6 - 2/*BEZIER*/);
-			}
-
-			if (alpha == 1) {
-				light.set(r, g, b, a);
-				dark.r = r2;
-				dark.g = g2;
-				dark.b = b2;
-			} else {
-				if (blend == MixBlend.setup) {
-					light.setFromColor(slot.data.color);
-					let setupDark = slot.data.darkColor;
-					dark.r = setupDark.r;
-					dark.g = setupDark.g;
-					dark.b = setupDark.b;
-				}
-				light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha);
-				dark.r += (r2 - dark.r) * alpha;
-				dark.g += (g2 - dark.g) * alpha;
-				dark.b += (b2 - dark.b) * alpha;
-			}
-		}
-	}
-
-	/** Changes a slot's {@link Slot#color} and {@link Slot#darkColor} for two color tinting. */
-	export class RGB2Timeline extends CurveTimeline implements SlotTimeline{
-		slotIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
-			super(frameCount, bezierCount, [
-				Property.rgb + "|" + slotIndex,
-				Property.rgb2 + "|" + slotIndex
-			]);
-			this.slotIndex = slotIndex;
-		}
-
-		getFrameEntries () {
-			return 7/*ENTRIES*/;
-		}
-
-		/** Sets the time in seconds, light, and dark colors for the specified key frame. */
-		setFrame (frame: number, time: number, r: number, g: number, b: number, r2: number, g2: number, b2: number) {
-			frame *= 7/*ENTRIES*/;
-			this.frames[frame] = time;
-			this.frames[frame + 1/*R*/] = r;
-			this.frames[frame + 2/*G*/] = g;
-			this.frames[frame + 3/*B*/] = b;
-			this.frames[frame + 4/*R2*/] = r2;
-			this.frames[frame + 5/*G2*/] = g2;
-			this.frames[frame + 6/*B2*/] = b2;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let slot = skeleton.slots[this.slotIndex];
-			if (!slot.bone.active) return;
-
-			let frames = this.frames;
-			let light = slot.color, dark = slot.darkColor;
-			if (time < frames[0]) {
-				let setupLight = slot.data.color, setupDark = slot.data.darkColor;
-				switch (blend) {
-				case MixBlend.setup:
-					light.r = setupLight.r;
-					light.g = setupLight.g;
-					light.b = setupLight.b;
-					dark.r = setupDark.r;
-					dark.g = setupDark.g;
-					dark.b = setupDark.b;
-					return;
-				case MixBlend.first:
-					light.r += (setupLight.r - light.r) * alpha;
-					light.g += (setupLight.g - light.g) * alpha;
-					light.b += (setupLight.b - light.b) * alpha;
-					dark.r += (setupDark.r - dark.r) * alpha;
-					dark.g += (setupDark.g - dark.g) * alpha;
-					dark.b += (setupDark.b - dark.b) * alpha;
-				}
-				return;
-			}
-
-			let r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0;
-			let i = Timeline.search(frames, time, 7/*ENTRIES*/);
-			let curveType = this.curves[i / 7/*ENTRIES*/];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				r = frames[i + 1/*R*/];
-				g = frames[i + 2/*G*/];
-				b = frames[i + 3/*B*/];
-				r2 = frames[i + 4/*R2*/];
-				g2 = frames[i + 5/*G2*/];
-				b2 = frames[i + 6/*B2*/];
-				let t = (time - before) / (frames[i + 7/*ENTRIES*/] - before);
-				r += (frames[i + 7/*ENTRIES*/ + 1/*R*/] - r) * t;
-				g += (frames[i + 7/*ENTRIES*/ + 2/*G*/] - g) * t;
-				b += (frames[i + 7/*ENTRIES*/ + 3/*B*/] - b) * t;
-				r2 += (frames[i + 7/*ENTRIES*/ + 4/*R2*/] - r2) * t;
-				g2 += (frames[i + 7/*ENTRIES*/ + 5/*G2*/] - g2) * t;
-				b2 += (frames[i + 7/*ENTRIES*/ + 6/*B2*/] - b2) * t;
-				break;
-			case 1/*STEPPED*/:
-				r = frames[i + 1/*R*/];
-				g = frames[i + 2/*G*/];
-				b = frames[i + 3/*B*/];
-				r2 = frames[i + 4/*R2*/];
-				g2 = frames[i + 5/*G2*/];
-				b2 = frames[i + 6/*B2*/];
-				break;
-			default:
-				r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
-				g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-				b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
-				r2 = this.getBezierValue(time, i, 4/*R2*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
-				g2 = this.getBezierValue(time, i, 5/*G2*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
-				b2 = this.getBezierValue(time, i, 6/*B2*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
-			}
-
-			if (alpha == 1) {
-				light.r = r;
-				light.g = g;
-				light.b = b;
-				dark.r = r2;
-				dark.g = g2;
-				dark.b = b2;
-			} else {
-				if (blend == MixBlend.setup) {
-					let setupLight = slot.data.color, setupDark = slot.data.darkColor;
-					light.r = setupLight.r;
-					light.g = setupLight.g;
-					light.b = setupLight.b;
-					dark.r = setupDark.r;
-					dark.g = setupDark.g;
-					dark.b = setupDark.b;
-				}
-				light.r += (r - light.r) * alpha;
-				light.g += (g - light.g) * alpha;
-				light.b += (b - light.b) * alpha;
-				dark.r += (r2 - dark.r) * alpha;
-				dark.g += (g2 - dark.g) * alpha;
-				dark.b += (b2 - dark.b) * alpha;
-			}
-		}
-	}
-
-	/** Changes a slot's {@link Slot#attachment}. */
-	export class AttachmentTimeline extends Timeline implements SlotTimeline {
-		slotIndex = 0;
-
-		/** The attachment name for each key frame. May contain null values to clear the attachment. */
-		attachmentNames: Array<string>;
-
-		constructor (frameCount: number, slotIndex: number) {
-			super(frameCount, [
-				Property.attachment + "|" + slotIndex
-			]);
-			this.slotIndex = slotIndex;
-			this.attachmentNames = new Array<string>(frameCount);
-		}
-
-		getFrameCount () {
-			return this.frames.length;
-		}
-
-		/** Sets the time in seconds and the attachment name for the specified key frame. */
-		setFrame (frame: number, time: number, attachmentName: string) {
-			this.frames[frame] = time;
-			this.attachmentNames[frame] = attachmentName;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let slot = skeleton.slots[this.slotIndex];
-			if (!slot.bone.active) return;
-
-			if (direction == MixDirection.mixOut) {
-				if (blend == MixBlend.setup) this.setAttachment(skeleton, slot, slot.data.attachmentName);
-				return;
-			}
-
-			if (time < this.frames[0]) {
-				if (blend == MixBlend.setup || blend == MixBlend.first) this.setAttachment(skeleton, slot, slot.data.attachmentName);
-				return;
-			}
-
-			this.setAttachment(skeleton, slot, this.attachmentNames[Timeline.search1(this.frames, time)]);
-		}
-
-		setAttachment(skeleton: Skeleton, slot: Slot, attachmentName: string) {
-			slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(this.slotIndex, attachmentName));
-		}
-	}
-
-	/** Changes a slot's {@link Slot#deform} to deform a {@link VertexAttachment}. */
-	export class DeformTimeline extends CurveTimeline implements SlotTimeline {
-		slotIndex = 0;
-
-		/** The attachment that will be deformed. */
-		attachment: VertexAttachment;
-
-		/** The vertices for each key frame. */
-		vertices: Array<ArrayLike<number>>;
-
-		constructor (frameCount: number, bezierCount: number, slotIndex: number, attachment: VertexAttachment) {
-			super(frameCount, bezierCount, [
-				Property.deform + "|" + slotIndex + "|" + attachment.id
-			]);
-			this.slotIndex = slotIndex;
-			this.attachment = attachment;
-			this.vertices = new Array<ArrayLike<number>>(frameCount);
-		}
-
-		getFrameCount () {
-			return this.frames.length;
-		}
-
-		/** Sets the time in seconds and the vertices for the specified key frame.
-		 * @param vertices Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. */
-		setFrame (frame: number, time: number, vertices: ArrayLike<number>) {
-			this.frames[frame] = time;
-			this.vertices[frame] = vertices;
-		}
-
-		/** @param value1 Ignored (0 is used for a deform timeline).
-		 * @param value2 Ignored (1 is used for a deform timeline). */
-		setBezier (bezier: number, frame: number, value: number, time1: number, value1: number, cx1: number, cy1: number, cx2: number,
-			cy2: number, time2: number, value2: number) {
-			let curves = this.curves;
-			let i = this.getFrameCount() + bezier * 18/*BEZIER_SIZE*/;
-			if (value == 0) curves[frame] = 2/*BEZIER*/ + i;
-			let tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = cy2 * 0.03 - cy1 * 0.06;
-			let dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = (cy1 - cy2 + 0.33333333) * 0.018;
-			let ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy;
-			let dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = cy1 * 0.3 + tmpy + dddy * 0.16666667;
-			let x = time1 + dx, y = dy;
-			for (let n = i + 18/*BEZIER_SIZE*/; i < n; i += 2) {
-				curves[i] = x;
-				curves[i + 1] = y;
-				dx += ddx;
-				dy += ddy;
-				ddx += dddx;
-				ddy += dddy;
-				x += dx;
-				y += dy;
-			}
-		}
-
-		getCurvePercent (time: number, frame: number) {
-			let curves = this.curves;
-			let i = curves[frame];
-			switch (i) {
-			case 0/*LINEAR*/:
-				let x = this.frames[frame];
-				return (time - x) / (this.frames[frame + this.getFrameEntries()] - x);
-			case 1/*STEPPED*/:
-				return 0;
-			}
-			i -= 2/*BEZIER*/;
-			if (curves[i] > time) {
-				let x = this.frames[frame];
-				return curves[i + 1] * (time - x) / (curves[i] - x);
-			}
-			let n = i + 18/*BEZIER_SIZE*/;
-			for (i += 2; i < n; i += 2) {
-				if (curves[i] >= time) {
-					let x = curves[i - 2], y = curves[i - 1];
-					return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
-				}
-			}
-			let x = curves[n - 2], y = curves[n - 1];
-			return y + (1 - y) * (time - x) / (this.frames[frame + this.getFrameEntries()] - x);
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let slot: Slot = skeleton.slots[this.slotIndex];
-			if (!slot.bone.active) return;
-			let slotAttachment: Attachment = slot.getAttachment();
-			if (!(slotAttachment instanceof VertexAttachment) || (<VertexAttachment>slotAttachment).deformAttachment != this.attachment) return;
-
-			let deform: Array<number> = slot.deform;
-			if (deform.length == 0) blend = MixBlend.setup;
-
-			let vertices = this.vertices;
-			let vertexCount = vertices[0].length;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				let vertexAttachment = <VertexAttachment>slotAttachment;
-				switch (blend) {
-				case MixBlend.setup:
-					deform.length = 0;
-					return;
-				case MixBlend.first:
-					if (alpha == 1) {
-						deform.length = 0;
-						return;
-					}
-					deform.length = vertexCount;
-					if (!vertexAttachment.bones) {
-						// Unweighted vertex positions.
-						let setupVertices = vertexAttachment.vertices;
-						for (var i = 0; i < vertexCount; i++)
-							deform[i] += (setupVertices[i] - deform[i]) * alpha;
-					} else {
-						// Weighted deform offsets.
-						alpha = 1 - alpha;
-						for (var i = 0; i < vertexCount; i++)
-							deform[i] *= alpha;
-					}
-				}
-				return;
-			}
-
-			deform.length = vertexCount;
-			if (time >= frames[frames.length - 1]) { // Time is after last frame.
-				let lastVertices = vertices[frames.length - 1];
-				if (alpha == 1) {
-					if (blend == MixBlend.add) {
-						let vertexAttachment = slotAttachment as VertexAttachment;
-						if (!vertexAttachment.bones) {
-							// Unweighted vertex positions, with alpha.
-							let setupVertices = vertexAttachment.vertices;
-							for (let i = 0; i < vertexCount; i++)
-								deform[i] += lastVertices[i] - setupVertices[i];
-						} else {
-							// Weighted deform offsets, with alpha.
-							for (let i = 0; i < vertexCount; i++)
-								deform[i] += lastVertices[i];
-						}
-					} else
-						Utils.arrayCopy(lastVertices, 0, deform, 0, vertexCount);
-				} else {
-					switch (blend) {
-					case MixBlend.setup: {
-						let vertexAttachment = slotAttachment as VertexAttachment;
-						if (!vertexAttachment.bones) {
-							// Unweighted vertex positions, with alpha.
-							let setupVertices = vertexAttachment.vertices;
-							for (let i = 0; i < vertexCount; i++) {
-								let setup = setupVertices[i];
-								deform[i] = setup + (lastVertices[i] - setup) * alpha;
-							}
-						} else {
-							// Weighted deform offsets, with alpha.
-							for (let i = 0; i < vertexCount; i++)
-								deform[i] = lastVertices[i] * alpha;
-						}
-						break;
-					}
-					case MixBlend.first:
-					case MixBlend.replace:
-						for (let i = 0; i < vertexCount; i++)
-							deform[i] += (lastVertices[i] - deform[i]) * alpha;
-						break;
-					case MixBlend.add:
-						let vertexAttachment = slotAttachment as VertexAttachment;
-						if (!vertexAttachment.bones) {
-							// Unweighted vertex positions, with alpha.
-							let setupVertices = vertexAttachment.vertices;
-							for (let i = 0; i < vertexCount; i++)
-								deform[i] += (lastVertices[i] - setupVertices[i]) * alpha;
-						} else {
-							// Weighted deform offsets, with alpha.
-							for (let i = 0; i < vertexCount; i++)
-								deform[i] += lastVertices[i] * alpha;
-						}
-					}
-				}
-				return;
-			}
-
-			// Interpolate between the previous frame and the current frame.
-			let frame = Timeline.search1(frames, time);
-			let percent = this.getCurvePercent(time, frame);
-			let prevVertices = vertices[frame];
-			let nextVertices = vertices[frame + 1];
-
-			if (alpha == 1) {
-				if (blend == MixBlend.add) {
-					let vertexAttachment = slotAttachment as VertexAttachment;
-					if (!vertexAttachment.bones) {
-						// Unweighted vertex positions, with alpha.
-						let setupVertices = vertexAttachment.vertices;
-						for (let i = 0; i < vertexCount; i++) {
-							let prev = prevVertices[i];
-							deform[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i];
-						}
-					} else {
-						// Weighted deform offsets, with alpha.
-						for (let i = 0; i < vertexCount; i++) {
-							let prev = prevVertices[i];
-							deform[i] += prev + (nextVertices[i] - prev) * percent;
-						}
-					}
-				} else {
-					for (let i = 0; i < vertexCount; i++) {
-						let prev = prevVertices[i];
-						deform[i] = prev + (nextVertices[i] - prev) * percent;
-					}
-				}
-			} else {
-				switch (blend) {
-				case MixBlend.setup: {
-					let vertexAttachment = slotAttachment as VertexAttachment;
-					if (!vertexAttachment.bones) {
-						// Unweighted vertex positions, with alpha.
-						let setupVertices = vertexAttachment.vertices;
-						for (let i = 0; i < vertexCount; i++) {
-							let prev = prevVertices[i], setup = setupVertices[i];
-							deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
-						}
-					} else {
-						// Weighted deform offsets, with alpha.
-						for (let i = 0; i < vertexCount; i++) {
-							let prev = prevVertices[i];
-							deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
-						}
-					}
-					break;
-				}
-				case MixBlend.first:
-				case MixBlend.replace:
-					for (let i = 0; i < vertexCount; i++) {
-						let prev = prevVertices[i];
-						deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha;
-					}
-					break;
-				case MixBlend.add:
-					let vertexAttachment = slotAttachment as VertexAttachment;
-					if (!vertexAttachment.bones) {
-						// Unweighted vertex positions, with alpha.
-						let setupVertices = vertexAttachment.vertices;
-						for (let i = 0; i < vertexCount; i++) {
-							let prev = prevVertices[i];
-							deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha;
-						}
-					} else {
-						// Weighted deform offsets, with alpha.
-						for (let i = 0; i < vertexCount; i++) {
-							let prev = prevVertices[i];
-							deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha;
-						}
-					}
-				}
-			}
-		}
-	}
-
-	/** Fires an {@link Event} when specific animation times are reached. */
-	export class EventTimeline extends Timeline {
-		static propertyIds = [ "" + Property.event ];
-
-		/** The event for each key frame. */
-		events: Array<Event>;
-
-		constructor (frameCount: number) {
-			super(frameCount, EventTimeline.propertyIds);
-
-			this.events = new Array<Event>(frameCount);
-		}
-
-		getFrameCount () {
-			return this.frames.length;
-		}
-
-		/** Sets the time in seconds and the event for the specified key frame. */
-		setFrame (frame: number, event: Event) {
-			this.frames[frame] = event.time;
-			this.events[frame] = event;
-		}
-
-		/** Fires events for frames > `lastTime` and <= `time`. */
-		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			if (!firedEvents) return;
-
-			let frames = this.frames;
-			let frameCount = this.frames.length;
-
-			if (lastTime > time) { // Fire events after last time for looped animations.
-				this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha, blend, direction);
-				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.
-
-			let i = 0;
-			if (lastTime < frames[0])
-				i = 0;
-			else {
-				i = Timeline.search1(frames, lastTime) + 1;
-				let frameTime = frames[i];
-				while (i > 0) { // Fire multiple events with the same frame.
-					if (frames[i - 1] != frameTime) break;
-					i--;
-				}
-			}
-			for (; i < frameCount && time >= frames[i]; i++)
-				firedEvents.push(this.events[i]);
-		}
-	}
-
-	/** Changes a skeleton's {@link Skeleton#drawOrder}. */
-	export class DrawOrderTimeline extends Timeline {
-		static propertyIds = [ "" + Property.drawOrder ];
-
-		/** The draw order for each key frame. See {@link #setFrame(int, float, int[])}. */
-		drawOrders: Array<Array<number>>;
-
-		constructor (frameCount: number) {
-			super(frameCount, DrawOrderTimeline.propertyIds);
-			this.drawOrders = new Array<Array<number>>(frameCount);
-		}
-
-		getFrameCount () {
-			return this.frames.length;
-		}
-
-		/** Sets the time in seconds and the draw order for the specified key frame.
-		 * @param drawOrder For each slot in {@link Skeleton#slots}, the index of the new draw order. May be null to use setup pose
-		 *           draw order. */
-		setFrame (frame: number, time: number, drawOrder: Array<number>) {
-			this.frames[frame] = time;
-			this.drawOrders[frame] = drawOrder;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			if (direction == MixDirection.mixOut) {
-				if (blend == MixBlend.setup) Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length);
-				return;
-			}
-
-			if (time < this.frames[0]) {
-				if (blend == MixBlend.setup || blend == MixBlend.first) Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length);
-				return;
-			}
-
-			let drawOrderToSetupIndex = this.drawOrders[Timeline.search1(this.frames, time)];
-			if (!drawOrderToSetupIndex)
-				Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length);
-			else {
-				let drawOrder: Array<Slot> = skeleton.drawOrder;
-				let slots: Array<Slot> = skeleton.slots;
-				for (let i = 0, n = drawOrderToSetupIndex.length; i < n; i++)
-					drawOrder[i] = slots[drawOrderToSetupIndex[i]];
-			}
-		}
-	}
-
-	/** Changes an IK constraint's {@link IkConstraint#mix}, {@link IkConstraint#softness},
-	 * {@link IkConstraint#bendDirection}, {@link IkConstraint#stretch}, and {@link IkConstraint#compress}. */
-	export class IkConstraintTimeline extends CurveTimeline {
-		/** The index of the IK constraint slot in {@link Skeleton#ikConstraints} that will be changed. */
-		ikConstraintIndex: number;
-
-		constructor (frameCount: number, bezierCount: number, ikConstraintIndex: number) {
-			super(frameCount, bezierCount, [
-				Property.ikConstraint + "|" + ikConstraintIndex
-			]);
-			this.ikConstraintIndex = ikConstraintIndex;
-		}
-
-		getFrameEntries () {
-			return 6/*ENTRIES*/;
-		}
-
-		/** Sets the time in seconds, mix, softness, bend direction, compress, and stretch for the specified key frame. */
-		setFrame (frame: number, time: number, mix: number, softness: number, bendDirection: number, compress: boolean, stretch: boolean) {
-			frame *= 6/*ENTRIES*/;
-			this.frames[frame] = time;
-			this.frames[frame + 1/*MIX*/] = mix;
-			this.frames[frame + 2/*SOFTNESS*/] = softness;
-			this.frames[frame + 3/*BEND_DIRECTION*/] = bendDirection;
-			this.frames[frame + 4/*COMPRESS*/] = compress ? 1 : 0;
-			this.frames[frame + 5/*STRETCH*/] = stretch ? 1 : 0;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let constraint: IkConstraint = skeleton.ikConstraints[this.ikConstraintIndex];
-			if (!constraint.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					constraint.mix = constraint.data.mix;
-					constraint.softness = constraint.data.softness;
-					constraint.bendDirection = constraint.data.bendDirection;
-					constraint.compress = constraint.data.compress;
-					constraint.stretch = constraint.data.stretch;
-					return;
-				case MixBlend.first:
-					constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
-					constraint.softness += (constraint.data.softness - constraint.softness) * alpha;
-					constraint.bendDirection = constraint.data.bendDirection;
-					constraint.compress = constraint.data.compress;
-					constraint.stretch = constraint.data.stretch;
-				}
-				return;
-			}
-
-			let mix = 0, softness = 0;
-			let i = Timeline.search(frames, time, 6/*ENTRIES*/)
-			let curveType = this.curves[i / 6/*ENTRIES*/];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				mix = frames[i + 1/*MIX*/];
-				softness = frames[i + 2/*SOFTNESS*/];
-				let t = (time - before) / (frames[i + 6/*ENTRIES*/] - before);
-				mix += (frames[i + 6/*ENTRIES*/ + 1/*MIX*/] - mix) * t;
-				softness += (frames[i + 6/*ENTRIES*/ + 2/*SOFTNESS*/] - softness) * t;
-				break;
-			case 1/*STEPPED*/:
-				mix = frames[i + 1/*MIX*/];
-				softness = frames[i + 2/*SOFTNESS*/];
-				break;
-			default:
-				mix = this.getBezierValue(time, i, 1/*MIX*/, curveType - 2/*BEZIER*/);
-				softness = this.getBezierValue(time, i, 2/*SOFTNESS*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-			}
-
-			if (blend == MixBlend.setup) {
-				constraint.mix = constraint.data.mix + (mix - constraint.data.mix) * alpha;
-				constraint.softness = constraint.data.softness + (softness - constraint.data.softness) * alpha;
-
-				if (direction == MixDirection.mixOut) {
-					constraint.bendDirection = constraint.data.bendDirection;
-					constraint.compress = constraint.data.compress;
-					constraint.stretch = constraint.data.stretch;
-				} else {
-					constraint.bendDirection = frames[i + 3/*BEND_DIRECTION*/];
-					constraint.compress = frames[i + 4/*COMPRESS*/] != 0;
-					constraint.stretch = frames[i + 5/*STRETCH*/] != 0;
-				}
-			} else {
-				constraint.mix += (mix - constraint.mix) * alpha;
-				constraint.softness += (softness - constraint.softness) * alpha;
-				if (direction == MixDirection.mixIn) {
-					constraint.bendDirection = frames[i + 3/*BEND_DIRECTION*/];
-					constraint.compress = frames[i + 4/*COMPRESS*/] != 0;
-					constraint.stretch = frames[i + 5/*STRETCH*/] != 0;
-				}
-			}
-		}
-	}
-
-	/** Changes a transform constraint's {@link TransformConstraint#rotateMix}, {@link TransformConstraint#translateMix},
-	 * {@link TransformConstraint#scaleMix}, and {@link TransformConstraint#shearMix}. */
-	export class TransformConstraintTimeline extends CurveTimeline {
-		/** The index of the transform constraint slot in {@link Skeleton#transformConstraints} that will be changed. */
-		transformConstraintIndex: number;
-
-		constructor (frameCount: number, bezierCount: number, transformConstraintIndex: number) {
-			super(frameCount, bezierCount, [
-				Property.transformConstraint + "|" + transformConstraintIndex
-			]);
-			this.transformConstraintIndex = transformConstraintIndex;
-		}
-
-		getFrameEntries () {
-			return 7/*ENTRIES*/;
-		}
-
-		/** The time in seconds, rotate mix, translate mix, scale mix, and shear mix for the specified key frame. */
-		setFrame (frame: number, time: number, mixRotate: number, mixX: number, mixY: number, mixScaleX: number, mixScaleY: number,
-			mixShearY: number) {
-			let frames = this.frames;
-			frame *= 7/*ENTRIES*/;
-			frames[frame] = time;
-			frames[frame + 1/*ROTATE*/] = mixRotate;
-			frames[frame + 2/*X*/] = mixX;
-			frames[frame + 3/*Y*/] = mixY;
-			frames[frame + 4/*SCALEX*/] = mixScaleX;
-			frames[frame + 5/*SCALEY*/] = mixScaleY;
-			frames[frame + 6/*SHEARY*/] = mixShearY;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let constraint: TransformConstraint = skeleton.transformConstraints[this.transformConstraintIndex];
-			if (!constraint.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				let data = constraint.data;
-				switch (blend) {
-				case MixBlend.setup:
-					constraint.mixRotate = data.mixRotate;
-					constraint.mixX = data.mixX;
-					constraint.mixY = data.mixY;
-					constraint.mixScaleX = data.mixScaleX;
-					constraint.mixScaleY = data.mixScaleY;
-					constraint.mixShearY = data.mixShearY;
-					return;
-				case MixBlend.first:
-					constraint.mixRotate += (data.mixRotate - constraint.mixRotate) * alpha;
-					constraint.mixX += (data.mixX - constraint.mixX) * alpha;
-					constraint.mixY += (data.mixY - constraint.mixY) * alpha;
-					constraint.mixScaleX += (data.mixScaleX - constraint.mixScaleX) * alpha;
-					constraint.mixScaleY += (data.mixScaleY - constraint.mixScaleY) * alpha;
-					constraint.mixShearY += (data.mixShearY - constraint.mixShearY) * alpha;
-				}
-				return;
-			}
-
-			let rotate, x, y, scaleX, scaleY, shearY;
-			let i = Timeline.search(frames, time, 7/*ENTRIES*/);
-			let curveType = this.curves[i / 7/*ENTRIES*/];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				rotate = frames[i + 1/*ROTATE*/];
-				x = frames[i + 2/*X*/];
-				y = frames[i + 3/*Y*/];
-				scaleX = frames[i + 4/*SCALEX*/];
-				scaleY = frames[i + 5/*SCALEY*/];
-				shearY = frames[i + 6/*SHEARY*/];
-				let t = (time - before) / (frames[i + 7/*ENTRIES*/] - before);
-				rotate += (frames[i + 7/*ENTRIES*/ + 1/*ROTATE*/] - rotate) * t;
-				x += (frames[i + 7/*ENTRIES*/ + 2/*X*/] - x) * t;
-				y += (frames[i + 7/*ENTRIES*/ + 3/*Y*/] - y) * t;
-				scaleX += (frames[i + 7/*ENTRIES*/ + 4/*SCALEX*/] - scaleX) * t;
-				scaleY += (frames[i + 7/*ENTRIES*/ + 5/*SCALEY*/] - scaleY) * t;
-				shearY += (frames[i + 7/*ENTRIES*/ + 6/*SHEARY*/] - shearY) * t;
-				break;
-			case 1/*STEPPED*/:
-				rotate = frames[i + 1/*ROTATE*/];
-				x = frames[i + 2/*X*/];
-				y = frames[i + 3/*Y*/];
-				scaleX = frames[i + 4/*SCALEX*/];
-				scaleY = frames[i + 5/*SCALEY*/];
-				shearY = frames[i + 6/*SHEARY*/];
-				break;
-			default:
-				rotate = this.getBezierValue(time, i, 1/*ROTATE*/, curveType - 2/*BEZIER*/);
-				x = this.getBezierValue(time, i, 2/*X*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-				y = this.getBezierValue(time, i, 3/*Y*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
-				scaleX = this.getBezierValue(time, i, 4/*SCALEX*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
-				scaleY = this.getBezierValue(time, i, 5/*SCALEY*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
-				shearY = this.getBezierValue(time, i, 6/*SHEARY*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
-			}
-
-			if (blend == MixBlend.setup) {
-				let data = constraint.data;
-				constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
-				constraint.mixX = data.mixX + (x - data.mixX) * alpha;
-				constraint.mixY = data.mixY + (y - data.mixY) * alpha;
-				constraint.mixScaleX = data.mixScaleX + (scaleX - data.mixScaleX) * alpha;
-				constraint.mixScaleY = data.mixScaleY + (scaleY - data.mixScaleY) * alpha;
-				constraint.mixShearY = data.mixShearY + (shearY - data.mixShearY) * alpha;
-			} else {
-				constraint.mixRotate += (rotate - constraint.mixRotate) * alpha;
-				constraint.mixX += (x - constraint.mixX) * alpha;
-				constraint.mixY += (y - constraint.mixY) * alpha;
-				constraint.mixScaleX += (scaleX - constraint.mixScaleX) * alpha;
-				constraint.mixScaleY += (scaleY - constraint.mixScaleY) * alpha;
-				constraint.mixShearY += (shearY - constraint.mixShearY) * alpha;
-			}
-		}
-	}
-
-	/** Changes a path constraint's {@link PathConstraint#position}. */
-	export class PathConstraintPositionTimeline extends CurveTimeline1 {
-		/** The index of the path constraint slot in {@link Skeleton#pathConstraints} that will be changed. */
-		pathConstraintIndex: number;
-
-		constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
-			super(frameCount, bezierCount, Property.pathConstraintPosition + "|" + pathConstraintIndex);
-			this.pathConstraintIndex = pathConstraintIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
-			if (!constraint.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					constraint.position = constraint.data.position;
-					return;
-				case MixBlend.first:
-					constraint.position += (constraint.data.position - constraint.position) * alpha;
-				}
-				return;
-			}
-
-			let position = this.getCurveValue(time);
-
-			if (blend == MixBlend.setup)
-				constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
-			else
-				constraint.position += (position - constraint.position) * alpha;
-		}
-	}
-
-	/** Changes a path constraint's {@link PathConstraint#spacing}. */
-	export class PathConstraintSpacingTimeline extends CurveTimeline1 {
-		/** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */
-		pathConstraintIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
-			super(frameCount, bezierCount, Property.pathConstraintSpacing + "|" + pathConstraintIndex);
-			this.pathConstraintIndex = pathConstraintIndex;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
-			if (!constraint.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					constraint.spacing = constraint.data.spacing;
-					return;
-				case MixBlend.first:
-					constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
-				}
-				return;
-			}
-
-			let spacing = this.getCurveValue(time);
-
-			if (blend == MixBlend.setup)
-				constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
-			else
-				constraint.spacing += (spacing - constraint.spacing) * alpha;
-		}
-	}
-
-	/** Changes a transform constraint's {@link PathConstraint#getMixRotate()}, {@link PathConstraint#getMixX()}, and
-	 * {@link PathConstraint#getMixY()}. */
-	export class PathConstraintMixTimeline extends CurveTimeline {
-		/** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */
-		pathConstraintIndex = 0;
-
-		constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
-			super(frameCount, bezierCount, [
-				Property.pathConstraintMix + "|" + pathConstraintIndex
-			]);
-			this.pathConstraintIndex = pathConstraintIndex;
-		}
-
-		getFrameEntries () {
-			return 4/*ENTRIES*/;
-		}
-
-		setFrame (frame: number, time: number, mixRotate: number, mixX: number, mixY: number) {
-			let frames = this.frames;
-			frame <<= 2;
-			frames[frame] = time;
-			frames[frame + 1/*ROTATE*/] = mixRotate;
-			frames[frame + 2/*X*/] = mixX;
-			frames[frame + 3/*Y*/] = mixY;
-		}
-
-		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
-			let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
-			if (!constraint.active) return;
-
-			let frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.setup:
-					constraint.mixRotate = constraint.data.mixRotate;
-					constraint.mixX = constraint.data.mixX;
-					constraint.mixY = constraint.data.mixY;
-					return;
-				case MixBlend.first:
-					constraint.mixRotate += (constraint.data.mixRotate - constraint.mixRotate) * alpha;
-					constraint.mixX += (constraint.data.mixX - constraint.mixX) * alpha;
-					constraint.mixY += (constraint.data.mixY - constraint.mixY) * alpha;
-				}
-				return;
-			}
-
-			let rotate, x, y;
-			let i = Timeline.search(frames, time, 4/*ENTRIES*/);
-			let curveType = this.curves[i >> 2];
-			switch (curveType) {
-			case 0/*LINEAR*/:
-				let before = frames[i];
-				rotate = frames[i + 1/*ROTATE*/];
-				x = frames[i + 2/*X*/];
-				y = frames[i + 3/*Y*/];
-				let t = (time - before) / (frames[i + 4/*ENTRIES*/] - before);
-				rotate += (frames[i + 4/*ENTRIES*/ + 1/*ROTATE*/] - rotate) * t;
-				x += (frames[i + 4/*ENTRIES*/ + 2/*X*/] - x) * t;
-				y += (frames[i + 4/*ENTRIES*/ + 3/*Y*/] - y) * t;
-				break;
-			case 1/*STEPPED*/:
-				rotate = frames[i + 1/*ROTATE*/];
-				x = frames[i + 2/*X*/];
-				y = frames[i + 3/*Y*/];
-				break;
-			default:
-				rotate = this.getBezierValue(time, i, 1/*ROTATE*/, curveType - 2/*BEZIER*/);
-				x = this.getBezierValue(time, i, 2/*X*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
-				y = this.getBezierValue(time, i, 3/*Y*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
-			}
-
-			if (blend == MixBlend.setup) {
-				let data = constraint.data;
-				constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
-				constraint.mixX = data.mixX + (x - data.mixX) * alpha;
-				constraint.mixY = data.mixY + (y - data.mixY) * alpha;
-			} else {
-				constraint.mixRotate += (rotate - constraint.mixRotate) * alpha;
-				constraint.mixX += (x - constraint.mixX) * alpha;
-				constraint.mixY += (y - constraint.mixY) * alpha;
-			}
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** A simple container for a list of timelines and a name. */
+	export class Animation {
+		/** The animation's name, which is unique across all animations in the skeleton. */
+		name: string;
+		timelines: Array<Timeline>;
+		timelineIds: StringSet;
+
+		/** The duration of the animation in seconds, which is the highest time of all keys in the timeline. */
+		duration: number;
+
+		constructor (name: string, timelines: Array<Timeline>, duration: number) {
+			if (!name) throw new Error("name cannot be null.");
+			this.name = name;
+			this.setTimelines(timelines);
+			this.duration = duration;
+		}
+
+		setTimelines (timelines: Array<Timeline>) {
+			if (!timelines) throw new Error("timelines cannot be null.");
+			this.timelines = timelines;
+			this.timelineIds = new StringSet();
+			for (var i = 0; i < timelines.length; i++)
+				this.timelineIds.addAll(timelines[i].getPropertyIds());
+		}
+
+		hasTimeline (ids: string[]): boolean {
+			for (let i = 0; i < ids.length; i++)
+				if (this.timelineIds.contains(ids[i])) return true;
+			return false;
+		}
+
+		/** Applies all the animation's timelines to the specified skeleton.
+		 *
+		 * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}.
+		 * @param loop If true, the animation repeats after {@link #getDuration()}.
+		 * @param events May be null to ignore fired events. */
+		apply (skeleton: Skeleton, lastTime: number, time: number, loop: boolean, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			if (!skeleton) throw new Error("skeleton cannot be null.");
+
+			if (loop && this.duration != 0) {
+				time %= this.duration;
+				if (lastTime > 0) lastTime %= this.duration;
+			}
+
+			let timelines = this.timelines;
+			for (let i = 0, n = timelines.length; i < n; i++)
+				timelines[i].apply(skeleton, lastTime, time, events, alpha, blend, direction);
+		}
+	}
+
+	/** Controls how a timeline value is mixed with the setup pose value or current pose value when a timeline's `alpha`
+	 * < 1.
+	 *
+	 * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */
+	export enum MixBlend {
+		/** Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup
+		 * value is set. */
+		setup,
+		/** Transitions from the current value to the timeline value. Before the first key, transitions from the current value to
+		 * the setup value. Timelines which perform instant transitions, such as {@link DrawOrderTimeline} or
+		 * {@link AttachmentTimeline}, use the setup value before the first key.
+		 *
+		 * `first` is intended for the first animations applied, not for animations layered on top of those. */
+		first,
+		/** Transitions from the current value to the timeline value. No change is made before the first key (the current value is
+		 * kept until the first key).
+		 *
+		 * `replace` is intended for animations layered on top of others, not for the first animations applied. */
+		replace,
+		/** Transitions from the current value to the current value plus the timeline value. No change is made before the first key
+		 * (the current value is kept until the first key).
+		 *
+		 * `add` is intended for animations layered on top of others, not for the first animations applied. Properties
+		 * keyed by additive animations must be set manually or by another animation before applying the additive animations, else
+		 * the property values will increase continually. */
+		add
+	}
+
+	/** Indicates whether a timeline's `alpha` is mixing out over time toward 0 (the setup or current pose value) or
+	 * mixing in toward 1 (the timeline's value).
+	 *
+	 * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */
+	export enum MixDirection {
+		mixIn, mixOut
+	}
+
+	const Property = {
+		rotate: 0,
+		x: 1,
+		y: 2,
+		scaleX: 3,
+		scaleY: 4,
+		shearX: 5,
+		shearY: 6,
+
+		rgb: 7,
+		alpha: 8,
+		rgb2: 9,
+
+		attachment: 10,
+		deform: 11,
+
+		event: 12,
+		drawOrder: 13,
+
+		ikConstraint: 14,
+		transformConstraint: 15,
+
+		pathConstraintPosition: 16,
+		pathConstraintSpacing: 17,
+		pathConstraintMix: 18
+	}
+
+	/** The interface for all timelines. */
+	export abstract class Timeline {
+		propertyIds: string[];
+		frames: ArrayLike<number>;
+
+		constructor (frameCount: number, propertyIds: string[]) {
+			this.propertyIds = propertyIds;
+			this.frames = Utils.newFloatArray(frameCount * this.getFrameEntries());
+		}
+
+		getPropertyIds () {
+			return this.propertyIds;
+		}
+
+		getFrameEntries (): number {
+			return 1;
+		}
+
+		getFrameCount () {
+			return this.frames.length / this.getFrameEntries();
+		}
+
+		getDuration (): number {
+			return this.frames[this.frames.length - this.getFrameEntries()];
+		}
+
+		abstract apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;
+
+		static search1 (frames: ArrayLike<number>, time: number) {
+			let n = frames.length;
+			for (let i = 1; i < n; i++)
+				if (frames[i] > time) return i - 1;
+			return n - 1;
+		}
+
+		static search (frames: ArrayLike<number>, time: number, step: number) {
+			let n = frames.length;
+			for (let i = step; i < n; i += step)
+				if (frames[i] > time) return i - step;
+			return n - step;
+		}
+	}
+
+	export interface BoneTimeline {
+		/** The index of the bone in {@link Skeleton#bones} that will be changed. */
+		boneIndex: number;
+	}
+
+	export interface SlotTimeline {
+		/** The index of the slot in {@link Skeleton#slots} that will be changed. */
+		slotIndex: number;
+	}
+
+	/** The base class for timelines that use interpolation between key frame values. */
+	export abstract class CurveTimeline extends Timeline {
+		protected curves: ArrayLike<number>; // type, x, y, ...
+
+		constructor (frameCount: number, bezierCount: number, propertyIds: string[]) {
+			super(frameCount, propertyIds);
+			this.curves = Utils.newFloatArray(frameCount + bezierCount * 18/*BEZIER_SIZE*/);
+			this.curves[frameCount - 1] = 1/*STEPPED*/;
+		}
+
+		/** Sets the specified key frame to linear interpolation. */
+		setLinear (frame: number) {
+			this.curves[frame] = 0/*LINEAR*/;
+		}
+
+		/** Sets the specified key frame to stepped interpolation. */
+		setStepped (frame: number) {
+			this.curves[frame] = 1/*STEPPED*/;
+		}
+
+		/** Shrinks the storage for Bezier curves, for use when <code>bezierCount</code> (specified in the constructor) was larger
+		 * than the actual number of Bezier curves. */
+		shrink (bezierCount: number) {
+			let size = this.getFrameCount() + bezierCount * 18/*BEZIER_SIZE*/;
+			if (this.curves.length > size) {
+				let newCurves = Utils.newFloatArray(size);
+				Utils.arrayCopy(this.curves, 0, newCurves, 0, size);
+				this.curves = newCurves;
+			}
+		}
+
+		/** Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than
+		 * one curve per frame.
+		 * @param bezier The ordinal of this Bezier curve for this timeline, between 0 and <code>bezierCount - 1</code> (specified
+		 *           in the constructor), inclusive.
+		 * @param frame Between 0 and <code>frameCount - 1</code>, inclusive.
+		 * @param value The index of the value for this frame that this curve is used for.
+		 * @param time1 The time for the first key.
+		 * @param value1 The value for the first key.
+		 * @param cx1 The time for the first Bezier handle.
+		 * @param cy1 The value for the first Bezier handle.
+		 * @param cx2 The time of the second Bezier handle.
+		 * @param cy2 The value for the second Bezier handle.
+		 * @param time2 The time for the second key.
+		 * @param value2 The value for the second key. */
+		setBezier (bezier: number, frame: number, value: number, time1: number, value1: number, cx1: number, cy1: number, cx2: number,
+			cy2: number, time2: number, value2: number) {
+			let curves = this.curves;
+			let i = this.getFrameCount() + bezier * 18/*BEZIER_SIZE*/;
+			if (value == 0) curves[frame] = 2/*BEZIER*/ + i;
+			let tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = (value1 - cy1 * 2 + cy2) * 0.03;
+			let dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006;
+			let ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy;
+			let dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = (cy1 - value1) * 0.3 + tmpy + dddy * 0.16666667;
+			let x = time1 + dx, y = value1 + dy;
+			for (let n = i + 18/*BEZIER_SIZE*/; i < n; i += 2) {
+				curves[i] = x;
+				curves[i + 1] = y;
+				dx += ddx;
+				dy += ddy;
+				ddx += dddx;
+				ddy += dddy;
+				x += dx;
+				y += dy;
+			}
+		}
+
+		/** Returns the Bezier interpolated value for the specified time.
+		 * @param frameIndex The index into {@link #getFrames()} for the values of the frame before <code>time</code>.
+		 * @param valueOffset The offset from <code>frameIndex</code> to the value this curve is used for.
+		 * @param i The index of the Bezier segments. See {@link #getCurveType(int)}. */
+		getBezierValue (time: number, frameIndex: number, valueOffset: number, i: number) {
+			let curves = this.curves;
+			if (curves[i] > time) {
+				let x = this.frames[frameIndex], y = this.frames[frameIndex + valueOffset];
+				return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
+			}
+			let n = i + 18/*BEZIER_SIZE*/;
+			for (i += 2; i < n; i += 2) {
+				if (curves[i] >= time) {
+					let x = curves[i - 2], y = curves[i - 1];
+					return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
+				}
+			}
+			frameIndex += this.getFrameEntries();
+			let x = curves[n - 2], y = curves[n - 1];
+			return y + (time - x) / (this.frames[frameIndex] - x) * (this.frames[frameIndex + valueOffset] - y);
+		}
+	}
+
+	export abstract class CurveTimeline1 extends CurveTimeline {
+		constructor (frameCount: number, bezierCount: number, propertyId: string) {
+			super(frameCount, bezierCount, [propertyId]);
+		}
+
+		getFrameEntries () {
+			return 2/*ENTRIES*/;
+		}
+
+		/** Sets the time and value for the specified frame.
+		 * @param frame Between 0 and <code>frameCount</code>, inclusive.
+		 * @param time The frame time in seconds. */
+		setFrame (frame: number, time: number, value: number) {
+			frame <<= 1;
+			this.frames[frame] = time;
+			this.frames[frame + 1/*VALUE*/] = value;
+		}
+
+		/** Returns the interpolated value for the specified time. */
+		getCurveValue (time: number) {
+			let frames = this.frames;
+			let i = frames.length - 2;
+			for (let ii = 2; ii <= i; ii += 2) {
+				if (frames[ii] > time) {
+					i = ii - 2;
+					break;
+				}
+			}
+
+			let curveType = this.curves[i >> 1];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i], value = frames[i + 1/*VALUE*/];
+					return value + (time - before) / (frames[i + 2/*ENTRIES*/] - before) * (frames[i + 2/*ENTRIES*/ + 1/*VALUE*/] - value);
+				case 1/*STEPPED*/:
+					return frames[i + 1/*VALUE*/];
+			}
+			return this.getBezierValue(time, i, 1/*VALUE*/, curveType - 2/*BEZIER*/);
+		}
+	}
+
+	/** The base class for a {@link CurveTimeline} which sets two properties. */
+	export abstract class CurveTimeline2 extends CurveTimeline {
+		/** @param bezierCount The maximum number of Bezier curves. See {@link #shrink(int)}.
+		 * @param propertyIds Unique identifiers for the properties the timeline modifies. */
+		constructor (frameCount: number, bezierCount: number, propertyId1: string, propertyId2: string) {
+			super(frameCount, bezierCount, [propertyId1, propertyId2]);
+		}
+
+		getFrameEntries () {
+			return 3/*ENTRIES*/;
+		}
+
+		/** Sets the time and values for the specified frame.
+		 * @param frame Between 0 and <code>frameCount</code>, inclusive.
+		 * @param time The frame time in seconds. */
+		setFrame (frame: number, time: number, value1: number, value2: number) {
+			frame *= 3/*ENTRIES*/;
+			this.frames[frame] = time;
+			this.frames[frame + 1/*VALUE1*/] = value1;
+			this.frames[frame + 2/*VALUE2*/] = value2;
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#rotation}. */
+	export class RotateTimeline extends CurveTimeline1 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount, Property.rotate + "|" + boneIndex);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.rotation = bone.data.rotation;
+						return;
+					case MixBlend.first:
+						bone.rotation += (bone.data.rotation - bone.rotation) * alpha;
+				}
+				return;
+			}
+
+			let r = this.getCurveValue(time);
+			switch (blend) {
+				case MixBlend.setup:
+					bone.rotation = bone.data.rotation + r * alpha;
+					break;
+				case MixBlend.first:
+				case MixBlend.replace:
+					r += bone.data.rotation - bone.rotation;
+				case MixBlend.add:
+					bone.rotation += r * alpha;
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#x} and {@link Bone#y}. */
+	export class TranslateTimeline extends CurveTimeline2 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount,
+				Property.x + "|" + boneIndex,
+				Property.y + "|" + boneIndex,
+			);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.x = bone.data.x;
+						bone.y = bone.data.y;
+						return;
+					case MixBlend.first:
+						bone.x += (bone.data.x - bone.x) * alpha;
+						bone.y += (bone.data.y - bone.y) * alpha;
+				}
+				return;
+			}
+
+			let x = 0, y = 0;
+			let i = Timeline.search(frames, time, 3/*ENTRIES*/);
+			let curveType = this.curves[i / 3/*ENTRIES*/];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					x = frames[i + 1/*VALUE1*/];
+					y = frames[i + 2/*VALUE2*/];
+					let t = (time - before) / (frames[i + 3/*ENTRIES*/] - before);
+					x += (frames[i + 3/*ENTRIES*/ + 1/*VALUE1*/] - x) * t;
+					y += (frames[i + 3/*ENTRIES*/ + 2/*VALUE2*/] - y) * t;
+					break;
+				case 1/*STEPPED*/:
+					x = frames[i + 1/*VALUE1*/];
+					y = frames[i + 2/*VALUE2*/];
+					break;
+				default:
+					x = this.getBezierValue(time, i, 1/*VALUE1*/, curveType - 2/*BEZIER*/);
+					y = this.getBezierValue(time, i, 2/*VALUE2*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+			}
+
+			switch (blend) {
+				case MixBlend.setup:
+					bone.x = bone.data.x + x * alpha;
+					bone.y = bone.data.y + y * alpha;
+					break;
+				case MixBlend.first:
+				case MixBlend.replace:
+					bone.x += (bone.data.x + x - bone.x) * alpha;
+					bone.y += (bone.data.y + y - bone.y) * alpha;
+					break;
+				case MixBlend.add:
+					bone.x += x * alpha;
+					bone.y += y * alpha;
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#x}. */
+	export class TranslateXTimeline extends CurveTimeline1 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount, Property.x + "|" + boneIndex);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.x = bone.data.x;
+						return;
+					case MixBlend.first:
+						bone.x += (bone.data.x - bone.x) * alpha;
+				}
+				return;
+			}
+
+			let x = this.getCurveValue(time);
+			switch (blend) {
+				case MixBlend.setup:
+					bone.x = bone.data.x + x * alpha;
+					break;
+				case MixBlend.first:
+				case MixBlend.replace:
+					bone.x += (bone.data.x + x - bone.x) * alpha;
+					break;
+				case MixBlend.add:
+					bone.x += x * alpha;
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#x}. */
+	export class TranslateYTimeline extends CurveTimeline1 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount, Property.y + "|" + boneIndex);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.y = bone.data.y;
+						return;
+					case MixBlend.first:
+						bone.y += (bone.data.y - bone.y) * alpha;
+				}
+				return;
+			}
+
+			let y = this.getCurveValue(time);
+			switch (blend) {
+				case MixBlend.setup:
+					bone.y = bone.data.y + y * alpha;
+					break;
+				case MixBlend.first:
+				case MixBlend.replace:
+					bone.y += (bone.data.y + y - bone.y) * alpha;
+					break;
+				case MixBlend.add:
+					bone.y += y * alpha;
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */
+	export class ScaleTimeline extends CurveTimeline2 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount,
+				Property.scaleX + "|" + boneIndex,
+				Property.scaleY + "|" + boneIndex
+			);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.scaleX = bone.data.scaleX;
+						bone.scaleY = bone.data.scaleY;
+						return;
+					case MixBlend.first:
+						bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
+						bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
+				}
+				return;
+			}
+
+			let x, y;
+			let i = Timeline.search(frames, time, 3/*ENTRIES*/);
+			let curveType = this.curves[i / 3/*ENTRIES*/];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					x = frames[i + 1/*VALUE1*/];
+					y = frames[i + 2/*VALUE2*/];
+					let t = (time - before) / (frames[i + 3/*ENTRIES*/] - before);
+					x += (frames[i + 3/*ENTRIES*/ + 1/*VALUE1*/] - x) * t;
+					y += (frames[i + 3/*ENTRIES*/ + 2/*VALUE2*/] - y) * t;
+					break;
+				case 1/*STEPPED*/:
+					x = frames[i + 1/*VALUE1*/];
+					y = frames[i + 2/*VALUE2*/];
+					break;
+				default:
+					x = this.getBezierValue(time, i, 1/*VALUE1*/, curveType - 2/*BEZIER*/);
+					y = this.getBezierValue(time, i, 2/*VALUE2*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+			}
+			x *= bone.data.scaleX;
+			y *= bone.data.scaleY;
+
+			if (alpha == 1) {
+				if (blend == MixBlend.add) {
+					bone.scaleX += x - bone.data.scaleX;
+					bone.scaleY += y - bone.data.scaleY;
+				} else {
+					bone.scaleX = x;
+					bone.scaleY = y;
+				}
+			} else {
+				let bx = 0, by = 0;
+				if (direction == MixDirection.mixOut) {
+					switch (blend) {
+						case MixBlend.setup:
+							bx = bone.data.scaleX;
+							by = bone.data.scaleY;
+							bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
+							bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
+							break;
+						case MixBlend.first:
+						case MixBlend.replace:
+							bx = bone.scaleX;
+							by = bone.scaleY;
+							bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
+							bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
+							break;
+						case MixBlend.add:
+							bx = bone.scaleX;
+							by = bone.scaleY;
+							bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bone.data.scaleX) * alpha;
+							bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - bone.data.scaleY) * alpha;
+					}
+				} else {
+					switch (blend) {
+						case MixBlend.setup:
+							bx = Math.abs(bone.data.scaleX) * MathUtils.signum(x);
+							by = Math.abs(bone.data.scaleY) * MathUtils.signum(y);
+							bone.scaleX = bx + (x - bx) * alpha;
+							bone.scaleY = by + (y - by) * alpha;
+							break;
+						case MixBlend.first:
+						case MixBlend.replace:
+							bx = Math.abs(bone.scaleX) * MathUtils.signum(x);
+							by = Math.abs(bone.scaleY) * MathUtils.signum(y);
+							bone.scaleX = bx + (x - bx) * alpha;
+							bone.scaleY = by + (y - by) * alpha;
+							break;
+						case MixBlend.add:
+							bx = MathUtils.signum(x);
+							by = MathUtils.signum(y);
+							bone.scaleX = Math.abs(bone.scaleX) * bx + (x - Math.abs(bone.data.scaleX) * bx) * alpha;
+							bone.scaleY = Math.abs(bone.scaleY) * by + (y - Math.abs(bone.data.scaleY) * by) * alpha;
+					}
+				}
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */
+	export class ScaleXTimeline extends CurveTimeline1 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount, Property.scaleX + "|" + boneIndex);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.scaleX = bone.data.scaleX;
+						return;
+					case MixBlend.first:
+						bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
+				}
+				return;
+			}
+
+			let x = this.getCurveValue(time) * bone.data.scaleX;
+			if (alpha == 1) {
+				if (blend == MixBlend.add)
+					bone.scaleX += x - bone.data.scaleX;
+				else
+					bone.scaleX = x;
+			} else {
+				// Mixing out uses sign of setup or current pose, else use sign of key.
+				let bx = 0;
+				if (direction == MixDirection.mixOut) {
+					switch (blend) {
+						case MixBlend.setup:
+							bx = bone.data.scaleX;
+							bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
+							break;
+						case MixBlend.first:
+						case MixBlend.replace:
+							bx = bone.scaleX;
+							bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
+							break;
+						case MixBlend.add:
+							bx = bone.scaleX;
+							bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bone.data.scaleX) * alpha;
+					}
+				} else {
+					switch (blend) {
+						case MixBlend.setup:
+							bx = Math.abs(bone.data.scaleX) * MathUtils.signum(x);
+							bone.scaleX = bx + (x - bx) * alpha;
+							break;
+						case MixBlend.first:
+						case MixBlend.replace:
+							bx = Math.abs(bone.scaleX) * MathUtils.signum(x);
+							bone.scaleX = bx + (x - bx) * alpha;
+							break;
+						case MixBlend.add:
+							bx = MathUtils.signum(x);
+							bone.scaleX = Math.abs(bone.scaleX) * bx + (x - Math.abs(bone.data.scaleX) * bx) * alpha;
+					}
+				}
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */
+	export class ScaleYTimeline extends CurveTimeline1 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount, Property.scaleY + "|" + boneIndex);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.scaleY = bone.data.scaleY;
+						return;
+					case MixBlend.first:
+						bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
+				}
+				return;
+			}
+
+			let y = this.getCurveValue(time) * bone.data.scaleY;
+			if (alpha == 1) {
+				if (blend == MixBlend.add)
+					bone.scaleY += y - bone.data.scaleY;
+				else
+					bone.scaleY = y;
+			} else {
+				// Mixing out uses sign of setup or current pose, else use sign of key.
+				let by = 0;
+				if (direction == MixDirection.mixOut) {
+					switch (blend) {
+						case MixBlend.setup:
+							by = bone.data.scaleY;
+							bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
+							break;
+						case MixBlend.first:
+						case MixBlend.replace:
+							by = bone.scaleY;
+							bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
+							break;
+						case MixBlend.add:
+							by = bone.scaleY;
+							bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - bone.data.scaleY) * alpha;
+					}
+				} else {
+					switch (blend) {
+						case MixBlend.setup:
+							by = Math.abs(bone.data.scaleY) * MathUtils.signum(y);
+							bone.scaleY = by + (y - by) * alpha;
+							break;
+						case MixBlend.first:
+						case MixBlend.replace:
+							by = Math.abs(bone.scaleY) * MathUtils.signum(y);
+							bone.scaleY = by + (y - by) * alpha;
+							break;
+						case MixBlend.add:
+							by = MathUtils.signum(y);
+							bone.scaleY = Math.abs(bone.scaleY) * by + (y - Math.abs(bone.data.scaleY) * by) * alpha;
+					}
+				}
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
+	export class ShearTimeline extends CurveTimeline2 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount,
+				Property.shearX + "|" + boneIndex,
+				Property.shearY + "|" + boneIndex
+			);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.shearX = bone.data.shearX;
+						bone.shearY = bone.data.shearY;
+						return;
+					case MixBlend.first:
+						bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
+						bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
+				}
+				return;
+			}
+
+			let x = 0, y = 0;
+			let i = Timeline.search(frames, time, 3/*ENTRIES*/);
+			let curveType = this.curves[i / 3/*ENTRIES*/];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					x = frames[i + 1/*VALUE1*/];
+					y = frames[i + 2/*VALUE2*/];
+					let t = (time - before) / (frames[i + 3/*ENTRIES*/] - before);
+					x += (frames[i + 3/*ENTRIES*/ + 1/*VALUE1*/] - x) * t;
+					y += (frames[i + 3/*ENTRIES*/ + 2/*VALUE2*/] - y) * t;
+					break;
+				case 1/*STEPPED*/:
+					x = frames[i + 1/*VALUE1*/];
+					y = frames[i + 2/*VALUE2*/];
+					break;
+				default:
+					x = this.getBezierValue(time, i, 1/*VALUE1*/, curveType - 2/*BEZIER*/);
+					y = this.getBezierValue(time, i, 2/*VALUE2*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+			}
+
+			switch (blend) {
+				case MixBlend.setup:
+					bone.shearX = bone.data.shearX + x * alpha;
+					bone.shearY = bone.data.shearY + y * alpha;
+					break;
+				case MixBlend.first:
+				case MixBlend.replace:
+					bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
+					bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
+					break;
+				case MixBlend.add:
+					bone.shearX += x * alpha;
+					bone.shearY += y * alpha;
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
+	export class ShearXTimeline extends CurveTimeline1 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount, Property.shearX + "|" + boneIndex);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.shearX = bone.data.shearX;
+						return;
+					case MixBlend.first:
+						bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
+				}
+				return;
+			}
+
+			let x = this.getCurveValue(time);
+			switch (blend) {
+				case MixBlend.setup:
+					bone.shearX = bone.data.shearX + x * alpha;
+					break;
+				case MixBlend.first:
+				case MixBlend.replace:
+					bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
+					break;
+				case MixBlend.add:
+					bone.shearX += x * alpha;
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
+	export class ShearYTimeline extends CurveTimeline1 implements BoneTimeline {
+		boneIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, boneIndex: number) {
+			super(frameCount, bezierCount, Property.shearY + "|" + boneIndex);
+			this.boneIndex = boneIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let bone = skeleton.bones[this.boneIndex];
+			if (!bone.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.shearY = bone.data.shearY;
+						return;
+					case MixBlend.first:
+						bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
+				}
+				return;
+			}
+
+			let y = this.getCurveValue(time);
+			switch (blend) {
+				case MixBlend.setup:
+					bone.shearY = bone.data.shearY + y * alpha;
+					break;
+				case MixBlend.first:
+				case MixBlend.replace:
+					bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
+					break;
+				case MixBlend.add:
+					bone.shearY += y * alpha;
+			}
+		}
+	}
+
+	/** Changes a slot's {@link Slot#color}. */
+	export class RGBATimeline extends CurveTimeline implements SlotTimeline {
+		slotIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
+			super(frameCount, bezierCount, [
+				Property.rgb + "|" + slotIndex,
+				Property.alpha + "|" + slotIndex
+			]);
+			this.slotIndex = slotIndex;
+		}
+
+		getFrameEntries () {
+			return 5/*ENTRIES*/;
+		}
+
+		/** Sets the time in seconds, red, green, blue, and alpha for the specified key frame. */
+		setFrame (frame: number, time: number, r: number, g: number, b: number, a: number) {
+			frame *= 5/*ENTRIES*/;
+			this.frames[frame] = time;
+			this.frames[frame + 1/*R*/] = r;
+			this.frames[frame + 2/*G*/] = g;
+			this.frames[frame + 3/*B*/] = b;
+			this.frames[frame + 4/*A*/] = a;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let slot = skeleton.slots[this.slotIndex];
+			if (!slot.bone.active) return;
+
+			let frames = this.frames;
+			let color = slot.color;
+			if (time < frames[0]) {
+				let setup = slot.data.color;
+				switch (blend) {
+					case MixBlend.setup:
+						color.setFromColor(setup);
+						return;
+					case MixBlend.first:
+						color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha,
+							(setup.a - color.a) * alpha);
+				}
+				return;
+			}
+
+			let r = 0, g = 0, b = 0, a = 0;
+			let i = Timeline.search(frames, time, 5/*ENTRIES*/);
+			let curveType = this.curves[i / 5/*ENTRIES*/];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					r = frames[i + 1/*R*/];
+					g = frames[i + 2/*G*/];
+					b = frames[i + 3/*B*/];
+					a = frames[i + 4/*A*/];
+					let t = (time - before) / (frames[i + 5/*ENTRIES*/] - before);
+					r += (frames[i + 5/*ENTRIES*/ + 1/*R*/] - r) * t;
+					g += (frames[i + 5/*ENTRIES*/ + 2/*G*/] - g) * t;
+					b += (frames[i + 5/*ENTRIES*/ + 3/*B*/] - b) * t;
+					a += (frames[i + 5/*ENTRIES*/ + 4/*A*/] - a) * t;
+					break;
+				case 1/*STEPPED*/:
+					r = frames[i + 1/*R*/];
+					g = frames[i + 2/*G*/];
+					b = frames[i + 3/*B*/];
+					a = frames[i + 4/*A*/];
+					break;
+				default:
+					r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
+					g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+					b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
+					a = this.getBezierValue(time, i, 4/*A*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
+			}
+			if (alpha == 1)
+				color.set(r, g, b, a);
+			else {
+				if (blend == MixBlend.setup) color.setFromColor(slot.data.color);
+				color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
+			}
+		}
+	}
+
+	/** Changes a slot's {@link Slot#color}. */
+	export class RGBTimeline extends CurveTimeline implements SlotTimeline {
+		slotIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
+			super(frameCount, bezierCount, [
+				Property.rgb + "|" + slotIndex
+			]);
+			this.slotIndex = slotIndex;
+		}
+
+		getFrameEntries () {
+			return 4/*ENTRIES*/;
+		}
+
+		/** Sets the time in seconds, red, green, blue, and alpha for the specified key frame. */
+		setFrame (frame: number, time: number, r: number, g: number, b: number) {
+			frame <<= 2;
+			this.frames[frame] = time;
+			this.frames[frame + 1/*R*/] = r;
+			this.frames[frame + 2/*G*/] = g;
+			this.frames[frame + 3/*B*/] = b;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let slot = skeleton.slots[this.slotIndex];
+			if (!slot.bone.active) return;
+
+			let frames = this.frames;
+			let color = slot.color;
+			if (time < frames[0]) {
+				let setup = slot.data.color;
+				switch (blend) {
+					case MixBlend.setup:
+						color.r = setup.r;
+						color.g = setup.g;
+						color.b = setup.b;
+						return;
+					case MixBlend.first:
+						color.r += (setup.r - color.r) * alpha;
+						color.g += (setup.g - color.g) * alpha;
+						color.b += (setup.b - color.b) * alpha;
+				}
+				return;
+			}
+
+			let r = 0, g = 0, b = 0;
+			let i = Timeline.search(frames, time, 4/*ENTRIES*/);
+			let curveType = this.curves[i >> 2];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					r = frames[i + 1/*R*/];
+					g = frames[i + 2/*G*/];
+					b = frames[i + 3/*B*/];
+					let t = (time - before) / (frames[i + 4/*ENTRIES*/] - before);
+					r += (frames[i + 4/*ENTRIES*/ + 1/*R*/] - r) * t;
+					g += (frames[i + 4/*ENTRIES*/ + 2/*G*/] - g) * t;
+					b += (frames[i + 4/*ENTRIES*/ + 3/*B*/] - b) * t;
+					break;
+				case 1/*STEPPED*/:
+					r = frames[i + 1/*R*/];
+					g = frames[i + 2/*G*/];
+					b = frames[i + 3/*B*/];
+					break;
+				default:
+					r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
+					g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+					b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
+			}
+			if (alpha == 1) {
+				color.r = r;
+				color.g = g;
+				color.b = b;
+			} else {
+				if (blend == MixBlend.setup) {
+					let setup = slot.data.color;
+					color.r = setup.r;
+					color.g = setup.g;
+					color.b = setup.b;
+				}
+				color.r += (r - color.r) * alpha;
+				color.g += (g - color.g) * alpha;
+				color.b += (b - color.b) * alpha;
+			}
+		}
+	}
+
+	/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
+	export class AlphaTimeline extends CurveTimeline1 implements SlotTimeline {
+		slotIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
+			super(frameCount, bezierCount, Property.alpha + "|" + slotIndex);
+			this.slotIndex = slotIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let slot = skeleton.slots[this.slotIndex];
+			if (!slot.bone.active) return;
+
+			let color = slot.color;
+			if (time < this.frames[0]) { // Time is before first frame.
+				let setup = slot.data.color;
+				switch (blend) {
+					case MixBlend.setup:
+						color.a = setup.a;
+						return;
+					case MixBlend.first:
+						color.a += (setup.a - color.a) * alpha;
+				}
+				return;
+			}
+
+			let a = this.getCurveValue(time);
+			if (alpha == 1)
+				color.a = a;
+			else {
+				if (blend == MixBlend.setup) color.a = slot.data.color.a;
+				color.a += (a - color.a) * alpha;
+			}
+		}
+	}
+
+	/** Changes a slot's {@link Slot#color} and {@link Slot#darkColor} for two color tinting. */
+	export class RGBA2Timeline extends CurveTimeline implements SlotTimeline {
+		slotIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
+			super(frameCount, bezierCount, [
+				Property.rgb + "|" + slotIndex,
+				Property.alpha + "|" + slotIndex,
+				Property.rgb2 + "|" + slotIndex
+			]);
+			this.slotIndex = slotIndex;
+		}
+
+		getFrameEntries () {
+			return 8/*ENTRIES*/;
+		}
+
+		/** Sets the time in seconds, light, and dark colors for the specified key frame. */
+		setFrame (frame: number, time: number, r: number, g: number, b: number, a: number, r2: number, g2: number, b2: number) {
+			frame <<= 3;
+			this.frames[frame] = time;
+			this.frames[frame + 1/*R*/] = r;
+			this.frames[frame + 2/*G*/] = g;
+			this.frames[frame + 3/*B*/] = b;
+			this.frames[frame + 4/*A*/] = a;
+			this.frames[frame + 5/*R2*/] = r2;
+			this.frames[frame + 6/*G2*/] = g2;
+			this.frames[frame + 7/*B2*/] = b2;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let slot = skeleton.slots[this.slotIndex];
+			if (!slot.bone.active) return;
+
+			let frames = this.frames;
+			let light = slot.color, dark = slot.darkColor;
+			if (time < frames[0]) {
+				let setupLight = slot.data.color, setupDark = slot.data.darkColor;
+				switch (blend) {
+					case MixBlend.setup:
+						light.setFromColor(setupLight);
+						dark.r = setupDark.r;
+						dark.g = setupDark.g;
+						dark.b = setupDark.b;
+						return;
+					case MixBlend.first:
+						light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
+							(setupLight.a - light.a) * alpha);
+						dark.r += (setupDark.r - dark.r) * alpha;
+						dark.g += (setupDark.g - dark.g) * alpha;
+						dark.b += (setupDark.b - dark.b) * alpha;
+				}
+				return;
+			}
+
+			let r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0;
+			let i = Timeline.search(frames, time, 8/*ENTRIES*/);
+			let curveType = this.curves[i >> 3];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					r = frames[i + 1/*R*/];
+					g = frames[i + 2/*G*/];
+					b = frames[i + 3/*B*/];
+					a = frames[i + 4/*A*/];
+					r2 = frames[i + 5/*R2*/];
+					g2 = frames[i + 6/*G2*/];
+					b2 = frames[i + 7/*B2*/];
+					let t = (time - before) / (frames[i + 8/*ENTRIES*/] - before);
+					r += (frames[i + 8/*ENTRIES*/ + 1/*R*/] - r) * t;
+					g += (frames[i + 8/*ENTRIES*/ + 2/*G*/] - g) * t;
+					b += (frames[i + 8/*ENTRIES*/ + 3/*B*/] - b) * t;
+					a += (frames[i + 8/*ENTRIES*/ + 4/*A*/] - a) * t;
+					r2 += (frames[i + 8/*ENTRIES*/ + 5/*R2*/] - r2) * t;
+					g2 += (frames[i + 8/*ENTRIES*/ + 6/*G2*/] - g2) * t;
+					b2 += (frames[i + 8/*ENTRIES*/ + 7/*B2*/] - b2) * t;
+					break;
+				case 1/*STEPPED*/:
+					r = frames[i + 1/*R*/];
+					g = frames[i + 2/*G*/];
+					b = frames[i + 3/*B*/];
+					a = frames[i + 4/*A*/];
+					r2 = frames[i + 5/*R2*/];
+					g2 = frames[i + 6/*G2*/];
+					b2 = frames[i + 7/*B2*/];
+					break;
+				default:
+					r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
+					g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+					b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
+					a = this.getBezierValue(time, i, 4/*A*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
+					r2 = this.getBezierValue(time, i, 5/*R2*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
+					g2 = this.getBezierValue(time, i, 6/*G2*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
+					b2 = this.getBezierValue(time, i, 7/*B2*/, curveType + 18/*BEZIER_SIZE*/ * 6 - 2/*BEZIER*/);
+			}
+
+			if (alpha == 1) {
+				light.set(r, g, b, a);
+				dark.r = r2;
+				dark.g = g2;
+				dark.b = b2;
+			} else {
+				if (blend == MixBlend.setup) {
+					light.setFromColor(slot.data.color);
+					let setupDark = slot.data.darkColor;
+					dark.r = setupDark.r;
+					dark.g = setupDark.g;
+					dark.b = setupDark.b;
+				}
+				light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha);
+				dark.r += (r2 - dark.r) * alpha;
+				dark.g += (g2 - dark.g) * alpha;
+				dark.b += (b2 - dark.b) * alpha;
+			}
+		}
+	}
+
+	/** Changes a slot's {@link Slot#color} and {@link Slot#darkColor} for two color tinting. */
+	export class RGB2Timeline extends CurveTimeline implements SlotTimeline {
+		slotIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, slotIndex: number) {
+			super(frameCount, bezierCount, [
+				Property.rgb + "|" + slotIndex,
+				Property.rgb2 + "|" + slotIndex
+			]);
+			this.slotIndex = slotIndex;
+		}
+
+		getFrameEntries () {
+			return 7/*ENTRIES*/;
+		}
+
+		/** Sets the time in seconds, light, and dark colors for the specified key frame. */
+		setFrame (frame: number, time: number, r: number, g: number, b: number, r2: number, g2: number, b2: number) {
+			frame *= 7/*ENTRIES*/;
+			this.frames[frame] = time;
+			this.frames[frame + 1/*R*/] = r;
+			this.frames[frame + 2/*G*/] = g;
+			this.frames[frame + 3/*B*/] = b;
+			this.frames[frame + 4/*R2*/] = r2;
+			this.frames[frame + 5/*G2*/] = g2;
+			this.frames[frame + 6/*B2*/] = b2;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let slot = skeleton.slots[this.slotIndex];
+			if (!slot.bone.active) return;
+
+			let frames = this.frames;
+			let light = slot.color, dark = slot.darkColor;
+			if (time < frames[0]) {
+				let setupLight = slot.data.color, setupDark = slot.data.darkColor;
+				switch (blend) {
+					case MixBlend.setup:
+						light.r = setupLight.r;
+						light.g = setupLight.g;
+						light.b = setupLight.b;
+						dark.r = setupDark.r;
+						dark.g = setupDark.g;
+						dark.b = setupDark.b;
+						return;
+					case MixBlend.first:
+						light.r += (setupLight.r - light.r) * alpha;
+						light.g += (setupLight.g - light.g) * alpha;
+						light.b += (setupLight.b - light.b) * alpha;
+						dark.r += (setupDark.r - dark.r) * alpha;
+						dark.g += (setupDark.g - dark.g) * alpha;
+						dark.b += (setupDark.b - dark.b) * alpha;
+				}
+				return;
+			}
+
+			let r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0;
+			let i = Timeline.search(frames, time, 7/*ENTRIES*/);
+			let curveType = this.curves[i / 7/*ENTRIES*/];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					r = frames[i + 1/*R*/];
+					g = frames[i + 2/*G*/];
+					b = frames[i + 3/*B*/];
+					r2 = frames[i + 4/*R2*/];
+					g2 = frames[i + 5/*G2*/];
+					b2 = frames[i + 6/*B2*/];
+					let t = (time - before) / (frames[i + 7/*ENTRIES*/] - before);
+					r += (frames[i + 7/*ENTRIES*/ + 1/*R*/] - r) * t;
+					g += (frames[i + 7/*ENTRIES*/ + 2/*G*/] - g) * t;
+					b += (frames[i + 7/*ENTRIES*/ + 3/*B*/] - b) * t;
+					r2 += (frames[i + 7/*ENTRIES*/ + 4/*R2*/] - r2) * t;
+					g2 += (frames[i + 7/*ENTRIES*/ + 5/*G2*/] - g2) * t;
+					b2 += (frames[i + 7/*ENTRIES*/ + 6/*B2*/] - b2) * t;
+					break;
+				case 1/*STEPPED*/:
+					r = frames[i + 1/*R*/];
+					g = frames[i + 2/*G*/];
+					b = frames[i + 3/*B*/];
+					r2 = frames[i + 4/*R2*/];
+					g2 = frames[i + 5/*G2*/];
+					b2 = frames[i + 6/*B2*/];
+					break;
+				default:
+					r = this.getBezierValue(time, i, 1/*R*/, curveType - 2/*BEZIER*/);
+					g = this.getBezierValue(time, i, 2/*G*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+					b = this.getBezierValue(time, i, 3/*B*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
+					r2 = this.getBezierValue(time, i, 4/*R2*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
+					g2 = this.getBezierValue(time, i, 5/*G2*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
+					b2 = this.getBezierValue(time, i, 6/*B2*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
+			}
+
+			if (alpha == 1) {
+				light.r = r;
+				light.g = g;
+				light.b = b;
+				dark.r = r2;
+				dark.g = g2;
+				dark.b = b2;
+			} else {
+				if (blend == MixBlend.setup) {
+					let setupLight = slot.data.color, setupDark = slot.data.darkColor;
+					light.r = setupLight.r;
+					light.g = setupLight.g;
+					light.b = setupLight.b;
+					dark.r = setupDark.r;
+					dark.g = setupDark.g;
+					dark.b = setupDark.b;
+				}
+				light.r += (r - light.r) * alpha;
+				light.g += (g - light.g) * alpha;
+				light.b += (b - light.b) * alpha;
+				dark.r += (r2 - dark.r) * alpha;
+				dark.g += (g2 - dark.g) * alpha;
+				dark.b += (b2 - dark.b) * alpha;
+			}
+		}
+	}
+
+	/** Changes a slot's {@link Slot#attachment}. */
+	export class AttachmentTimeline extends Timeline implements SlotTimeline {
+		slotIndex = 0;
+
+		/** The attachment name for each key frame. May contain null values to clear the attachment. */
+		attachmentNames: Array<string>;
+
+		constructor (frameCount: number, slotIndex: number) {
+			super(frameCount, [
+				Property.attachment + "|" + slotIndex
+			]);
+			this.slotIndex = slotIndex;
+			this.attachmentNames = new Array<string>(frameCount);
+		}
+
+		getFrameCount () {
+			return this.frames.length;
+		}
+
+		/** Sets the time in seconds and the attachment name for the specified key frame. */
+		setFrame (frame: number, time: number, attachmentName: string) {
+			this.frames[frame] = time;
+			this.attachmentNames[frame] = attachmentName;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let slot = skeleton.slots[this.slotIndex];
+			if (!slot.bone.active) return;
+
+			if (direction == MixDirection.mixOut) {
+				if (blend == MixBlend.setup) this.setAttachment(skeleton, slot, slot.data.attachmentName);
+				return;
+			}
+
+			if (time < this.frames[0]) {
+				if (blend == MixBlend.setup || blend == MixBlend.first) this.setAttachment(skeleton, slot, slot.data.attachmentName);
+				return;
+			}
+
+			this.setAttachment(skeleton, slot, this.attachmentNames[Timeline.search1(this.frames, time)]);
+		}
+
+		setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string) {
+			slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(this.slotIndex, attachmentName));
+		}
+	}
+
+	/** Changes a slot's {@link Slot#deform} to deform a {@link VertexAttachment}. */
+	export class DeformTimeline extends CurveTimeline implements SlotTimeline {
+		slotIndex = 0;
+
+		/** The attachment that will be deformed. */
+		attachment: VertexAttachment;
+
+		/** The vertices for each key frame. */
+		vertices: Array<ArrayLike<number>>;
+
+		constructor (frameCount: number, bezierCount: number, slotIndex: number, attachment: VertexAttachment) {
+			super(frameCount, bezierCount, [
+				Property.deform + "|" + slotIndex + "|" + attachment.id
+			]);
+			this.slotIndex = slotIndex;
+			this.attachment = attachment;
+			this.vertices = new Array<ArrayLike<number>>(frameCount);
+		}
+
+		getFrameCount () {
+			return this.frames.length;
+		}
+
+		/** Sets the time in seconds and the vertices for the specified key frame.
+		 * @param vertices Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. */
+		setFrame (frame: number, time: number, vertices: ArrayLike<number>) {
+			this.frames[frame] = time;
+			this.vertices[frame] = vertices;
+		}
+
+		/** @param value1 Ignored (0 is used for a deform timeline).
+		 * @param value2 Ignored (1 is used for a deform timeline). */
+		setBezier (bezier: number, frame: number, value: number, time1: number, value1: number, cx1: number, cy1: number, cx2: number,
+			cy2: number, time2: number, value2: number) {
+			let curves = this.curves;
+			let i = this.getFrameCount() + bezier * 18/*BEZIER_SIZE*/;
+			if (value == 0) curves[frame] = 2/*BEZIER*/ + i;
+			let tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = cy2 * 0.03 - cy1 * 0.06;
+			let dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = (cy1 - cy2 + 0.33333333) * 0.018;
+			let ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy;
+			let dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = cy1 * 0.3 + tmpy + dddy * 0.16666667;
+			let x = time1 + dx, y = dy;
+			for (let n = i + 18/*BEZIER_SIZE*/; i < n; i += 2) {
+				curves[i] = x;
+				curves[i + 1] = y;
+				dx += ddx;
+				dy += ddy;
+				ddx += dddx;
+				ddy += dddy;
+				x += dx;
+				y += dy;
+			}
+		}
+
+		getCurvePercent (time: number, frame: number) {
+			let curves = this.curves;
+			let i = curves[frame];
+			switch (i) {
+				case 0/*LINEAR*/:
+					let x = this.frames[frame];
+					return (time - x) / (this.frames[frame + this.getFrameEntries()] - x);
+				case 1/*STEPPED*/:
+					return 0;
+			}
+			i -= 2/*BEZIER*/;
+			if (curves[i] > time) {
+				let x = this.frames[frame];
+				return curves[i + 1] * (time - x) / (curves[i] - x);
+			}
+			let n = i + 18/*BEZIER_SIZE*/;
+			for (i += 2; i < n; i += 2) {
+				if (curves[i] >= time) {
+					let x = curves[i - 2], y = curves[i - 1];
+					return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
+				}
+			}
+			let x = curves[n - 2], y = curves[n - 1];
+			return y + (1 - y) * (time - x) / (this.frames[frame + this.getFrameEntries()] - x);
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let slot: Slot = skeleton.slots[this.slotIndex];
+			if (!slot.bone.active) return;
+			let slotAttachment: Attachment = slot.getAttachment();
+			if (!(slotAttachment instanceof VertexAttachment) || (<VertexAttachment>slotAttachment).deformAttachment != this.attachment) return;
+
+			let deform: Array<number> = slot.deform;
+			if (deform.length == 0) blend = MixBlend.setup;
+
+			let vertices = this.vertices;
+			let vertexCount = vertices[0].length;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				let vertexAttachment = <VertexAttachment>slotAttachment;
+				switch (blend) {
+					case MixBlend.setup:
+						deform.length = 0;
+						return;
+					case MixBlend.first:
+						if (alpha == 1) {
+							deform.length = 0;
+							return;
+						}
+						deform.length = vertexCount;
+						if (!vertexAttachment.bones) {
+							// Unweighted vertex positions.
+							let setupVertices = vertexAttachment.vertices;
+							for (var i = 0; i < vertexCount; i++)
+								deform[i] += (setupVertices[i] - deform[i]) * alpha;
+						} else {
+							// Weighted deform offsets.
+							alpha = 1 - alpha;
+							for (var i = 0; i < vertexCount; i++)
+								deform[i] *= alpha;
+						}
+				}
+				return;
+			}
+
+			deform.length = vertexCount;
+			if (time >= frames[frames.length - 1]) { // Time is after last frame.
+				let lastVertices = vertices[frames.length - 1];
+				if (alpha == 1) {
+					if (blend == MixBlend.add) {
+						let vertexAttachment = slotAttachment as VertexAttachment;
+						if (!vertexAttachment.bones) {
+							// Unweighted vertex positions, with alpha.
+							let setupVertices = vertexAttachment.vertices;
+							for (let i = 0; i < vertexCount; i++)
+								deform[i] += lastVertices[i] - setupVertices[i];
+						} else {
+							// Weighted deform offsets, with alpha.
+							for (let i = 0; i < vertexCount; i++)
+								deform[i] += lastVertices[i];
+						}
+					} else
+						Utils.arrayCopy(lastVertices, 0, deform, 0, vertexCount);
+				} else {
+					switch (blend) {
+						case MixBlend.setup: {
+							let vertexAttachment = slotAttachment as VertexAttachment;
+							if (!vertexAttachment.bones) {
+								// Unweighted vertex positions, with alpha.
+								let setupVertices = vertexAttachment.vertices;
+								for (let i = 0; i < vertexCount; i++) {
+									let setup = setupVertices[i];
+									deform[i] = setup + (lastVertices[i] - setup) * alpha;
+								}
+							} else {
+								// Weighted deform offsets, with alpha.
+								for (let i = 0; i < vertexCount; i++)
+									deform[i] = lastVertices[i] * alpha;
+							}
+							break;
+						}
+						case MixBlend.first:
+						case MixBlend.replace:
+							for (let i = 0; i < vertexCount; i++)
+								deform[i] += (lastVertices[i] - deform[i]) * alpha;
+							break;
+						case MixBlend.add:
+							let vertexAttachment = slotAttachment as VertexAttachment;
+							if (!vertexAttachment.bones) {
+								// Unweighted vertex positions, with alpha.
+								let setupVertices = vertexAttachment.vertices;
+								for (let i = 0; i < vertexCount; i++)
+									deform[i] += (lastVertices[i] - setupVertices[i]) * alpha;
+							} else {
+								// Weighted deform offsets, with alpha.
+								for (let i = 0; i < vertexCount; i++)
+									deform[i] += lastVertices[i] * alpha;
+							}
+					}
+				}
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			let frame = Timeline.search1(frames, time);
+			let percent = this.getCurvePercent(time, frame);
+			let prevVertices = vertices[frame];
+			let nextVertices = vertices[frame + 1];
+
+			if (alpha == 1) {
+				if (blend == MixBlend.add) {
+					let vertexAttachment = slotAttachment as VertexAttachment;
+					if (!vertexAttachment.bones) {
+						// Unweighted vertex positions, with alpha.
+						let setupVertices = vertexAttachment.vertices;
+						for (let i = 0; i < vertexCount; i++) {
+							let prev = prevVertices[i];
+							deform[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i];
+						}
+					} else {
+						// Weighted deform offsets, with alpha.
+						for (let i = 0; i < vertexCount; i++) {
+							let prev = prevVertices[i];
+							deform[i] += prev + (nextVertices[i] - prev) * percent;
+						}
+					}
+				} else {
+					for (let i = 0; i < vertexCount; i++) {
+						let prev = prevVertices[i];
+						deform[i] = prev + (nextVertices[i] - prev) * percent;
+					}
+				}
+			} else {
+				switch (blend) {
+					case MixBlend.setup: {
+						let vertexAttachment = slotAttachment as VertexAttachment;
+						if (!vertexAttachment.bones) {
+							// Unweighted vertex positions, with alpha.
+							let setupVertices = vertexAttachment.vertices;
+							for (let i = 0; i < vertexCount; i++) {
+								let prev = prevVertices[i], setup = setupVertices[i];
+								deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
+							}
+						} else {
+							// Weighted deform offsets, with alpha.
+							for (let i = 0; i < vertexCount; i++) {
+								let prev = prevVertices[i];
+								deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
+							}
+						}
+						break;
+					}
+					case MixBlend.first:
+					case MixBlend.replace:
+						for (let i = 0; i < vertexCount; i++) {
+							let prev = prevVertices[i];
+							deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha;
+						}
+						break;
+					case MixBlend.add:
+						let vertexAttachment = slotAttachment as VertexAttachment;
+						if (!vertexAttachment.bones) {
+							// Unweighted vertex positions, with alpha.
+							let setupVertices = vertexAttachment.vertices;
+							for (let i = 0; i < vertexCount; i++) {
+								let prev = prevVertices[i];
+								deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha;
+							}
+						} else {
+							// Weighted deform offsets, with alpha.
+							for (let i = 0; i < vertexCount; i++) {
+								let prev = prevVertices[i];
+								deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha;
+							}
+						}
+				}
+			}
+		}
+	}
+
+	/** Fires an {@link Event} when specific animation times are reached. */
+	export class EventTimeline extends Timeline {
+		static propertyIds = ["" + Property.event];
+
+		/** The event for each key frame. */
+		events: Array<Event>;
+
+		constructor (frameCount: number) {
+			super(frameCount, EventTimeline.propertyIds);
+
+			this.events = new Array<Event>(frameCount);
+		}
+
+		getFrameCount () {
+			return this.frames.length;
+		}
+
+		/** Sets the time in seconds and the event for the specified key frame. */
+		setFrame (frame: number, event: Event) {
+			this.frames[frame] = event.time;
+			this.events[frame] = event;
+		}
+
+		/** Fires events for frames > `lastTime` and <= `time`. */
+		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			if (!firedEvents) return;
+
+			let frames = this.frames;
+			let frameCount = this.frames.length;
+
+			if (lastTime > time) { // Fire events after last time for looped animations.
+				this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha, blend, direction);
+				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.
+
+			let i = 0;
+			if (lastTime < frames[0])
+				i = 0;
+			else {
+				i = Timeline.search1(frames, lastTime) + 1;
+				let frameTime = frames[i];
+				while (i > 0) { // Fire multiple events with the same frame.
+					if (frames[i - 1] != frameTime) break;
+					i--;
+				}
+			}
+			for (; i < frameCount && time >= frames[i]; i++)
+				firedEvents.push(this.events[i]);
+		}
+	}
+
+	/** Changes a skeleton's {@link Skeleton#drawOrder}. */
+	export class DrawOrderTimeline extends Timeline {
+		static propertyIds = ["" + Property.drawOrder];
+
+		/** The draw order for each key frame. See {@link #setFrame(int, float, int[])}. */
+		drawOrders: Array<Array<number>>;
+
+		constructor (frameCount: number) {
+			super(frameCount, DrawOrderTimeline.propertyIds);
+			this.drawOrders = new Array<Array<number>>(frameCount);
+		}
+
+		getFrameCount () {
+			return this.frames.length;
+		}
+
+		/** Sets the time in seconds and the draw order for the specified key frame.
+		 * @param drawOrder For each slot in {@link Skeleton#slots}, the index of the new draw order. May be null to use setup pose
+		 *           draw order. */
+		setFrame (frame: number, time: number, drawOrder: Array<number>) {
+			this.frames[frame] = time;
+			this.drawOrders[frame] = drawOrder;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			if (direction == MixDirection.mixOut) {
+				if (blend == MixBlend.setup) Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length);
+				return;
+			}
+
+			if (time < this.frames[0]) {
+				if (blend == MixBlend.setup || blend == MixBlend.first) Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length);
+				return;
+			}
+
+			let drawOrderToSetupIndex = this.drawOrders[Timeline.search1(this.frames, time)];
+			if (!drawOrderToSetupIndex)
+				Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length);
+			else {
+				let drawOrder: Array<Slot> = skeleton.drawOrder;
+				let slots: Array<Slot> = skeleton.slots;
+				for (let i = 0, n = drawOrderToSetupIndex.length; i < n; i++)
+					drawOrder[i] = slots[drawOrderToSetupIndex[i]];
+			}
+		}
+	}
+
+	/** Changes an IK constraint's {@link IkConstraint#mix}, {@link IkConstraint#softness},
+	 * {@link IkConstraint#bendDirection}, {@link IkConstraint#stretch}, and {@link IkConstraint#compress}. */
+	export class IkConstraintTimeline extends CurveTimeline {
+		/** The index of the IK constraint slot in {@link Skeleton#ikConstraints} that will be changed. */
+		ikConstraintIndex: number;
+
+		constructor (frameCount: number, bezierCount: number, ikConstraintIndex: number) {
+			super(frameCount, bezierCount, [
+				Property.ikConstraint + "|" + ikConstraintIndex
+			]);
+			this.ikConstraintIndex = ikConstraintIndex;
+		}
+
+		getFrameEntries () {
+			return 6/*ENTRIES*/;
+		}
+
+		/** Sets the time in seconds, mix, softness, bend direction, compress, and stretch for the specified key frame. */
+		setFrame (frame: number, time: number, mix: number, softness: number, bendDirection: number, compress: boolean, stretch: boolean) {
+			frame *= 6/*ENTRIES*/;
+			this.frames[frame] = time;
+			this.frames[frame + 1/*MIX*/] = mix;
+			this.frames[frame + 2/*SOFTNESS*/] = softness;
+			this.frames[frame + 3/*BEND_DIRECTION*/] = bendDirection;
+			this.frames[frame + 4/*COMPRESS*/] = compress ? 1 : 0;
+			this.frames[frame + 5/*STRETCH*/] = stretch ? 1 : 0;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let constraint: IkConstraint = skeleton.ikConstraints[this.ikConstraintIndex];
+			if (!constraint.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						constraint.mix = constraint.data.mix;
+						constraint.softness = constraint.data.softness;
+						constraint.bendDirection = constraint.data.bendDirection;
+						constraint.compress = constraint.data.compress;
+						constraint.stretch = constraint.data.stretch;
+						return;
+					case MixBlend.first:
+						constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
+						constraint.softness += (constraint.data.softness - constraint.softness) * alpha;
+						constraint.bendDirection = constraint.data.bendDirection;
+						constraint.compress = constraint.data.compress;
+						constraint.stretch = constraint.data.stretch;
+				}
+				return;
+			}
+
+			let mix = 0, softness = 0;
+			let i = Timeline.search(frames, time, 6/*ENTRIES*/)
+			let curveType = this.curves[i / 6/*ENTRIES*/];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					mix = frames[i + 1/*MIX*/];
+					softness = frames[i + 2/*SOFTNESS*/];
+					let t = (time - before) / (frames[i + 6/*ENTRIES*/] - before);
+					mix += (frames[i + 6/*ENTRIES*/ + 1/*MIX*/] - mix) * t;
+					softness += (frames[i + 6/*ENTRIES*/ + 2/*SOFTNESS*/] - softness) * t;
+					break;
+				case 1/*STEPPED*/:
+					mix = frames[i + 1/*MIX*/];
+					softness = frames[i + 2/*SOFTNESS*/];
+					break;
+				default:
+					mix = this.getBezierValue(time, i, 1/*MIX*/, curveType - 2/*BEZIER*/);
+					softness = this.getBezierValue(time, i, 2/*SOFTNESS*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+			}
+
+			if (blend == MixBlend.setup) {
+				constraint.mix = constraint.data.mix + (mix - constraint.data.mix) * alpha;
+				constraint.softness = constraint.data.softness + (softness - constraint.data.softness) * alpha;
+
+				if (direction == MixDirection.mixOut) {
+					constraint.bendDirection = constraint.data.bendDirection;
+					constraint.compress = constraint.data.compress;
+					constraint.stretch = constraint.data.stretch;
+				} else {
+					constraint.bendDirection = frames[i + 3/*BEND_DIRECTION*/];
+					constraint.compress = frames[i + 4/*COMPRESS*/] != 0;
+					constraint.stretch = frames[i + 5/*STRETCH*/] != 0;
+				}
+			} else {
+				constraint.mix += (mix - constraint.mix) * alpha;
+				constraint.softness += (softness - constraint.softness) * alpha;
+				if (direction == MixDirection.mixIn) {
+					constraint.bendDirection = frames[i + 3/*BEND_DIRECTION*/];
+					constraint.compress = frames[i + 4/*COMPRESS*/] != 0;
+					constraint.stretch = frames[i + 5/*STRETCH*/] != 0;
+				}
+			}
+		}
+	}
+
+	/** Changes a transform constraint's {@link TransformConstraint#rotateMix}, {@link TransformConstraint#translateMix},
+	 * {@link TransformConstraint#scaleMix}, and {@link TransformConstraint#shearMix}. */
+	export class TransformConstraintTimeline extends CurveTimeline {
+		/** The index of the transform constraint slot in {@link Skeleton#transformConstraints} that will be changed. */
+		transformConstraintIndex: number;
+
+		constructor (frameCount: number, bezierCount: number, transformConstraintIndex: number) {
+			super(frameCount, bezierCount, [
+				Property.transformConstraint + "|" + transformConstraintIndex
+			]);
+			this.transformConstraintIndex = transformConstraintIndex;
+		}
+
+		getFrameEntries () {
+			return 7/*ENTRIES*/;
+		}
+
+		/** The time in seconds, rotate mix, translate mix, scale mix, and shear mix for the specified key frame. */
+		setFrame (frame: number, time: number, mixRotate: number, mixX: number, mixY: number, mixScaleX: number, mixScaleY: number,
+			mixShearY: number) {
+			let frames = this.frames;
+			frame *= 7/*ENTRIES*/;
+			frames[frame] = time;
+			frames[frame + 1/*ROTATE*/] = mixRotate;
+			frames[frame + 2/*X*/] = mixX;
+			frames[frame + 3/*Y*/] = mixY;
+			frames[frame + 4/*SCALEX*/] = mixScaleX;
+			frames[frame + 5/*SCALEY*/] = mixScaleY;
+			frames[frame + 6/*SHEARY*/] = mixShearY;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let constraint: TransformConstraint = skeleton.transformConstraints[this.transformConstraintIndex];
+			if (!constraint.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				let data = constraint.data;
+				switch (blend) {
+					case MixBlend.setup:
+						constraint.mixRotate = data.mixRotate;
+						constraint.mixX = data.mixX;
+						constraint.mixY = data.mixY;
+						constraint.mixScaleX = data.mixScaleX;
+						constraint.mixScaleY = data.mixScaleY;
+						constraint.mixShearY = data.mixShearY;
+						return;
+					case MixBlend.first:
+						constraint.mixRotate += (data.mixRotate - constraint.mixRotate) * alpha;
+						constraint.mixX += (data.mixX - constraint.mixX) * alpha;
+						constraint.mixY += (data.mixY - constraint.mixY) * alpha;
+						constraint.mixScaleX += (data.mixScaleX - constraint.mixScaleX) * alpha;
+						constraint.mixScaleY += (data.mixScaleY - constraint.mixScaleY) * alpha;
+						constraint.mixShearY += (data.mixShearY - constraint.mixShearY) * alpha;
+				}
+				return;
+			}
+
+			let rotate, x, y, scaleX, scaleY, shearY;
+			let i = Timeline.search(frames, time, 7/*ENTRIES*/);
+			let curveType = this.curves[i / 7/*ENTRIES*/];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					rotate = frames[i + 1/*ROTATE*/];
+					x = frames[i + 2/*X*/];
+					y = frames[i + 3/*Y*/];
+					scaleX = frames[i + 4/*SCALEX*/];
+					scaleY = frames[i + 5/*SCALEY*/];
+					shearY = frames[i + 6/*SHEARY*/];
+					let t = (time - before) / (frames[i + 7/*ENTRIES*/] - before);
+					rotate += (frames[i + 7/*ENTRIES*/ + 1/*ROTATE*/] - rotate) * t;
+					x += (frames[i + 7/*ENTRIES*/ + 2/*X*/] - x) * t;
+					y += (frames[i + 7/*ENTRIES*/ + 3/*Y*/] - y) * t;
+					scaleX += (frames[i + 7/*ENTRIES*/ + 4/*SCALEX*/] - scaleX) * t;
+					scaleY += (frames[i + 7/*ENTRIES*/ + 5/*SCALEY*/] - scaleY) * t;
+					shearY += (frames[i + 7/*ENTRIES*/ + 6/*SHEARY*/] - shearY) * t;
+					break;
+				case 1/*STEPPED*/:
+					rotate = frames[i + 1/*ROTATE*/];
+					x = frames[i + 2/*X*/];
+					y = frames[i + 3/*Y*/];
+					scaleX = frames[i + 4/*SCALEX*/];
+					scaleY = frames[i + 5/*SCALEY*/];
+					shearY = frames[i + 6/*SHEARY*/];
+					break;
+				default:
+					rotate = this.getBezierValue(time, i, 1/*ROTATE*/, curveType - 2/*BEZIER*/);
+					x = this.getBezierValue(time, i, 2/*X*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+					y = this.getBezierValue(time, i, 3/*Y*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
+					scaleX = this.getBezierValue(time, i, 4/*SCALEX*/, curveType + 18/*BEZIER_SIZE*/ * 3 - 2/*BEZIER*/);
+					scaleY = this.getBezierValue(time, i, 5/*SCALEY*/, curveType + 18/*BEZIER_SIZE*/ * 4 - 2/*BEZIER*/);
+					shearY = this.getBezierValue(time, i, 6/*SHEARY*/, curveType + 18/*BEZIER_SIZE*/ * 5 - 2/*BEZIER*/);
+			}
+
+			if (blend == MixBlend.setup) {
+				let data = constraint.data;
+				constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
+				constraint.mixX = data.mixX + (x - data.mixX) * alpha;
+				constraint.mixY = data.mixY + (y - data.mixY) * alpha;
+				constraint.mixScaleX = data.mixScaleX + (scaleX - data.mixScaleX) * alpha;
+				constraint.mixScaleY = data.mixScaleY + (scaleY - data.mixScaleY) * alpha;
+				constraint.mixShearY = data.mixShearY + (shearY - data.mixShearY) * alpha;
+			} else {
+				constraint.mixRotate += (rotate - constraint.mixRotate) * alpha;
+				constraint.mixX += (x - constraint.mixX) * alpha;
+				constraint.mixY += (y - constraint.mixY) * alpha;
+				constraint.mixScaleX += (scaleX - constraint.mixScaleX) * alpha;
+				constraint.mixScaleY += (scaleY - constraint.mixScaleY) * alpha;
+				constraint.mixShearY += (shearY - constraint.mixShearY) * alpha;
+			}
+		}
+	}
+
+	/** Changes a path constraint's {@link PathConstraint#position}. */
+	export class PathConstraintPositionTimeline extends CurveTimeline1 {
+		/** The index of the path constraint slot in {@link Skeleton#pathConstraints} that will be changed. */
+		pathConstraintIndex: number;
+
+		constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
+			super(frameCount, bezierCount, Property.pathConstraintPosition + "|" + pathConstraintIndex);
+			this.pathConstraintIndex = pathConstraintIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
+			if (!constraint.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						constraint.position = constraint.data.position;
+						return;
+					case MixBlend.first:
+						constraint.position += (constraint.data.position - constraint.position) * alpha;
+				}
+				return;
+			}
+
+			let position = this.getCurveValue(time);
+
+			if (blend == MixBlend.setup)
+				constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
+			else
+				constraint.position += (position - constraint.position) * alpha;
+		}
+	}
+
+	/** Changes a path constraint's {@link PathConstraint#spacing}. */
+	export class PathConstraintSpacingTimeline extends CurveTimeline1 {
+		/** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */
+		pathConstraintIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
+			super(frameCount, bezierCount, Property.pathConstraintSpacing + "|" + pathConstraintIndex);
+			this.pathConstraintIndex = pathConstraintIndex;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
+			if (!constraint.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						constraint.spacing = constraint.data.spacing;
+						return;
+					case MixBlend.first:
+						constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
+				}
+				return;
+			}
+
+			let spacing = this.getCurveValue(time);
+
+			if (blend == MixBlend.setup)
+				constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
+			else
+				constraint.spacing += (spacing - constraint.spacing) * alpha;
+		}
+	}
+
+	/** Changes a transform constraint's {@link PathConstraint#getMixRotate()}, {@link PathConstraint#getMixX()}, and
+	 * {@link PathConstraint#getMixY()}. */
+	export class PathConstraintMixTimeline extends CurveTimeline {
+		/** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */
+		pathConstraintIndex = 0;
+
+		constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
+			super(frameCount, bezierCount, [
+				Property.pathConstraintMix + "|" + pathConstraintIndex
+			]);
+			this.pathConstraintIndex = pathConstraintIndex;
+		}
+
+		getFrameEntries () {
+			return 4/*ENTRIES*/;
+		}
+
+		setFrame (frame: number, time: number, mixRotate: number, mixX: number, mixY: number) {
+			let frames = this.frames;
+			frame <<= 2;
+			frames[frame] = time;
+			frames[frame + 1/*ROTATE*/] = mixRotate;
+			frames[frame + 2/*X*/] = mixX;
+			frames[frame + 3/*Y*/] = mixY;
+		}
+
+		apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
+			let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
+			if (!constraint.active) return;
+
+			let frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						constraint.mixRotate = constraint.data.mixRotate;
+						constraint.mixX = constraint.data.mixX;
+						constraint.mixY = constraint.data.mixY;
+						return;
+					case MixBlend.first:
+						constraint.mixRotate += (constraint.data.mixRotate - constraint.mixRotate) * alpha;
+						constraint.mixX += (constraint.data.mixX - constraint.mixX) * alpha;
+						constraint.mixY += (constraint.data.mixY - constraint.mixY) * alpha;
+				}
+				return;
+			}
+
+			let rotate, x, y;
+			let i = Timeline.search(frames, time, 4/*ENTRIES*/);
+			let curveType = this.curves[i >> 2];
+			switch (curveType) {
+				case 0/*LINEAR*/:
+					let before = frames[i];
+					rotate = frames[i + 1/*ROTATE*/];
+					x = frames[i + 2/*X*/];
+					y = frames[i + 3/*Y*/];
+					let t = (time - before) / (frames[i + 4/*ENTRIES*/] - before);
+					rotate += (frames[i + 4/*ENTRIES*/ + 1/*ROTATE*/] - rotate) * t;
+					x += (frames[i + 4/*ENTRIES*/ + 2/*X*/] - x) * t;
+					y += (frames[i + 4/*ENTRIES*/ + 3/*Y*/] - y) * t;
+					break;
+				case 1/*STEPPED*/:
+					rotate = frames[i + 1/*ROTATE*/];
+					x = frames[i + 2/*X*/];
+					y = frames[i + 3/*Y*/];
+					break;
+				default:
+					rotate = this.getBezierValue(time, i, 1/*ROTATE*/, curveType - 2/*BEZIER*/);
+					x = this.getBezierValue(time, i, 2/*X*/, curveType + 18/*BEZIER_SIZE*/ - 2/*BEZIER*/);
+					y = this.getBezierValue(time, i, 3/*Y*/, curveType + 18/*BEZIER_SIZE*/ * 2 - 2/*BEZIER*/);
+			}
+
+			if (blend == MixBlend.setup) {
+				let data = constraint.data;
+				constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
+				constraint.mixX = data.mixX + (x - data.mixX) * alpha;
+				constraint.mixY = data.mixY + (y - data.mixY) * alpha;
+			} else {
+				constraint.mixRotate += (rotate - constraint.mixRotate) * alpha;
+				constraint.mixX += (x - constraint.mixX) * alpha;
+				constraint.mixY += (y - constraint.mixY) * alpha;
+			}
+		}
+	}
+}

+ 1171 - 1171
spine-ts/core/src/AnimationState.ts

@@ -1,1171 +1,1171 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies
-	 * multiple animations on top of each other (layering).
-	 *
-	 * See [Applying Animations](http://esotericsoftware.com/spine-applying-animations/) in the Spine Runtimes Guide. */
-	export class AnimationState {
-		private static emptyAnimation (): Animation {
-			if (!_emptyAnimation) _emptyAnimation = new Animation("<empty>", [], 0);
-			return _emptyAnimation;
-		}
-
-		/** The AnimationStateData to look up mix durations. */
-		data: AnimationStateData;
-
-		/** The list of tracks that currently have animations, which may contain null entries. */
-		tracks = new Array<TrackEntry>();
-
-		/** Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower
-		 * or faster. Defaults to 1.
-		 *
-		 * See TrackEntry {@link TrackEntry#timeScale} for affecting a single animation. */
-		timeScale = 1;
-		unkeyedState = 0;
-
-		events = new Array<Event>();
-		listeners = new Array<AnimationStateListener>();
-		queue = new EventQueue(this);
-		propertyIDs = new StringSet();
-		animationsChanged = false;
-
-		trackEntryPool = new Pool<TrackEntry>(() => new TrackEntry());
-
-		constructor (data: AnimationStateData) {
-			this.data = data;
-		}
-
-		/** Increments each track entry {@link TrackEntry#trackTime()}, setting queued animations as current if needed. */
-		update (delta: number) {
-			delta *= this.timeScale;
-			let tracks = this.tracks;
-			for (let i = 0, n = tracks.length; i < n; i++) {
-				let current = tracks[i];
-				if (!current) continue;
-
-				current.animationLast = current.nextAnimationLast;
-				current.trackLast = current.nextTrackLast;
-
-				let currentDelta = delta * current.timeScale;
-
-				if (current.delay > 0) {
-					current.delay -= currentDelta;
-					if (current.delay > 0) continue;
-					currentDelta = -current.delay;
-					current.delay = 0;
-				}
-
-				let next = current.next;
-				if (next) {
-					// When the next entry's delay is passed, change to the next entry, preserving leftover time.
-					let nextTime = current.trackLast - next.delay;
-					if (nextTime >= 0) {
-						next.delay = 0;
-						next.trackTime += current.timeScale == 0 ? 0 : (nextTime / current.timeScale + delta) * next.timeScale;
-						current.trackTime += currentDelta;
-						this.setCurrent(i, next, true);
-						while (next.mixingFrom) {
-							next.mixTime += delta;
-							next = next.mixingFrom;
-						}
-						continue;
-					}
-				} else if (current.trackLast >= current.trackEnd && !current.mixingFrom) {
-					tracks[i] = null;
-					this.queue.end(current);
-					this.clearNext(current);
-					continue;
-				}
-				if (current.mixingFrom && this.updateMixingFrom(current, delta)) {
-					// End mixing from entries once all have completed.
-					let from = current.mixingFrom;
-					current.mixingFrom = null;
-					if (from) from.mixingTo = null;
-					while (from) {
-						this.queue.end(from);
-						from = from.mixingFrom;
-					}
-				}
-
-				current.trackTime += currentDelta;
-			}
-
-			this.queue.drain();
-		}
-
-		/** Returns true when all mixing from entries are complete. */
-		updateMixingFrom (to: TrackEntry, delta: number): boolean {
-			let from = to.mixingFrom;
-			if (!from) return true;
-
-			let finished = this.updateMixingFrom(from, delta);
-
-			from.animationLast = from.nextAnimationLast;
-			from.trackLast = from.nextTrackLast;
-
-			// Require mixTime > 0 to ensure the mixing from entry was applied at least once.
-			if (to.mixTime > 0 && to.mixTime >= to.mixDuration) {
-				// Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
-				if (from.totalAlpha == 0 || to.mixDuration == 0) {
-					to.mixingFrom = from.mixingFrom;
-					if (from.mixingFrom) from.mixingFrom.mixingTo = to;
-					to.interruptAlpha = from.interruptAlpha;
-					this.queue.end(from);
-				}
-				return finished;
-			}
-
-			from.trackTime += delta * from.timeScale;
-			to.mixTime += delta;
-			return false;
-		}
-
-		/** Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the
-		 * animation state can be applied to multiple skeletons to pose them identically.
-		 * @returns True if any animations were applied. */
-		apply (skeleton: Skeleton) : boolean {
-			if (!skeleton) throw new Error("skeleton cannot be null.");
-			if (this.animationsChanged) this._animationsChanged();
-
-			let events = this.events;
-			let tracks = this.tracks;
-			let applied = false;
-
-			for (let i = 0, n = tracks.length; i < n; i++) {
-				let current = tracks[i];
-				if (!current || current.delay > 0) continue;
-				applied = true;
-				let blend: MixBlend = i == 0 ? MixBlend.first : current.mixBlend;
-
-				// Apply mixing from entries first.
-				let mix = current.alpha;
-				if (current.mixingFrom)
-					mix *= this.applyMixingFrom(current, skeleton, blend);
-				else if (current.trackTime >= current.trackEnd && !current.next)
-					mix = 0;
-
-				// Apply current entry.
-				let animationLast = current.animationLast, animationTime = current.getAnimationTime(), applyTime = animationTime;
-				let applyEvents = events;
-				if (current.reverse) {
-					applyTime = current.animation.duration - applyTime;
-					applyEvents = null;
-				}
-				let timelines = current.animation.timelines;
-				let timelineCount = timelines.length;
-				if ((i == 0 && mix == 1) || blend == MixBlend.add) {
-					for (let ii = 0; ii < timelineCount; ii++) {
-						// Fixes issue #302 on IOS9 where mix, blend sometimes became undefined and caused assets
-						// to sometimes stop rendering when using color correction, as their RGBA values become NaN.
-						// (https://github.com/pixijs/pixi-spine/issues/302)
-						Utils.webkit602BugfixHelper(mix, blend);
-						var timeline = timelines[ii];
-						if (timeline instanceof AttachmentTimeline)
-							this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true);
-						else
-							timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.mixIn);
-					}
-				} else {
-					let timelineMode = current.timelineMode;
-
-					let firstFrame = current.timelinesRotation.length != timelineCount << 1;
-					if (firstFrame) current.timelinesRotation.length = timelineCount << 1;
-
-					for (let ii = 0; ii < timelineCount; ii++) {
-						let timeline = timelines[ii];
-						let timelineBlend = timelineMode[ii] == SUBSEQUENT ? blend : MixBlend.setup;
-						if (timeline instanceof RotateTimeline) {
-							this.applyRotateTimeline(timeline, skeleton, applyTime, mix, timelineBlend, current.timelinesRotation, ii << 1, firstFrame);
-						} else if (timeline instanceof AttachmentTimeline) {
-							this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true);
-						} else {
-							// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
-							Utils.webkit602BugfixHelper(mix, blend);
-							timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.mixIn);
-						}
-					}
-				}
-				this.queueEvents(current, animationTime);
-				events.length = 0;
-				current.nextAnimationLast = animationTime;
-				current.nextTrackLast = current.trackTime;
-			}
-
-			// Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so
-			// subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or
-			// the time is before the first key).
-			var setupState = this.unkeyedState + SETUP;
-			var slots = skeleton.slots;
-			for (var i = 0, n = skeleton.slots.length; i < n; i++) {
-				var slot = slots[i];
-				if (slot.attachmentState == setupState) {
-					var attachmentName = slot.data.attachmentName;
-					slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(slot.data.index, attachmentName));
-				}
-			}
-			this.unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot.
-
-			this.queue.drain();
-			return applied;
-		}
-
-		applyMixingFrom (to: TrackEntry, skeleton: Skeleton, blend: MixBlend) {
-			let from = to.mixingFrom;
-			if (from.mixingFrom) this.applyMixingFrom(from, skeleton, blend);
-
-			let mix = 0;
-			if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
-				mix = 1;
-				if (blend == MixBlend.first) blend = MixBlend.setup;
-			} else {
-				mix = to.mixTime / to.mixDuration;
-				if (mix > 1) mix = 1;
-				if (blend != MixBlend.first) blend = from.mixBlend;
-			}
-
-			let attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
-			let timelines = from.animation.timelines;
-			let timelineCount = timelines.length;
-			let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
-			let animationLast = from.animationLast, animationTime = from.getAnimationTime(), applyTime = animationTime;
-			let events = null;
-			if (from.reverse)
-				applyTime = from.animation.duration - applyTime;
-			else if (mix < from.eventThreshold)
-				events = this.events;
-
-			if (blend == MixBlend.add) {
-				for (let i = 0; i < timelineCount; i++)
-					timelines[i].apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.mixOut);
-			} else {
-				let timelineMode = from.timelineMode;
-				let timelineHoldMix = from.timelineHoldMix;
-
-				let firstFrame = from.timelinesRotation.length != timelineCount << 1;
-				if (firstFrame) from.timelinesRotation.length = timelineCount << 1;
-
-				from.totalAlpha = 0;
-				for (let i = 0; i < timelineCount; i++) {
-					let timeline = timelines[i];
-					let direction = MixDirection.mixOut;
-					let timelineBlend: MixBlend;
-					let alpha = 0;
-					switch (timelineMode[i]) {
-					case SUBSEQUENT:
-						if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
-						timelineBlend = blend;
-						alpha = alphaMix;
-						break;
-					case FIRST:
-						timelineBlend = MixBlend.setup;
-						alpha = alphaMix;
-						break;
-					case HOLD_SUBSEQUENT:
-						timelineBlend = blend;
-						alpha = alphaHold;
-						break;
-					case HOLD_FIRST:
-						timelineBlend = MixBlend.setup;
-						alpha = alphaHold;
-						break;
-					default:
-						timelineBlend = MixBlend.setup;
-						let holdMix = timelineHoldMix[i];
-						alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
-						break;
-					}
-					from.totalAlpha += alpha;
-
-					if (timeline instanceof RotateTimeline)
-						this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame);
-					else if (timeline instanceof AttachmentTimeline)
-						this.applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments);
-					else {
-						// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
-						Utils.webkit602BugfixHelper(alpha, blend);
-						if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend == MixBlend.setup)
-							direction = MixDirection.mixIn;
-						timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction);
-					}
-				}
-			}
-
-			if (to.mixDuration > 0) this.queueEvents(from, animationTime);
-			this.events.length = 0;
-			from.nextAnimationLast = animationTime;
-			from.nextTrackLast = from.trackTime;
-
-			return mix;
-		}
-
-		applyAttachmentTimeline (timeline: AttachmentTimeline, skeleton: Skeleton, time: number, blend: MixBlend, attachments: boolean) {
-			var slot = skeleton.slots[timeline.slotIndex];
-			if (!slot.bone.active) return;
-
-			if (time < timeline.frames[0]) { // Time is before first frame.
-				if (blend == MixBlend.setup || blend == MixBlend.first)
-					this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
-			} else
-				this.setAttachment(skeleton, slot, timeline.attachmentNames[Timeline.search1(timeline.frames, time)], attachments);
-
-			// If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
-			if (slot.attachmentState <= this.unkeyedState) slot.attachmentState = this.unkeyedState + SETUP;
-		}
-
-		setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string, attachments: boolean) {
-			slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(slot.data.index, attachmentName));
-			if (attachments) slot.attachmentState = this.unkeyedState + CURRENT;
-		}
-
-		applyRotateTimeline (timeline: RotateTimeline, skeleton: Skeleton, time: number, alpha: number, blend: MixBlend,
-			timelinesRotation: Array<number>, i: number, firstFrame: boolean) {
-
-			if (firstFrame) timelinesRotation[i] = 0;
-
-			if (alpha == 1) {
-				timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.mixIn);
-				return;
-			}
-
-			let bone = skeleton.bones[timeline.boneIndex];
-			if (!bone.active) return;
-			let frames = timeline.frames;
-			let r1 = 0, r2 = 0;
-			if (time < frames[0]) {
-				switch (blend) {
-					case MixBlend.setup:
-						bone.rotation = bone.data.rotation;
-					default:
-						return;
-					case MixBlend.first:
-						r1 = bone.rotation;
-						r2 = bone.data.rotation;
-				}
-			} else {
-				r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation;
-				r2 = bone.data.rotation + timeline.getCurveValue(time);
-			}
-
-			// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
-			let total = 0, diff = r2 - r1;
-			diff -= (16384 - ((16384.499999999996 - diff / 360) | 0)) * 360;
-			if (diff == 0) {
-				total = timelinesRotation[i];
-			} else {
-				let lastTotal = 0, lastDiff = 0;
-				if (firstFrame) {
-					lastTotal = 0;
-					lastDiff = diff;
-				} else {
-					lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
-					lastDiff = timelinesRotation[i + 1]; // Difference between bones.
-				}
-				let current = diff > 0, dir = lastTotal >= 0;
-				// Detect cross at 0 (not 180).
-				if (MathUtils.signum(lastDiff) != MathUtils.signum(diff) && Math.abs(lastDiff) <= 90) {
-					// A cross after a 360 rotation is a loop.
-					if (Math.abs(lastTotal) > 180) lastTotal += 360 * MathUtils.signum(lastTotal);
-					dir = current;
-				}
-				total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
-				if (dir != current) total += 360 * MathUtils.signum(lastTotal);
-				timelinesRotation[i] = total;
-			}
-			timelinesRotation[i + 1] = diff;
-			bone.rotation = r1 + total * alpha;
-		}
-
-		queueEvents (entry: TrackEntry, animationTime: number) {
-			let animationStart = entry.animationStart, animationEnd = entry.animationEnd;
-			let duration = animationEnd - animationStart;
-			let trackLastWrapped = entry.trackLast % duration;
-
-			// Queue events before complete.
-			let events = this.events;
-			let i = 0, n = events.length;
-			for (; i < n; i++) {
-				let event = events[i];
-				if (event.time < trackLastWrapped) break;
-				if (event.time > animationEnd) continue; // Discard events outside animation start/end.
-				this.queue.event(entry, event);
-			}
-
-			// Queue complete if completed a loop iteration or the animation.
-			let complete = false;
-			if (entry.loop)
-				complete = duration == 0 || trackLastWrapped > entry.trackTime % duration;
-			else
-				complete = animationTime >= animationEnd && entry.animationLast < animationEnd;
-			if (complete) this.queue.complete(entry);
-
-			// Queue events after complete.
-			for (; i < n; i++) {
-				let event = events[i];
-				if (event.time < animationStart) continue; // Discard events outside animation start/end.
-				this.queue.event(entry, event);
-			}
-		}
-
-		/** Removes all animations from all tracks, leaving skeletons in their current pose.
-		 *
-		 * It may be desired to use {@link AnimationState#setEmptyAnimation()} to mix the skeletons back to the setup pose,
-		 * rather than leaving them in their current pose. */
-		clearTracks () {
-			let oldDrainDisabled = this.queue.drainDisabled;
-			this.queue.drainDisabled = true;
-			for (let i = 0, n = this.tracks.length; i < n; i++)
-				this.clearTrack(i);
-			this.tracks.length = 0;
-			this.queue.drainDisabled = oldDrainDisabled;
-			this.queue.drain();
-		}
-
-		/** Removes all animations from the track, leaving skeletons in their current pose.
-		 *
-		 * It may be desired to use {@link AnimationState#setEmptyAnimation()} to mix the skeletons back to the setup pose,
-		 * rather than leaving them in their current pose. */
-		clearTrack (trackIndex: number) {
-			if (trackIndex >= this.tracks.length) return;
-			let current = this.tracks[trackIndex];
-			if (!current) return;
-
-			this.queue.end(current);
-
-			this.clearNext(current);
-
-			let entry = current;
-			while (true) {
-				let from = entry.mixingFrom;
-				if (!from) break;
-				this.queue.end(from);
-				entry.mixingFrom = null;
-				entry.mixingTo = null;
-				entry = from;
-			}
-
-			this.tracks[current.trackIndex] = null;
-
-			this.queue.drain();
-		}
-
-		setCurrent (index: number, current: TrackEntry, interrupt: boolean) {
-			let from = this.expandToIndex(index);
-			this.tracks[index] = current;
-			current.previous = null;
-
-			if (from) {
-				if (interrupt) this.queue.interrupt(from);
-				current.mixingFrom = from;
-				from.mixingTo = current;
-				current.mixTime = 0;
-
-				// Store the interrupted mix percentage.
-				if (from.mixingFrom && from.mixDuration > 0)
-					current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration);
-
-				from.timelinesRotation.length = 0; // Reset rotation for mixing out, in case entry was mixed in.
-			}
-
-			this.queue.start(current);
-		}
-
-		/** Sets an animation by name.
-	 	*
-	 	* See {@link #setAnimationWith()}. */
-		setAnimation (trackIndex: number, animationName: string, loop: boolean = false) {
-			let animation = this.data.skeletonData.findAnimation(animationName);
-			if (!animation) throw new Error("Animation not found: " + animationName);
-			return this.setAnimationWith(trackIndex, animation, loop);
-		}
-
-		/** Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never
-		 * applied to a skeleton, it is replaced (not mixed from).
-		 * @param loop If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its
-		 *           duration. In either case {@link TrackEntry#trackEnd} determines when the track is cleared.
-		 * @returns A track entry to allow further customization of animation playback. References to the track entry must not be kept
-		 *         after the {@link AnimationStateListener#dispose()} event occurs. */
-		setAnimationWith (trackIndex: number, animation: Animation, loop: boolean = false) {
-			if (!animation) throw new Error("animation cannot be null.");
-			let interrupt = true;
-			let current = this.expandToIndex(trackIndex);
-			if (current) {
-				if (current.nextTrackLast == -1) {
-					// Don't mix from an entry that was never applied.
-					this.tracks[trackIndex] = current.mixingFrom;
-					this.queue.interrupt(current);
-					this.queue.end(current);
-					this.clearNext(current);
-					current = current.mixingFrom;
-					interrupt = false;
-				} else
-					this.clearNext(current);
-			}
-			let entry = this.trackEntry(trackIndex, animation, loop, current);
-			this.setCurrent(trackIndex, entry, interrupt);
-			this.queue.drain();
-			return entry;
-		}
-
-		/** Queues an animation by name.
-		 *
-		 * See {@link #addAnimationWith()}. */
-		addAnimation (trackIndex: number, animationName: string, loop: boolean = false, delay: number = 0) {
-			let animation = this.data.skeletonData.findAnimation(animationName);
-			if (!animation) throw new Error("Animation not found: " + animationName);
-			return this.addAnimationWith(trackIndex, animation, loop, delay);
-		}
-
-		/** Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is
-		 * equivalent to calling {@link #setAnimationWith()}.
-		 * @param delay If > 0, sets {@link TrackEntry#delay}. If <= 0, the delay set is the duration of the previous track entry
-		 *           minus any mix duration (from the {@link AnimationStateData}) plus the specified `delay` (ie the mix
-		 *           ends at (`delay` = 0) or before (`delay` < 0) the previous track entry duration). If the
-		 *           previous entry is looping, its next loop completion is used instead of its duration.
-		 * @returns A track entry to allow further customization of animation playback. References to the track entry must not be kept
-		 *         after the {@link AnimationStateListener#dispose()} event occurs. */
-		addAnimationWith (trackIndex: number, animation: Animation, loop: boolean = false, delay: number = 0) {
-			if (!animation) throw new Error("animation cannot be null.");
-
-			let last = this.expandToIndex(trackIndex);
-			if (last) {
-				while (last.next)
-					last = last.next;
-			}
-
-			let entry = this.trackEntry(trackIndex, animation, loop, last);
-
-			if (!last) {
-				this.setCurrent(trackIndex, entry, true);
-				this.queue.drain();
-			} else {
-				last.next = entry;
-				entry.previous = last;
-				if (delay <= 0) delay += last.getTrackComplete() - entry.mixDuration;
-			}
-
-			entry.delay = delay;
-			return entry;
-		}
-
-		/** Sets an empty animation for a track, discarding any queued animations, and sets the track entry's
-		 * {@link TrackEntry#mixduration}. An empty animation has no timelines and serves as a placeholder for mixing in or out.
-		 *
-		 * Mixing out is done by setting an empty animation with a mix duration using either {@link #setEmptyAnimation()},
-		 * {@link #setEmptyAnimations()}, or {@link #addEmptyAnimation()}. Mixing to an empty animation causes
-		 * the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation
-		 * transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of
-		 * 0 still mixes out over one frame.
-		 *
-		 * Mixing in is done by first setting an empty animation, then adding an animation using
-		 * {@link #addAnimation()} and on the returned track entry, set the
-		 * {@link TrackEntry#setMixDuration()}. Mixing from an empty animation causes the new animation to be applied more and
-		 * more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the
-		 * setup pose value if no lower tracks key the property to the value keyed in the new animation. */
-		setEmptyAnimation (trackIndex: number, mixDuration: number = 0) {
-			let entry = this.setAnimationWith(trackIndex, AnimationState.emptyAnimation(), false);
-			entry.mixDuration = mixDuration;
-			entry.trackEnd = mixDuration;
-			return entry;
-		}
-
-		/** Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's
-		 * {@link TrackEntry#mixDuration}. If the track is empty, it is equivalent to calling
-		 * {@link #setEmptyAnimation()}.
-		 *
-		 * See {@link #setEmptyAnimation()}.
-		 * @param delay If > 0, sets {@link TrackEntry#delay}. If <= 0, the delay set is the duration of the previous track entry
-		 *           minus any mix duration plus the specified `delay` (ie the mix ends at (`delay` = 0) or
-		 *           before (`delay` < 0) the previous track entry duration). If the previous entry is looping, its next
-		 *           loop completion is used instead of its duration.
-		 * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
-		 *         after the {@link AnimationStateListener#dispose()} event occurs. */
-		addEmptyAnimation (trackIndex: number, mixDuration: number = 0, delay: number = 0) {
-			let entry = this.addAnimationWith(trackIndex, AnimationState.emptyAnimation(), false, delay);
-			if (delay <= 0) entry.delay += entry.mixDuration - mixDuration;
-			entry.mixDuration = mixDuration;
-			entry.trackEnd = mixDuration;
-			return entry;
-		}
-
-		/** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix
-	 	* duration. */
-		setEmptyAnimations (mixDuration: number = 0) {
-			let oldDrainDisabled = this.queue.drainDisabled;
-			this.queue.drainDisabled = true;
-			for (let i = 0, n = this.tracks.length; i < n; i++) {
-				let current = this.tracks[i];
-				if (current) this.setEmptyAnimation(current.trackIndex, mixDuration);
-			}
-			this.queue.drainDisabled = oldDrainDisabled;
-			this.queue.drain();
-		}
-
-		expandToIndex (index: number) {
-			if (index < this.tracks.length) return this.tracks[index];
-			Utils.ensureArrayCapacity(this.tracks, index + 1, null);
-			this.tracks.length = index + 1;
-			return null;
-		}
-
-		/** @param last May be null. */
-		trackEntry (trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry) {
-			let entry = this.trackEntryPool.obtain();
-			entry.trackIndex = trackIndex;
-			entry.animation = animation;
-			entry.loop = loop;
-			entry.holdPrevious = false;
-
-			entry.eventThreshold = 0;
-			entry.attachmentThreshold = 0;
-			entry.drawOrderThreshold = 0;
-
-			entry.animationStart = 0;
-			entry.animationEnd = animation.duration;
-			entry.animationLast = -1;
-			entry.nextAnimationLast = -1;
-
-			entry.delay = 0;
-			entry.trackTime = 0;
-			entry.trackLast = -1;
-			entry.nextTrackLast = -1;
-			entry.trackEnd = Number.MAX_VALUE;
-			entry.timeScale = 1;
-
-			entry.alpha = 1;
-			entry.interruptAlpha = 1;
-			entry.mixTime = 0;
-			entry.mixDuration = !last ? 0 : this.data.getMix(last.animation, animation);
-			entry.mixBlend = MixBlend.replace;
-			return entry;
-		}
-
-		/** Removes the {@link TrackEntry#getNext() next entry} and all entries after it for the specified entry. */
-		clearNext (entry: TrackEntry) {
-			let next = entry.next;
-			while (next) {
-				this.queue.dispose(next);
-				next = next.next;
-			}
-			entry.next = null;
-		}
-
-		_animationsChanged () {
-			this.animationsChanged = false;
-
-			this.propertyIDs.clear();
-			let tracks = this.tracks;
-			for (let i = 0, n = tracks.length; i < n; i++) {
-				let entry = tracks[i];
-				if (!entry) continue;
-				while (entry.mixingFrom)
-					entry = entry.mixingFrom;
-				do {
-					if (!entry.mixingTo || entry.mixBlend != MixBlend.add) this.computeHold(entry);
-					entry = entry.mixingTo;
-				} while (entry);
-			}
-		}
-
-		computeHold (entry: TrackEntry) {
-			let to = entry.mixingTo;
-			let timelines = entry.animation.timelines;
-			let timelinesCount = entry.animation.timelines.length;
-			let timelineMode = entry.timelineMode;
-			timelineMode.length = timelinesCount;
-			let timelineHoldMix = entry.timelineHoldMix;
-			timelineHoldMix.length = 0;
-			let propertyIDs = this.propertyIDs;
-
-			if (to && to.holdPrevious) {
-				for (let i = 0; i < timelinesCount; i++)
-					timelineMode[i] = propertyIDs.addAll(timelines[i].getPropertyIds()) ? HOLD_FIRST : HOLD_SUBSEQUENT;
-				return;
-			}
-
-			outer:
-			for (let i = 0; i < timelinesCount; i++) {
-				let timeline = timelines[i];
-				let ids = timeline.getPropertyIds();
-				if (!propertyIDs.addAll(ids))
-					timelineMode[i] = SUBSEQUENT;
-				else if (!to || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline
-					|| timeline instanceof EventTimeline || !to.animation.hasTimeline(ids)) {
-					timelineMode[i] = FIRST;
-				} else {
-					for (let next = to.mixingTo; next; next = next.mixingTo) {
-						if (next.animation.hasTimeline(ids)) continue;
-						if (entry.mixDuration > 0) {
-							timelineMode[i] = HOLD_MIX;
-							timelineHoldMix[i] = next;
-							continue outer;
-						}
-						break;
-					}
-					timelineMode[i] = HOLD_FIRST;
-				}
-			}
-		}
-
-		/** Returns the track entry for the animation currently playing on the track, or null if no animation is currently playing. */
-		getCurrent (trackIndex: number) {
-			if (trackIndex >= this.tracks.length) return null;
-			return this.tracks[trackIndex];
-		}
-
-		/** Adds a listener to receive events for all track entries. */
-		addListener (listener: AnimationStateListener) {
-			if (!listener) throw new Error("listener cannot be null.");
-			this.listeners.push(listener);
-		}
-
-		/** Removes the listener added with {@link #addListener()}. */
-		removeListener (listener: AnimationStateListener) {
-			let index = this.listeners.indexOf(listener);
-			if (index >= 0) this.listeners.splice(index, 1);
-		}
-
-		/** Removes all listeners added with {@link #addListener()}. */
-		clearListeners () {
-			this.listeners.length = 0;
-		}
-
-		/** Discards all listener notifications that have not yet been delivered. This can be useful to call from an
-		 * {@link AnimationStateListener} when it is known that further notifications that may have been already queued for delivery
-		 * are not wanted because new animations are being set. */
-		clearListenerNotifications () {
-			this.queue.clear();
-		}
-	}
-
-	/** Stores settings and other state for the playback of an animation on an {@link AnimationState} track.
-	 *
-	 * References to a track entry must not be kept after the {@link AnimationStateListener#dispose()} event occurs. */
-	export class TrackEntry {
-		/** The animation to apply for this track entry. */
-		animation: Animation;
-
-		previous: TrackEntry;
-
-		/** The animation queued to start after this animation, or null. `next` makes up a linked list. */
-		next: TrackEntry;
-
-		/** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no
-		 * mixing is currently occuring. When mixing from multiple animations, `mixingFrom` makes up a linked list. */
-		mixingFrom: TrackEntry;
-
-		/** The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is
-		 * currently occuring. When mixing to multiple animations, `mixingTo` makes up a linked list. */
-		mixingTo: TrackEntry;
-
-		/** The listener for events generated by this track entry, or null.
-		 *
-		 * A track entry returned from {@link AnimationState#setAnimation()} is already the current animation
-		 * for the track, so the track entry listener {@link AnimationStateListener#start()} will not be called. */
-		listener: AnimationStateListener;
-
-		/** The index of the track where this track entry is either current or queued.
-		 *
-		 * See {@link AnimationState#getCurrent()}. */
-		trackIndex: number;
-
-		/** If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its
-		 * duration. */
-		loop: boolean;
-
-		/** If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead
-		 * of being mixed out.
-		 *
-		 * When mixing between animations that key the same property, if a lower track also keys that property then the value will
-		 * briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0%
-		 * while the second animation mixes from 0% to 100%. Setting `holdPrevious` to true applies the first animation
-		 * at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which
-		 * keys the property, only when a higher track also keys the property.
-		 *
-		 * Snapping will occur if `holdPrevious` is true and this animation does not key all the same properties as the
-		 * previous animation. */
-		holdPrevious: boolean;
-
-		reverse: boolean;
-
-		/** When the mix percentage ({@link #mixTime} / {@link #mixDuration}) is less than the
-		 * `eventThreshold`, event timelines are applied while this animation is being mixed out. Defaults to 0, so event
-		 * timelines are not applied while this animation is being mixed out. */
-		eventThreshold: number;
-
-		/** When the mix percentage ({@link #mixtime} / {@link #mixDuration}) is less than the
-		 * `attachmentThreshold`, attachment timelines are applied while this animation is being mixed out. Defaults to
-		 * 0, so attachment timelines are not applied while this animation is being mixed out. */
-		attachmentThreshold: number;
-
-		/** When the mix percentage ({@link #mixTime} / {@link #mixDuration}) is less than the
-		 * `drawOrderThreshold`, draw order timelines are applied while this animation is being mixed out. Defaults to 0,
-		 * so draw order timelines are not applied while this animation is being mixed out. */
-		drawOrderThreshold: number;
-
-		/** Seconds when this animation starts, both initially and after looping. Defaults to 0.
-		 *
-		 * When changing the `animationStart` time, it often makes sense to set {@link #animationLast} to the same
-		 * value to prevent timeline keys before the start time from triggering. */
-		animationStart: number;
-
-		/** Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will
-		 * loop back to {@link #animationStart} at this time. Defaults to the animation {@link Animation#duration}. */
-		animationEnd: number;
-
-
-		/** The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this
-		 * animation is applied, event timelines will fire all events between the `animationLast` time (exclusive) and
-		 * `animationTime` (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation
-		 * is applied. */
-		animationLast: number;
-
-		nextAnimationLast: number;
-
-		/** Seconds to postpone playing the animation. When this track entry is the current track entry, `delay`
-		 * postpones incrementing the {@link #trackTime}. When this track entry is queued, `delay` is the time from
-		 * the start of the previous animation to when this track entry will become the current track entry (ie when the previous
-		 * track entry {@link TrackEntry#trackTime} >= this track entry's `delay`).
-		 *
-		 * {@link #timeScale} affects the delay. */
-		delay: number;
-
-		/** Current time in seconds this track entry has been the current track entry. The track time determines
-		 * {@link #animationTime}. The track time can be set to start the animation at a time other than 0, without affecting
-		 * looping. */
-		trackTime: number;
-
-		trackLast: number; nextTrackLast: number;
-
-		/** The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float
-		 * value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time
-		 * is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the
-		 * properties keyed by the animation are set to the setup pose and the track is cleared.
-		 *
-		 * It may be desired to use {@link AnimationState#addEmptyAnimation()} rather than have the animation
-		 * abruptly cease being applied. */
-		trackEnd: number;
-
-		/** Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or
-		 * faster. Defaults to 1.
-		 *
-		 * {@link #mixTime} is not affected by track entry time scale, so {@link #mixDuration} may need to be adjusted to
-		 * match the animation speed.
-		 *
-		 * When using {@link AnimationState#addAnimation()} with a `delay` <= 0, note the
-		 * {@link #delay} is set using the mix duration from the {@link AnimationStateData}, assuming time scale to be 1. If
-		 * the time scale is not 1, the delay may need to be adjusted.
-		 *
-		 * See AnimationState {@link AnimationState#timeScale} for affecting all animations. */
-		timeScale: number;
-
-		/** Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults
-		 * to 1, which overwrites the skeleton's current pose with this animation.
-		 *
-		 * Typically track 0 is used to completely pose the skeleton, then alpha is used on higher tracks. It doesn't make sense to
-		 * use alpha on track 0 if the skeleton pose is from the last frame render. */
-		alpha: number;
-
-		/** Seconds from 0 to the {@link #getMixDuration()} when mixing from the previous animation to this animation. May be
-		 * slightly more than `mixDuration` when the mix is complete. */
-		mixTime: number;
-
-		/** Seconds for mixing from the previous animation to this animation. Defaults to the value provided by AnimationStateData
-		 * {@link AnimationStateData#getMix()} based on the animation before this animation (if any).
-		 *
-		 * A mix duration of 0 still mixes out over one frame to provide the track entry being mixed out a chance to revert the
-		 * properties it was animating.
-		 *
-		 * The `mixDuration` can be set manually rather than use the value from
-		 * {@link AnimationStateData#getMix()}. In that case, the `mixDuration` can be set for a new
-		 * track entry only before {@link AnimationState#update(float)} is first called.
-		 *
-		 * When using {@link AnimationState#addAnimation()} with a `delay` <= 0, note the
-		 * {@link #delay} is set using the mix duration from the {@link AnimationStateData}, not a mix duration set
-		 * afterward. */
-		mixDuration: number; interruptAlpha: number; totalAlpha: number;
-
-		/** Controls how properties keyed in the animation are mixed with lower tracks. Defaults to {@link MixBlend#replace}, which
-		 * replaces the values from the lower tracks with the animation values. {@link MixBlend#add} adds the animation values to
-		 * the values from the lower tracks.
-		 *
-		 * The `mixBlend` can be set for a new track entry only before {@link AnimationState#apply()} is first
-		 * called. */
-		mixBlend = MixBlend.replace;
-		timelineMode = new Array<number>();
-		timelineHoldMix = new Array<TrackEntry>();
-		timelinesRotation = new Array<number>();
-
-		reset () {
-			this.next = null;
-			this.previous = null;
-			this.mixingFrom = null;
-			this.mixingTo = null;
-			this.animation = null;
-			this.listener = null;
-			this.timelineMode.length = 0;
-			this.timelineHoldMix.length = 0;
-			this.timelinesRotation.length = 0;
-		}
-
-		/** Uses {@link #trackTime} to compute the `animationTime`, which is between {@link #animationStart}
-		 * and {@link #animationEnd}. When the `trackTime` is 0, the `animationTime` is equal to the
-		 * `animationStart` time. */
-		getAnimationTime () {
-			if (this.loop) {
-				let duration = this.animationEnd - this.animationStart;
-				if (duration == 0) return this.animationStart;
-				return (this.trackTime % duration) + this.animationStart;
-			}
-			return Math.min(this.trackTime + this.animationStart, this.animationEnd);
-		}
-
-		setAnimationLast(animationLast: number) {
-			this.animationLast = animationLast;
-			this.nextAnimationLast = animationLast;
-		}
-
-		/** Returns true if at least one loop has been completed.
-		 *
-		 * See {@link AnimationStateListener#complete()}. */
-		isComplete () {
-			return this.trackTime >= this.animationEnd - this.animationStart;
-		}
-
-		/** Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the
-		 * long way around when using {@link #alpha} and starting animations on other tracks.
-		 *
-		 * Mixing with {@link MixBlend#replace} involves finding a rotation between two others, which has two possible solutions:
-		 * the short way or the long way around. The two rotations likely change over time, so which direction is the short or long
-		 * way also changes. If the short way was always chosen, bones would flip to the other side when that direction became the
-		 * long way. TrackEntry chooses the short way the first time it is applied and remembers that direction. */
-		resetRotationDirections () {
-			this.timelinesRotation.length = 0;
-		}
-
-		getTrackComplete() {
-			let duration = this.animationEnd - this.animationStart;
-			if (duration != 0) {
-				if (this.loop) return duration * (1 + ((this.trackTime / duration) | 0)); // Completion of next loop.
-				if (this.trackTime < duration) return duration; // Before duration.
-			}
-			return this.trackTime; // Next update.
-		}
-	}
-
-	export class EventQueue {
-		objects: Array<any> = [];
-		drainDisabled = false;
-		animState: AnimationState;
-
-		constructor(animState: AnimationState) {
-			this.animState = animState;
-		}
-
-		start (entry: TrackEntry) {
-			this.objects.push(EventType.start);
-			this.objects.push(entry);
-			this.animState.animationsChanged = true;
-		}
-
-		interrupt (entry: TrackEntry) {
-			this.objects.push(EventType.interrupt);
-			this.objects.push(entry);
-		}
-
-		end (entry: TrackEntry) {
-			this.objects.push(EventType.end);
-			this.objects.push(entry);
-			this.animState.animationsChanged = true;
-		}
-
-		dispose (entry: TrackEntry) {
-			this.objects.push(EventType.dispose);
-			this.objects.push(entry);
-		}
-
-		complete (entry: TrackEntry) {
-			this.objects.push(EventType.complete);
-			this.objects.push(entry);
-		}
-
-		event (entry: TrackEntry, event: Event) {
-			this.objects.push(EventType.event);
-			this.objects.push(entry);
-			this.objects.push(event);
-		}
-
-		drain () {
-			if (this.drainDisabled) return;
-			this.drainDisabled = true;
-
-			let objects = this.objects;
-			let listeners = this.animState.listeners;
-
-			for (let i = 0; i < objects.length; i += 2) {
-				let type = objects[i] as EventType;
-				let entry = objects[i + 1] as TrackEntry;
-				switch (type) {
-				case EventType.start:
-					if (entry.listener && entry.listener.start) entry.listener.start(entry);
-					for (let ii = 0; ii < listeners.length; ii++)
-						if (listeners[ii].start) listeners[ii].start(entry);
-					break;
-				case EventType.interrupt:
-					if (entry.listener && entry.listener.interrupt) entry.listener.interrupt(entry);
-					for (let ii = 0; ii < listeners.length; ii++)
-						if (listeners[ii].interrupt) listeners[ii].interrupt(entry);
-					break;
-				case EventType.end:
-					if (entry.listener && entry.listener.end) entry.listener.end(entry);
-					for (let ii = 0; ii < listeners.length; ii++)
-						if (listeners[ii].end) listeners[ii].end(entry);
-					// Fall through.
-				case EventType.dispose:
-					if (entry.listener && entry.listener.dispose) entry.listener.dispose(entry);
-					for (let ii = 0; ii < listeners.length; ii++)
-						if (listeners[ii].dispose) listeners[ii].dispose(entry);
-					this.animState.trackEntryPool.free(entry);
-					break;
-				case EventType.complete:
-					if (entry.listener && entry.listener.complete) entry.listener.complete(entry);
-					for (let ii = 0; ii < listeners.length; ii++)
-						if (listeners[ii].complete) listeners[ii].complete(entry);
-					break;
-				case EventType.event:
-					let event = objects[i++ + 2] as Event;
-					if (entry.listener && entry.listener.event) entry.listener.event(entry, event);
-					for (let ii = 0; ii < listeners.length; ii++)
-						if (listeners[ii].event) listeners[ii].event(entry, event);
-					break;
-				}
-			}
-			this.clear();
-
-			this.drainDisabled = false;
-		}
-
-		clear () {
-			this.objects.length = 0;
-		}
-	}
-
-	export enum EventType {
-		start, interrupt, end, dispose, complete, event
-	}
-
-	/** The interface to implement for receiving TrackEntry events. It is always safe to call AnimationState methods when receiving
-	 * events.
-	 *
-	 * See TrackEntry {@link TrackEntry#listener} and AnimationState
-	 * {@link AnimationState#addListener()}. */
-	export interface AnimationStateListener {
-		/** Invoked when this entry has been set as the current entry. */
-		start (entry: TrackEntry): void;
-
-		/** Invoked when another entry has replaced this entry as the current entry. This entry may continue being applied for
-		 * mixing. */
-		interrupt (entry: TrackEntry): void;
-
-		/** Invoked when this entry is no longer the current entry and will never be applied again. */
-		end (entry: TrackEntry): void;
-
-		/** Invoked when this entry will be disposed. This may occur without the entry ever being set as the current entry.
-		 * References to the entry should not be kept after dispose is called, as it may be destroyed or reused. */
-		dispose (entry: TrackEntry): void;
-
-		/** Invoked every time this entry's animation completes a loop. */
-		complete (entry: TrackEntry): void;
-
-		/** Invoked when this entry's animation triggers an event. */
-		event (entry: TrackEntry, event: Event): void;
-	}
-
-	export abstract class AnimationStateAdapter implements AnimationStateListener {
-		start (entry: TrackEntry) {
-		}
-
-		interrupt (entry: TrackEntry) {
-		}
-
-		end (entry: TrackEntry) {
-		}
-
-		dispose (entry: TrackEntry) {
-		}
-
-		complete (entry: TrackEntry) {
-		}
-
-		event (entry: TrackEntry, event: Event) {
-		}
-	}
-
-	/** 1. A previously applied timeline has set this property.
-	 *
-	 * Result: Mix from the current pose to the timeline pose. */
-	const SUBSEQUENT = 0;
-	/** 1. This is the first timeline to set this property.
-	 * 2. The next track entry applied after this one does not have a timeline to set this property.
-	 *
-	 * Result: Mix from the setup pose to the timeline pose. */
-	const FIRST = 1;
-	/** 1) A previously applied timeline has set this property.<br>
-	 * 2) The next track entry to be applied does have a timeline to set this property.<br>
-	 * 3) The next track entry after that one does not have a timeline to set this property.<br>
-	 * Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading
-	 * animations that key the same property. A subsequent timeline will set this property using a mix. */
-	const HOLD_SUBSEQUENT = 2;
-	/** 1) This is the first timeline to set this property.<br>
-	 * 2) The next track entry to be applied does have a timeline to set this property.<br>
-	 * 3) The next track entry after that one does not have a timeline to set this property.<br>
-	 * Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations
-	 * that key the same property. A subsequent timeline will set this property using a mix. */
-	const HOLD_FIRST = 3;
-	/** 1. This is the first timeline to set this property.
-	 * 2. The next track entry to be applied does have a timeline to set this property.
-	 * 3. The next track entry after that one does have a timeline to set this property.
-	 * 4. timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property.
-	 *
-	 * Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than
-	 * 2 track entries in a row have a timeline that sets the same property.
-	 *
-	 * Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid
-	 * "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A
-	 * (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap into
-	 * place. */
-	const HOLD_MIX = 4;
-
-	const SETUP = 1;
-	const CURRENT = 2;
-
-	let _emptyAnimation: Animation = null;
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies
+	 * multiple animations on top of each other (layering).
+	 *
+	 * See [Applying Animations](http://esotericsoftware.com/spine-applying-animations/) in the Spine Runtimes Guide. */
+	export class AnimationState {
+		private static emptyAnimation (): Animation {
+			if (!_emptyAnimation) _emptyAnimation = new Animation("<empty>", [], 0);
+			return _emptyAnimation;
+		}
+
+		/** The AnimationStateData to look up mix durations. */
+		data: AnimationStateData;
+
+		/** The list of tracks that currently have animations, which may contain null entries. */
+		tracks = new Array<TrackEntry>();
+
+		/** Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower
+		 * or faster. Defaults to 1.
+		 *
+		 * See TrackEntry {@link TrackEntry#timeScale} for affecting a single animation. */
+		timeScale = 1;
+		unkeyedState = 0;
+
+		events = new Array<Event>();
+		listeners = new Array<AnimationStateListener>();
+		queue = new EventQueue(this);
+		propertyIDs = new StringSet();
+		animationsChanged = false;
+
+		trackEntryPool = new Pool<TrackEntry>(() => new TrackEntry());
+
+		constructor (data: AnimationStateData) {
+			this.data = data;
+		}
+
+		/** Increments each track entry {@link TrackEntry#trackTime()}, setting queued animations as current if needed. */
+		update (delta: number) {
+			delta *= this.timeScale;
+			let tracks = this.tracks;
+			for (let i = 0, n = tracks.length; i < n; i++) {
+				let current = tracks[i];
+				if (!current) continue;
+
+				current.animationLast = current.nextAnimationLast;
+				current.trackLast = current.nextTrackLast;
+
+				let currentDelta = delta * current.timeScale;
+
+				if (current.delay > 0) {
+					current.delay -= currentDelta;
+					if (current.delay > 0) continue;
+					currentDelta = -current.delay;
+					current.delay = 0;
+				}
+
+				let next = current.next;
+				if (next) {
+					// When the next entry's delay is passed, change to the next entry, preserving leftover time.
+					let nextTime = current.trackLast - next.delay;
+					if (nextTime >= 0) {
+						next.delay = 0;
+						next.trackTime += current.timeScale == 0 ? 0 : (nextTime / current.timeScale + delta) * next.timeScale;
+						current.trackTime += currentDelta;
+						this.setCurrent(i, next, true);
+						while (next.mixingFrom) {
+							next.mixTime += delta;
+							next = next.mixingFrom;
+						}
+						continue;
+					}
+				} else if (current.trackLast >= current.trackEnd && !current.mixingFrom) {
+					tracks[i] = null;
+					this.queue.end(current);
+					this.clearNext(current);
+					continue;
+				}
+				if (current.mixingFrom && this.updateMixingFrom(current, delta)) {
+					// End mixing from entries once all have completed.
+					let from = current.mixingFrom;
+					current.mixingFrom = null;
+					if (from) from.mixingTo = null;
+					while (from) {
+						this.queue.end(from);
+						from = from.mixingFrom;
+					}
+				}
+
+				current.trackTime += currentDelta;
+			}
+
+			this.queue.drain();
+		}
+
+		/** Returns true when all mixing from entries are complete. */
+		updateMixingFrom (to: TrackEntry, delta: number): boolean {
+			let from = to.mixingFrom;
+			if (!from) return true;
+
+			let finished = this.updateMixingFrom(from, delta);
+
+			from.animationLast = from.nextAnimationLast;
+			from.trackLast = from.nextTrackLast;
+
+			// Require mixTime > 0 to ensure the mixing from entry was applied at least once.
+			if (to.mixTime > 0 && to.mixTime >= to.mixDuration) {
+				// Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
+				if (from.totalAlpha == 0 || to.mixDuration == 0) {
+					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom) from.mixingFrom.mixingTo = to;
+					to.interruptAlpha = from.interruptAlpha;
+					this.queue.end(from);
+				}
+				return finished;
+			}
+
+			from.trackTime += delta * from.timeScale;
+			to.mixTime += delta;
+			return false;
+		}
+
+		/** Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the
+		 * animation state can be applied to multiple skeletons to pose them identically.
+		 * @returns True if any animations were applied. */
+		apply (skeleton: Skeleton): boolean {
+			if (!skeleton) throw new Error("skeleton cannot be null.");
+			if (this.animationsChanged) this._animationsChanged();
+
+			let events = this.events;
+			let tracks = this.tracks;
+			let applied = false;
+
+			for (let i = 0, n = tracks.length; i < n; i++) {
+				let current = tracks[i];
+				if (!current || current.delay > 0) continue;
+				applied = true;
+				let blend: MixBlend = i == 0 ? MixBlend.first : current.mixBlend;
+
+				// Apply mixing from entries first.
+				let mix = current.alpha;
+				if (current.mixingFrom)
+					mix *= this.applyMixingFrom(current, skeleton, blend);
+				else if (current.trackTime >= current.trackEnd && !current.next)
+					mix = 0;
+
+				// Apply current entry.
+				let animationLast = current.animationLast, animationTime = current.getAnimationTime(), applyTime = animationTime;
+				let applyEvents = events;
+				if (current.reverse) {
+					applyTime = current.animation.duration - applyTime;
+					applyEvents = null;
+				}
+				let timelines = current.animation.timelines;
+				let timelineCount = timelines.length;
+				if ((i == 0 && mix == 1) || blend == MixBlend.add) {
+					for (let ii = 0; ii < timelineCount; ii++) {
+						// Fixes issue #302 on IOS9 where mix, blend sometimes became undefined and caused assets
+						// to sometimes stop rendering when using color correction, as their RGBA values become NaN.
+						// (https://github.com/pixijs/pixi-spine/issues/302)
+						Utils.webkit602BugfixHelper(mix, blend);
+						var timeline = timelines[ii];
+						if (timeline instanceof AttachmentTimeline)
+							this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true);
+						else
+							timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.mixIn);
+					}
+				} else {
+					let timelineMode = current.timelineMode;
+
+					let firstFrame = current.timelinesRotation.length != timelineCount << 1;
+					if (firstFrame) current.timelinesRotation.length = timelineCount << 1;
+
+					for (let ii = 0; ii < timelineCount; ii++) {
+						let timeline = timelines[ii];
+						let timelineBlend = timelineMode[ii] == SUBSEQUENT ? blend : MixBlend.setup;
+						if (timeline instanceof RotateTimeline) {
+							this.applyRotateTimeline(timeline, skeleton, applyTime, mix, timelineBlend, current.timelinesRotation, ii << 1, firstFrame);
+						} else if (timeline instanceof AttachmentTimeline) {
+							this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true);
+						} else {
+							// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
+							Utils.webkit602BugfixHelper(mix, blend);
+							timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.mixIn);
+						}
+					}
+				}
+				this.queueEvents(current, animationTime);
+				events.length = 0;
+				current.nextAnimationLast = animationTime;
+				current.nextTrackLast = current.trackTime;
+			}
+
+			// Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so
+			// subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or
+			// the time is before the first key).
+			var setupState = this.unkeyedState + SETUP;
+			var slots = skeleton.slots;
+			for (var i = 0, n = skeleton.slots.length; i < n; i++) {
+				var slot = slots[i];
+				if (slot.attachmentState == setupState) {
+					var attachmentName = slot.data.attachmentName;
+					slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(slot.data.index, attachmentName));
+				}
+			}
+			this.unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot.
+
+			this.queue.drain();
+			return applied;
+		}
+
+		applyMixingFrom (to: TrackEntry, skeleton: Skeleton, blend: MixBlend) {
+			let from = to.mixingFrom;
+			if (from.mixingFrom) this.applyMixingFrom(from, skeleton, blend);
+
+			let mix = 0;
+			if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
+				mix = 1;
+				if (blend == MixBlend.first) blend = MixBlend.setup;
+			} else {
+				mix = to.mixTime / to.mixDuration;
+				if (mix > 1) mix = 1;
+				if (blend != MixBlend.first) blend = from.mixBlend;
+			}
+
+			let attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
+			let timelines = from.animation.timelines;
+			let timelineCount = timelines.length;
+			let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
+			let animationLast = from.animationLast, animationTime = from.getAnimationTime(), applyTime = animationTime;
+			let events = null;
+			if (from.reverse)
+				applyTime = from.animation.duration - applyTime;
+			else if (mix < from.eventThreshold)
+				events = this.events;
+
+			if (blend == MixBlend.add) {
+				for (let i = 0; i < timelineCount; i++)
+					timelines[i].apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.mixOut);
+			} else {
+				let timelineMode = from.timelineMode;
+				let timelineHoldMix = from.timelineHoldMix;
+
+				let firstFrame = from.timelinesRotation.length != timelineCount << 1;
+				if (firstFrame) from.timelinesRotation.length = timelineCount << 1;
+
+				from.totalAlpha = 0;
+				for (let i = 0; i < timelineCount; i++) {
+					let timeline = timelines[i];
+					let direction = MixDirection.mixOut;
+					let timelineBlend: MixBlend;
+					let alpha = 0;
+					switch (timelineMode[i]) {
+						case SUBSEQUENT:
+							if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
+							timelineBlend = blend;
+							alpha = alphaMix;
+							break;
+						case FIRST:
+							timelineBlend = MixBlend.setup;
+							alpha = alphaMix;
+							break;
+						case HOLD_SUBSEQUENT:
+							timelineBlend = blend;
+							alpha = alphaHold;
+							break;
+						case HOLD_FIRST:
+							timelineBlend = MixBlend.setup;
+							alpha = alphaHold;
+							break;
+						default:
+							timelineBlend = MixBlend.setup;
+							let holdMix = timelineHoldMix[i];
+							alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
+							break;
+					}
+					from.totalAlpha += alpha;
+
+					if (timeline instanceof RotateTimeline)
+						this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame);
+					else if (timeline instanceof AttachmentTimeline)
+						this.applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments);
+					else {
+						// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
+						Utils.webkit602BugfixHelper(alpha, blend);
+						if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend == MixBlend.setup)
+							direction = MixDirection.mixIn;
+						timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction);
+					}
+				}
+			}
+
+			if (to.mixDuration > 0) this.queueEvents(from, animationTime);
+			this.events.length = 0;
+			from.nextAnimationLast = animationTime;
+			from.nextTrackLast = from.trackTime;
+
+			return mix;
+		}
+
+		applyAttachmentTimeline (timeline: AttachmentTimeline, skeleton: Skeleton, time: number, blend: MixBlend, attachments: boolean) {
+			var slot = skeleton.slots[timeline.slotIndex];
+			if (!slot.bone.active) return;
+
+			if (time < timeline.frames[0]) { // Time is before first frame.
+				if (blend == MixBlend.setup || blend == MixBlend.first)
+					this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
+			} else
+				this.setAttachment(skeleton, slot, timeline.attachmentNames[Timeline.search1(timeline.frames, time)], attachments);
+
+			// If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
+			if (slot.attachmentState <= this.unkeyedState) slot.attachmentState = this.unkeyedState + SETUP;
+		}
+
+		setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string, attachments: boolean) {
+			slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(slot.data.index, attachmentName));
+			if (attachments) slot.attachmentState = this.unkeyedState + CURRENT;
+		}
+
+		applyRotateTimeline (timeline: RotateTimeline, skeleton: Skeleton, time: number, alpha: number, blend: MixBlend,
+			timelinesRotation: Array<number>, i: number, firstFrame: boolean) {
+
+			if (firstFrame) timelinesRotation[i] = 0;
+
+			if (alpha == 1) {
+				timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.mixIn);
+				return;
+			}
+
+			let bone = skeleton.bones[timeline.boneIndex];
+			if (!bone.active) return;
+			let frames = timeline.frames;
+			let r1 = 0, r2 = 0;
+			if (time < frames[0]) {
+				switch (blend) {
+					case MixBlend.setup:
+						bone.rotation = bone.data.rotation;
+					default:
+						return;
+					case MixBlend.first:
+						r1 = bone.rotation;
+						r2 = bone.data.rotation;
+				}
+			} else {
+				r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation;
+				r2 = bone.data.rotation + timeline.getCurveValue(time);
+			}
+
+			// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
+			let total = 0, diff = r2 - r1;
+			diff -= (16384 - ((16384.499999999996 - diff / 360) | 0)) * 360;
+			if (diff == 0) {
+				total = timelinesRotation[i];
+			} else {
+				let lastTotal = 0, lastDiff = 0;
+				if (firstFrame) {
+					lastTotal = 0;
+					lastDiff = diff;
+				} else {
+					lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
+					lastDiff = timelinesRotation[i + 1]; // Difference between bones.
+				}
+				let current = diff > 0, dir = lastTotal >= 0;
+				// Detect cross at 0 (not 180).
+				if (MathUtils.signum(lastDiff) != MathUtils.signum(diff) && Math.abs(lastDiff) <= 90) {
+					// A cross after a 360 rotation is a loop.
+					if (Math.abs(lastTotal) > 180) lastTotal += 360 * MathUtils.signum(lastTotal);
+					dir = current;
+				}
+				total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
+				if (dir != current) total += 360 * MathUtils.signum(lastTotal);
+				timelinesRotation[i] = total;
+			}
+			timelinesRotation[i + 1] = diff;
+			bone.rotation = r1 + total * alpha;
+		}
+
+		queueEvents (entry: TrackEntry, animationTime: number) {
+			let animationStart = entry.animationStart, animationEnd = entry.animationEnd;
+			let duration = animationEnd - animationStart;
+			let trackLastWrapped = entry.trackLast % duration;
+
+			// Queue events before complete.
+			let events = this.events;
+			let i = 0, n = events.length;
+			for (; i < n; i++) {
+				let event = events[i];
+				if (event.time < trackLastWrapped) break;
+				if (event.time > animationEnd) continue; // Discard events outside animation start/end.
+				this.queue.event(entry, event);
+			}
+
+			// Queue complete if completed a loop iteration or the animation.
+			let complete = false;
+			if (entry.loop)
+				complete = duration == 0 || trackLastWrapped > entry.trackTime % duration;
+			else
+				complete = animationTime >= animationEnd && entry.animationLast < animationEnd;
+			if (complete) this.queue.complete(entry);
+
+			// Queue events after complete.
+			for (; i < n; i++) {
+				let event = events[i];
+				if (event.time < animationStart) continue; // Discard events outside animation start/end.
+				this.queue.event(entry, event);
+			}
+		}
+
+		/** Removes all animations from all tracks, leaving skeletons in their current pose.
+		 *
+		 * It may be desired to use {@link AnimationState#setEmptyAnimation()} to mix the skeletons back to the setup pose,
+		 * rather than leaving them in their current pose. */
+		clearTracks () {
+			let oldDrainDisabled = this.queue.drainDisabled;
+			this.queue.drainDisabled = true;
+			for (let i = 0, n = this.tracks.length; i < n; i++)
+				this.clearTrack(i);
+			this.tracks.length = 0;
+			this.queue.drainDisabled = oldDrainDisabled;
+			this.queue.drain();
+		}
+
+		/** Removes all animations from the track, leaving skeletons in their current pose.
+		 *
+		 * It may be desired to use {@link AnimationState#setEmptyAnimation()} to mix the skeletons back to the setup pose,
+		 * rather than leaving them in their current pose. */
+		clearTrack (trackIndex: number) {
+			if (trackIndex >= this.tracks.length) return;
+			let current = this.tracks[trackIndex];
+			if (!current) return;
+
+			this.queue.end(current);
+
+			this.clearNext(current);
+
+			let entry = current;
+			while (true) {
+				let from = entry.mixingFrom;
+				if (!from) break;
+				this.queue.end(from);
+				entry.mixingFrom = null;
+				entry.mixingTo = null;
+				entry = from;
+			}
+
+			this.tracks[current.trackIndex] = null;
+
+			this.queue.drain();
+		}
+
+		setCurrent (index: number, current: TrackEntry, interrupt: boolean) {
+			let from = this.expandToIndex(index);
+			this.tracks[index] = current;
+			current.previous = null;
+
+			if (from) {
+				if (interrupt) this.queue.interrupt(from);
+				current.mixingFrom = from;
+				from.mixingTo = current;
+				current.mixTime = 0;
+
+				// Store the interrupted mix percentage.
+				if (from.mixingFrom && from.mixDuration > 0)
+					current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration);
+
+				from.timelinesRotation.length = 0; // Reset rotation for mixing out, in case entry was mixed in.
+			}
+
+			this.queue.start(current);
+		}
+
+		/** Sets an animation by name.
+	 	*
+	 	* See {@link #setAnimationWith()}. */
+		setAnimation (trackIndex: number, animationName: string, loop: boolean = false) {
+			let animation = this.data.skeletonData.findAnimation(animationName);
+			if (!animation) throw new Error("Animation not found: " + animationName);
+			return this.setAnimationWith(trackIndex, animation, loop);
+		}
+
+		/** Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never
+		 * applied to a skeleton, it is replaced (not mixed from).
+		 * @param loop If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its
+		 *           duration. In either case {@link TrackEntry#trackEnd} determines when the track is cleared.
+		 * @returns A track entry to allow further customization of animation playback. References to the track entry must not be kept
+		 *         after the {@link AnimationStateListener#dispose()} event occurs. */
+		setAnimationWith (trackIndex: number, animation: Animation, loop: boolean = false) {
+			if (!animation) throw new Error("animation cannot be null.");
+			let interrupt = true;
+			let current = this.expandToIndex(trackIndex);
+			if (current) {
+				if (current.nextTrackLast == -1) {
+					// Don't mix from an entry that was never applied.
+					this.tracks[trackIndex] = current.mixingFrom;
+					this.queue.interrupt(current);
+					this.queue.end(current);
+					this.clearNext(current);
+					current = current.mixingFrom;
+					interrupt = false;
+				} else
+					this.clearNext(current);
+			}
+			let entry = this.trackEntry(trackIndex, animation, loop, current);
+			this.setCurrent(trackIndex, entry, interrupt);
+			this.queue.drain();
+			return entry;
+		}
+
+		/** Queues an animation by name.
+		 *
+		 * See {@link #addAnimationWith()}. */
+		addAnimation (trackIndex: number, animationName: string, loop: boolean = false, delay: number = 0) {
+			let animation = this.data.skeletonData.findAnimation(animationName);
+			if (!animation) throw new Error("Animation not found: " + animationName);
+			return this.addAnimationWith(trackIndex, animation, loop, delay);
+		}
+
+		/** Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is
+		 * equivalent to calling {@link #setAnimationWith()}.
+		 * @param delay If > 0, sets {@link TrackEntry#delay}. If <= 0, the delay set is the duration of the previous track entry
+		 *           minus any mix duration (from the {@link AnimationStateData}) plus the specified `delay` (ie the mix
+		 *           ends at (`delay` = 0) or before (`delay` < 0) the previous track entry duration). If the
+		 *           previous entry is looping, its next loop completion is used instead of its duration.
+		 * @returns A track entry to allow further customization of animation playback. References to the track entry must not be kept
+		 *         after the {@link AnimationStateListener#dispose()} event occurs. */
+		addAnimationWith (trackIndex: number, animation: Animation, loop: boolean = false, delay: number = 0) {
+			if (!animation) throw new Error("animation cannot be null.");
+
+			let last = this.expandToIndex(trackIndex);
+			if (last) {
+				while (last.next)
+					last = last.next;
+			}
+
+			let entry = this.trackEntry(trackIndex, animation, loop, last);
+
+			if (!last) {
+				this.setCurrent(trackIndex, entry, true);
+				this.queue.drain();
+			} else {
+				last.next = entry;
+				entry.previous = last;
+				if (delay <= 0) delay += last.getTrackComplete() - entry.mixDuration;
+			}
+
+			entry.delay = delay;
+			return entry;
+		}
+
+		/** Sets an empty animation for a track, discarding any queued animations, and sets the track entry's
+		 * {@link TrackEntry#mixduration}. An empty animation has no timelines and serves as a placeholder for mixing in or out.
+		 *
+		 * Mixing out is done by setting an empty animation with a mix duration using either {@link #setEmptyAnimation()},
+		 * {@link #setEmptyAnimations()}, or {@link #addEmptyAnimation()}. Mixing to an empty animation causes
+		 * the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation
+		 * transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of
+		 * 0 still mixes out over one frame.
+		 *
+		 * Mixing in is done by first setting an empty animation, then adding an animation using
+		 * {@link #addAnimation()} and on the returned track entry, set the
+		 * {@link TrackEntry#setMixDuration()}. Mixing from an empty animation causes the new animation to be applied more and
+		 * more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the
+		 * setup pose value if no lower tracks key the property to the value keyed in the new animation. */
+		setEmptyAnimation (trackIndex: number, mixDuration: number = 0) {
+			let entry = this.setAnimationWith(trackIndex, AnimationState.emptyAnimation(), false);
+			entry.mixDuration = mixDuration;
+			entry.trackEnd = mixDuration;
+			return entry;
+		}
+
+		/** Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's
+		 * {@link TrackEntry#mixDuration}. If the track is empty, it is equivalent to calling
+		 * {@link #setEmptyAnimation()}.
+		 *
+		 * See {@link #setEmptyAnimation()}.
+		 * @param delay If > 0, sets {@link TrackEntry#delay}. If <= 0, the delay set is the duration of the previous track entry
+		 *           minus any mix duration plus the specified `delay` (ie the mix ends at (`delay` = 0) or
+		 *           before (`delay` < 0) the previous track entry duration). If the previous entry is looping, its next
+		 *           loop completion is used instead of its duration.
+		 * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
+		 *         after the {@link AnimationStateListener#dispose()} event occurs. */
+		addEmptyAnimation (trackIndex: number, mixDuration: number = 0, delay: number = 0) {
+			let entry = this.addAnimationWith(trackIndex, AnimationState.emptyAnimation(), false, delay);
+			if (delay <= 0) entry.delay += entry.mixDuration - mixDuration;
+			entry.mixDuration = mixDuration;
+			entry.trackEnd = mixDuration;
+			return entry;
+		}
+
+		/** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix
+	 	* duration. */
+		setEmptyAnimations (mixDuration: number = 0) {
+			let oldDrainDisabled = this.queue.drainDisabled;
+			this.queue.drainDisabled = true;
+			for (let i = 0, n = this.tracks.length; i < n; i++) {
+				let current = this.tracks[i];
+				if (current) this.setEmptyAnimation(current.trackIndex, mixDuration);
+			}
+			this.queue.drainDisabled = oldDrainDisabled;
+			this.queue.drain();
+		}
+
+		expandToIndex (index: number) {
+			if (index < this.tracks.length) return this.tracks[index];
+			Utils.ensureArrayCapacity(this.tracks, index + 1, null);
+			this.tracks.length = index + 1;
+			return null;
+		}
+
+		/** @param last May be null. */
+		trackEntry (trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry) {
+			let entry = this.trackEntryPool.obtain();
+			entry.trackIndex = trackIndex;
+			entry.animation = animation;
+			entry.loop = loop;
+			entry.holdPrevious = false;
+
+			entry.eventThreshold = 0;
+			entry.attachmentThreshold = 0;
+			entry.drawOrderThreshold = 0;
+
+			entry.animationStart = 0;
+			entry.animationEnd = animation.duration;
+			entry.animationLast = -1;
+			entry.nextAnimationLast = -1;
+
+			entry.delay = 0;
+			entry.trackTime = 0;
+			entry.trackLast = -1;
+			entry.nextTrackLast = -1;
+			entry.trackEnd = Number.MAX_VALUE;
+			entry.timeScale = 1;
+
+			entry.alpha = 1;
+			entry.interruptAlpha = 1;
+			entry.mixTime = 0;
+			entry.mixDuration = !last ? 0 : this.data.getMix(last.animation, animation);
+			entry.mixBlend = MixBlend.replace;
+			return entry;
+		}
+
+		/** Removes the {@link TrackEntry#getNext() next entry} and all entries after it for the specified entry. */
+		clearNext (entry: TrackEntry) {
+			let next = entry.next;
+			while (next) {
+				this.queue.dispose(next);
+				next = next.next;
+			}
+			entry.next = null;
+		}
+
+		_animationsChanged () {
+			this.animationsChanged = false;
+
+			this.propertyIDs.clear();
+			let tracks = this.tracks;
+			for (let i = 0, n = tracks.length; i < n; i++) {
+				let entry = tracks[i];
+				if (!entry) continue;
+				while (entry.mixingFrom)
+					entry = entry.mixingFrom;
+				do {
+					if (!entry.mixingTo || entry.mixBlend != MixBlend.add) this.computeHold(entry);
+					entry = entry.mixingTo;
+				} while (entry);
+			}
+		}
+
+		computeHold (entry: TrackEntry) {
+			let to = entry.mixingTo;
+			let timelines = entry.animation.timelines;
+			let timelinesCount = entry.animation.timelines.length;
+			let timelineMode = entry.timelineMode;
+			timelineMode.length = timelinesCount;
+			let timelineHoldMix = entry.timelineHoldMix;
+			timelineHoldMix.length = 0;
+			let propertyIDs = this.propertyIDs;
+
+			if (to && to.holdPrevious) {
+				for (let i = 0; i < timelinesCount; i++)
+					timelineMode[i] = propertyIDs.addAll(timelines[i].getPropertyIds()) ? HOLD_FIRST : HOLD_SUBSEQUENT;
+				return;
+			}
+
+			outer:
+			for (let i = 0; i < timelinesCount; i++) {
+				let timeline = timelines[i];
+				let ids = timeline.getPropertyIds();
+				if (!propertyIDs.addAll(ids))
+					timelineMode[i] = SUBSEQUENT;
+				else if (!to || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline
+					|| timeline instanceof EventTimeline || !to.animation.hasTimeline(ids)) {
+					timelineMode[i] = FIRST;
+				} else {
+					for (let next = to.mixingTo; next; next = next.mixingTo) {
+						if (next.animation.hasTimeline(ids)) continue;
+						if (entry.mixDuration > 0) {
+							timelineMode[i] = HOLD_MIX;
+							timelineHoldMix[i] = next;
+							continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = HOLD_FIRST;
+				}
+			}
+		}
+
+		/** Returns the track entry for the animation currently playing on the track, or null if no animation is currently playing. */
+		getCurrent (trackIndex: number) {
+			if (trackIndex >= this.tracks.length) return null;
+			return this.tracks[trackIndex];
+		}
+
+		/** Adds a listener to receive events for all track entries. */
+		addListener (listener: AnimationStateListener) {
+			if (!listener) throw new Error("listener cannot be null.");
+			this.listeners.push(listener);
+		}
+
+		/** Removes the listener added with {@link #addListener()}. */
+		removeListener (listener: AnimationStateListener) {
+			let index = this.listeners.indexOf(listener);
+			if (index >= 0) this.listeners.splice(index, 1);
+		}
+
+		/** Removes all listeners added with {@link #addListener()}. */
+		clearListeners () {
+			this.listeners.length = 0;
+		}
+
+		/** Discards all listener notifications that have not yet been delivered. This can be useful to call from an
+		 * {@link AnimationStateListener} when it is known that further notifications that may have been already queued for delivery
+		 * are not wanted because new animations are being set. */
+		clearListenerNotifications () {
+			this.queue.clear();
+		}
+	}
+
+	/** Stores settings and other state for the playback of an animation on an {@link AnimationState} track.
+	 *
+	 * References to a track entry must not be kept after the {@link AnimationStateListener#dispose()} event occurs. */
+	export class TrackEntry {
+		/** The animation to apply for this track entry. */
+		animation: Animation;
+
+		previous: TrackEntry;
+
+		/** The animation queued to start after this animation, or null. `next` makes up a linked list. */
+		next: TrackEntry;
+
+		/** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no
+		 * mixing is currently occuring. When mixing from multiple animations, `mixingFrom` makes up a linked list. */
+		mixingFrom: TrackEntry;
+
+		/** The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is
+		 * currently occuring. When mixing to multiple animations, `mixingTo` makes up a linked list. */
+		mixingTo: TrackEntry;
+
+		/** The listener for events generated by this track entry, or null.
+		 *
+		 * A track entry returned from {@link AnimationState#setAnimation()} is already the current animation
+		 * for the track, so the track entry listener {@link AnimationStateListener#start()} will not be called. */
+		listener: AnimationStateListener;
+
+		/** The index of the track where this track entry is either current or queued.
+		 *
+		 * See {@link AnimationState#getCurrent()}. */
+		trackIndex: number;
+
+		/** If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its
+		 * duration. */
+		loop: boolean;
+
+		/** If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead
+		 * of being mixed out.
+		 *
+		 * When mixing between animations that key the same property, if a lower track also keys that property then the value will
+		 * briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0%
+		 * while the second animation mixes from 0% to 100%. Setting `holdPrevious` to true applies the first animation
+		 * at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which
+		 * keys the property, only when a higher track also keys the property.
+		 *
+		 * Snapping will occur if `holdPrevious` is true and this animation does not key all the same properties as the
+		 * previous animation. */
+		holdPrevious: boolean;
+
+		reverse: boolean;
+
+		/** When the mix percentage ({@link #mixTime} / {@link #mixDuration}) is less than the
+		 * `eventThreshold`, event timelines are applied while this animation is being mixed out. Defaults to 0, so event
+		 * timelines are not applied while this animation is being mixed out. */
+		eventThreshold: number;
+
+		/** When the mix percentage ({@link #mixtime} / {@link #mixDuration}) is less than the
+		 * `attachmentThreshold`, attachment timelines are applied while this animation is being mixed out. Defaults to
+		 * 0, so attachment timelines are not applied while this animation is being mixed out. */
+		attachmentThreshold: number;
+
+		/** When the mix percentage ({@link #mixTime} / {@link #mixDuration}) is less than the
+		 * `drawOrderThreshold`, draw order timelines are applied while this animation is being mixed out. Defaults to 0,
+		 * so draw order timelines are not applied while this animation is being mixed out. */
+		drawOrderThreshold: number;
+
+		/** Seconds when this animation starts, both initially and after looping. Defaults to 0.
+		 *
+		 * When changing the `animationStart` time, it often makes sense to set {@link #animationLast} to the same
+		 * value to prevent timeline keys before the start time from triggering. */
+		animationStart: number;
+
+		/** Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will
+		 * loop back to {@link #animationStart} at this time. Defaults to the animation {@link Animation#duration}. */
+		animationEnd: number;
+
+
+		/** The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this
+		 * animation is applied, event timelines will fire all events between the `animationLast` time (exclusive) and
+		 * `animationTime` (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation
+		 * is applied. */
+		animationLast: number;
+
+		nextAnimationLast: number;
+
+		/** Seconds to postpone playing the animation. When this track entry is the current track entry, `delay`
+		 * postpones incrementing the {@link #trackTime}. When this track entry is queued, `delay` is the time from
+		 * the start of the previous animation to when this track entry will become the current track entry (ie when the previous
+		 * track entry {@link TrackEntry#trackTime} >= this track entry's `delay`).
+		 *
+		 * {@link #timeScale} affects the delay. */
+		delay: number;
+
+		/** Current time in seconds this track entry has been the current track entry. The track time determines
+		 * {@link #animationTime}. The track time can be set to start the animation at a time other than 0, without affecting
+		 * looping. */
+		trackTime: number;
+
+		trackLast: number; nextTrackLast: number;
+
+		/** The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float
+		 * value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time
+		 * is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the
+		 * properties keyed by the animation are set to the setup pose and the track is cleared.
+		 *
+		 * It may be desired to use {@link AnimationState#addEmptyAnimation()} rather than have the animation
+		 * abruptly cease being applied. */
+		trackEnd: number;
+
+		/** Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or
+		 * faster. Defaults to 1.
+		 *
+		 * {@link #mixTime} is not affected by track entry time scale, so {@link #mixDuration} may need to be adjusted to
+		 * match the animation speed.
+		 *
+		 * When using {@link AnimationState#addAnimation()} with a `delay` <= 0, note the
+		 * {@link #delay} is set using the mix duration from the {@link AnimationStateData}, assuming time scale to be 1. If
+		 * the time scale is not 1, the delay may need to be adjusted.
+		 *
+		 * See AnimationState {@link AnimationState#timeScale} for affecting all animations. */
+		timeScale: number;
+
+		/** Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults
+		 * to 1, which overwrites the skeleton's current pose with this animation.
+		 *
+		 * Typically track 0 is used to completely pose the skeleton, then alpha is used on higher tracks. It doesn't make sense to
+		 * use alpha on track 0 if the skeleton pose is from the last frame render. */
+		alpha: number;
+
+		/** Seconds from 0 to the {@link #getMixDuration()} when mixing from the previous animation to this animation. May be
+		 * slightly more than `mixDuration` when the mix is complete. */
+		mixTime: number;
+
+		/** Seconds for mixing from the previous animation to this animation. Defaults to the value provided by AnimationStateData
+		 * {@link AnimationStateData#getMix()} based on the animation before this animation (if any).
+		 *
+		 * A mix duration of 0 still mixes out over one frame to provide the track entry being mixed out a chance to revert the
+		 * properties it was animating.
+		 *
+		 * The `mixDuration` can be set manually rather than use the value from
+		 * {@link AnimationStateData#getMix()}. In that case, the `mixDuration` can be set for a new
+		 * track entry only before {@link AnimationState#update(float)} is first called.
+		 *
+		 * When using {@link AnimationState#addAnimation()} with a `delay` <= 0, note the
+		 * {@link #delay} is set using the mix duration from the {@link AnimationStateData}, not a mix duration set
+		 * afterward. */
+		mixDuration: number; interruptAlpha: number; totalAlpha: number;
+
+		/** Controls how properties keyed in the animation are mixed with lower tracks. Defaults to {@link MixBlend#replace}, which
+		 * replaces the values from the lower tracks with the animation values. {@link MixBlend#add} adds the animation values to
+		 * the values from the lower tracks.
+		 *
+		 * The `mixBlend` can be set for a new track entry only before {@link AnimationState#apply()} is first
+		 * called. */
+		mixBlend = MixBlend.replace;
+		timelineMode = new Array<number>();
+		timelineHoldMix = new Array<TrackEntry>();
+		timelinesRotation = new Array<number>();
+
+		reset () {
+			this.next = null;
+			this.previous = null;
+			this.mixingFrom = null;
+			this.mixingTo = null;
+			this.animation = null;
+			this.listener = null;
+			this.timelineMode.length = 0;
+			this.timelineHoldMix.length = 0;
+			this.timelinesRotation.length = 0;
+		}
+
+		/** Uses {@link #trackTime} to compute the `animationTime`, which is between {@link #animationStart}
+		 * and {@link #animationEnd}. When the `trackTime` is 0, the `animationTime` is equal to the
+		 * `animationStart` time. */
+		getAnimationTime () {
+			if (this.loop) {
+				let duration = this.animationEnd - this.animationStart;
+				if (duration == 0) return this.animationStart;
+				return (this.trackTime % duration) + this.animationStart;
+			}
+			return Math.min(this.trackTime + this.animationStart, this.animationEnd);
+		}
+
+		setAnimationLast (animationLast: number) {
+			this.animationLast = animationLast;
+			this.nextAnimationLast = animationLast;
+		}
+
+		/** Returns true if at least one loop has been completed.
+		 *
+		 * See {@link AnimationStateListener#complete()}. */
+		isComplete () {
+			return this.trackTime >= this.animationEnd - this.animationStart;
+		}
+
+		/** Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the
+		 * long way around when using {@link #alpha} and starting animations on other tracks.
+		 *
+		 * Mixing with {@link MixBlend#replace} involves finding a rotation between two others, which has two possible solutions:
+		 * the short way or the long way around. The two rotations likely change over time, so which direction is the short or long
+		 * way also changes. If the short way was always chosen, bones would flip to the other side when that direction became the
+		 * long way. TrackEntry chooses the short way the first time it is applied and remembers that direction. */
+		resetRotationDirections () {
+			this.timelinesRotation.length = 0;
+		}
+
+		getTrackComplete () {
+			let duration = this.animationEnd - this.animationStart;
+			if (duration != 0) {
+				if (this.loop) return duration * (1 + ((this.trackTime / duration) | 0)); // Completion of next loop.
+				if (this.trackTime < duration) return duration; // Before duration.
+			}
+			return this.trackTime; // Next update.
+		}
+	}
+
+	export class EventQueue {
+		objects: Array<any> = [];
+		drainDisabled = false;
+		animState: AnimationState;
+
+		constructor (animState: AnimationState) {
+			this.animState = animState;
+		}
+
+		start (entry: TrackEntry) {
+			this.objects.push(EventType.start);
+			this.objects.push(entry);
+			this.animState.animationsChanged = true;
+		}
+
+		interrupt (entry: TrackEntry) {
+			this.objects.push(EventType.interrupt);
+			this.objects.push(entry);
+		}
+
+		end (entry: TrackEntry) {
+			this.objects.push(EventType.end);
+			this.objects.push(entry);
+			this.animState.animationsChanged = true;
+		}
+
+		dispose (entry: TrackEntry) {
+			this.objects.push(EventType.dispose);
+			this.objects.push(entry);
+		}
+
+		complete (entry: TrackEntry) {
+			this.objects.push(EventType.complete);
+			this.objects.push(entry);
+		}
+
+		event (entry: TrackEntry, event: Event) {
+			this.objects.push(EventType.event);
+			this.objects.push(entry);
+			this.objects.push(event);
+		}
+
+		drain () {
+			if (this.drainDisabled) return;
+			this.drainDisabled = true;
+
+			let objects = this.objects;
+			let listeners = this.animState.listeners;
+
+			for (let i = 0; i < objects.length; i += 2) {
+				let type = objects[i] as EventType;
+				let entry = objects[i + 1] as TrackEntry;
+				switch (type) {
+					case EventType.start:
+						if (entry.listener && entry.listener.start) entry.listener.start(entry);
+						for (let ii = 0; ii < listeners.length; ii++)
+							if (listeners[ii].start) listeners[ii].start(entry);
+						break;
+					case EventType.interrupt:
+						if (entry.listener && entry.listener.interrupt) entry.listener.interrupt(entry);
+						for (let ii = 0; ii < listeners.length; ii++)
+							if (listeners[ii].interrupt) listeners[ii].interrupt(entry);
+						break;
+					case EventType.end:
+						if (entry.listener && entry.listener.end) entry.listener.end(entry);
+						for (let ii = 0; ii < listeners.length; ii++)
+							if (listeners[ii].end) listeners[ii].end(entry);
+					// Fall through.
+					case EventType.dispose:
+						if (entry.listener && entry.listener.dispose) entry.listener.dispose(entry);
+						for (let ii = 0; ii < listeners.length; ii++)
+							if (listeners[ii].dispose) listeners[ii].dispose(entry);
+						this.animState.trackEntryPool.free(entry);
+						break;
+					case EventType.complete:
+						if (entry.listener && entry.listener.complete) entry.listener.complete(entry);
+						for (let ii = 0; ii < listeners.length; ii++)
+							if (listeners[ii].complete) listeners[ii].complete(entry);
+						break;
+					case EventType.event:
+						let event = objects[i++ + 2] as Event;
+						if (entry.listener && entry.listener.event) entry.listener.event(entry, event);
+						for (let ii = 0; ii < listeners.length; ii++)
+							if (listeners[ii].event) listeners[ii].event(entry, event);
+						break;
+				}
+			}
+			this.clear();
+
+			this.drainDisabled = false;
+		}
+
+		clear () {
+			this.objects.length = 0;
+		}
+	}
+
+	export enum EventType {
+		start, interrupt, end, dispose, complete, event
+	}
+
+	/** The interface to implement for receiving TrackEntry events. It is always safe to call AnimationState methods when receiving
+	 * events.
+	 *
+	 * See TrackEntry {@link TrackEntry#listener} and AnimationState
+	 * {@link AnimationState#addListener()}. */
+	export interface AnimationStateListener {
+		/** Invoked when this entry has been set as the current entry. */
+		start (entry: TrackEntry): void;
+
+		/** Invoked when another entry has replaced this entry as the current entry. This entry may continue being applied for
+		 * mixing. */
+		interrupt (entry: TrackEntry): void;
+
+		/** Invoked when this entry is no longer the current entry and will never be applied again. */
+		end (entry: TrackEntry): void;
+
+		/** Invoked when this entry will be disposed. This may occur without the entry ever being set as the current entry.
+		 * References to the entry should not be kept after dispose is called, as it may be destroyed or reused. */
+		dispose (entry: TrackEntry): void;
+
+		/** Invoked every time this entry's animation completes a loop. */
+		complete (entry: TrackEntry): void;
+
+		/** Invoked when this entry's animation triggers an event. */
+		event (entry: TrackEntry, event: Event): void;
+	}
+
+	export abstract class AnimationStateAdapter implements AnimationStateListener {
+		start (entry: TrackEntry) {
+		}
+
+		interrupt (entry: TrackEntry) {
+		}
+
+		end (entry: TrackEntry) {
+		}
+
+		dispose (entry: TrackEntry) {
+		}
+
+		complete (entry: TrackEntry) {
+		}
+
+		event (entry: TrackEntry, event: Event) {
+		}
+	}
+
+	/** 1. A previously applied timeline has set this property.
+	 *
+	 * Result: Mix from the current pose to the timeline pose. */
+	const SUBSEQUENT = 0;
+	/** 1. This is the first timeline to set this property.
+	 * 2. The next track entry applied after this one does not have a timeline to set this property.
+	 *
+	 * Result: Mix from the setup pose to the timeline pose. */
+	const FIRST = 1;
+	/** 1) A previously applied timeline has set this property.<br>
+	 * 2) The next track entry to be applied does have a timeline to set this property.<br>
+	 * 3) The next track entry after that one does not have a timeline to set this property.<br>
+	 * Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading
+	 * animations that key the same property. A subsequent timeline will set this property using a mix. */
+	const HOLD_SUBSEQUENT = 2;
+	/** 1) This is the first timeline to set this property.<br>
+	 * 2) The next track entry to be applied does have a timeline to set this property.<br>
+	 * 3) The next track entry after that one does not have a timeline to set this property.<br>
+	 * Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations
+	 * that key the same property. A subsequent timeline will set this property using a mix. */
+	const HOLD_FIRST = 3;
+	/** 1. This is the first timeline to set this property.
+	 * 2. The next track entry to be applied does have a timeline to set this property.
+	 * 3. The next track entry after that one does have a timeline to set this property.
+	 * 4. timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property.
+	 *
+	 * Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than
+	 * 2 track entries in a row have a timeline that sets the same property.
+	 *
+	 * Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid
+	 * "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A
+	 * (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap into
+	 * place. */
+	const HOLD_MIX = 4;
+
+	const SETUP = 1;
+	const CURRENT = 2;
+
+	let _emptyAnimation: Animation = null;
+}

+ 76 - 76
spine-ts/core/src/AnimationStateData.ts

@@ -1,76 +1,76 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores mix (crossfade) durations to be applied when {@link AnimationState} animations are changed. */
-	export class AnimationStateData {
-		/** The SkeletonData to look up animations when they are specified by name. */
-		skeletonData: SkeletonData;
-
-		animationToMixTime: Map<number> = { };
-
-		/** The mix duration to use when no mix duration has been defined between two animations. */
-		defaultMix = 0;
-
-		constructor (skeletonData: SkeletonData) {
-			if (!skeletonData) throw new Error("skeletonData cannot be null.");
-			this.skeletonData = skeletonData;
-		}
-
-		/** Sets a mix duration by animation name.
-		 *
-		 * See {@link #setMixWith()}. */
-		setMix (fromName: string, toName: string, duration: number) {
-			let from = this.skeletonData.findAnimation(fromName);
-			if (!from) throw new Error("Animation not found: " + fromName);
-			let to = this.skeletonData.findAnimation(toName);
-			if (!to) throw new Error("Animation not found: " + toName);
-			this.setMixWith(from, to, duration);
-		}
-
-		/** Sets the mix duration when changing from the specified animation to the other.
-		 *
-		 * See {@link TrackEntry#mixDuration}. */
-		setMixWith (from: Animation, to: Animation, duration: number) {
-			if (!from) throw new Error("from cannot be null.");
-			if (!to) throw new Error("to cannot be null.");
-			let key = from.name + "." + to.name;
-			this.animationToMixTime[key] = duration;
-		}
-
-		/** Returns the mix duration to use when changing from the specified animation to the other, or the {@link #defaultMix} if
-	 	* no mix duration has been set. */
-		getMix (from: Animation, to: Animation) {
-			let key = from.name + "." + to.name;
-			let value = this.animationToMixTime[key];
-			return value === undefined ? this.defaultMix : value;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores mix (crossfade) durations to be applied when {@link AnimationState} animations are changed. */
+	export class AnimationStateData {
+		/** The SkeletonData to look up animations when they are specified by name. */
+		skeletonData: SkeletonData;
+
+		animationToMixTime: Map<number> = {};
+
+		/** The mix duration to use when no mix duration has been defined between two animations. */
+		defaultMix = 0;
+
+		constructor (skeletonData: SkeletonData) {
+			if (!skeletonData) throw new Error("skeletonData cannot be null.");
+			this.skeletonData = skeletonData;
+		}
+
+		/** Sets a mix duration by animation name.
+		 *
+		 * See {@link #setMixWith()}. */
+		setMix (fromName: string, toName: string, duration: number) {
+			let from = this.skeletonData.findAnimation(fromName);
+			if (!from) throw new Error("Animation not found: " + fromName);
+			let to = this.skeletonData.findAnimation(toName);
+			if (!to) throw new Error("Animation not found: " + toName);
+			this.setMixWith(from, to, duration);
+		}
+
+		/** Sets the mix duration when changing from the specified animation to the other.
+		 *
+		 * See {@link TrackEntry#mixDuration}. */
+		setMixWith (from: Animation, to: Animation, duration: number) {
+			if (!from) throw new Error("from cannot be null.");
+			if (!to) throw new Error("to cannot be null.");
+			let key = from.name + "." + to.name;
+			this.animationToMixTime[key] = duration;
+		}
+
+		/** Returns the mix duration to use when changing from the specified animation to the other, or the {@link #defaultMix} if
+	 	* no mix duration has been set. */
+		getMix (from: Animation, to: Animation) {
+			let key = from.name + "." + to.name;
+			let value = this.animationToMixTime[key];
+			return value === undefined ? this.defaultMix : value;
+		}
+	}
+}

+ 284 - 284
spine-ts/core/src/AssetManager.ts

@@ -1,284 +1,284 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	export class AssetManager implements Disposable {
-		private pathPrefix: string;
-		private textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture;
-		private downloader: Downloader;
-		private assets: Map<any> = {};
-		private errors: Map<string> = {};
-		private toLoad = 0;
-		private loaded = 0;
-
-		constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader: Downloader = null) {
-			this.textureLoader = textureLoader;
-			this.pathPrefix = pathPrefix;
-			this.downloader = downloader || new Downloader();
-		}
-
-		private start (path: string): string {
-			this.toLoad++;
-			return this.pathPrefix + path;
-		}
-
-		private success (callback: (path: string, data: any) => void, path: string, asset: any) {
-			this.toLoad--;
-			this.loaded++;
-			this.assets[path] = asset;
-			if (callback) callback(path, asset);
-		}
-
-		private error (callback: (path: string, message: string) => void, path: string, message: string) {
-			this.toLoad--;
-			this.loaded++;
-			this.errors[path] = message;
-			if (callback) callback(path, message);
-		}
-
-		setRawDataURI(path: string, data: string) {
-			this.downloader.rawDataUris[this.pathPrefix + path] = data;
-		}
-
-		loadBinary(path: string,
-			success: (path: string, binary: Uint8Array) => void = null,
-			error: (path: string, message: string) => void = null) {
-			path = this.start(path);
-
-			this.downloader.downloadBinary(path, (data: Uint8Array): void => {
-				this.success(success, path, data);
-			}, (status: number, responseText: string): void => {
-				this.error(error, path, `Couldn't load binary ${path}: status ${status}, ${responseText}`);
-			});
-		}
-
-		loadText(path: string,
-			success: (path: string, text: string) => void = null,
-			error: (path: string, message: string) => void = null) {
-			path = this.start(path);
-
-			this.downloader.downloadText(path, (data: string): void => {
-				this.success(success, path, data);
-			}, (status: number, responseText: string): void => {
-				this.error(error, path, `Couldn't load text ${path}: status ${status}, ${responseText}`);
-			});
-		}
-
-		loadJson(path: string,
-			success: (path: string, object: object) => void = null,
-			error: (path: string, message: string) => void = null) {
-			path = this.start(path);
-
-			this.downloader.downloadJson(path, (data: object): void => {
-				this.success(success, path, data);
-			}, (status: number, responseText: string): void => {
-				this.error(error, path, `Couldn't load JSON ${path}: status ${status}, ${responseText}`);
-			});
-		}
-
-		loadTexture (path: string,
-			success: (path: string, texture: Texture) => void = null,
-			error: (path: string, message: string) => void = null) {
-			path = this.start(path);
-
-			let isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document);
-			let isWebWorker = !isBrowser && typeof importScripts !== 'undefined';
-			if (isWebWorker) {
-				fetch(path, {mode: <RequestMode>"cors"}).then( (response) => {
-					if (response.ok) return response.blob();
-					this.error(error, path, `Couldn't load image: ${path}`);
-					return null;
-				}).then( (blob) => {
-					return blob ? createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }) : null;
-				}).then( (bitmap) => {
-					if (bitmap) this.success(success, path, this.textureLoader(bitmap));
-				});
-			} else {
-				let image = new Image();
-				image.crossOrigin = "anonymous";
-				image.onload = () => {
-					this.success(success, path, this.textureLoader(image));
-				};
-				image.onerror = () => {
-					this.error(error, path, `Couldn't load image: ${path}`);
-				};
-				if (this.downloader.rawDataUris[path]) path = this.downloader.rawDataUris[path];
-				image.src = path;
-			}
-		}
-
-		loadTextureAtlas (path: string,
-			success: (path: string, atlas: TextureAtlas) => void = null,
-			error: (path: string, message: string) => void = null
-		) {
-			let index = path.lastIndexOf("/");
-			let parent = index >= 0 ? path.substring(0, index + 1) : "";
-			path = this.start(path);
-
-			this.downloader.downloadText(path, (atlasText: string): void => {
-				try {
-					let atlas = new TextureAtlas(atlasText);
-					let toLoad = atlas.pages.length, abort = false;
-					for (let page of atlas.pages) {
-						this.loadTexture(parent + page.name,
-							(imagePath: string, texture: Texture) => {
-								if (!abort) {
-									page.setTexture(texture);
-									if (--toLoad == 0) this.success(success, path, atlas);
-								}
-							},
-							(imagePath: string, message: string) => {
-								if (!abort) this.error(error, path, `Couldn't load texture atlas ${path} page image: ${imagePath}`);
-								abort = true;
-							}
-						);
-					}
-				} catch (e) {
-					this.error(error, path, `Couldn't parse texture atlas ${path}: ${e.message}`);
-				}
-			}, (status: number, responseText: string): void => {
-				this.error(error, path, `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`);
-			});
-		}
-
-		get (path: string) {
-			return this.assets[this.pathPrefix + path];
-		}
-
-		require (path: string) {
-			path = this.pathPrefix + path;
-			let asset = this.assets[path];
-			if (asset) return asset;
-			let error = this.errors[path];
-			throw Error("Asset not found: " + path + (error ? "\n" + error  : ""));
-		}
-
-		remove (path: string) {
-			path = this.pathPrefix + path;
-			let asset = this.assets[path];
-			if ((<any>asset).dispose) (<any>asset).dispose();
-			delete this.assets[path];
-			return asset;
-		}
-
-		removeAll () {
-			for (let key in this.assets) {
-				let asset = this.assets[key];
-				if ((<any>asset).dispose) (<any>asset).dispose();
-			}
-			this.assets = {};
-		}
-
-		isLoadingComplete (): boolean {
-			return this.toLoad == 0;
-		}
-
-		getToLoad (): number {
-			return this.toLoad;
-		}
-
-		getLoaded (): number {
-			return this.loaded;
-		}
-
-		dispose () {
-			this.removeAll();
-		}
-
-		hasErrors() {
-			return Object.keys(this.errors).length > 0;
-		}
-
-		getErrors() {
-			return this.errors;
-		}
-	}
-
-	export class Downloader {
-		private callbacks: Map<Array<Function>> = {};
-		rawDataUris: Map<string> = {};
-
-		downloadText (url: string, success: (data: string) => void, error: (status: number, responseText: string) => void) {
-			if (this.rawDataUris[url]) url = this.rawDataUris[url];
-			if (this.start(url, success, error)) return;
-			let request = new XMLHttpRequest();
-			request.overrideMimeType("text/html");
-			request.open("GET", url, true);
-			let done = () => {
-				this.finish(url, request.status, request.responseText);
-			};
-			request.onload = done;
-			request.onerror = done;
-			request.send();
-		}
-
-		downloadJson (url: string, success: (data: object) => void, error: (status: number, responseText: string) => void) {
-			this.downloadText(url, (data: string): void => {
-				success(JSON.parse(data));
-			}, error);
-		}
-
-		downloadBinary (url: string, success: (data: Uint8Array) => void, error: (status: number, responseText: string) => void) {
-			if (this.rawDataUris[url]) url = this.rawDataUris[url];
-			if (this.start(url, success, error)) return;
-			let request = new XMLHttpRequest();
-			request.open("GET", url, true);
-			request.responseType = "arraybuffer";
-			let onerror = () => {
-				this.finish(url, request.status, request.responseText);
-			};
-			request.onload = () => {
-				if (request.status == 200)
-					this.finish(url, 200, new Uint8Array(request.response as ArrayBuffer));
-				else
-					onerror();
-			};
-			request.onerror = onerror;
-			request.send();
-		}
-
-		private start (url: string, success: any, error: any) {
-			let callbacks = this.callbacks[url];
-			try {
-				if (callbacks) return true;
-				this.callbacks[url] = callbacks = [];
-			} finally {
-				callbacks.push(success, error);
-			}
-		}
-
-		private finish (url: string, status: number, data: any) {
-			let callbacks = this.callbacks[url];
-			delete this.callbacks[url];
-			let args = status == 200 ? [data] : [status, data];
-			for (let i = args.length - 1, n = callbacks.length; i < n; i += 2)
-				callbacks[i].apply(null, args);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	export class AssetManager implements Disposable {
+		private pathPrefix: string;
+		private textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture;
+		private downloader: Downloader;
+		private assets: Map<any> = {};
+		private errors: Map<string> = {};
+		private toLoad = 0;
+		private loaded = 0;
+
+		constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader: Downloader = null) {
+			this.textureLoader = textureLoader;
+			this.pathPrefix = pathPrefix;
+			this.downloader = downloader || new Downloader();
+		}
+
+		private start (path: string): string {
+			this.toLoad++;
+			return this.pathPrefix + path;
+		}
+
+		private success (callback: (path: string, data: any) => void, path: string, asset: any) {
+			this.toLoad--;
+			this.loaded++;
+			this.assets[path] = asset;
+			if (callback) callback(path, asset);
+		}
+
+		private error (callback: (path: string, message: string) => void, path: string, message: string) {
+			this.toLoad--;
+			this.loaded++;
+			this.errors[path] = message;
+			if (callback) callback(path, message);
+		}
+
+		setRawDataURI (path: string, data: string) {
+			this.downloader.rawDataUris[this.pathPrefix + path] = data;
+		}
+
+		loadBinary (path: string,
+			success: (path: string, binary: Uint8Array) => void = null,
+			error: (path: string, message: string) => void = null) {
+			path = this.start(path);
+
+			this.downloader.downloadBinary(path, (data: Uint8Array): void => {
+				this.success(success, path, data);
+			}, (status: number, responseText: string): void => {
+				this.error(error, path, `Couldn't load binary ${path}: status ${status}, ${responseText}`);
+			});
+		}
+
+		loadText (path: string,
+			success: (path: string, text: string) => void = null,
+			error: (path: string, message: string) => void = null) {
+			path = this.start(path);
+
+			this.downloader.downloadText(path, (data: string): void => {
+				this.success(success, path, data);
+			}, (status: number, responseText: string): void => {
+				this.error(error, path, `Couldn't load text ${path}: status ${status}, ${responseText}`);
+			});
+		}
+
+		loadJson (path: string,
+			success: (path: string, object: object) => void = null,
+			error: (path: string, message: string) => void = null) {
+			path = this.start(path);
+
+			this.downloader.downloadJson(path, (data: object): void => {
+				this.success(success, path, data);
+			}, (status: number, responseText: string): void => {
+				this.error(error, path, `Couldn't load JSON ${path}: status ${status}, ${responseText}`);
+			});
+		}
+
+		loadTexture (path: string,
+			success: (path: string, texture: Texture) => void = null,
+			error: (path: string, message: string) => void = null) {
+			path = this.start(path);
+
+			let isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document);
+			let isWebWorker = !isBrowser && typeof importScripts !== 'undefined';
+			if (isWebWorker) {
+				fetch(path, { mode: <RequestMode>"cors" }).then((response) => {
+					if (response.ok) return response.blob();
+					this.error(error, path, `Couldn't load image: ${path}`);
+					return null;
+				}).then((blob) => {
+					return blob ? createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }) : null;
+				}).then((bitmap) => {
+					if (bitmap) this.success(success, path, this.textureLoader(bitmap));
+				});
+			} else {
+				let image = new Image();
+				image.crossOrigin = "anonymous";
+				image.onload = () => {
+					this.success(success, path, this.textureLoader(image));
+				};
+				image.onerror = () => {
+					this.error(error, path, `Couldn't load image: ${path}`);
+				};
+				if (this.downloader.rawDataUris[path]) path = this.downloader.rawDataUris[path];
+				image.src = path;
+			}
+		}
+
+		loadTextureAtlas (path: string,
+			success: (path: string, atlas: TextureAtlas) => void = null,
+			error: (path: string, message: string) => void = null
+		) {
+			let index = path.lastIndexOf("/");
+			let parent = index >= 0 ? path.substring(0, index + 1) : "";
+			path = this.start(path);
+
+			this.downloader.downloadText(path, (atlasText: string): void => {
+				try {
+					let atlas = new TextureAtlas(atlasText);
+					let toLoad = atlas.pages.length, abort = false;
+					for (let page of atlas.pages) {
+						this.loadTexture(parent + page.name,
+							(imagePath: string, texture: Texture) => {
+								if (!abort) {
+									page.setTexture(texture);
+									if (--toLoad == 0) this.success(success, path, atlas);
+								}
+							},
+							(imagePath: string, message: string) => {
+								if (!abort) this.error(error, path, `Couldn't load texture atlas ${path} page image: ${imagePath}`);
+								abort = true;
+							}
+						);
+					}
+				} catch (e) {
+					this.error(error, path, `Couldn't parse texture atlas ${path}: ${e.message}`);
+				}
+			}, (status: number, responseText: string): void => {
+				this.error(error, path, `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`);
+			});
+		}
+
+		get (path: string) {
+			return this.assets[this.pathPrefix + path];
+		}
+
+		require (path: string) {
+			path = this.pathPrefix + path;
+			let asset = this.assets[path];
+			if (asset) return asset;
+			let error = this.errors[path];
+			throw Error("Asset not found: " + path + (error ? "\n" + error : ""));
+		}
+
+		remove (path: string) {
+			path = this.pathPrefix + path;
+			let asset = this.assets[path];
+			if ((<any>asset).dispose) (<any>asset).dispose();
+			delete this.assets[path];
+			return asset;
+		}
+
+		removeAll () {
+			for (let key in this.assets) {
+				let asset = this.assets[key];
+				if ((<any>asset).dispose) (<any>asset).dispose();
+			}
+			this.assets = {};
+		}
+
+		isLoadingComplete (): boolean {
+			return this.toLoad == 0;
+		}
+
+		getToLoad (): number {
+			return this.toLoad;
+		}
+
+		getLoaded (): number {
+			return this.loaded;
+		}
+
+		dispose () {
+			this.removeAll();
+		}
+
+		hasErrors () {
+			return Object.keys(this.errors).length > 0;
+		}
+
+		getErrors () {
+			return this.errors;
+		}
+	}
+
+	export class Downloader {
+		private callbacks: Map<Array<Function>> = {};
+		rawDataUris: Map<string> = {};
+
+		downloadText (url: string, success: (data: string) => void, error: (status: number, responseText: string) => void) {
+			if (this.rawDataUris[url]) url = this.rawDataUris[url];
+			if (this.start(url, success, error)) return;
+			let request = new XMLHttpRequest();
+			request.overrideMimeType("text/html");
+			request.open("GET", url, true);
+			let done = () => {
+				this.finish(url, request.status, request.responseText);
+			};
+			request.onload = done;
+			request.onerror = done;
+			request.send();
+		}
+
+		downloadJson (url: string, success: (data: object) => void, error: (status: number, responseText: string) => void) {
+			this.downloadText(url, (data: string): void => {
+				success(JSON.parse(data));
+			}, error);
+		}
+
+		downloadBinary (url: string, success: (data: Uint8Array) => void, error: (status: number, responseText: string) => void) {
+			if (this.rawDataUris[url]) url = this.rawDataUris[url];
+			if (this.start(url, success, error)) return;
+			let request = new XMLHttpRequest();
+			request.open("GET", url, true);
+			request.responseType = "arraybuffer";
+			let onerror = () => {
+				this.finish(url, request.status, request.responseText);
+			};
+			request.onload = () => {
+				if (request.status == 200)
+					this.finish(url, 200, new Uint8Array(request.response as ArrayBuffer));
+				else
+					onerror();
+			};
+			request.onerror = onerror;
+			request.send();
+		}
+
+		private start (url: string, success: any, error: any) {
+			let callbacks = this.callbacks[url];
+			try {
+				if (callbacks) return true;
+				this.callbacks[url] = callbacks = [];
+			} finally {
+				callbacks.push(success, error);
+			}
+		}
+
+		private finish (url: string, status: number, data: any) {
+			let callbacks = this.callbacks[url];
+			delete this.callbacks[url];
+			let args = status == 200 ? [data] : [status, data];
+			for (let i = args.length - 1, n = callbacks.length; i < n; i += 2)
+				callbacks[i].apply(null, args);
+		}
+	}
+}

+ 4 - 4
spine-ts/core/src/AtlasAttachmentLoader.ts

@@ -48,7 +48,7 @@ module spine {
 			return attachment;
 		}
 
-		newMeshAttachment (skin: Skin, name: string, path: string) : MeshAttachment {
+		newMeshAttachment (skin: Skin, name: string, path: string): MeshAttachment {
 			let region = this.atlas.findRegion(path);
 			if (!region) throw new Error("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
 			region.renderObject = region;
@@ -57,7 +57,7 @@ module spine {
 			return attachment;
 		}
 
-		newBoundingBoxAttachment (skin: Skin, name: string) : BoundingBoxAttachment {
+		newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment {
 			return new BoundingBoxAttachment(name);
 		}
 
@@ -65,11 +65,11 @@ module spine {
 			return new PathAttachment(name);
 		}
 
-		newPointAttachment(skin: Skin, name: string): PointAttachment {
+		newPointAttachment (skin: Skin, name: string): PointAttachment {
 			return new PointAttachment(name);
 		}
 
-		newClippingAttachment(skin: Skin, name: string): ClippingAttachment {
+		newClippingAttachment (skin: Skin, name: string): ClippingAttachment {
 			return new ClippingAttachment(name);
 		}
 	}

+ 378 - 378
spine-ts/core/src/Bone.ts

@@ -1,378 +1,378 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores a bone's current pose.
-	 *
-	 * A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a
-	 * local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a
-	 * constraint or application code modifies the world transform after it was computed from the local transform. */
-	export class Bone implements Updatable {
-		/** The bone's setup pose data. */
-		data: BoneData;
-
-		/** The skeleton this bone belongs to. */
-		skeleton: Skeleton;
-
-		/** The parent bone, or null if this is the root bone. */
-		parent: Bone;
-
-		/** The immediate children of this bone. */
-		children = new Array<Bone>();
-
-		/** The local x translation. */
-		x = 0;
-
-		/** The local y translation. */
-		y = 0;
-
-		/** The local rotation in degrees, counter clockwise. */
-		rotation = 0;
-
-		/** The local scaleX. */
-		scaleX = 0;
-
-		/** The local scaleY. */
-		scaleY = 0;
-
-		/** The local shearX. */
-		shearX = 0;
-
-		/** The local shearY. */
-		shearY = 0;
-
-		/** The applied local x translation. */
-		ax = 0;
-
-		/** The applied local y translation. */
-		ay = 0;
-
-		/** The applied local rotation in degrees, counter clockwise. */
-		arotation = 0;
-
-		/** The applied local scaleX. */
-		ascaleX = 0;
-
-		/** The applied local scaleY. */
-		ascaleY = 0;
-
-		/** The applied local shearX. */
-		ashearX = 0;
-
-		/** The applied local shearY. */
-		ashearY = 0;
-
-		/** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */
-		a = 0;
-
-		/** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */
-		b = 0;
-
-		/** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */
-		c = 0;
-
-		/** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */
-		d = 0;
-
-		/** The world X position. If changed, {@link #updateAppliedTransform()} should be called. */
-		worldY = 0;
-
-		/** The world Y position. If changed, {@link #updateAppliedTransform()} should be called. */
-		worldX = 0;
-
-		sorted = false;
-		active = false;
-
-		/** @param parent May be null. */
-		constructor (data: BoneData, skeleton: Skeleton, parent: Bone) {
-			if (!data) throw new Error("data cannot be null.");
-			if (!skeleton) throw new Error("skeleton cannot be null.");
-			this.data = data;
-			this.skeleton = skeleton;
-			this.parent = parent;
-			this.setToSetupPose();
-		}
-
-		/** Returns false when the bone has not been computed because {@link BoneData#skinRequired} is true and the
-	 	* {@link Skeleton#skin active skin} does not {@link Skin#bones contain} this bone. */
-		isActive () {
-			return this.active;
-		}
-
-		/** Computes the world transform using the parent bone and this bone's local applied transform. */
-		update () {
-			this.updateWorldTransformWith(this.ax, this.ay, this.arotation, this.ascaleX, this.ascaleY, this.ashearX, this.ashearY);
-		}
-
-		/** Computes the world transform using the parent bone and this bone's local transform.
-		 *
-		 * See {@link #updateWorldTransformWith()}. */
-		updateWorldTransform () {
-			this.updateWorldTransformWith(this.x, this.y, this.rotation, this.scaleX, this.scaleY, this.shearX, this.shearY);
-		}
-
-		/** Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the
-		 * specified local transform. Child bones are not updated.
-		 *
-		 * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
-		 * Runtimes Guide. */
-		updateWorldTransformWith (x: number, y: number, rotation: number, scaleX: number, scaleY: number, shearX: number, shearY: number) {
-			this.ax = x;
-			this.ay = y;
-			this.arotation = rotation;
-			this.ascaleX = scaleX;
-			this.ascaleY = scaleY;
-			this.ashearX = shearX;
-			this.ashearY = shearY;
-
-			let parent = this.parent;
-			if (!parent) { // Root bone.
-				let skeleton = this.skeleton;
-				let rotationY = rotation + 90 + shearY;
-				let sx = skeleton.scaleX;
-				let sy = skeleton.scaleY;
-				this.a = MathUtils.cosDeg(rotation + shearX) * scaleX * sx;
-				this.b = MathUtils.cosDeg(rotationY) * scaleY * sx;
-				this.c = MathUtils.sinDeg(rotation + shearX) * scaleX * sy;
-				this.d = MathUtils.sinDeg(rotationY) * scaleY * sy;
-				this.worldX = x * sx + skeleton.x;
-				this.worldY = y * sy + skeleton.y;
-				return;
-			}
-
-			let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
-			this.worldX = pa * x + pb * y + parent.worldX;
-			this.worldY = pc * x + pd * y + parent.worldY;
-
-			switch (this.data.transformMode) {
-			case TransformMode.Normal: {
-				let rotationY = rotation + 90 + shearY;
-				let la = MathUtils.cosDeg(rotation + shearX) * scaleX;
-				let lb = MathUtils.cosDeg(rotationY) * scaleY;
-				let lc = MathUtils.sinDeg(rotation + shearX) * scaleX;
-				let ld = MathUtils.sinDeg(rotationY) * scaleY;
-				this.a = pa * la + pb * lc;
-				this.b = pa * lb + pb * ld;
-				this.c = pc * la + pd * lc;
-				this.d = pc * lb + pd * ld;
-				return;
-			}
-			case TransformMode.OnlyTranslation: {
-				let rotationY = rotation + 90 + shearY;
-				this.a = MathUtils.cosDeg(rotation + shearX) * scaleX;
-				this.b = MathUtils.cosDeg(rotationY) * scaleY;
-				this.c = MathUtils.sinDeg(rotation + shearX) * scaleX;
-				this.d = MathUtils.sinDeg(rotationY) * scaleY;
-				break;
-			}
-			case TransformMode.NoRotationOrReflection: {
-				let s = pa * pa + pc * pc;
-				let prx = 0;
-				if (s > 0.0001) {
-					s = Math.abs(pa * pd - pb * pc) / s;
-					pa /= this.skeleton.scaleX;
-					pc /= this.skeleton.scaleY;
-					pb = pc * s;
-					pd = pa * s;
-					prx = Math.atan2(pc, pa) * MathUtils.radDeg;
-				} else {
-					pa = 0;
-					pc = 0;
-					prx = 90 - Math.atan2(pd, pb) * MathUtils.radDeg;
-				}
-				let rx = rotation + shearX - prx;
-				let ry = rotation + shearY - prx + 90;
-				let la = MathUtils.cosDeg(rx) * scaleX;
-				let lb = MathUtils.cosDeg(ry) * scaleY;
-				let lc = MathUtils.sinDeg(rx) * scaleX;
-				let ld = MathUtils.sinDeg(ry) * scaleY;
-				this.a = pa * la - pb * lc;
-				this.b = pa * lb - pb * ld;
-				this.c = pc * la + pd * lc;
-				this.d = pc * lb + pd * ld;
-				break;
-			}
-			case TransformMode.NoScale:
-			case TransformMode.NoScaleOrReflection: {
-				let cos = MathUtils.cosDeg(rotation);
-				let sin = MathUtils.sinDeg(rotation);
-				let za = (pa * cos + pb * sin) / this.skeleton.scaleX;
-				let zc = (pc * cos + pd * sin) / this.skeleton.scaleY;
-				let s = Math.sqrt(za * za + zc * zc);
-				if (s > 0.00001) s = 1 / s;
-				za *= s;
-				zc *= s;
-				s = Math.sqrt(za * za + zc * zc);
-				if (this.data.transformMode == TransformMode.NoScale
-					&& (pa * pd - pb * pc < 0) != (this.skeleton.scaleX < 0 != this.skeleton.scaleY < 0)) s = -s;
-				let r = Math.PI / 2 + Math.atan2(zc, za);
-				let zb = Math.cos(r) * s;
-				let zd = Math.sin(r) * s;
-				let la = MathUtils.cosDeg(shearX) * scaleX;
-				let lb = MathUtils.cosDeg(90 + shearY) * scaleY;
-				let lc = MathUtils.sinDeg(shearX) * scaleX;
-				let ld = MathUtils.sinDeg(90 + shearY) * scaleY;
-				this.a = za * la + zb * lc;
-				this.b = za * lb + zb * ld;
-				this.c = zc * la + zd * lc;
-				this.d = zc * lb + zd * ld;
-				break;
-			}
-			}
-			this.a *= this.skeleton.scaleX;
-			this.b *= this.skeleton.scaleX;
-			this.c *= this.skeleton.scaleY;
-			this.d *= this.skeleton.scaleY;
-		}
-
-		/** Sets this bone's local transform to the setup pose. */
-		setToSetupPose () {
-			let data = this.data;
-			this.x = data.x;
-			this.y = data.y;
-			this.rotation = data.rotation;
-			this.scaleX = data.scaleX;
-			this.scaleY = data.scaleY;
-			this.shearX = data.shearX;
-			this.shearY = data.shearY;
-		}
-
-		/** The world rotation for the X axis, calculated using {@link #a} and {@link #c}. */
-		getWorldRotationX () {
-			return Math.atan2(this.c, this.a) * MathUtils.radDeg;
-		}
-
-		/** The world rotation for the Y axis, calculated using {@link #b} and {@link #d}. */
-		getWorldRotationY () {
-			return Math.atan2(this.d, this.b) * MathUtils.radDeg;
-		}
-
-		/** The magnitude (always positive) of the world scale X, calculated using {@link #a} and {@link #c}. */
-		getWorldScaleX () {
-			return Math.sqrt(this.a * this.a + this.c * this.c);
-		}
-
-		/** The magnitude (always positive) of the world scale Y, calculated using {@link #b} and {@link #d}. */
-		getWorldScaleY () {
-			return Math.sqrt(this.b * this.b + this.d * this.d);
-		}
-
-		/** Computes the applied transform values from the world transform.
-		 *
-		 * If the world transform is modified (by a constraint, {@link #rotateWorld(float)}, etc) then this method should be called so
-		 * the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply other
-		 * constraints).
-		 *
-		 * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after
-		 * calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */
-		updateAppliedTransform () {
-			let parent = this.parent;
-			if (!parent) {
-				this.ax = this.worldX;
-				this.ay = this.worldY;
-				this.arotation = Math.atan2(this.c, this.a) * MathUtils.radDeg;
-				this.ascaleX = Math.sqrt(this.a * this.a + this.c * this.c);
-				this.ascaleY = Math.sqrt(this.b * this.b + this.d * this.d);
-				this.ashearX = 0;
-				this.ashearY = Math.atan2(this.a * this.b + this.c * this.d, this.a * this.d - this.b * this.c) * MathUtils.radDeg;
-				return;
-			}
-			let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
-			let pid = 1 / (pa * pd - pb * pc);
-			let dx = this.worldX - parent.worldX, dy = this.worldY - parent.worldY;
-			this.ax = (dx * pd * pid - dy * pb * pid);
-			this.ay = (dy * pa * pid - dx * pc * pid);
-			let ia = pid * pd;
-			let id = pid * pa;
-			let ib = pid * pb;
-			let ic = pid * pc;
-			let ra = ia * this.a - ib * this.c;
-			let rb = ia * this.b - ib * this.d;
-			let rc = id * this.c - ic * this.a;
-			let rd = id * this.d - ic * this.b;
-			this.ashearX = 0;
-			this.ascaleX = Math.sqrt(ra * ra + rc * rc);
-			if (this.ascaleX > 0.0001) {
-				let det = ra * rd - rb * rc;
-				this.ascaleY = det / this.ascaleX;
-				this.ashearY = Math.atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
-				this.arotation = Math.atan2(rc, ra) * MathUtils.radDeg;
-			} else {
-				this.ascaleX = 0;
-				this.ascaleY = Math.sqrt(rb * rb + rd * rd);
-				this.ashearY = 0;
-				this.arotation = 90 - Math.atan2(rd, rb) * MathUtils.radDeg;
-			}
-		}
-
-		/** Transforms a point from world coordinates to the bone's local coordinates. */
-		worldToLocal (world: Vector2) {
-			let invDet = 1 / (this.a * this.d - this.b * this.c);
-			let x = world.x - this.worldX, y = world.y - this.worldY;
-			world.x = x * this.d * invDet - y * this.b * invDet;
-			world.y = y * this.a * invDet - x * this.c * invDet;
-			return world;
-		}
-
-		/** Transforms a point from the bone's local coordinates to world coordinates. */
-		localToWorld (local: Vector2) {
-			let x = local.x, y = local.y;
-			local.x = x * this.a + y * this.b + this.worldX;
-			local.y = x * this.c + y * this.d + this.worldY;
-			return local;
-		}
-
-		/** Transforms a world rotation to a local rotation. */
-		worldToLocalRotation (worldRotation: number) {
-			let sin = MathUtils.sinDeg(worldRotation), cos = MathUtils.cosDeg(worldRotation);
-			return Math.atan2(this.a * sin - this.c * cos, this.d * cos - this.b * sin) * MathUtils.radDeg + this.rotation - this.shearX;
-		}
-
-		/** Transforms a local rotation to a world rotation. */
-		localToWorldRotation (localRotation: number) {
-			localRotation -= this.rotation - this.shearX;
-			let sin = MathUtils.sinDeg(localRotation), cos = MathUtils.cosDeg(localRotation);
-			return Math.atan2(cos * this.c + sin * this.d, cos * this.a + sin * this.b) * MathUtils.radDeg;
-		}
-
-		/** Rotates the world transform the specified amount.
-		 * <p>
-		 * After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and {@link #update()} will
-		 * need to be called on any child bones, recursively. */
-		rotateWorld (degrees: number) {
-			let a = this.a, b = this.b, c = this.c, d = this.d;
-			let cos = MathUtils.cosDeg(degrees), sin = MathUtils.sinDeg(degrees);
-			this.a = cos * a - sin * c;
-			this.b = cos * b - sin * d;
-			this.c = sin * a + cos * c;
-			this.d = sin * b + cos * d;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores a bone's current pose.
+	 *
+	 * A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a
+	 * local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a
+	 * constraint or application code modifies the world transform after it was computed from the local transform. */
+	export class Bone implements Updatable {
+		/** The bone's setup pose data. */
+		data: BoneData;
+
+		/** The skeleton this bone belongs to. */
+		skeleton: Skeleton;
+
+		/** The parent bone, or null if this is the root bone. */
+		parent: Bone;
+
+		/** The immediate children of this bone. */
+		children = new Array<Bone>();
+
+		/** The local x translation. */
+		x = 0;
+
+		/** The local y translation. */
+		y = 0;
+
+		/** The local rotation in degrees, counter clockwise. */
+		rotation = 0;
+
+		/** The local scaleX. */
+		scaleX = 0;
+
+		/** The local scaleY. */
+		scaleY = 0;
+
+		/** The local shearX. */
+		shearX = 0;
+
+		/** The local shearY. */
+		shearY = 0;
+
+		/** The applied local x translation. */
+		ax = 0;
+
+		/** The applied local y translation. */
+		ay = 0;
+
+		/** The applied local rotation in degrees, counter clockwise. */
+		arotation = 0;
+
+		/** The applied local scaleX. */
+		ascaleX = 0;
+
+		/** The applied local scaleY. */
+		ascaleY = 0;
+
+		/** The applied local shearX. */
+		ashearX = 0;
+
+		/** The applied local shearY. */
+		ashearY = 0;
+
+		/** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */
+		a = 0;
+
+		/** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */
+		b = 0;
+
+		/** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */
+		c = 0;
+
+		/** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */
+		d = 0;
+
+		/** The world X position. If changed, {@link #updateAppliedTransform()} should be called. */
+		worldY = 0;
+
+		/** The world Y position. If changed, {@link #updateAppliedTransform()} should be called. */
+		worldX = 0;
+
+		sorted = false;
+		active = false;
+
+		/** @param parent May be null. */
+		constructor (data: BoneData, skeleton: Skeleton, parent: Bone) {
+			if (!data) throw new Error("data cannot be null.");
+			if (!skeleton) throw new Error("skeleton cannot be null.");
+			this.data = data;
+			this.skeleton = skeleton;
+			this.parent = parent;
+			this.setToSetupPose();
+		}
+
+		/** Returns false when the bone has not been computed because {@link BoneData#skinRequired} is true and the
+	 	* {@link Skeleton#skin active skin} does not {@link Skin#bones contain} this bone. */
+		isActive () {
+			return this.active;
+		}
+
+		/** Computes the world transform using the parent bone and this bone's local applied transform. */
+		update () {
+			this.updateWorldTransformWith(this.ax, this.ay, this.arotation, this.ascaleX, this.ascaleY, this.ashearX, this.ashearY);
+		}
+
+		/** Computes the world transform using the parent bone and this bone's local transform.
+		 *
+		 * See {@link #updateWorldTransformWith()}. */
+		updateWorldTransform () {
+			this.updateWorldTransformWith(this.x, this.y, this.rotation, this.scaleX, this.scaleY, this.shearX, this.shearY);
+		}
+
+		/** Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the
+		 * specified local transform. Child bones are not updated.
+		 *
+		 * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
+		 * Runtimes Guide. */
+		updateWorldTransformWith (x: number, y: number, rotation: number, scaleX: number, scaleY: number, shearX: number, shearY: number) {
+			this.ax = x;
+			this.ay = y;
+			this.arotation = rotation;
+			this.ascaleX = scaleX;
+			this.ascaleY = scaleY;
+			this.ashearX = shearX;
+			this.ashearY = shearY;
+
+			let parent = this.parent;
+			if (!parent) { // Root bone.
+				let skeleton = this.skeleton;
+				let rotationY = rotation + 90 + shearY;
+				let sx = skeleton.scaleX;
+				let sy = skeleton.scaleY;
+				this.a = MathUtils.cosDeg(rotation + shearX) * scaleX * sx;
+				this.b = MathUtils.cosDeg(rotationY) * scaleY * sx;
+				this.c = MathUtils.sinDeg(rotation + shearX) * scaleX * sy;
+				this.d = MathUtils.sinDeg(rotationY) * scaleY * sy;
+				this.worldX = x * sx + skeleton.x;
+				this.worldY = y * sy + skeleton.y;
+				return;
+			}
+
+			let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+			this.worldX = pa * x + pb * y + parent.worldX;
+			this.worldY = pc * x + pd * y + parent.worldY;
+
+			switch (this.data.transformMode) {
+				case TransformMode.Normal: {
+					let rotationY = rotation + 90 + shearY;
+					let la = MathUtils.cosDeg(rotation + shearX) * scaleX;
+					let lb = MathUtils.cosDeg(rotationY) * scaleY;
+					let lc = MathUtils.sinDeg(rotation + shearX) * scaleX;
+					let ld = MathUtils.sinDeg(rotationY) * scaleY;
+					this.a = pa * la + pb * lc;
+					this.b = pa * lb + pb * ld;
+					this.c = pc * la + pd * lc;
+					this.d = pc * lb + pd * ld;
+					return;
+				}
+				case TransformMode.OnlyTranslation: {
+					let rotationY = rotation + 90 + shearY;
+					this.a = MathUtils.cosDeg(rotation + shearX) * scaleX;
+					this.b = MathUtils.cosDeg(rotationY) * scaleY;
+					this.c = MathUtils.sinDeg(rotation + shearX) * scaleX;
+					this.d = MathUtils.sinDeg(rotationY) * scaleY;
+					break;
+				}
+				case TransformMode.NoRotationOrReflection: {
+					let s = pa * pa + pc * pc;
+					let prx = 0;
+					if (s > 0.0001) {
+						s = Math.abs(pa * pd - pb * pc) / s;
+						pa /= this.skeleton.scaleX;
+						pc /= this.skeleton.scaleY;
+						pb = pc * s;
+						pd = pa * s;
+						prx = Math.atan2(pc, pa) * MathUtils.radDeg;
+					} else {
+						pa = 0;
+						pc = 0;
+						prx = 90 - Math.atan2(pd, pb) * MathUtils.radDeg;
+					}
+					let rx = rotation + shearX - prx;
+					let ry = rotation + shearY - prx + 90;
+					let la = MathUtils.cosDeg(rx) * scaleX;
+					let lb = MathUtils.cosDeg(ry) * scaleY;
+					let lc = MathUtils.sinDeg(rx) * scaleX;
+					let ld = MathUtils.sinDeg(ry) * scaleY;
+					this.a = pa * la - pb * lc;
+					this.b = pa * lb - pb * ld;
+					this.c = pc * la + pd * lc;
+					this.d = pc * lb + pd * ld;
+					break;
+				}
+				case TransformMode.NoScale:
+				case TransformMode.NoScaleOrReflection: {
+					let cos = MathUtils.cosDeg(rotation);
+					let sin = MathUtils.sinDeg(rotation);
+					let za = (pa * cos + pb * sin) / this.skeleton.scaleX;
+					let zc = (pc * cos + pd * sin) / this.skeleton.scaleY;
+					let s = Math.sqrt(za * za + zc * zc);
+					if (s > 0.00001) s = 1 / s;
+					za *= s;
+					zc *= s;
+					s = Math.sqrt(za * za + zc * zc);
+					if (this.data.transformMode == TransformMode.NoScale
+						&& (pa * pd - pb * pc < 0) != (this.skeleton.scaleX < 0 != this.skeleton.scaleY < 0)) s = -s;
+					let r = Math.PI / 2 + Math.atan2(zc, za);
+					let zb = Math.cos(r) * s;
+					let zd = Math.sin(r) * s;
+					let la = MathUtils.cosDeg(shearX) * scaleX;
+					let lb = MathUtils.cosDeg(90 + shearY) * scaleY;
+					let lc = MathUtils.sinDeg(shearX) * scaleX;
+					let ld = MathUtils.sinDeg(90 + shearY) * scaleY;
+					this.a = za * la + zb * lc;
+					this.b = za * lb + zb * ld;
+					this.c = zc * la + zd * lc;
+					this.d = zc * lb + zd * ld;
+					break;
+				}
+			}
+			this.a *= this.skeleton.scaleX;
+			this.b *= this.skeleton.scaleX;
+			this.c *= this.skeleton.scaleY;
+			this.d *= this.skeleton.scaleY;
+		}
+
+		/** Sets this bone's local transform to the setup pose. */
+		setToSetupPose () {
+			let data = this.data;
+			this.x = data.x;
+			this.y = data.y;
+			this.rotation = data.rotation;
+			this.scaleX = data.scaleX;
+			this.scaleY = data.scaleY;
+			this.shearX = data.shearX;
+			this.shearY = data.shearY;
+		}
+
+		/** The world rotation for the X axis, calculated using {@link #a} and {@link #c}. */
+		getWorldRotationX () {
+			return Math.atan2(this.c, this.a) * MathUtils.radDeg;
+		}
+
+		/** The world rotation for the Y axis, calculated using {@link #b} and {@link #d}. */
+		getWorldRotationY () {
+			return Math.atan2(this.d, this.b) * MathUtils.radDeg;
+		}
+
+		/** The magnitude (always positive) of the world scale X, calculated using {@link #a} and {@link #c}. */
+		getWorldScaleX () {
+			return Math.sqrt(this.a * this.a + this.c * this.c);
+		}
+
+		/** The magnitude (always positive) of the world scale Y, calculated using {@link #b} and {@link #d}. */
+		getWorldScaleY () {
+			return Math.sqrt(this.b * this.b + this.d * this.d);
+		}
+
+		/** Computes the applied transform values from the world transform.
+		 *
+		 * If the world transform is modified (by a constraint, {@link #rotateWorld(float)}, etc) then this method should be called so
+		 * the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply other
+		 * constraints).
+		 *
+		 * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after
+		 * calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */
+		updateAppliedTransform () {
+			let parent = this.parent;
+			if (!parent) {
+				this.ax = this.worldX;
+				this.ay = this.worldY;
+				this.arotation = Math.atan2(this.c, this.a) * MathUtils.radDeg;
+				this.ascaleX = Math.sqrt(this.a * this.a + this.c * this.c);
+				this.ascaleY = Math.sqrt(this.b * this.b + this.d * this.d);
+				this.ashearX = 0;
+				this.ashearY = Math.atan2(this.a * this.b + this.c * this.d, this.a * this.d - this.b * this.c) * MathUtils.radDeg;
+				return;
+			}
+			let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+			let pid = 1 / (pa * pd - pb * pc);
+			let dx = this.worldX - parent.worldX, dy = this.worldY - parent.worldY;
+			this.ax = (dx * pd * pid - dy * pb * pid);
+			this.ay = (dy * pa * pid - dx * pc * pid);
+			let ia = pid * pd;
+			let id = pid * pa;
+			let ib = pid * pb;
+			let ic = pid * pc;
+			let ra = ia * this.a - ib * this.c;
+			let rb = ia * this.b - ib * this.d;
+			let rc = id * this.c - ic * this.a;
+			let rd = id * this.d - ic * this.b;
+			this.ashearX = 0;
+			this.ascaleX = Math.sqrt(ra * ra + rc * rc);
+			if (this.ascaleX > 0.0001) {
+				let det = ra * rd - rb * rc;
+				this.ascaleY = det / this.ascaleX;
+				this.ashearY = Math.atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
+				this.arotation = Math.atan2(rc, ra) * MathUtils.radDeg;
+			} else {
+				this.ascaleX = 0;
+				this.ascaleY = Math.sqrt(rb * rb + rd * rd);
+				this.ashearY = 0;
+				this.arotation = 90 - Math.atan2(rd, rb) * MathUtils.radDeg;
+			}
+		}
+
+		/** Transforms a point from world coordinates to the bone's local coordinates. */
+		worldToLocal (world: Vector2) {
+			let invDet = 1 / (this.a * this.d - this.b * this.c);
+			let x = world.x - this.worldX, y = world.y - this.worldY;
+			world.x = x * this.d * invDet - y * this.b * invDet;
+			world.y = y * this.a * invDet - x * this.c * invDet;
+			return world;
+		}
+
+		/** Transforms a point from the bone's local coordinates to world coordinates. */
+		localToWorld (local: Vector2) {
+			let x = local.x, y = local.y;
+			local.x = x * this.a + y * this.b + this.worldX;
+			local.y = x * this.c + y * this.d + this.worldY;
+			return local;
+		}
+
+		/** Transforms a world rotation to a local rotation. */
+		worldToLocalRotation (worldRotation: number) {
+			let sin = MathUtils.sinDeg(worldRotation), cos = MathUtils.cosDeg(worldRotation);
+			return Math.atan2(this.a * sin - this.c * cos, this.d * cos - this.b * sin) * MathUtils.radDeg + this.rotation - this.shearX;
+		}
+
+		/** Transforms a local rotation to a world rotation. */
+		localToWorldRotation (localRotation: number) {
+			localRotation -= this.rotation - this.shearX;
+			let sin = MathUtils.sinDeg(localRotation), cos = MathUtils.cosDeg(localRotation);
+			return Math.atan2(cos * this.c + sin * this.d, cos * this.a + sin * this.b) * MathUtils.radDeg;
+		}
+
+		/** Rotates the world transform the specified amount.
+		 * <p>
+		 * After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and {@link #update()} will
+		 * need to be called on any child bones, recursively. */
+		rotateWorld (degrees: number) {
+			let a = this.a, b = this.b, c = this.c, d = this.d;
+			let cos = MathUtils.cosDeg(degrees), sin = MathUtils.sinDeg(degrees);
+			this.a = cos * a - sin * c;
+			this.b = cos * b - sin * d;
+			this.c = sin * a + cos * c;
+			this.d = sin * b + cos * d;
+		}
+	}
+}

+ 90 - 90
spine-ts/core/src/BoneData.ts

@@ -1,90 +1,90 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the setup pose for a {@link Bone}. */
-	export class BoneData {
-		/** The index of the bone in {@link Skeleton#getBones()}. */
-		index: number;
-
-		/** The name of the bone, which is unique across all bones in the skeleton. */
-		name: string;
-
-		/** @returns May be null. */
-		parent: BoneData;
-
-		/** The bone's length. */
-		length: number;
-
-		/** The local x translation. */
-		x = 0;
-
-		/** The local y translation. */
-		y = 0;
-
-		/** The local rotation. */
-		rotation = 0;
-
-		/** The local scaleX. */
-		scaleX = 1;
-
-		/** The local scaleY. */
-		scaleY = 1;
-
-		/** The local shearX. */
-		shearX = 0;
-
-		/** The local shearX. */
-		shearY = 0;
-
-		/** The transform mode for how parent world transforms affect this bone. */
-		transformMode = TransformMode.Normal;
-
-		/** When true, {@link Skeleton#updateWorldTransform()} only updates this bone if the {@link Skeleton#skin} contains this
-	 	* bone.
-	 	* @see Skin#bones */
-		skinRequired = false;
-
-		/** The color of the bone as it was in Spine. Available only when nonessential data was exported. Bones are not usually
-		 * rendered at runtime. */
-		color = new Color();
-
-		constructor (index: number, name: string, parent: BoneData) {
-			if (index < 0) throw new Error("index must be >= 0.");
-			if (!name) throw new Error("name cannot be null.");
-			this.index = index;
-			this.name = name;
-			this.parent = parent;
-		}
-	}
-
-	/** Determines how a bone inherits world transforms from parent bones. */
-	export enum TransformMode { Normal, OnlyTranslation, NoRotationOrReflection, NoScale, NoScaleOrReflection }
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the setup pose for a {@link Bone}. */
+	export class BoneData {
+		/** The index of the bone in {@link Skeleton#getBones()}. */
+		index: number;
+
+		/** The name of the bone, which is unique across all bones in the skeleton. */
+		name: string;
+
+		/** @returns May be null. */
+		parent: BoneData;
+
+		/** The bone's length. */
+		length: number;
+
+		/** The local x translation. */
+		x = 0;
+
+		/** The local y translation. */
+		y = 0;
+
+		/** The local rotation. */
+		rotation = 0;
+
+		/** The local scaleX. */
+		scaleX = 1;
+
+		/** The local scaleY. */
+		scaleY = 1;
+
+		/** The local shearX. */
+		shearX = 0;
+
+		/** The local shearX. */
+		shearY = 0;
+
+		/** The transform mode for how parent world transforms affect this bone. */
+		transformMode = TransformMode.Normal;
+
+		/** When true, {@link Skeleton#updateWorldTransform()} only updates this bone if the {@link Skeleton#skin} contains this
+	 	* bone.
+	 	* @see Skin#bones */
+		skinRequired = false;
+
+		/** The color of the bone as it was in Spine. Available only when nonessential data was exported. Bones are not usually
+		 * rendered at runtime. */
+		color = new Color();
+
+		constructor (index: number, name: string, parent: BoneData) {
+			if (index < 0) throw new Error("index must be >= 0.");
+			if (!name) throw new Error("name cannot be null.");
+			this.index = index;
+			this.name = name;
+			this.parent = parent;
+		}
+	}
+
+	/** Determines how a bone inherits world transforms from parent bones. */
+	export enum TransformMode { Normal, OnlyTranslation, NoRotationOrReflection, NoScale, NoScaleOrReflection }
+}

+ 1 - 1
spine-ts/core/src/ConstraintData.ts

@@ -30,6 +30,6 @@
 module spine {
 	/** The base class for all constraint datas. */
 	export abstract class ConstraintData {
-		constructor(public name: string, public order: number, public skinRequired: boolean) { }
+		constructor (public name: string, public order: number, public skinRequired: boolean) { }
 	}
 }

+ 52 - 52
spine-ts/core/src/Event.ts

@@ -1,52 +1,52 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the current pose values for an {@link Event}.
-	 *
-	 * See Timeline {@link Timeline#apply()},
-	 * AnimationStateListener {@link AnimationStateListener#event()}, and
-	 * [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */
-	export class Event {
-		data: EventData;
-		intValue: number;
-		floatValue: number;
-		stringValue: string;
-		time: number;
-		volume: number;
-		balance: number;
-
-		constructor (time: number, data: EventData) {
-			if (!data) throw new Error("data cannot be null.");
-			this.time = time;
-			this.data = data;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the current pose values for an {@link Event}.
+	 *
+	 * See Timeline {@link Timeline#apply()},
+	 * AnimationStateListener {@link AnimationStateListener#event()}, and
+	 * [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */
+	export class Event {
+		data: EventData;
+		intValue: number;
+		floatValue: number;
+		stringValue: string;
+		time: number;
+		volume: number;
+		balance: number;
+
+		constructor (time: number, data: EventData) {
+			if (!data) throw new Error("data cannot be null.");
+			this.time = time;
+			this.data = data;
+		}
+	}
+}

+ 47 - 47
spine-ts/core/src/EventData.ts

@@ -1,47 +1,47 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	/** Stores the setup pose values for an {@link Event}.
-	 *
-	 * See [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */
-	export class EventData {
-		name: string;
-		intValue: number;
-		floatValue: number;
-		stringValue: string;
-		audioPath: string;
-		volume: number;
-		balance: number;
-
-		constructor (name: string) {
-			this.name = name;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	/** Stores the setup pose values for an {@link Event}.
+	 *
+	 * See [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */
+	export class EventData {
+		name: string;
+		intValue: number;
+		floatValue: number;
+		stringValue: string;
+		audioPath: string;
+		volume: number;
+		balance: number;
+
+		constructor (name: string) {
+			this.name = name;
+		}
+	}
+}

+ 294 - 294
spine-ts/core/src/IkConstraint.ts

@@ -1,294 +1,294 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of
-	 * the last bone is as close to the target bone as possible.
-	 *
-	 * See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */
-	export class IkConstraint implements Updatable {
-		/** The IK constraint's setup pose data. */
-		data: IkConstraintData;
-
-		/** The bones that will be modified by this IK constraint. */
-		bones: Array<Bone>;
-
-		/** The bone that is the IK target. */
-		target: Bone;
-
-		/** Controls the bend direction of the IK bones, either 1 or -1. */
-		bendDirection = 0;
-
-		/** When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. */
-		compress = false;
-
-		/** When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained
-		 * and the parent bone has local nonuniform scale, stretch is not applied. */
-		stretch = false;
-
-		/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */
-		mix = 1;
-
-		/** For two bone IK, the distance from the maximum reach of the bones that rotation will slow. */
-		softness = 0;
-		active = false;
-
-		constructor (data: IkConstraintData, skeleton: Skeleton) {
-			if (!data) throw new Error("data cannot be null.");
-			if (!skeleton) throw new Error("skeleton cannot be null.");
-			this.data = data;
-			this.mix = data.mix;
-			this.softness = data.softness;
-			this.bendDirection = data.bendDirection;
-			this.compress = data.compress;
-			this.stretch = data.stretch;
-
-			this.bones = new Array<Bone>();
-			for (let i = 0; i < data.bones.length; i++)
-				this.bones.push(skeleton.findBone(data.bones[i].name));
-			this.target = skeleton.findBone(data.target.name);
-		}
-
-		isActive () {
-			return this.active;
-		}
-
-		update () {
-			if (this.mix == 0) return;
-			let target = this.target;
-			let bones = this.bones;
-			switch (bones.length) {
-			case 1:
-				this.apply1(bones[0], target.worldX, target.worldY, this.compress, this.stretch, this.data.uniform, this.mix);
-				break;
-			case 2:
-				this.apply2(bones[0], bones[1], target.worldX, target.worldY, this.bendDirection, this.stretch, this.data.uniform, this.softness, this.mix);
-				break;
-			}
-		}
-
-		/** Applies 1 bone IK. The target is specified in the world coordinate system. */
-		apply1 (bone: Bone, targetX: number, targetY: number, compress: boolean, stretch: boolean, uniform: boolean, alpha: number) {
-			let p = bone.parent;
-			let pa = p.a, pb = p.b, pc = p.c, pd = p.d;
-			let rotationIK = -bone.ashearX - bone.arotation, tx = 0, ty = 0;
-
-			switch(bone.data.transformMode) {
-				case TransformMode.OnlyTranslation:
-					tx = targetX - bone.worldX;
-					ty = targetY - bone.worldY;
-					break;
-				case TransformMode.NoRotationOrReflection:
-					let s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
-					let sa = pa / bone.skeleton.scaleX;
-					let sc = pc / bone.skeleton.scaleY;
-					pb = -sc * s * bone.skeleton.scaleX;
-					pd = sa * s * bone.skeleton.scaleY;
-					rotationIK += Math.atan2(sc, sa) * MathUtils.radDeg;
-					// Fall through
-				default:
-					let x = targetX - p.worldX, y = targetY - p.worldY;
-					let d = pa * pd - pb * pc;
-					tx = (x * pd - y * pb) / d - bone.ax;
-					ty = (y * pa - x * pc) / d - bone.ay;
-			}
-			rotationIK += Math.atan2(ty, tx) * MathUtils.radDeg;
-			if (bone.ascaleX < 0) rotationIK += 180;
-			if (rotationIK > 180)
-				rotationIK -= 360;
-			else if (rotationIK < -180)
-				rotationIK += 360;
-			let sx = bone.ascaleX, sy = bone.ascaleY;
-			if (compress || stretch) {
-				switch (bone.data.transformMode) {
-					case TransformMode.NoScale:
-					case TransformMode.NoScaleOrReflection:
-						tx = targetX - bone.worldX;
-						ty = targetY - bone.worldY;
-				}
-				let b = bone.data.length * sx, dd = Math.sqrt(tx * tx + ty * ty);
-				if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001) {
-					let s = (dd / b - 1) * alpha + 1;
-					sx *= s;
-					if (uniform) sy *= s;
-				}
-			}
-			bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX,
-				bone.ashearY);
-		}
-
-		/** Applies 2 bone IK. The target is specified in the world coordinate system.
-		 * @param child A direct descendant of the parent bone. */
-		apply2 (parent: Bone, child: Bone, targetX: number, targetY: number, bendDir: number, stretch: boolean, uniform: boolean, softness: number, alpha: number) {
-			let px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX;
-			let os1 = 0, os2 = 0, s2 = 0;
-			if (psx < 0) {
-				psx = -psx;
-				os1 = 180;
-				s2 = -1;
-			} else {
-				os1 = 0;
-				s2 = 1;
-			}
-			if (psy < 0) {
-				psy = -psy;
-				s2 = -s2;
-			}
-			if (csx < 0) {
-				csx = -csx;
-				os2 = 180;
-			} else
-				os2 = 0;
-			let cx = child.ax, cy = 0, cwx = 0, cwy = 0, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
-			let u = Math.abs(psx - psy) <= 0.0001;
-			if (!u || stretch) {
-				cy = 0;
-				cwx = a * cx + parent.worldX;
-				cwy = c * cx + parent.worldY;
-			} else {
-				cy = child.ay;
-				cwx = a * cx + b * cy + parent.worldX;
-				cwy = c * cx + d * cy + parent.worldY;
-			}
-			let pp = parent.parent;
-			a = pp.a;
-			b = pp.b;
-			c = pp.c;
-			d = pp.d;
-			let id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY;
-			let dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
-			let l1 = Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
-			if (l1 < 0.0001) {
-				this.apply1(parent, targetX, targetY, false, stretch, false, alpha);
-				child.updateWorldTransformWith(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
-				return;
-			}
-			x = targetX - pp.worldX;
-			y = targetY - pp.worldY;
-			let tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
-			let dd = tx * tx + ty * ty;
-			if (softness != 0) {
-				softness *= psx * (csx + 1) * 0.5;
-				let td = Math.sqrt(dd), sd = td - l1 - l2 * psx + softness;
-				if (sd > 0) {
-					let p = Math.min(1, sd / (softness * 2)) - 1;
-					p = (sd - softness * (1 - p * p)) / td;
-					tx -= p * tx;
-					ty -= p * ty;
-					dd = tx * tx + ty * ty;
-				}
-			}
-			outer:
-			if (u) {
-				l2 *= psx;
-				let cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
-				if (cos < -1) {
-					cos = -1;
-					a2 = Math.PI * bendDir;
-				} else if (cos > 1) {
-					cos = 1;
-					a2 = 0;
-					if (stretch) {
-						a = (Math.sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
-						sx *= a;
-						if (uniform) sy *= a;
-					}
-				} else
-					a2 = Math.acos(cos) * bendDir;
-				a = l1 + l2 * cos;
-				b = l2 * Math.sin(a2);
-				a1 = Math.atan2(ty * a - tx * b, tx * a + ty * b);
-			} else {
-				a = psx * l2;
-				b = psy * l2;
-				let aa = a * a, bb = b * b, ta = Math.atan2(ty, tx);
-				c = bb * l1 * l1 + aa * dd - aa * bb;
-				let c1 = -2 * bb * l1, c2 = bb - aa;
-				d = c1 * c1 - 4 * c2 * c;
-				if (d >= 0) {
-					let q = Math.sqrt(d);
-					if (c1 < 0) q = -q;
-					q = -(c1 + q) * 0.5;
-					let r0 = q / c2, r1 = c / q;
-					let r = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
-					if (r * r <= dd) {
-						y = Math.sqrt(dd - r * r) * bendDir;
-						a1 = ta - Math.atan2(y, r);
-						a2 = Math.atan2(y / psy, (r - l1) / psx);
-						break outer;
-					}
-				}
-				let minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0;
-				let maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
-				c = -a * l1 / (aa - bb);
-				if (c >= -1 && c <= 1) {
-					c = Math.acos(c);
-					x = a * Math.cos(c) + l1;
-					y = b * Math.sin(c);
-					d = x * x + y * y;
-					if (d < minDist) {
-						minAngle = c;
-						minDist = d;
-						minX = x;
-						minY = y;
-					}
-					if (d > maxDist) {
-						maxAngle = c;
-						maxDist = d;
-						maxX = x;
-						maxY = y;
-					}
-				}
-				if (dd <= (minDist + maxDist) * 0.5) {
-					a1 = ta - Math.atan2(minY * bendDir, minX);
-					a2 = minAngle * bendDir;
-				} else {
-					a1 = ta - Math.atan2(maxY * bendDir, maxX);
-					a2 = maxAngle * bendDir;
-				}
-			}
-			let os = Math.atan2(cy, cx) * s2;
-			let rotation = parent.arotation;
-			a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation;
-			if (a1 > 180)
-				a1 -= 360;
-			else if (a1 < -180) //
-				a1 += 360;
-			parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, sx, sy, 0, 0);
-			rotation = child.arotation;
-			a2 = ((a2 + os) * MathUtils.radDeg - child.ashearX) * s2 + os2 - rotation;
-			if (a2 > 180)
-				a2 -= 360;
-			else if (a2 < -180) //
-				a2 += 360;
-			child.updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of
+	 * the last bone is as close to the target bone as possible.
+	 *
+	 * See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */
+	export class IkConstraint implements Updatable {
+		/** The IK constraint's setup pose data. */
+		data: IkConstraintData;
+
+		/** The bones that will be modified by this IK constraint. */
+		bones: Array<Bone>;
+
+		/** The bone that is the IK target. */
+		target: Bone;
+
+		/** Controls the bend direction of the IK bones, either 1 or -1. */
+		bendDirection = 0;
+
+		/** When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. */
+		compress = false;
+
+		/** When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained
+		 * and the parent bone has local nonuniform scale, stretch is not applied. */
+		stretch = false;
+
+		/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */
+		mix = 1;
+
+		/** For two bone IK, the distance from the maximum reach of the bones that rotation will slow. */
+		softness = 0;
+		active = false;
+
+		constructor (data: IkConstraintData, skeleton: Skeleton) {
+			if (!data) throw new Error("data cannot be null.");
+			if (!skeleton) throw new Error("skeleton cannot be null.");
+			this.data = data;
+			this.mix = data.mix;
+			this.softness = data.softness;
+			this.bendDirection = data.bendDirection;
+			this.compress = data.compress;
+			this.stretch = data.stretch;
+
+			this.bones = new Array<Bone>();
+			for (let i = 0; i < data.bones.length; i++)
+				this.bones.push(skeleton.findBone(data.bones[i].name));
+			this.target = skeleton.findBone(data.target.name);
+		}
+
+		isActive () {
+			return this.active;
+		}
+
+		update () {
+			if (this.mix == 0) return;
+			let target = this.target;
+			let bones = this.bones;
+			switch (bones.length) {
+				case 1:
+					this.apply1(bones[0], target.worldX, target.worldY, this.compress, this.stretch, this.data.uniform, this.mix);
+					break;
+				case 2:
+					this.apply2(bones[0], bones[1], target.worldX, target.worldY, this.bendDirection, this.stretch, this.data.uniform, this.softness, this.mix);
+					break;
+			}
+		}
+
+		/** Applies 1 bone IK. The target is specified in the world coordinate system. */
+		apply1 (bone: Bone, targetX: number, targetY: number, compress: boolean, stretch: boolean, uniform: boolean, alpha: number) {
+			let p = bone.parent;
+			let pa = p.a, pb = p.b, pc = p.c, pd = p.d;
+			let rotationIK = -bone.ashearX - bone.arotation, tx = 0, ty = 0;
+
+			switch (bone.data.transformMode) {
+				case TransformMode.OnlyTranslation:
+					tx = targetX - bone.worldX;
+					ty = targetY - bone.worldY;
+					break;
+				case TransformMode.NoRotationOrReflection:
+					let s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
+					let sa = pa / bone.skeleton.scaleX;
+					let sc = pc / bone.skeleton.scaleY;
+					pb = -sc * s * bone.skeleton.scaleX;
+					pd = sa * s * bone.skeleton.scaleY;
+					rotationIK += Math.atan2(sc, sa) * MathUtils.radDeg;
+				// Fall through
+				default:
+					let x = targetX - p.worldX, y = targetY - p.worldY;
+					let d = pa * pd - pb * pc;
+					tx = (x * pd - y * pb) / d - bone.ax;
+					ty = (y * pa - x * pc) / d - bone.ay;
+			}
+			rotationIK += Math.atan2(ty, tx) * MathUtils.radDeg;
+			if (bone.ascaleX < 0) rotationIK += 180;
+			if (rotationIK > 180)
+				rotationIK -= 360;
+			else if (rotationIK < -180)
+				rotationIK += 360;
+			let sx = bone.ascaleX, sy = bone.ascaleY;
+			if (compress || stretch) {
+				switch (bone.data.transformMode) {
+					case TransformMode.NoScale:
+					case TransformMode.NoScaleOrReflection:
+						tx = targetX - bone.worldX;
+						ty = targetY - bone.worldY;
+				}
+				let b = bone.data.length * sx, dd = Math.sqrt(tx * tx + ty * ty);
+				if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001) {
+					let s = (dd / b - 1) * alpha + 1;
+					sx *= s;
+					if (uniform) sy *= s;
+				}
+			}
+			bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX,
+				bone.ashearY);
+		}
+
+		/** Applies 2 bone IK. The target is specified in the world coordinate system.
+		 * @param child A direct descendant of the parent bone. */
+		apply2 (parent: Bone, child: Bone, targetX: number, targetY: number, bendDir: number, stretch: boolean, uniform: boolean, softness: number, alpha: number) {
+			let px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX;
+			let os1 = 0, os2 = 0, s2 = 0;
+			if (psx < 0) {
+				psx = -psx;
+				os1 = 180;
+				s2 = -1;
+			} else {
+				os1 = 0;
+				s2 = 1;
+			}
+			if (psy < 0) {
+				psy = -psy;
+				s2 = -s2;
+			}
+			if (csx < 0) {
+				csx = -csx;
+				os2 = 180;
+			} else
+				os2 = 0;
+			let cx = child.ax, cy = 0, cwx = 0, cwy = 0, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
+			let u = Math.abs(psx - psy) <= 0.0001;
+			if (!u || stretch) {
+				cy = 0;
+				cwx = a * cx + parent.worldX;
+				cwy = c * cx + parent.worldY;
+			} else {
+				cy = child.ay;
+				cwx = a * cx + b * cy + parent.worldX;
+				cwy = c * cx + d * cy + parent.worldY;
+			}
+			let pp = parent.parent;
+			a = pp.a;
+			b = pp.b;
+			c = pp.c;
+			d = pp.d;
+			let id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY;
+			let dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
+			let l1 = Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
+			if (l1 < 0.0001) {
+				this.apply1(parent, targetX, targetY, false, stretch, false, alpha);
+				child.updateWorldTransformWith(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
+				return;
+			}
+			x = targetX - pp.worldX;
+			y = targetY - pp.worldY;
+			let tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
+			let dd = tx * tx + ty * ty;
+			if (softness != 0) {
+				softness *= psx * (csx + 1) * 0.5;
+				let td = Math.sqrt(dd), sd = td - l1 - l2 * psx + softness;
+				if (sd > 0) {
+					let p = Math.min(1, sd / (softness * 2)) - 1;
+					p = (sd - softness * (1 - p * p)) / td;
+					tx -= p * tx;
+					ty -= p * ty;
+					dd = tx * tx + ty * ty;
+				}
+			}
+			outer:
+			if (u) {
+				l2 *= psx;
+				let cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
+				if (cos < -1) {
+					cos = -1;
+					a2 = Math.PI * bendDir;
+				} else if (cos > 1) {
+					cos = 1;
+					a2 = 0;
+					if (stretch) {
+						a = (Math.sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
+						sx *= a;
+						if (uniform) sy *= a;
+					}
+				} else
+					a2 = Math.acos(cos) * bendDir;
+				a = l1 + l2 * cos;
+				b = l2 * Math.sin(a2);
+				a1 = Math.atan2(ty * a - tx * b, tx * a + ty * b);
+			} else {
+				a = psx * l2;
+				b = psy * l2;
+				let aa = a * a, bb = b * b, ta = Math.atan2(ty, tx);
+				c = bb * l1 * l1 + aa * dd - aa * bb;
+				let c1 = -2 * bb * l1, c2 = bb - aa;
+				d = c1 * c1 - 4 * c2 * c;
+				if (d >= 0) {
+					let q = Math.sqrt(d);
+					if (c1 < 0) q = -q;
+					q = -(c1 + q) * 0.5;
+					let r0 = q / c2, r1 = c / q;
+					let r = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
+					if (r * r <= dd) {
+						y = Math.sqrt(dd - r * r) * bendDir;
+						a1 = ta - Math.atan2(y, r);
+						a2 = Math.atan2(y / psy, (r - l1) / psx);
+						break outer;
+					}
+				}
+				let minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0;
+				let maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
+				c = -a * l1 / (aa - bb);
+				if (c >= -1 && c <= 1) {
+					c = Math.acos(c);
+					x = a * Math.cos(c) + l1;
+					y = b * Math.sin(c);
+					d = x * x + y * y;
+					if (d < minDist) {
+						minAngle = c;
+						minDist = d;
+						minX = x;
+						minY = y;
+					}
+					if (d > maxDist) {
+						maxAngle = c;
+						maxDist = d;
+						maxX = x;
+						maxY = y;
+					}
+				}
+				if (dd <= (minDist + maxDist) * 0.5) {
+					a1 = ta - Math.atan2(minY * bendDir, minX);
+					a2 = minAngle * bendDir;
+				} else {
+					a1 = ta - Math.atan2(maxY * bendDir, maxX);
+					a2 = maxAngle * bendDir;
+				}
+			}
+			let os = Math.atan2(cy, cx) * s2;
+			let rotation = parent.arotation;
+			a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation;
+			if (a1 > 180)
+				a1 -= 360;
+			else if (a1 < -180) //
+				a1 += 360;
+			parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, sx, sy, 0, 0);
+			rotation = child.arotation;
+			a2 = ((a2 + os) * MathUtils.radDeg - child.ashearX) * s2 + os2 - rotation;
+			if (a2 > 180)
+				a2 -= 360;
+			else if (a2 < -180) //
+				a2 += 360;
+			child.updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
+		}
+	}
+}

+ 66 - 66
spine-ts/core/src/IkConstraintData.ts

@@ -1,66 +1,66 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the setup pose for an {@link IkConstraint}.
-	 * <p>
-	 * See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */
-	export class IkConstraintData extends ConstraintData {
-		/** The bones that are constrained by this IK constraint. */
-		bones = new Array<BoneData>();
-
-		/** The bone that is the IK target. */
-		target: BoneData;
-
-		/** Controls the bend direction of the IK bones, either 1 or -1. */
-		bendDirection = 1;
-
-		/** When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. */
-		compress = false;
-
-		/** When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained
-		 * and the parent bone has local nonuniform scale, stretch is not applied. */
-		stretch = false;
-
-		/** When true, only a single bone is being constrained, and {@link #getCompress()} or {@link #getStretch()} is used, the bone
-		 * is scaled on both the X and Y axes. */
-		uniform = false;
-
-		/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */
-		mix = 1;
-
-		/** For two bone IK, the distance from the maximum reach of the bones that rotation will slow. */
-		softness = 0;
-
-		constructor (name: string) {
-			super(name, 0, false);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the setup pose for an {@link IkConstraint}.
+	 * <p>
+	 * See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */
+	export class IkConstraintData extends ConstraintData {
+		/** The bones that are constrained by this IK constraint. */
+		bones = new Array<BoneData>();
+
+		/** The bone that is the IK target. */
+		target: BoneData;
+
+		/** Controls the bend direction of the IK bones, either 1 or -1. */
+		bendDirection = 1;
+
+		/** When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. */
+		compress = false;
+
+		/** When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained
+		 * and the parent bone has local nonuniform scale, stretch is not applied. */
+		stretch = false;
+
+		/** When true, only a single bone is being constrained, and {@link #getCompress()} or {@link #getStretch()} is used, the bone
+		 * is scaled on both the X and Y axes. */
+		uniform = false;
+
+		/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */
+		mix = 1;
+
+		/** For two bone IK, the distance from the maximum reach of the bones that rotation will slow. */
+		softness = 0;
+
+		constructor (name: string) {
+			super(name, 0, false);
+		}
+	}
+}

+ 483 - 483
spine-ts/core/src/PathConstraint.ts

@@ -1,483 +1,483 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the
-	 * constrained bones so they follow a {@link PathAttachment}.
-	 *
-	 * See [Path constraints](http://esotericsoftware.com/spine-path-constraints) in the Spine User Guide. */
-	export class PathConstraint implements Updatable {
-		static NONE = -1; static BEFORE = -2; static AFTER = -3;
-		static epsilon = 0.00001;
-
-		/** The path constraint's setup pose data. */
-		data: PathConstraintData;
-
-		/** The bones that will be modified by this path constraint. */
-		bones: Array<Bone>;
-
-		/** The slot whose path attachment will be used to constrained the bones. */
-		target: Slot;
-
-		/** The position along the path. */
-		position = 0;
-
-		/** The spacing between bones. */
-		spacing = 0;
-
-		mixRotate = 0;
-
-		mixX = 0;
-
-		mixY = 0;
-
-		spaces = new Array<number>(); positions = new Array<number>();
-		world = new Array<number>(); curves = new Array<number>(); lengths = new Array<number>();
-		segments = new Array<number>();
-
-		active = false;
-
-		constructor (data: PathConstraintData, skeleton: Skeleton) {
-			if (!data) throw new Error("data cannot be null.");
-			if (!skeleton) throw new Error("skeleton cannot be null.");
-			this.data = data;
-			this.bones = new Array<Bone>();
-			for (let i = 0, n = data.bones.length; i < n; i++)
-				this.bones.push(skeleton.findBone(data.bones[i].name));
-			this.target = skeleton.findSlot(data.target.name);
-			this.position = data.position;
-			this.spacing = data.spacing;
-			this.mixRotate = data.mixRotate;
-			this.mixX = data.mixX;
-			this.mixY = data.mixY;
-		}
-
-		isActive () {
-			return this.active;
-		}
-
-		update () {
-			let attachment = this.target.getAttachment();
-			if (!(attachment instanceof PathAttachment)) return;
-
-			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY;
-			if (mixRotate == 0 && mixX == 0 && mixY == 0) return;
-
-			let data = this.data;
-			let tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale;
-
-			let bones = this.bones;
-			let boneCount = bones.length, spacesCount = tangents ? boneCount : boneCount + 1;
-			let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array<number> = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : null;
-			let spacing = this.spacing;
-
-			switch (data.spacingMode) {
-			case SpacingMode.Percent:
-				if (scale) {
-					for (let i = 0, n = spacesCount - 1; i < n; i++) {
-						let bone = bones[i];
-						let setupLength = bone.data.length;
-						if (setupLength < PathConstraint.epsilon)
-							lengths[i] = 0;
-						else {
-							let x = setupLength * bone.a, y = setupLength * bone.c;
-							lengths[i] = Math.sqrt(x * x + y * y);
-						}
-					}
-				}
-				Utils.arrayFill(spaces, 1, spacesCount, spacing);
-				break;
-			case SpacingMode.Proportional:
-				let sum = 0;
-				for (let i = 0, n = spacesCount - 1; i < n;) {
-					let bone = bones[i];
-					let setupLength = bone.data.length;
-					if (setupLength < PathConstraint.epsilon) {
-						if (scale) lengths[i] = 0;
-						spaces[++i] = spacing;
-					} else {
-						let x = setupLength * bone.a, y = setupLength * bone.c;
-						let length = Math.sqrt(x * x + y * y);
-						if (scale) lengths[i] = length;
-						spaces[++i] = length;
-						sum += length;
-					}
-				}
-				if (sum > 0) {
-					sum = spacesCount / sum * spacing;
-					for (let i = 1; i < spacesCount; i++)
-						spaces[i] *= sum;
-				}
-				break;
-			default:
-				let lengthSpacing = data.spacingMode == SpacingMode.Length;
-				for (let i = 0, n = spacesCount - 1; i < n;) {
-					let bone = bones[i];
-					let setupLength = bone.data.length;
-					if (setupLength < PathConstraint.epsilon) {
-						if (scale) lengths[i] = 0;
-						spaces[++i] = spacing;
-					} else {
-						let x = setupLength * bone.a, y = setupLength * bone.c;
-						let length = Math.sqrt(x * x + y * y);
-						if (scale) lengths[i] = length;
-						spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
-					}
-				}
-			}
-
-			let positions = this.computeWorldPositions(<PathAttachment>attachment, spacesCount, tangents);
-			let boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
-			let tip = false;
-			if (offsetRotation == 0)
-				tip = data.rotateMode == RotateMode.Chain;
-			else {
-				tip = false;
-				let p = this.target.bone;
-				offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.degRad : -MathUtils.degRad;
-			}
-			for (let i = 0, p = 3; i < boneCount; i++, p += 3) {
-				let bone = bones[i];
-				bone.worldX += (boneX - bone.worldX) * mixX;
-				bone.worldY += (boneY - bone.worldY) * mixY;
-				let x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
-				if (scale) {
-					let length = lengths[i];
-					if (length != 0) {
-						let s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
-						bone.a *= s;
-						bone.c *= s;
-					}
-				}
-				boneX = x;
-				boneY = y;
-				if (mixRotate > 0) {
-					let a = bone.a, b = bone.b, c = bone.c, d = bone.d, r = 0, cos = 0, sin = 0;
-					if (tangents)
-						r = positions[p - 1];
-					else if (spaces[i + 1] == 0)
-						r = positions[p + 2];
-					else
-						r = Math.atan2(dy, dx);
-					r -= Math.atan2(c, a);
-					if (tip) {
-						cos = Math.cos(r);
-						sin = Math.sin(r);
-						let length = bone.data.length;
-						boneX += (length * (cos * a - sin * c) - dx) * mixRotate;
-						boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
-					} else {
-						r += offsetRotation;
-					}
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) //
-						r += MathUtils.PI2;
-					r *= mixRotate;
-					cos = Math.cos(r);
-					sin = Math.sin(r);
-					bone.a = cos * a - sin * c;
-					bone.b = cos * b - sin * d;
-					bone.c = sin * a + cos * c;
-					bone.d = sin * b + cos * d;
-				}
-				bone.updateAppliedTransform();
-			}
-		}
-
-		computeWorldPositions (path: PathAttachment, spacesCount: number, tangents: boolean) {
-			let target = this.target;
-			let position = this.position;
-			let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array<number> = null;
-			let closed = path.closed;
-			let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PathConstraint.NONE;
-
-			if (!path.constantSpeed) {
-				let lengths = path.lengths;
-				curveCount -= closed ? 1 : 2;
-				let pathLength = lengths[curveCount];
-				if (this.data.positionMode == PositionMode.Percent) position *= pathLength;
-
-				let multiplier;
-				switch (this.data.spacingMode) {
-				case SpacingMode.Percent:
-					multiplier = pathLength;
-					break;
-				case SpacingMode.Proportional:
-					multiplier = pathLength / spacesCount;
-					break;
-				default:
-					multiplier = 1;
-				}
-				world = Utils.setArraySize(this.world, 8);
-				for (let i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
-					let space = spaces[i] * multiplier;
-					position += space;
-					let p = position;
-
-					if (closed) {
-						p %= pathLength;
-						if (p < 0) p += pathLength;
-						curve = 0;
-					} else if (p < 0) {
-						if (prevCurve != PathConstraint.BEFORE) {
-							prevCurve = PathConstraint.BEFORE;
-							path.computeWorldVertices(target, 2, 4, world, 0, 2);
-						}
-						this.addBeforePosition(p, world, 0, out, o);
-						continue;
-					} else if (p > pathLength) {
-						if (prevCurve != PathConstraint.AFTER) {
-							prevCurve = PathConstraint.AFTER;
-							path.computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2);
-						}
-						this.addAfterPosition(p - pathLength, world, 0, out, o);
-						continue;
-					}
-
-					// Determine curve containing position.
-					for (;; curve++) {
-						let length = lengths[curve];
-						if (p > length) continue;
-						if (curve == 0)
-							p /= length;
-						else {
-							let prev = lengths[curve - 1];
-							p = (p - prev) / (length - prev);
-						}
-						break;
-					}
-					if (curve != prevCurve) {
-						prevCurve = curve;
-						if (closed && curve == curveCount) {
-							path.computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2);
-							path.computeWorldVertices(target, 0, 4, world, 4, 2);
-						} else
-							path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2);
-					}
-					this.addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
-						tangents || (i > 0 && space == 0));
-				}
-				return out;
-			}
-
-			// World vertices.
-			if (closed) {
-				verticesLength += 2;
-				world = Utils.setArraySize(this.world, verticesLength);
-				path.computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2);
-				path.computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2);
-				world[verticesLength - 2] = world[0];
-				world[verticesLength - 1] = world[1];
-			} else {
-				curveCount--;
-				verticesLength -= 4;
-				world = Utils.setArraySize(this.world, verticesLength);
-				path.computeWorldVertices(target, 2, verticesLength, world, 0, 2);
-			}
-
-			// Curve lengths.
-			let curves = Utils.setArraySize(this.curves, curveCount);
-			let pathLength = 0;
-			let x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
-			let tmpx = 0, tmpy = 0, dddfx = 0, dddfy = 0, ddfx = 0, ddfy = 0, dfx = 0, dfy = 0;
-			for (let i = 0, w = 2; i < curveCount; i++, w += 6) {
-				cx1 = world[w];
-				cy1 = world[w + 1];
-				cx2 = world[w + 2];
-				cy2 = world[w + 3];
-				x2 = world[w + 4];
-				y2 = world[w + 5];
-				tmpx = (x1 - cx1 * 2 + cx2) * 0.1875;
-				tmpy = (y1 - cy1 * 2 + cy2) * 0.1875;
-				dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375;
-				dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375;
-				ddfx = tmpx * 2 + dddfx;
-				ddfy = tmpy * 2 + dddfy;
-				dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667;
-				dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667;
-				pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
-				dfx += ddfx;
-				dfy += ddfy;
-				ddfx += dddfx;
-				ddfy += dddfy;
-				pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
-				dfx += ddfx;
-				dfy += ddfy;
-				pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
-				dfx += ddfx + dddfx;
-				dfy += ddfy + dddfy;
-				pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
-				curves[i] = pathLength;
-				x1 = x2;
-				y1 = y2;
-			}
-
-			if (this.data.positionMode == PositionMode.Percent) position *= pathLength;
-
-			let multiplier;
-			switch (this.data.spacingMode) {
-			case SpacingMode.Percent:
-				multiplier = pathLength;
-				break;
-			case SpacingMode.Proportional:
-				multiplier = pathLength / spacesCount;
-				break;
-			default:
-				multiplier = 1;
-			}
-
-			let segments = this.segments;
-			let curveLength = 0;
-			for (let i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
-				let space = spaces[i] * multiplier;
-				position += space;
-				let p = position;
-
-				if (closed) {
-					p %= pathLength;
-					if (p < 0) p += pathLength;
-					curve = 0;
-				} else if (p < 0) {
-					this.addBeforePosition(p, world, 0, out, o);
-					continue;
-				} else if (p > pathLength) {
-					this.addAfterPosition(p - pathLength, world, verticesLength - 4, out, o);
-					continue;
-				}
-
-				// Determine curve containing position.
-				for (;; curve++) {
-					let length = curves[curve];
-					if (p > length) continue;
-					if (curve == 0)
-						p /= length;
-					else {
-						let prev = curves[curve - 1];
-						p = (p - prev) / (length - prev);
-					}
-					break;
-				}
-
-				// Curve segment lengths.
-				if (curve != prevCurve) {
-					prevCurve = curve;
-					let ii = curve * 6;
-					x1 = world[ii];
-					y1 = world[ii + 1];
-					cx1 = world[ii + 2];
-					cy1 = world[ii + 3];
-					cx2 = world[ii + 4];
-					cy2 = world[ii + 5];
-					x2 = world[ii + 6];
-					y2 = world[ii + 7];
-					tmpx = (x1 - cx1 * 2 + cx2) * 0.03;
-					tmpy = (y1 - cy1 * 2 + cy2) * 0.03;
-					dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006;
-					dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006;
-					ddfx = tmpx * 2 + dddfx;
-					ddfy = tmpy * 2 + dddfy;
-					dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667;
-					dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667;
-					curveLength = Math.sqrt(dfx * dfx + dfy * dfy);
-					segments[0] = curveLength;
-					for (ii = 1; ii < 8; ii++) {
-						dfx += ddfx;
-						dfy += ddfy;
-						ddfx += dddfx;
-						ddfy += dddfy;
-						curveLength += Math.sqrt(dfx * dfx + dfy * dfy);
-						segments[ii] = curveLength;
-					}
-					dfx += ddfx;
-					dfy += ddfy;
-					curveLength += Math.sqrt(dfx * dfx + dfy * dfy);
-					segments[8] = curveLength;
-					dfx += ddfx + dddfx;
-					dfy += ddfy + dddfy;
-					curveLength += Math.sqrt(dfx * dfx + dfy * dfy);
-					segments[9] = curveLength;
-					segment = 0;
-				}
-
-				// Weight by segment length.
-				p *= curveLength;
-				for (;; segment++) {
-					let length = segments[segment];
-					if (p > length) continue;
-					if (segment == 0)
-						p /= length;
-					else {
-						let prev = segments[segment - 1];
-						p = segment + (p - prev) / (length - prev);
-					}
-					break;
-				}
-				this.addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0));
-			}
-			return out;
-		}
-
-		addBeforePosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) {
-			let x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = Math.atan2(dy, dx);
-			out[o] = x1 + p * Math.cos(r);
-			out[o + 1] = y1 + p * Math.sin(r);
-			out[o + 2] = r;
-		}
-
-		addAfterPosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) {
-			let x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = Math.atan2(dy, dx);
-			out[o] = x1 + p * Math.cos(r);
-			out[o + 1] = y1 + p * Math.sin(r);
-			out[o + 2] = r;
-		}
-
-		addCurvePosition (p: number, x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number,
-			out: Array<number>, o: number, tangents: boolean) {
-			if (p == 0 || isNaN(p)) {
-				out[o] = x1;
-				out[o + 1] = y1;
-				out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
-				return;
-			}
-			let tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
-			let ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
-			let x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
-			out[o] = x;
-			out[o + 1] = y;
-			if (tangents) {
-				if (p < 0.001)
-					out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
-				else
-					out[o + 2] = Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
-			}
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the
+	 * constrained bones so they follow a {@link PathAttachment}.
+	 *
+	 * See [Path constraints](http://esotericsoftware.com/spine-path-constraints) in the Spine User Guide. */
+	export class PathConstraint implements Updatable {
+		static NONE = -1; static BEFORE = -2; static AFTER = -3;
+		static epsilon = 0.00001;
+
+		/** The path constraint's setup pose data. */
+		data: PathConstraintData;
+
+		/** The bones that will be modified by this path constraint. */
+		bones: Array<Bone>;
+
+		/** The slot whose path attachment will be used to constrained the bones. */
+		target: Slot;
+
+		/** The position along the path. */
+		position = 0;
+
+		/** The spacing between bones. */
+		spacing = 0;
+
+		mixRotate = 0;
+
+		mixX = 0;
+
+		mixY = 0;
+
+		spaces = new Array<number>(); positions = new Array<number>();
+		world = new Array<number>(); curves = new Array<number>(); lengths = new Array<number>();
+		segments = new Array<number>();
+
+		active = false;
+
+		constructor (data: PathConstraintData, skeleton: Skeleton) {
+			if (!data) throw new Error("data cannot be null.");
+			if (!skeleton) throw new Error("skeleton cannot be null.");
+			this.data = data;
+			this.bones = new Array<Bone>();
+			for (let i = 0, n = data.bones.length; i < n; i++)
+				this.bones.push(skeleton.findBone(data.bones[i].name));
+			this.target = skeleton.findSlot(data.target.name);
+			this.position = data.position;
+			this.spacing = data.spacing;
+			this.mixRotate = data.mixRotate;
+			this.mixX = data.mixX;
+			this.mixY = data.mixY;
+		}
+
+		isActive () {
+			return this.active;
+		}
+
+		update () {
+			let attachment = this.target.getAttachment();
+			if (!(attachment instanceof PathAttachment)) return;
+
+			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY;
+			if (mixRotate == 0 && mixX == 0 && mixY == 0) return;
+
+			let data = this.data;
+			let tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale;
+
+			let bones = this.bones;
+			let boneCount = bones.length, spacesCount = tangents ? boneCount : boneCount + 1;
+			let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array<number> = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : null;
+			let spacing = this.spacing;
+
+			switch (data.spacingMode) {
+				case SpacingMode.Percent:
+					if (scale) {
+						for (let i = 0, n = spacesCount - 1; i < n; i++) {
+							let bone = bones[i];
+							let setupLength = bone.data.length;
+							if (setupLength < PathConstraint.epsilon)
+								lengths[i] = 0;
+							else {
+								let x = setupLength * bone.a, y = setupLength * bone.c;
+								lengths[i] = Math.sqrt(x * x + y * y);
+							}
+						}
+					}
+					Utils.arrayFill(spaces, 1, spacesCount, spacing);
+					break;
+				case SpacingMode.Proportional:
+					let sum = 0;
+					for (let i = 0, n = spacesCount - 1; i < n;) {
+						let bone = bones[i];
+						let setupLength = bone.data.length;
+						if (setupLength < PathConstraint.epsilon) {
+							if (scale) lengths[i] = 0;
+							spaces[++i] = spacing;
+						} else {
+							let x = setupLength * bone.a, y = setupLength * bone.c;
+							let length = Math.sqrt(x * x + y * y);
+							if (scale) lengths[i] = length;
+							spaces[++i] = length;
+							sum += length;
+						}
+					}
+					if (sum > 0) {
+						sum = spacesCount / sum * spacing;
+						for (let i = 1; i < spacesCount; i++)
+							spaces[i] *= sum;
+					}
+					break;
+				default:
+					let lengthSpacing = data.spacingMode == SpacingMode.Length;
+					for (let i = 0, n = spacesCount - 1; i < n;) {
+						let bone = bones[i];
+						let setupLength = bone.data.length;
+						if (setupLength < PathConstraint.epsilon) {
+							if (scale) lengths[i] = 0;
+							spaces[++i] = spacing;
+						} else {
+							let x = setupLength * bone.a, y = setupLength * bone.c;
+							let length = Math.sqrt(x * x + y * y);
+							if (scale) lengths[i] = length;
+							spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
+						}
+					}
+			}
+
+			let positions = this.computeWorldPositions(<PathAttachment>attachment, spacesCount, tangents);
+			let boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
+			let tip = false;
+			if (offsetRotation == 0)
+				tip = data.rotateMode == RotateMode.Chain;
+			else {
+				tip = false;
+				let p = this.target.bone;
+				offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.degRad : -MathUtils.degRad;
+			}
+			for (let i = 0, p = 3; i < boneCount; i++, p += 3) {
+				let bone = bones[i];
+				bone.worldX += (boneX - bone.worldX) * mixX;
+				bone.worldY += (boneY - bone.worldY) * mixY;
+				let x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
+				if (scale) {
+					let length = lengths[i];
+					if (length != 0) {
+						let s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
+						bone.a *= s;
+						bone.c *= s;
+					}
+				}
+				boneX = x;
+				boneY = y;
+				if (mixRotate > 0) {
+					let a = bone.a, b = bone.b, c = bone.c, d = bone.d, r = 0, cos = 0, sin = 0;
+					if (tangents)
+						r = positions[p - 1];
+					else if (spaces[i + 1] == 0)
+						r = positions[p + 2];
+					else
+						r = Math.atan2(dy, dx);
+					r -= Math.atan2(c, a);
+					if (tip) {
+						cos = Math.cos(r);
+						sin = Math.sin(r);
+						let length = bone.data.length;
+						boneX += (length * (cos * a - sin * c) - dx) * mixRotate;
+						boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
+					} else {
+						r += offsetRotation;
+					}
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) //
+						r += MathUtils.PI2;
+					r *= mixRotate;
+					cos = Math.cos(r);
+					sin = Math.sin(r);
+					bone.a = cos * a - sin * c;
+					bone.b = cos * b - sin * d;
+					bone.c = sin * a + cos * c;
+					bone.d = sin * b + cos * d;
+				}
+				bone.updateAppliedTransform();
+			}
+		}
+
+		computeWorldPositions (path: PathAttachment, spacesCount: number, tangents: boolean) {
+			let target = this.target;
+			let position = this.position;
+			let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array<number> = null;
+			let closed = path.closed;
+			let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PathConstraint.NONE;
+
+			if (!path.constantSpeed) {
+				let lengths = path.lengths;
+				curveCount -= closed ? 1 : 2;
+				let pathLength = lengths[curveCount];
+				if (this.data.positionMode == PositionMode.Percent) position *= pathLength;
+
+				let multiplier;
+				switch (this.data.spacingMode) {
+					case SpacingMode.Percent:
+						multiplier = pathLength;
+						break;
+					case SpacingMode.Proportional:
+						multiplier = pathLength / spacesCount;
+						break;
+					default:
+						multiplier = 1;
+				}
+				world = Utils.setArraySize(this.world, 8);
+				for (let i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
+					let space = spaces[i] * multiplier;
+					position += space;
+					let p = position;
+
+					if (closed) {
+						p %= pathLength;
+						if (p < 0) p += pathLength;
+						curve = 0;
+					} else if (p < 0) {
+						if (prevCurve != PathConstraint.BEFORE) {
+							prevCurve = PathConstraint.BEFORE;
+							path.computeWorldVertices(target, 2, 4, world, 0, 2);
+						}
+						this.addBeforePosition(p, world, 0, out, o);
+						continue;
+					} else if (p > pathLength) {
+						if (prevCurve != PathConstraint.AFTER) {
+							prevCurve = PathConstraint.AFTER;
+							path.computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2);
+						}
+						this.addAfterPosition(p - pathLength, world, 0, out, o);
+						continue;
+					}
+
+					// Determine curve containing position.
+					for (; ; curve++) {
+						let length = lengths[curve];
+						if (p > length) continue;
+						if (curve == 0)
+							p /= length;
+						else {
+							let prev = lengths[curve - 1];
+							p = (p - prev) / (length - prev);
+						}
+						break;
+					}
+					if (curve != prevCurve) {
+						prevCurve = curve;
+						if (closed && curve == curveCount) {
+							path.computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2);
+							path.computeWorldVertices(target, 0, 4, world, 4, 2);
+						} else
+							path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2);
+					}
+					this.addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
+						tangents || (i > 0 && space == 0));
+				}
+				return out;
+			}
+
+			// World vertices.
+			if (closed) {
+				verticesLength += 2;
+				world = Utils.setArraySize(this.world, verticesLength);
+				path.computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2);
+				path.computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2);
+				world[verticesLength - 2] = world[0];
+				world[verticesLength - 1] = world[1];
+			} else {
+				curveCount--;
+				verticesLength -= 4;
+				world = Utils.setArraySize(this.world, verticesLength);
+				path.computeWorldVertices(target, 2, verticesLength, world, 0, 2);
+			}
+
+			// Curve lengths.
+			let curves = Utils.setArraySize(this.curves, curveCount);
+			let pathLength = 0;
+			let x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
+			let tmpx = 0, tmpy = 0, dddfx = 0, dddfy = 0, ddfx = 0, ddfy = 0, dfx = 0, dfy = 0;
+			for (let i = 0, w = 2; i < curveCount; i++, w += 6) {
+				cx1 = world[w];
+				cy1 = world[w + 1];
+				cx2 = world[w + 2];
+				cy2 = world[w + 3];
+				x2 = world[w + 4];
+				y2 = world[w + 5];
+				tmpx = (x1 - cx1 * 2 + cx2) * 0.1875;
+				tmpy = (y1 - cy1 * 2 + cy2) * 0.1875;
+				dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375;
+				dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375;
+				ddfx = tmpx * 2 + dddfx;
+				ddfy = tmpy * 2 + dddfy;
+				dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667;
+				dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667;
+				pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
+				dfx += ddfx;
+				dfy += ddfy;
+				ddfx += dddfx;
+				ddfy += dddfy;
+				pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
+				dfx += ddfx;
+				dfy += ddfy;
+				pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
+				dfx += ddfx + dddfx;
+				dfy += ddfy + dddfy;
+				pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
+				curves[i] = pathLength;
+				x1 = x2;
+				y1 = y2;
+			}
+
+			if (this.data.positionMode == PositionMode.Percent) position *= pathLength;
+
+			let multiplier;
+			switch (this.data.spacingMode) {
+				case SpacingMode.Percent:
+					multiplier = pathLength;
+					break;
+				case SpacingMode.Proportional:
+					multiplier = pathLength / spacesCount;
+					break;
+				default:
+					multiplier = 1;
+			}
+
+			let segments = this.segments;
+			let curveLength = 0;
+			for (let i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
+				let space = spaces[i] * multiplier;
+				position += space;
+				let p = position;
+
+				if (closed) {
+					p %= pathLength;
+					if (p < 0) p += pathLength;
+					curve = 0;
+				} else if (p < 0) {
+					this.addBeforePosition(p, world, 0, out, o);
+					continue;
+				} else if (p > pathLength) {
+					this.addAfterPosition(p - pathLength, world, verticesLength - 4, out, o);
+					continue;
+				}
+
+				// Determine curve containing position.
+				for (; ; curve++) {
+					let length = curves[curve];
+					if (p > length) continue;
+					if (curve == 0)
+						p /= length;
+					else {
+						let prev = curves[curve - 1];
+						p = (p - prev) / (length - prev);
+					}
+					break;
+				}
+
+				// Curve segment lengths.
+				if (curve != prevCurve) {
+					prevCurve = curve;
+					let ii = curve * 6;
+					x1 = world[ii];
+					y1 = world[ii + 1];
+					cx1 = world[ii + 2];
+					cy1 = world[ii + 3];
+					cx2 = world[ii + 4];
+					cy2 = world[ii + 5];
+					x2 = world[ii + 6];
+					y2 = world[ii + 7];
+					tmpx = (x1 - cx1 * 2 + cx2) * 0.03;
+					tmpy = (y1 - cy1 * 2 + cy2) * 0.03;
+					dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006;
+					dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006;
+					ddfx = tmpx * 2 + dddfx;
+					ddfy = tmpy * 2 + dddfy;
+					dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667;
+					dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667;
+					curveLength = Math.sqrt(dfx * dfx + dfy * dfy);
+					segments[0] = curveLength;
+					for (ii = 1; ii < 8; ii++) {
+						dfx += ddfx;
+						dfy += ddfy;
+						ddfx += dddfx;
+						ddfy += dddfy;
+						curveLength += Math.sqrt(dfx * dfx + dfy * dfy);
+						segments[ii] = curveLength;
+					}
+					dfx += ddfx;
+					dfy += ddfy;
+					curveLength += Math.sqrt(dfx * dfx + dfy * dfy);
+					segments[8] = curveLength;
+					dfx += ddfx + dddfx;
+					dfy += ddfy + dddfy;
+					curveLength += Math.sqrt(dfx * dfx + dfy * dfy);
+					segments[9] = curveLength;
+					segment = 0;
+				}
+
+				// Weight by segment length.
+				p *= curveLength;
+				for (; ; segment++) {
+					let length = segments[segment];
+					if (p > length) continue;
+					if (segment == 0)
+						p /= length;
+					else {
+						let prev = segments[segment - 1];
+						p = segment + (p - prev) / (length - prev);
+					}
+					break;
+				}
+				this.addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0));
+			}
+			return out;
+		}
+
+		addBeforePosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) {
+			let x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = Math.atan2(dy, dx);
+			out[o] = x1 + p * Math.cos(r);
+			out[o + 1] = y1 + p * Math.sin(r);
+			out[o + 2] = r;
+		}
+
+		addAfterPosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) {
+			let x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = Math.atan2(dy, dx);
+			out[o] = x1 + p * Math.cos(r);
+			out[o + 1] = y1 + p * Math.sin(r);
+			out[o + 2] = r;
+		}
+
+		addCurvePosition (p: number, x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number,
+			out: Array<number>, o: number, tangents: boolean) {
+			if (p == 0 || isNaN(p)) {
+				out[o] = x1;
+				out[o + 1] = y1;
+				out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
+				return;
+			}
+			let tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
+			let ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
+			let x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
+			out[o] = x;
+			out[o + 1] = y;
+			if (tangents) {
+				if (p < 0.001)
+					out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
+				else
+					out[o + 2] = Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
+			}
+		}
+	}
+}

+ 84 - 84
spine-ts/core/src/PathConstraintData.ts

@@ -1,84 +1,84 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the setup pose for a {@link PathConstraint}.
-	 *
-	 * See [path constraints](http://esotericsoftware.com/spine-path-constraints) in the Spine User Guide. */
-	export class PathConstraintData extends ConstraintData {
-
-		/** The bones that will be modified by this path constraint. */
-		bones = new Array<BoneData>();
-
-		/** The slot whose path attachment will be used to constrained the bones. */
-		target: SlotData;
-
-		/** The mode for positioning the first bone on the path. */
-		positionMode: PositionMode;
-
-		/** The mode for positioning the bones after the first bone on the path. */
-		spacingMode: SpacingMode;
-
-		/** The mode for adjusting the rotation of the bones. */
-		rotateMode: RotateMode;
-
-		/** An offset added to the constrained bone rotation. */
-		offsetRotation: number;
-
-		/** The position along the path. */
-		position: number;
-
-		/** The spacing between bones. */
-		spacing: number;
-
-		mixRotate = 0;
-		mixX = 0;
-		mixY = 0;
-
-		constructor (name: string) {
-			super(name, 0, false);
-		}
-	}
-
-	/** Controls how the first bone is positioned along the path.
-	 *
-	 * See [position](http://esotericsoftware.com/spine-path-constraints#Position) in the Spine User Guide. */
-	export enum PositionMode { Fixed, Percent }
-
-	/** Controls how bones after the first bone are positioned along the path.
-	 *
-	 * See [spacing](http://esotericsoftware.com/spine-path-constraints#Spacing) in the Spine User Guide. */
-	export enum SpacingMode { Length, Fixed, Percent, Proportional }
-
-	/** Controls how bones are rotated, translated, and scaled to match the path.
-	 *
-	 * See [rotate mix](http://esotericsoftware.com/spine-path-constraints#Rotate-mix) in the Spine User Guide. */
-	export enum RotateMode { Tangent, Chain, ChainScale }
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the setup pose for a {@link PathConstraint}.
+	 *
+	 * See [path constraints](http://esotericsoftware.com/spine-path-constraints) in the Spine User Guide. */
+	export class PathConstraintData extends ConstraintData {
+
+		/** The bones that will be modified by this path constraint. */
+		bones = new Array<BoneData>();
+
+		/** The slot whose path attachment will be used to constrained the bones. */
+		target: SlotData;
+
+		/** The mode for positioning the first bone on the path. */
+		positionMode: PositionMode;
+
+		/** The mode for positioning the bones after the first bone on the path. */
+		spacingMode: SpacingMode;
+
+		/** The mode for adjusting the rotation of the bones. */
+		rotateMode: RotateMode;
+
+		/** An offset added to the constrained bone rotation. */
+		offsetRotation: number;
+
+		/** The position along the path. */
+		position: number;
+
+		/** The spacing between bones. */
+		spacing: number;
+
+		mixRotate = 0;
+		mixX = 0;
+		mixY = 0;
+
+		constructor (name: string) {
+			super(name, 0, false);
+		}
+	}
+
+	/** Controls how the first bone is positioned along the path.
+	 *
+	 * See [position](http://esotericsoftware.com/spine-path-constraints#Position) in the Spine User Guide. */
+	export enum PositionMode { Fixed, Percent }
+
+	/** Controls how bones after the first bone are positioned along the path.
+	 *
+	 * See [spacing](http://esotericsoftware.com/spine-path-constraints#Spacing) in the Spine User Guide. */
+	export enum SpacingMode { Length, Fixed, Percent, Proportional }
+
+	/** Controls how bones are rotated, translated, and scaled to match the path.
+	 *
+	 * See [rotate mix](http://esotericsoftware.com/spine-path-constraints#Rotate-mix) in the Spine User Guide. */
+	export enum RotateMode { Tangent, Chain, ChainScale }
+}

+ 638 - 638
spine-ts/core/src/Skeleton.ts

@@ -1,638 +1,638 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the current pose for a skeleton.
-	 *
-	 * See [Instance objects](http://esotericsoftware.com/spine-runtime-architecture#Instance-objects) in the Spine Runtimes Guide. */
-	export class Skeleton {
-		/** The skeleton's setup pose data. */
-		data: SkeletonData;
-
-		/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
-		bones: Array<Bone>;
-
-		/** The skeleton's slots. */
-		slots: Array<Slot>;
-
-		/** The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order. */
-		drawOrder: Array<Slot>;
-
-		/** The skeleton's IK constraints. */
-		ikConstraints: Array<IkConstraint>;
-
-		/** The skeleton's transform constraints. */
-		transformConstraints: Array<TransformConstraint>;
-
-		/** The skeleton's path constraints. */
-		pathConstraints: Array<PathConstraint>;
-
-		/** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */
-		_updateCache = new Array<Updatable>();
-
-		/** The skeleton's current skin. May be null. */
-		skin: Skin;
-
-		/** The color to tint all the skeleton's attachments. */
-		color: Color;
-
-		/** Returns the skeleton's time. This can be used for tracking, such as with Slot {@link Slot#attachmentTime}.
-		 * <p>
-		 * See {@link #update()}. */
-		time = 0;
-
-		/** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale
-	 	* inheritance. */
-		scaleX = 1;
-
-		/** Scales the entire skeleton on the Y axis. This affects all bones, even if the bone's transform mode disallows scale
-	 	* inheritance. */
-		scaleY = 1;
-
-		/** Sets the skeleton X position, which is added to the root bone worldX position. */
-		x = 0;
-
-		/** Sets the skeleton Y position, which is added to the root bone worldY position. */
-		y = 0;
-
-		constructor (data: SkeletonData) {
-			if (!data) throw new Error("data cannot be null.");
-			this.data = data;
-
-			this.bones = new Array<Bone>();
-			for (let i = 0; i < data.bones.length; i++) {
-				let boneData = data.bones[i];
-				let bone: Bone;
-				if (!boneData.parent)
-					bone = new Bone(boneData, this, null);
-				else {
-					let parent = this.bones[boneData.parent.index];
-					bone = new Bone(boneData, this, parent);
-					parent.children.push(bone);
-				}
-				this.bones.push(bone);
-			}
-
-			this.slots = new Array<Slot>();
-			this.drawOrder = new Array<Slot>();
-			for (let i = 0; i < data.slots.length; i++) {
-				let slotData = data.slots[i];
-				let bone = this.bones[slotData.boneData.index];
-				let slot = new Slot(slotData, bone);
-				this.slots.push(slot);
-				this.drawOrder.push(slot);
-			}
-
-			this.ikConstraints = new Array<IkConstraint>();
-			for (let i = 0; i < data.ikConstraints.length; i++) {
-				let ikConstraintData = data.ikConstraints[i];
-				this.ikConstraints.push(new IkConstraint(ikConstraintData, this));
-			}
-
-			this.transformConstraints = new Array<TransformConstraint>();
-			for (let i = 0; i < data.transformConstraints.length; i++) {
-				let transformConstraintData = data.transformConstraints[i];
-				this.transformConstraints.push(new TransformConstraint(transformConstraintData, this));
-			}
-
-			this.pathConstraints = new Array<PathConstraint>();
-			for (let i = 0; i < data.pathConstraints.length; i++) {
-				let pathConstraintData = data.pathConstraints[i];
-				this.pathConstraints.push(new PathConstraint(pathConstraintData, this));
-			}
-
-			this.color = new Color(1, 1, 1, 1);
-			this.updateCache();
-		}
-
-		/** Caches information about bones and constraints. Must be called if the {@link #getSkin()} is modified or if bones,
-		 * constraints, or weighted path attachments are added or removed. */
-		updateCache () {
-			let updateCache = this._updateCache;
-			updateCache.length = 0;
-
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++) {
-				let bone = bones[i];
-				bone.sorted = bone.data.skinRequired;
-				bone.active = !bone.sorted;
-			}
-
-			if (this.skin) {
-				let skinBones = this.skin.bones;
-				for (let i = 0, n = this.skin.bones.length; i < n; i++) {
-					let bone = this.bones[skinBones[i].index];
-					do {
-						bone.sorted = false;
-						bone.active = true;
-						bone = bone.parent;
-					} while (bone);
-				}
-			}
-
-			// IK first, lowest hierarchy depth first.
-			let ikConstraints = this.ikConstraints;
-			let transformConstraints = this.transformConstraints;
-			let pathConstraints = this.pathConstraints;
-			let ikCount = ikConstraints.length, transformCount = transformConstraints.length, pathCount = pathConstraints.length;
-			let constraintCount = ikCount + transformCount + pathCount;
-
-			outer:
-			for (let i = 0; i < constraintCount; i++) {
-				for (let ii = 0; ii < ikCount; ii++) {
-					let constraint = ikConstraints[ii];
-					if (constraint.data.order == i) {
-						this.sortIkConstraint(constraint);
-						continue outer;
-					}
-				}
-				for (let ii = 0; ii < transformCount; ii++) {
-					let constraint = transformConstraints[ii];
-					if (constraint.data.order == i) {
-						this.sortTransformConstraint(constraint);
-						continue outer;
-					}
-				}
-				for (let ii = 0; ii < pathCount; ii++) {
-					let constraint = pathConstraints[ii];
-					if (constraint.data.order == i) {
-						this.sortPathConstraint(constraint);
-						continue outer;
-					}
-				}
-			}
-
-			for (let i = 0, n = bones.length; i < n; i++)
-				this.sortBone(bones[i]);
-		}
-
-		sortIkConstraint (constraint: IkConstraint) {
-			constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)));
-			if (!constraint.active) return;
-
-			let target = constraint.target;
-			this.sortBone(target);
-
-			let constrained = constraint.bones;
-			let parent = constrained[0];
-			this.sortBone(parent);
-
-			if (constrained.length == 1) {
-				this._updateCache.push(constraint);
-				this.sortReset(parent.children);
-			} else {
-				let child = constrained[constrained.length - 1];
-				this.sortBone(child);
-
-				this._updateCache.push(constraint);
-
-				this.sortReset(parent.children);
-				child.sorted = true;
-			}
-		}
-
-		sortPathConstraint (constraint: PathConstraint) {
-			constraint.active = constraint.target.bone.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)));
-			if (!constraint.active) return;
-
-			let slot = constraint.target;
-			let slotIndex = slot.data.index;
-			let slotBone = slot.bone;
-			if (this.skin) this.sortPathConstraintAttachment(this.skin, slotIndex, slotBone);
-			if (this.data.defaultSkin && this.data.defaultSkin != this.skin)
-				this.sortPathConstraintAttachment(this.data.defaultSkin, slotIndex, slotBone);
-			for (let i = 0, n = this.data.skins.length; i < n; i++)
-				this.sortPathConstraintAttachment(this.data.skins[i], slotIndex, slotBone);
-
-			let attachment = slot.getAttachment();
-			if (attachment instanceof PathAttachment) this.sortPathConstraintAttachmentWith(attachment, slotBone);
-
-			let constrained = constraint.bones;
-			let boneCount = constrained.length;
-			for (let i = 0; i < boneCount; i++)
-				this.sortBone(constrained[i]);
-
-			this._updateCache.push(constraint);
-
-			for (let i = 0; i < boneCount; i++)
-				this.sortReset(constrained[i].children);
-			for (let i = 0; i < boneCount; i++)
-				constrained[i].sorted = true;
-		}
-
-		sortTransformConstraint (constraint: TransformConstraint) {
-			constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)));
-			if (!constraint.active) return;
-
-			this.sortBone(constraint.target);
-
-			let constrained = constraint.bones;
-			let boneCount = constrained.length;
-			if (constraint.data.local) {
-				for (let i = 0; i < boneCount; i++) {
-					let child = constrained[i];
-					this.sortBone(child.parent);
-					this.sortBone(child);
-				}
-			} else {
-				for (let i = 0; i < boneCount; i++) {
-					this.sortBone(constrained[i]);
-				}
-			}
-
-			this._updateCache.push(constraint);
-
-			for (let i = 0; i < boneCount; i++)
-				this.sortReset(constrained[i].children);
-			for (let i = 0; i < boneCount; i++)
-				constrained[i].sorted = true;
-		}
-
-		sortPathConstraintAttachment (skin: Skin, slotIndex: number, slotBone: Bone) {
-			let attachments = skin.attachments[slotIndex];
-			if (!attachments) return;
-			for (let key in attachments) {
-				this.sortPathConstraintAttachmentWith(attachments[key], slotBone);
-			}
-		}
-
-		sortPathConstraintAttachmentWith (attachment: Attachment, slotBone: Bone) {
-			if (!(attachment instanceof PathAttachment)) return;
-			let pathBones = (<PathAttachment>attachment).bones;
-			if (!pathBones)
-				this.sortBone(slotBone);
-			else {
-				let bones = this.bones;
-				for (let i = 0, n = pathBones.length; i < n;) {
-					let nn = pathBones[i++];
-					nn += i;
-					while (i < nn)
-						this.sortBone(bones[pathBones[i++]]);
-				}
-			}
-		}
-
-		sortBone (bone: Bone) {
-			if (bone.sorted) return;
-			let parent = bone.parent;
-			if (parent) this.sortBone(parent);
-			bone.sorted = true;
-			this._updateCache.push(bone);
-		}
-
-		sortReset (bones: Array<Bone>) {
-			for (let i = 0, n = bones.length; i < n; i++) {
-				let bone = bones[i];
-				if (!bone.active) continue;
-				if (bone.sorted) this.sortReset(bone.children);
-				bone.sorted = false;
-			}
-		}
-
-		/** Updates the world transform for each bone and applies all constraints.
-		 *
-		 * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
-		 * Runtimes Guide. */
-		updateWorldTransform () {
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++) {
-				let bone = bones[i];
-				bone.ax = bone.x;
-				bone.ay = bone.y;
-				bone.arotation = bone.rotation;
-				bone.ascaleX = bone.scaleX;
-				bone.ascaleY = bone.scaleY;
-				bone.ashearX = bone.shearX;
-				bone.ashearY = bone.shearY;
-			}
-
-			let updateCache = this._updateCache;
-			for (let i = 0, n = updateCache.length; i < n; i++)
-				updateCache[i].update();
-		}
-
-		updateWorldTransformWith (parent: Bone) {
-			// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
-			let rootBone = this.getRootBone();
-			let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
-			rootBone.worldX = pa * this.x + pb * this.y + parent.worldX;
-			rootBone.worldY = pc * this.x + pd * this.y + parent.worldY;
-
-			let rotationY = rootBone.rotation + 90 + rootBone.shearY;
-			let la = MathUtils.cosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
-			let lb = MathUtils.cosDeg(rotationY) * rootBone.scaleY;
-			let lc = MathUtils.sinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
-			let ld = MathUtils.sinDeg(rotationY) * rootBone.scaleY;
-			rootBone.a = (pa * la + pb * lc) * this.scaleX;
-			rootBone.b = (pa * lb + pb * ld) * this.scaleX;
-			rootBone.c = (pc * la + pd * lc) * this.scaleY;
-			rootBone.d = (pc * lb + pd * ld) * this.scaleY;
-
-			// Update everything except root bone.
-			let updateCache = this._updateCache;
-			for (let i = 0, n = updateCache.length; i < n; i++) {
-				let updatable = updateCache[i];
-				if (updatable != rootBone) updatable.update();
-			}
-		}
-
-		/** Sets the bones, constraints, and slots to their setup pose values. */
-		setToSetupPose () {
-			this.setBonesToSetupPose();
-			this.setSlotsToSetupPose();
-		}
-
-		/** Sets the bones and constraints to their setup pose values. */
-		setBonesToSetupPose () {
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++)
-				bones[i].setToSetupPose();
-
-			let ikConstraints = this.ikConstraints;
-			for (let i = 0, n = ikConstraints.length; i < n; i++) {
-				let constraint = ikConstraints[i];
-				constraint.mix = constraint.data.mix;
-				constraint.softness = constraint.data.softness;
-				constraint.bendDirection = constraint.data.bendDirection;
-				constraint.compress = constraint.data.compress;
-				constraint.stretch = constraint.data.stretch;
-			}
-
-			let transformConstraints = this.transformConstraints;
-			for (let i = 0, n = transformConstraints.length; i < n; i++) {
-				let constraint = transformConstraints[i];
-				let data = constraint.data;
-				constraint.mixRotate = data.mixRotate;
-				constraint.mixX = data.mixX;
-				constraint.mixY = data.mixY;
-				constraint.mixScaleX = data.mixScaleX;
-				constraint.mixScaleY = data.mixScaleY;
-				constraint.mixShearY = data.mixShearY;
-			}
-
-			let pathConstraints = this.pathConstraints;
-			for (let i = 0, n = pathConstraints.length; i < n; i++) {
-				let constraint = pathConstraints[i];
-				let data = constraint.data;
-				constraint.position = data.position;
-				constraint.spacing = data.spacing;
-				constraint.mixRotate = data.mixRotate;
-				constraint.mixX = data.mixX;
-				constraint.mixY = data.mixY;
-			}
-		}
-
-		/** Sets the slots and draw order to their setup pose values. */
-		setSlotsToSetupPose () {
-			let slots = this.slots;
-			Utils.arrayCopy(slots, 0, this.drawOrder, 0, slots.length);
-			for (let i = 0, n = slots.length; i < n; i++)
-				slots[i].setToSetupPose();
-		}
-
-		/** @returns May return null. */
-		getRootBone () {
-			if (this.bones.length == 0) return null;
-			return this.bones[0];
-		}
-
-		/** @returns May be null. */
-		findBone (boneName: string) {
-			if (!boneName) throw new Error("boneName cannot be null.");
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++) {
-				let bone = bones[i];
-				if (bone.data.name == boneName) return bone;
-			}
-			return null;
-		}
-
-		/** @returns -1 if the bone was not found. */
-		findBoneIndex (boneName: string) {
-			if (!boneName) throw new Error("boneName cannot be null.");
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++)
-				if (bones[i].data.name == boneName) return i;
-			return -1;
-		}
-
-		/** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
-		 * repeatedly.
-		 * @returns May be null. */
-		findSlot (slotName: string) {
-			if (!slotName) throw new Error("slotName cannot be null.");
-			let slots = this.slots;
-			for (let i = 0, n = slots.length; i < n; i++) {
-				let slot = slots[i];
-				if (slot.data.name == slotName) return slot;
-			}
-			return null;
-		}
-
-		/** @returns -1 if the bone was not found. */
-		findSlotIndex (slotName: string) {
-			if (!slotName) throw new Error("slotName cannot be null.");
-			let slots = this.slots;
-			for (let i = 0, n = slots.length; i < n; i++)
-				if (slots[i].data.name == slotName) return i;
-			return -1;
-		}
-
-		/** Sets a skin by name.
-		 *
-		 * See {@link #setSkin()}. */
-		setSkinByName (skinName: string) {
-			let skin = this.data.findSkin(skinName);
-			if (!skin) throw new Error("Skin not found: " + skinName);
-			this.setSkin(skin);
-		}
-
-		/** Sets the skin used to look up attachments before looking in the {@link SkeletonData#defaultSkin default skin}. If the
-		 * skin is changed, {@link #updateCache()} is called.
-		 *
-		 * Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was no
-		 * old skin, each slot's setup mode attachment is attached from the new skin.
-		 *
-		 * After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling
-		 * {@link #setSlotsToSetupPose()}. Also, often {@link AnimationState#apply()} is called before the next time the
-		 * skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin.
-		 * @param newSkin May be null. */
-		setSkin (newSkin: Skin) {
-			if (newSkin == this.skin) return;
-			if (newSkin) {
-				if (this.skin)
-					newSkin.attachAll(this, this.skin);
-				else {
-					let slots = this.slots;
-					for (let i = 0, n = slots.length; i < n; i++) {
-						let slot = slots[i];
-						let name = slot.data.attachmentName;
-						if (name) {
-							let attachment: Attachment = newSkin.getAttachment(i, name);
-							if (attachment) slot.setAttachment(attachment);
-						}
-					}
-				}
-			}
-			this.skin = newSkin;
-			this.updateCache();
-		}
-
-
-		/** Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment
-		 * name.
-		 *
-		 * See {@link #getAttachment()}.
-		 * @returns May be null. */
-		getAttachmentByName (slotName: string, attachmentName: string): Attachment {
-			return this.getAttachment(this.data.findSlotIndex(slotName), attachmentName);
-		}
-
-		/** Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot index and
-		 * attachment name. First the skin is checked and if the attachment was not found, the default skin is checked.
-		 *
-		 * See [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide.
-		 * @returns May be null. */
-		getAttachment (slotIndex: number, attachmentName: string): Attachment {
-			if (!attachmentName) throw new Error("attachmentName cannot be null.");
-			if (this.skin) {
-				let attachment: Attachment = this.skin.getAttachment(slotIndex, attachmentName);
-				if (attachment) return attachment;
-			}
-			if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName);
-			return null;
-		}
-
-		/** A convenience method to set an attachment by finding the slot with {@link #findSlot()}, finding the attachment with
-		 * {@link #getAttachment()}, then setting the slot's {@link Slot#attachment}.
-		 * @param attachmentName May be null to clear the slot's attachment. */
-		setAttachment (slotName: string, attachmentName: string) {
-			if (!slotName) throw new Error("slotName cannot be null.");
-			let slots = this.slots;
-			for (let i = 0, n = slots.length; i < n; i++) {
-				let slot = slots[i];
-				if (slot.data.name == slotName) {
-					let attachment: Attachment = null;
-					if (attachmentName) {
-						attachment = this.getAttachment(i, attachmentName);
-						if (!attachment) throw new Error("Attachment not found: " + attachmentName + ", for slot: " + slotName);
-					}
-					slot.setAttachment(attachment);
-					return;
-				}
-			}
-			throw new Error("Slot not found: " + slotName);
-		}
-
-
-		/** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
-		 * than to call it repeatedly.
-		 * @return May be null. */
-		findIkConstraint (constraintName: string) {
-			if (!constraintName) throw new Error("constraintName cannot be null.");
-			let ikConstraints = this.ikConstraints;
-			for (let i = 0, n = ikConstraints.length; i < n; i++) {
-				let ikConstraint = ikConstraints[i];
-				if (ikConstraint.data.name == constraintName) return ikConstraint;
-			}
-			return null;
-		}
-
-		/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
-		 * this method than to call it repeatedly.
-		 * @return May be null. */
-		findTransformConstraint (constraintName: string) {
-			if (!constraintName) throw new Error("constraintName cannot be null.");
-			let transformConstraints = this.transformConstraints;
-			for (let i = 0, n = transformConstraints.length; i < n; i++) {
-				let constraint = transformConstraints[i];
-				if (constraint.data.name == constraintName) return constraint;
-			}
-			return null;
-		}
-
-		/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
-		 * than to call it repeatedly.
-		 * @return May be null. */
-		findPathConstraint (constraintName: string) {
-			if (!constraintName) throw new Error("constraintName cannot be null.");
-			let pathConstraints = this.pathConstraints;
-			for (let i = 0, n = pathConstraints.length; i < n; i++) {
-				let constraint = pathConstraints[i];
-				if (constraint.data.name == constraintName) return constraint;
-			}
-			return null;
-		}
-
-		/** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose.
-		 * @param offset An output value, the distance from the skeleton origin to the bottom left corner of the AABB.
-		 * @param size An output value, the width and height of the AABB.
-		 * @param temp Working memory to temporarily store attachments' computed world vertices. */
-		getBounds (offset: Vector2, size: Vector2, temp: Array<number> = new Array<number>(2)) {
-			if (!offset) throw new Error("offset cannot be null.");
-			if (!size) throw new Error("size cannot be null.");
-			let drawOrder = this.drawOrder;
-			let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
-			for (let i = 0, n = drawOrder.length; i < n; i++) {
-				let slot = drawOrder[i];
-				if (!slot.bone.active) continue;
-				let verticesLength = 0;
-				let vertices: ArrayLike<number> = null;
-				let attachment = slot.getAttachment();
-				if (attachment instanceof RegionAttachment) {
-					verticesLength = 8;
-					vertices = Utils.setArraySize(temp, verticesLength, 0);
-					(<RegionAttachment>attachment).computeWorldVertices(slot.bone, vertices, 0, 2);
-				} else if (attachment instanceof MeshAttachment) {
-					let mesh = (<MeshAttachment>attachment);
-					verticesLength = mesh.worldVerticesLength;
-					vertices = Utils.setArraySize(temp, verticesLength, 0);
-					mesh.computeWorldVertices(slot, 0, verticesLength, vertices, 0, 2);
-				}
-				if (vertices) {
-					for (let ii = 0, nn = vertices.length; ii < nn; ii += 2) {
-						let x = vertices[ii], y = vertices[ii + 1];
-						minX = Math.min(minX, x);
-						minY = Math.min(minY, y);
-						maxX = Math.max(maxX, x);
-						maxY = Math.max(maxY, y);
-					}
-				}
-			}
-			offset.set(minX, minY);
-			size.set(maxX - minX, maxY - minY);
-		}
-
-		/** Increments the skeleton's {@link #time}. */
-		update (delta: number) {
-			this.time += delta;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the current pose for a skeleton.
+	 *
+	 * See [Instance objects](http://esotericsoftware.com/spine-runtime-architecture#Instance-objects) in the Spine Runtimes Guide. */
+	export class Skeleton {
+		/** The skeleton's setup pose data. */
+		data: SkeletonData;
+
+		/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
+		bones: Array<Bone>;
+
+		/** The skeleton's slots. */
+		slots: Array<Slot>;
+
+		/** The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order. */
+		drawOrder: Array<Slot>;
+
+		/** The skeleton's IK constraints. */
+		ikConstraints: Array<IkConstraint>;
+
+		/** The skeleton's transform constraints. */
+		transformConstraints: Array<TransformConstraint>;
+
+		/** The skeleton's path constraints. */
+		pathConstraints: Array<PathConstraint>;
+
+		/** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */
+		_updateCache = new Array<Updatable>();
+
+		/** The skeleton's current skin. May be null. */
+		skin: Skin;
+
+		/** The color to tint all the skeleton's attachments. */
+		color: Color;
+
+		/** Returns the skeleton's time. This can be used for tracking, such as with Slot {@link Slot#attachmentTime}.
+		 * <p>
+		 * See {@link #update()}. */
+		time = 0;
+
+		/** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale
+	 	* inheritance. */
+		scaleX = 1;
+
+		/** Scales the entire skeleton on the Y axis. This affects all bones, even if the bone's transform mode disallows scale
+	 	* inheritance. */
+		scaleY = 1;
+
+		/** Sets the skeleton X position, which is added to the root bone worldX position. */
+		x = 0;
+
+		/** Sets the skeleton Y position, which is added to the root bone worldY position. */
+		y = 0;
+
+		constructor (data: SkeletonData) {
+			if (!data) throw new Error("data cannot be null.");
+			this.data = data;
+
+			this.bones = new Array<Bone>();
+			for (let i = 0; i < data.bones.length; i++) {
+				let boneData = data.bones[i];
+				let bone: Bone;
+				if (!boneData.parent)
+					bone = new Bone(boneData, this, null);
+				else {
+					let parent = this.bones[boneData.parent.index];
+					bone = new Bone(boneData, this, parent);
+					parent.children.push(bone);
+				}
+				this.bones.push(bone);
+			}
+
+			this.slots = new Array<Slot>();
+			this.drawOrder = new Array<Slot>();
+			for (let i = 0; i < data.slots.length; i++) {
+				let slotData = data.slots[i];
+				let bone = this.bones[slotData.boneData.index];
+				let slot = new Slot(slotData, bone);
+				this.slots.push(slot);
+				this.drawOrder.push(slot);
+			}
+
+			this.ikConstraints = new Array<IkConstraint>();
+			for (let i = 0; i < data.ikConstraints.length; i++) {
+				let ikConstraintData = data.ikConstraints[i];
+				this.ikConstraints.push(new IkConstraint(ikConstraintData, this));
+			}
+
+			this.transformConstraints = new Array<TransformConstraint>();
+			for (let i = 0; i < data.transformConstraints.length; i++) {
+				let transformConstraintData = data.transformConstraints[i];
+				this.transformConstraints.push(new TransformConstraint(transformConstraintData, this));
+			}
+
+			this.pathConstraints = new Array<PathConstraint>();
+			for (let i = 0; i < data.pathConstraints.length; i++) {
+				let pathConstraintData = data.pathConstraints[i];
+				this.pathConstraints.push(new PathConstraint(pathConstraintData, this));
+			}
+
+			this.color = new Color(1, 1, 1, 1);
+			this.updateCache();
+		}
+
+		/** Caches information about bones and constraints. Must be called if the {@link #getSkin()} is modified or if bones,
+		 * constraints, or weighted path attachments are added or removed. */
+		updateCache () {
+			let updateCache = this._updateCache;
+			updateCache.length = 0;
+
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++) {
+				let bone = bones[i];
+				bone.sorted = bone.data.skinRequired;
+				bone.active = !bone.sorted;
+			}
+
+			if (this.skin) {
+				let skinBones = this.skin.bones;
+				for (let i = 0, n = this.skin.bones.length; i < n; i++) {
+					let bone = this.bones[skinBones[i].index];
+					do {
+						bone.sorted = false;
+						bone.active = true;
+						bone = bone.parent;
+					} while (bone);
+				}
+			}
+
+			// IK first, lowest hierarchy depth first.
+			let ikConstraints = this.ikConstraints;
+			let transformConstraints = this.transformConstraints;
+			let pathConstraints = this.pathConstraints;
+			let ikCount = ikConstraints.length, transformCount = transformConstraints.length, pathCount = pathConstraints.length;
+			let constraintCount = ikCount + transformCount + pathCount;
+
+			outer:
+			for (let i = 0; i < constraintCount; i++) {
+				for (let ii = 0; ii < ikCount; ii++) {
+					let constraint = ikConstraints[ii];
+					if (constraint.data.order == i) {
+						this.sortIkConstraint(constraint);
+						continue outer;
+					}
+				}
+				for (let ii = 0; ii < transformCount; ii++) {
+					let constraint = transformConstraints[ii];
+					if (constraint.data.order == i) {
+						this.sortTransformConstraint(constraint);
+						continue outer;
+					}
+				}
+				for (let ii = 0; ii < pathCount; ii++) {
+					let constraint = pathConstraints[ii];
+					if (constraint.data.order == i) {
+						this.sortPathConstraint(constraint);
+						continue outer;
+					}
+				}
+			}
+
+			for (let i = 0, n = bones.length; i < n; i++)
+				this.sortBone(bones[i]);
+		}
+
+		sortIkConstraint (constraint: IkConstraint) {
+			constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)));
+			if (!constraint.active) return;
+
+			let target = constraint.target;
+			this.sortBone(target);
+
+			let constrained = constraint.bones;
+			let parent = constrained[0];
+			this.sortBone(parent);
+
+			if (constrained.length == 1) {
+				this._updateCache.push(constraint);
+				this.sortReset(parent.children);
+			} else {
+				let child = constrained[constrained.length - 1];
+				this.sortBone(child);
+
+				this._updateCache.push(constraint);
+
+				this.sortReset(parent.children);
+				child.sorted = true;
+			}
+		}
+
+		sortPathConstraint (constraint: PathConstraint) {
+			constraint.active = constraint.target.bone.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)));
+			if (!constraint.active) return;
+
+			let slot = constraint.target;
+			let slotIndex = slot.data.index;
+			let slotBone = slot.bone;
+			if (this.skin) this.sortPathConstraintAttachment(this.skin, slotIndex, slotBone);
+			if (this.data.defaultSkin && this.data.defaultSkin != this.skin)
+				this.sortPathConstraintAttachment(this.data.defaultSkin, slotIndex, slotBone);
+			for (let i = 0, n = this.data.skins.length; i < n; i++)
+				this.sortPathConstraintAttachment(this.data.skins[i], slotIndex, slotBone);
+
+			let attachment = slot.getAttachment();
+			if (attachment instanceof PathAttachment) this.sortPathConstraintAttachmentWith(attachment, slotBone);
+
+			let constrained = constraint.bones;
+			let boneCount = constrained.length;
+			for (let i = 0; i < boneCount; i++)
+				this.sortBone(constrained[i]);
+
+			this._updateCache.push(constraint);
+
+			for (let i = 0; i < boneCount; i++)
+				this.sortReset(constrained[i].children);
+			for (let i = 0; i < boneCount; i++)
+				constrained[i].sorted = true;
+		}
+
+		sortTransformConstraint (constraint: TransformConstraint) {
+			constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)));
+			if (!constraint.active) return;
+
+			this.sortBone(constraint.target);
+
+			let constrained = constraint.bones;
+			let boneCount = constrained.length;
+			if (constraint.data.local) {
+				for (let i = 0; i < boneCount; i++) {
+					let child = constrained[i];
+					this.sortBone(child.parent);
+					this.sortBone(child);
+				}
+			} else {
+				for (let i = 0; i < boneCount; i++) {
+					this.sortBone(constrained[i]);
+				}
+			}
+
+			this._updateCache.push(constraint);
+
+			for (let i = 0; i < boneCount; i++)
+				this.sortReset(constrained[i].children);
+			for (let i = 0; i < boneCount; i++)
+				constrained[i].sorted = true;
+		}
+
+		sortPathConstraintAttachment (skin: Skin, slotIndex: number, slotBone: Bone) {
+			let attachments = skin.attachments[slotIndex];
+			if (!attachments) return;
+			for (let key in attachments) {
+				this.sortPathConstraintAttachmentWith(attachments[key], slotBone);
+			}
+		}
+
+		sortPathConstraintAttachmentWith (attachment: Attachment, slotBone: Bone) {
+			if (!(attachment instanceof PathAttachment)) return;
+			let pathBones = (<PathAttachment>attachment).bones;
+			if (!pathBones)
+				this.sortBone(slotBone);
+			else {
+				let bones = this.bones;
+				for (let i = 0, n = pathBones.length; i < n;) {
+					let nn = pathBones[i++];
+					nn += i;
+					while (i < nn)
+						this.sortBone(bones[pathBones[i++]]);
+				}
+			}
+		}
+
+		sortBone (bone: Bone) {
+			if (bone.sorted) return;
+			let parent = bone.parent;
+			if (parent) this.sortBone(parent);
+			bone.sorted = true;
+			this._updateCache.push(bone);
+		}
+
+		sortReset (bones: Array<Bone>) {
+			for (let i = 0, n = bones.length; i < n; i++) {
+				let bone = bones[i];
+				if (!bone.active) continue;
+				if (bone.sorted) this.sortReset(bone.children);
+				bone.sorted = false;
+			}
+		}
+
+		/** Updates the world transform for each bone and applies all constraints.
+		 *
+		 * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
+		 * Runtimes Guide. */
+		updateWorldTransform () {
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++) {
+				let bone = bones[i];
+				bone.ax = bone.x;
+				bone.ay = bone.y;
+				bone.arotation = bone.rotation;
+				bone.ascaleX = bone.scaleX;
+				bone.ascaleY = bone.scaleY;
+				bone.ashearX = bone.shearX;
+				bone.ashearY = bone.shearY;
+			}
+
+			let updateCache = this._updateCache;
+			for (let i = 0, n = updateCache.length; i < n; i++)
+				updateCache[i].update();
+		}
+
+		updateWorldTransformWith (parent: Bone) {
+			// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
+			let rootBone = this.getRootBone();
+			let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+			rootBone.worldX = pa * this.x + pb * this.y + parent.worldX;
+			rootBone.worldY = pc * this.x + pd * this.y + parent.worldY;
+
+			let rotationY = rootBone.rotation + 90 + rootBone.shearY;
+			let la = MathUtils.cosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
+			let lb = MathUtils.cosDeg(rotationY) * rootBone.scaleY;
+			let lc = MathUtils.sinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
+			let ld = MathUtils.sinDeg(rotationY) * rootBone.scaleY;
+			rootBone.a = (pa * la + pb * lc) * this.scaleX;
+			rootBone.b = (pa * lb + pb * ld) * this.scaleX;
+			rootBone.c = (pc * la + pd * lc) * this.scaleY;
+			rootBone.d = (pc * lb + pd * ld) * this.scaleY;
+
+			// Update everything except root bone.
+			let updateCache = this._updateCache;
+			for (let i = 0, n = updateCache.length; i < n; i++) {
+				let updatable = updateCache[i];
+				if (updatable != rootBone) updatable.update();
+			}
+		}
+
+		/** Sets the bones, constraints, and slots to their setup pose values. */
+		setToSetupPose () {
+			this.setBonesToSetupPose();
+			this.setSlotsToSetupPose();
+		}
+
+		/** Sets the bones and constraints to their setup pose values. */
+		setBonesToSetupPose () {
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++)
+				bones[i].setToSetupPose();
+
+			let ikConstraints = this.ikConstraints;
+			for (let i = 0, n = ikConstraints.length; i < n; i++) {
+				let constraint = ikConstraints[i];
+				constraint.mix = constraint.data.mix;
+				constraint.softness = constraint.data.softness;
+				constraint.bendDirection = constraint.data.bendDirection;
+				constraint.compress = constraint.data.compress;
+				constraint.stretch = constraint.data.stretch;
+			}
+
+			let transformConstraints = this.transformConstraints;
+			for (let i = 0, n = transformConstraints.length; i < n; i++) {
+				let constraint = transformConstraints[i];
+				let data = constraint.data;
+				constraint.mixRotate = data.mixRotate;
+				constraint.mixX = data.mixX;
+				constraint.mixY = data.mixY;
+				constraint.mixScaleX = data.mixScaleX;
+				constraint.mixScaleY = data.mixScaleY;
+				constraint.mixShearY = data.mixShearY;
+			}
+
+			let pathConstraints = this.pathConstraints;
+			for (let i = 0, n = pathConstraints.length; i < n; i++) {
+				let constraint = pathConstraints[i];
+				let data = constraint.data;
+				constraint.position = data.position;
+				constraint.spacing = data.spacing;
+				constraint.mixRotate = data.mixRotate;
+				constraint.mixX = data.mixX;
+				constraint.mixY = data.mixY;
+			}
+		}
+
+		/** Sets the slots and draw order to their setup pose values. */
+		setSlotsToSetupPose () {
+			let slots = this.slots;
+			Utils.arrayCopy(slots, 0, this.drawOrder, 0, slots.length);
+			for (let i = 0, n = slots.length; i < n; i++)
+				slots[i].setToSetupPose();
+		}
+
+		/** @returns May return null. */
+		getRootBone () {
+			if (this.bones.length == 0) return null;
+			return this.bones[0];
+		}
+
+		/** @returns May be null. */
+		findBone (boneName: string) {
+			if (!boneName) throw new Error("boneName cannot be null.");
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++) {
+				let bone = bones[i];
+				if (bone.data.name == boneName) return bone;
+			}
+			return null;
+		}
+
+		/** @returns -1 if the bone was not found. */
+		findBoneIndex (boneName: string) {
+			if (!boneName) throw new Error("boneName cannot be null.");
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++)
+				if (bones[i].data.name == boneName) return i;
+			return -1;
+		}
+
+		/** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
+		 * repeatedly.
+		 * @returns May be null. */
+		findSlot (slotName: string) {
+			if (!slotName) throw new Error("slotName cannot be null.");
+			let slots = this.slots;
+			for (let i = 0, n = slots.length; i < n; i++) {
+				let slot = slots[i];
+				if (slot.data.name == slotName) return slot;
+			}
+			return null;
+		}
+
+		/** @returns -1 if the bone was not found. */
+		findSlotIndex (slotName: string) {
+			if (!slotName) throw new Error("slotName cannot be null.");
+			let slots = this.slots;
+			for (let i = 0, n = slots.length; i < n; i++)
+				if (slots[i].data.name == slotName) return i;
+			return -1;
+		}
+
+		/** Sets a skin by name.
+		 *
+		 * See {@link #setSkin()}. */
+		setSkinByName (skinName: string) {
+			let skin = this.data.findSkin(skinName);
+			if (!skin) throw new Error("Skin not found: " + skinName);
+			this.setSkin(skin);
+		}
+
+		/** Sets the skin used to look up attachments before looking in the {@link SkeletonData#defaultSkin default skin}. If the
+		 * skin is changed, {@link #updateCache()} is called.
+		 *
+		 * Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was no
+		 * old skin, each slot's setup mode attachment is attached from the new skin.
+		 *
+		 * After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling
+		 * {@link #setSlotsToSetupPose()}. Also, often {@link AnimationState#apply()} is called before the next time the
+		 * skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin.
+		 * @param newSkin May be null. */
+		setSkin (newSkin: Skin) {
+			if (newSkin == this.skin) return;
+			if (newSkin) {
+				if (this.skin)
+					newSkin.attachAll(this, this.skin);
+				else {
+					let slots = this.slots;
+					for (let i = 0, n = slots.length; i < n; i++) {
+						let slot = slots[i];
+						let name = slot.data.attachmentName;
+						if (name) {
+							let attachment: Attachment = newSkin.getAttachment(i, name);
+							if (attachment) slot.setAttachment(attachment);
+						}
+					}
+				}
+			}
+			this.skin = newSkin;
+			this.updateCache();
+		}
+
+
+		/** Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment
+		 * name.
+		 *
+		 * See {@link #getAttachment()}.
+		 * @returns May be null. */
+		getAttachmentByName (slotName: string, attachmentName: string): Attachment {
+			return this.getAttachment(this.data.findSlotIndex(slotName), attachmentName);
+		}
+
+		/** Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot index and
+		 * attachment name. First the skin is checked and if the attachment was not found, the default skin is checked.
+		 *
+		 * See [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide.
+		 * @returns May be null. */
+		getAttachment (slotIndex: number, attachmentName: string): Attachment {
+			if (!attachmentName) throw new Error("attachmentName cannot be null.");
+			if (this.skin) {
+				let attachment: Attachment = this.skin.getAttachment(slotIndex, attachmentName);
+				if (attachment) return attachment;
+			}
+			if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName);
+			return null;
+		}
+
+		/** A convenience method to set an attachment by finding the slot with {@link #findSlot()}, finding the attachment with
+		 * {@link #getAttachment()}, then setting the slot's {@link Slot#attachment}.
+		 * @param attachmentName May be null to clear the slot's attachment. */
+		setAttachment (slotName: string, attachmentName: string) {
+			if (!slotName) throw new Error("slotName cannot be null.");
+			let slots = this.slots;
+			for (let i = 0, n = slots.length; i < n; i++) {
+				let slot = slots[i];
+				if (slot.data.name == slotName) {
+					let attachment: Attachment = null;
+					if (attachmentName) {
+						attachment = this.getAttachment(i, attachmentName);
+						if (!attachment) throw new Error("Attachment not found: " + attachmentName + ", for slot: " + slotName);
+					}
+					slot.setAttachment(attachment);
+					return;
+				}
+			}
+			throw new Error("Slot not found: " + slotName);
+		}
+
+
+		/** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
+		 * than to call it repeatedly.
+		 * @return May be null. */
+		findIkConstraint (constraintName: string) {
+			if (!constraintName) throw new Error("constraintName cannot be null.");
+			let ikConstraints = this.ikConstraints;
+			for (let i = 0, n = ikConstraints.length; i < n; i++) {
+				let ikConstraint = ikConstraints[i];
+				if (ikConstraint.data.name == constraintName) return ikConstraint;
+			}
+			return null;
+		}
+
+		/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
+		 * this method than to call it repeatedly.
+		 * @return May be null. */
+		findTransformConstraint (constraintName: string) {
+			if (!constraintName) throw new Error("constraintName cannot be null.");
+			let transformConstraints = this.transformConstraints;
+			for (let i = 0, n = transformConstraints.length; i < n; i++) {
+				let constraint = transformConstraints[i];
+				if (constraint.data.name == constraintName) return constraint;
+			}
+			return null;
+		}
+
+		/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
+		 * than to call it repeatedly.
+		 * @return May be null. */
+		findPathConstraint (constraintName: string) {
+			if (!constraintName) throw new Error("constraintName cannot be null.");
+			let pathConstraints = this.pathConstraints;
+			for (let i = 0, n = pathConstraints.length; i < n; i++) {
+				let constraint = pathConstraints[i];
+				if (constraint.data.name == constraintName) return constraint;
+			}
+			return null;
+		}
+
+		/** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose.
+		 * @param offset An output value, the distance from the skeleton origin to the bottom left corner of the AABB.
+		 * @param size An output value, the width and height of the AABB.
+		 * @param temp Working memory to temporarily store attachments' computed world vertices. */
+		getBounds (offset: Vector2, size: Vector2, temp: Array<number> = new Array<number>(2)) {
+			if (!offset) throw new Error("offset cannot be null.");
+			if (!size) throw new Error("size cannot be null.");
+			let drawOrder = this.drawOrder;
+			let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
+			for (let i = 0, n = drawOrder.length; i < n; i++) {
+				let slot = drawOrder[i];
+				if (!slot.bone.active) continue;
+				let verticesLength = 0;
+				let vertices: ArrayLike<number> = null;
+				let attachment = slot.getAttachment();
+				if (attachment instanceof RegionAttachment) {
+					verticesLength = 8;
+					vertices = Utils.setArraySize(temp, verticesLength, 0);
+					(<RegionAttachment>attachment).computeWorldVertices(slot.bone, vertices, 0, 2);
+				} else if (attachment instanceof MeshAttachment) {
+					let mesh = (<MeshAttachment>attachment);
+					verticesLength = mesh.worldVerticesLength;
+					vertices = Utils.setArraySize(temp, verticesLength, 0);
+					mesh.computeWorldVertices(slot, 0, verticesLength, vertices, 0, 2);
+				}
+				if (vertices) {
+					for (let ii = 0, nn = vertices.length; ii < nn; ii += 2) {
+						let x = vertices[ii], y = vertices[ii + 1];
+						minX = Math.min(minX, x);
+						minY = Math.min(minY, y);
+						maxX = Math.max(maxX, x);
+						maxY = Math.max(maxY, y);
+					}
+				}
+			}
+			offset.set(minX, minY);
+			size.set(maxX - minX, maxY - minY);
+		}
+
+		/** Increments the skeleton's {@link #time}. */
+		update (delta: number) {
+			this.time += delta;
+		}
+	}
+}

+ 299 - 299
spine-ts/core/src/SkeletonBinary.ts

@@ -268,161 +268,161 @@ module spine {
 			return skin;
 		}
 
-		private readAttachment(input: BinaryInput, skeletonData: SkeletonData, skin: Skin, slotIndex: number, attachmentName: string, nonessential: boolean): Attachment {
+		private readAttachment (input: BinaryInput, skeletonData: SkeletonData, skin: Skin, slotIndex: number, attachmentName: string, nonessential: boolean): Attachment {
 			let scale = this.scale;
 
 			let name = input.readStringRef();
 			if (!name) name = attachmentName;
 
 			switch (input.readByte()) {
-			case AttachmentType.Region: {
-				let path = input.readStringRef();
-				let rotation = input.readFloat();
-				let x = input.readFloat();
-				let y = input.readFloat();
-				let scaleX = input.readFloat();
-				let scaleY = input.readFloat();
-				let width = input.readFloat();
-				let height = input.readFloat();
-				let color = input.readInt32();
-
-				if (!path) path = name;
-				let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
-				if (!region) return null;
-				region.path = path;
-				region.x = x * scale;
-				region.y = y * scale;
-				region.scaleX = scaleX;
-				region.scaleY = scaleY;
-				region.rotation = rotation;
-				region.width = width * scale;
-				region.height = height * scale;
-				Color.rgba8888ToColor(region.color, color);
-				region.updateOffset();
-				return region;
-			}
-			case AttachmentType.BoundingBox: {
-				let vertexCount = input.readInt(true);
-				let vertices = this.readVertices(input, vertexCount);
-				let color = nonessential ? input.readInt32() : 0;
-
-				let box = this.attachmentLoader.newBoundingBoxAttachment(skin, name);
-				if (!box) return null;
-				box.worldVerticesLength = vertexCount << 1;
-				box.vertices = vertices.vertices;
-				box.bones = vertices.bones;
-				if (nonessential) Color.rgba8888ToColor(box.color, color);
-				return box;
-			}
-			case AttachmentType.Mesh: {
-				let path = input.readStringRef();
-				let color = input.readInt32();
-				let vertexCount = input.readInt(true);
-				let uvs = this.readFloatArray(input, vertexCount << 1, 1);
-				let triangles = this.readShortArray(input);
-				let vertices = this.readVertices(input, vertexCount);
-				let hullLength = input.readInt(true);
-				let edges = null;
-				let width = 0, height = 0;
-				if (nonessential) {
-					edges = this.readShortArray(input);
-					width = input.readFloat();
-					height = input.readFloat();
+				case AttachmentType.Region: {
+					let path = input.readStringRef();
+					let rotation = input.readFloat();
+					let x = input.readFloat();
+					let y = input.readFloat();
+					let scaleX = input.readFloat();
+					let scaleY = input.readFloat();
+					let width = input.readFloat();
+					let height = input.readFloat();
+					let color = input.readInt32();
+
+					if (!path) path = name;
+					let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
+					if (!region) return null;
+					region.path = path;
+					region.x = x * scale;
+					region.y = y * scale;
+					region.scaleX = scaleX;
+					region.scaleY = scaleY;
+					region.rotation = rotation;
+					region.width = width * scale;
+					region.height = height * scale;
+					Color.rgba8888ToColor(region.color, color);
+					region.updateOffset();
+					return region;
 				}
-
-				if (!path) path = name;
-				let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
-				if (!mesh) return null;
-				mesh.path = path;
-				Color.rgba8888ToColor(mesh.color, color);
-				mesh.bones = vertices.bones;
-				mesh.vertices = vertices.vertices;
-				mesh.worldVerticesLength = vertexCount << 1;
-				mesh.triangles = triangles;
-				mesh.regionUVs = uvs;
-				mesh.updateUVs();
-				mesh.hullLength = hullLength << 1;
-				if (nonessential) {
-					mesh.edges = edges;
-					mesh.width = width * scale;
-					mesh.height = height * scale;
+				case AttachmentType.BoundingBox: {
+					let vertexCount = input.readInt(true);
+					let vertices = this.readVertices(input, vertexCount);
+					let color = nonessential ? input.readInt32() : 0;
+
+					let box = this.attachmentLoader.newBoundingBoxAttachment(skin, name);
+					if (!box) return null;
+					box.worldVerticesLength = vertexCount << 1;
+					box.vertices = vertices.vertices;
+					box.bones = vertices.bones;
+					if (nonessential) Color.rgba8888ToColor(box.color, color);
+					return box;
 				}
-				return mesh;
-			}
-			case AttachmentType.LinkedMesh: {
-				let path = input.readStringRef();
-				let color = input.readInt32();
-				let skinName = input.readStringRef();
-				let parent = input.readStringRef();
-				let inheritDeform = input.readBoolean();
-				let width = 0, height = 0;
-				if (nonessential) {
-					width = input.readFloat();
-					height = input.readFloat();
+				case AttachmentType.Mesh: {
+					let path = input.readStringRef();
+					let color = input.readInt32();
+					let vertexCount = input.readInt(true);
+					let uvs = this.readFloatArray(input, vertexCount << 1, 1);
+					let triangles = this.readShortArray(input);
+					let vertices = this.readVertices(input, vertexCount);
+					let hullLength = input.readInt(true);
+					let edges = null;
+					let width = 0, height = 0;
+					if (nonessential) {
+						edges = this.readShortArray(input);
+						width = input.readFloat();
+						height = input.readFloat();
+					}
+
+					if (!path) path = name;
+					let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
+					if (!mesh) return null;
+					mesh.path = path;
+					Color.rgba8888ToColor(mesh.color, color);
+					mesh.bones = vertices.bones;
+					mesh.vertices = vertices.vertices;
+					mesh.worldVerticesLength = vertexCount << 1;
+					mesh.triangles = triangles;
+					mesh.regionUVs = uvs;
+					mesh.updateUVs();
+					mesh.hullLength = hullLength << 1;
+					if (nonessential) {
+						mesh.edges = edges;
+						mesh.width = width * scale;
+						mesh.height = height * scale;
+					}
+					return mesh;
 				}
+				case AttachmentType.LinkedMesh: {
+					let path = input.readStringRef();
+					let color = input.readInt32();
+					let skinName = input.readStringRef();
+					let parent = input.readStringRef();
+					let inheritDeform = input.readBoolean();
+					let width = 0, height = 0;
+					if (nonessential) {
+						width = input.readFloat();
+						height = input.readFloat();
+					}
 
-				if (!path) path = name;
-				let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
-				if (!mesh) return null;
-				mesh.path = path;
-				Color.rgba8888ToColor(mesh.color, color);
-				if (nonessential) {
-					mesh.width = width * scale;
-					mesh.height = height * scale;
+					if (!path) path = name;
+					let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
+					if (!mesh) return null;
+					mesh.path = path;
+					Color.rgba8888ToColor(mesh.color, color);
+					if (nonessential) {
+						mesh.width = width * scale;
+						mesh.height = height * scale;
+					}
+					this.linkedMeshes.push(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform));
+					return mesh;
+				}
+				case AttachmentType.Path: {
+					let closed = input.readBoolean();
+					let constantSpeed = input.readBoolean();
+					let vertexCount = input.readInt(true);
+					let vertices = this.readVertices(input, vertexCount);
+					let lengths = Utils.newArray(vertexCount / 3, 0);
+					for (let i = 0, n = lengths.length; i < n; i++)
+						lengths[i] = input.readFloat() * scale;
+					let color = nonessential ? input.readInt32() : 0;
+
+					let path = this.attachmentLoader.newPathAttachment(skin, name);
+					if (!path) return null;
+					path.closed = closed;
+					path.constantSpeed = constantSpeed;
+					path.worldVerticesLength = vertexCount << 1;
+					path.vertices = vertices.vertices;
+					path.bones = vertices.bones;
+					path.lengths = lengths;
+					if (nonessential) Color.rgba8888ToColor(path.color, color);
+					return path;
+				}
+				case AttachmentType.Point: {
+					let rotation = input.readFloat();
+					let x = input.readFloat();
+					let y = input.readFloat();
+					let color = nonessential ? input.readInt32() : 0;
+
+					let point = this.attachmentLoader.newPointAttachment(skin, name);
+					if (!point) return null;
+					point.x = x * scale;
+					point.y = y * scale;
+					point.rotation = rotation;
+					if (nonessential) Color.rgba8888ToColor(point.color, color);
+					return point;
+				}
+				case AttachmentType.Clipping: {
+					let endSlotIndex = input.readInt(true);
+					let vertexCount = input.readInt(true);
+					let vertices = this.readVertices(input, vertexCount);
+					let color = nonessential ? input.readInt32() : 0;
+
+					let clip = this.attachmentLoader.newClippingAttachment(skin, name);
+					if (!clip) return null;
+					clip.endSlot = skeletonData.slots[endSlotIndex];
+					clip.worldVerticesLength = vertexCount << 1;
+					clip.vertices = vertices.vertices;
+					clip.bones = vertices.bones;
+					if (nonessential) Color.rgba8888ToColor(clip.color, color);
+					return clip;
 				}
-				this.linkedMeshes.push(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform));
-				return mesh;
-			}
-			case AttachmentType.Path: {
-				let closed = input.readBoolean();
-				let constantSpeed = input.readBoolean();
-				let vertexCount = input.readInt(true);
-				let vertices = this.readVertices(input, vertexCount);
-				let lengths = Utils.newArray(vertexCount / 3, 0);
-				for (let i = 0, n = lengths.length; i < n; i++)
-					lengths[i] = input.readFloat() * scale;
-				let color = nonessential ? input.readInt32() : 0;
-
-				let path = this.attachmentLoader.newPathAttachment(skin, name);
-				if (!path) return null;
-				path.closed = closed;
-				path.constantSpeed = constantSpeed;
-				path.worldVerticesLength = vertexCount << 1;
-				path.vertices = vertices.vertices;
-				path.bones = vertices.bones;
-				path.lengths = lengths;
-				if (nonessential) Color.rgba8888ToColor(path.color, color);
-				return path;
-			}
-			case AttachmentType.Point: {
-				let rotation = input.readFloat();
-				let x = input.readFloat();
-				let y = input.readFloat();
-				let color = nonessential ? input.readInt32() : 0;
-
-				let point = this.attachmentLoader.newPointAttachment(skin, name);
-				if (!point) return null;
-				point.x = x * scale;
-				point.y = y * scale;
-				point.rotation = rotation;
-				if (nonessential) Color.rgba8888ToColor(point.color, color);
-				return point;
-			}
-			case AttachmentType.Clipping: {
-				let endSlotIndex = input.readInt(true);
-				let vertexCount = input.readInt(true);
-				let vertices = this.readVertices(input, vertexCount);
-				let color = nonessential ? input.readInt32() : 0;
-
-				let clip = this.attachmentLoader.newClippingAttachment(skin, name);
-				if (!clip) return null;
-				clip.endSlot = skeletonData.slots[endSlotIndex];
-				clip.worldVerticesLength = vertexCount << 1;
-				clip.vertices = vertices.vertices;
-				clip.bones = vertices.bones;
-				if (nonessential) Color.rgba8888ToColor(clip.color, color);
-				return clip;
-			}
 			}
 			return null;
 		}
@@ -504,7 +504,7 @@ module spine {
 							let b = input.readUnsignedByte() / 255.0;
 							let a = input.readUnsignedByte() / 255.0;
 
-							for (let frame = 0, bezier = 0;; frame++) {
+							for (let frame = 0, bezier = 0; ; frame++) {
 								timeline.setFrame(frame, time, r, g, b, a);
 								if (frame == frameLast) break;
 
@@ -542,7 +542,7 @@ module spine {
 							let g = input.readUnsignedByte() / 255.0;
 							let b = input.readUnsignedByte() / 255.0;
 
-							for (let frame = 0, bezier = 0;; frame++) {
+							for (let frame = 0, bezier = 0; ; frame++) {
 								timeline.setFrame(frame, time, r, g, b);
 								if (frame == frameLast) break;
 
@@ -581,7 +581,7 @@ module spine {
 							let g2 = input.readUnsignedByte() / 255.0;
 							let b2 = input.readUnsignedByte() / 255.0;
 
-							for (let frame = 0, bezier = 0;; frame++) {
+							for (let frame = 0, bezier = 0; ; frame++) {
 								timeline.setFrame(frame, time, r, g, b, a, r2, g2, b2);
 								if (frame == frameLast) break;
 								let time2 = input.readFloat();
@@ -630,7 +630,7 @@ module spine {
 							let g2 = input.readUnsignedByte() / 255.0;
 							let b2 = input.readUnsignedByte() / 255.0;
 
-							for (let frame = 0, bezier = 0;; frame++) {
+							for (let frame = 0, bezier = 0; ; frame++) {
 								timeline.setFrame(frame, time, r, g, b, r2, g2, b2);
 								if (frame == frameLast) break;
 								let time2 = input.readFloat();
@@ -667,17 +667,17 @@ module spine {
 						case SLOT_ALPHA: {
 							let timeline = new AlphaTimeline(frameCount, input.readInt(true), slotIndex);
 							let time = input.readFloat(), a = input.readUnsignedByte() / 255;
-							for (let frame = 0, bezier = 0;; frame++) {
+							for (let frame = 0, bezier = 0; ; frame++) {
 								timeline.setFrame(frame, time, a);
 								if (frame == frameLast) break;
 								let time2 = input.readFloat();
 								let a2 = input.readUnsignedByte() / 255;
 								switch (input.readByte()) {
-								case CURVE_STEPPED:
-									timeline.setStepped(frame);
-									break;
-								case CURVE_BEZIER:
-									setBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1);
+									case CURVE_STEPPED:
+										timeline.setStepped(frame);
+										break;
+									case CURVE_BEZIER:
+										setBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1);
 								}
 								time = time2;
 								a = a2;
@@ -695,35 +695,35 @@ module spine {
 				for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 					let type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true);
 					switch (type) {
-					case BONE_ROTATE:
-						timelines.push(readTimeline1(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1));
-						break;
-					case BONE_TRANSLATE:
-						timelines.push(readTimeline2(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale));
-						break;
-					case BONE_TRANSLATEX:
-						timelines.push(readTimeline1(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale));
-						break;
-					case BONE_TRANSLATEY:
-						timelines.push(readTimeline1(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale));
-						break;
-					case BONE_SCALE:
-						timelines.push(readTimeline2(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1));
-						break;
-					case BONE_SCALEX:
-						timelines.push(readTimeline1(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1));
-						break;
-					case BONE_SCALEY:
-						timelines.push(readTimeline1(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1));
-						break;
-					case BONE_SHEAR:
-						timelines.push(readTimeline2(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1));
-						break;
-					case BONE_SHEARX:
-						timelines.push(readTimeline1(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1));
-						break;
-					case BONE_SHEARY:
-						timelines.push(readTimeline1(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1));
+						case BONE_ROTATE:
+							timelines.push(readTimeline1(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1));
+							break;
+						case BONE_TRANSLATE:
+							timelines.push(readTimeline2(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale));
+							break;
+						case BONE_TRANSLATEX:
+							timelines.push(readTimeline1(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale));
+							break;
+						case BONE_TRANSLATEY:
+							timelines.push(readTimeline1(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale));
+							break;
+						case BONE_SCALE:
+							timelines.push(readTimeline2(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1));
+							break;
+						case BONE_SCALEX:
+							timelines.push(readTimeline1(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1));
+							break;
+						case BONE_SCALEY:
+							timelines.push(readTimeline1(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1));
+							break;
+						case BONE_SHEAR:
+							timelines.push(readTimeline2(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1));
+							break;
+						case BONE_SHEARX:
+							timelines.push(readTimeline1(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1));
+							break;
+						case BONE_SHEARY:
+							timelines.push(readTimeline1(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1));
 					}
 				}
 			}
@@ -733,17 +733,17 @@ module spine {
 				let index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1;
 				let timeline = new IkConstraintTimeline(frameCount, input.readInt(true), index);
 				let time = input.readFloat(), mix = input.readFloat(), softness = input.readFloat() * scale;
-				for (let frame = 0, bezier = 0;; frame++) {
+				for (let frame = 0, bezier = 0; ; frame++) {
 					timeline.setFrame(frame, time, mix, softness, input.readByte(), input.readBoolean(), input.readBoolean());
 					if (frame == frameLast) break;
 					let time2 = input.readFloat(), mix2 = input.readFloat(), softness2 = input.readFloat() * scale;
 					switch (input.readByte()) {
-					case CURVE_STEPPED:
-						timeline.setStepped(frame);
-						break;
-					case CURVE_BEZIER:
-						setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1);
-						setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale);
+						case CURVE_STEPPED:
+							timeline.setStepped(frame);
+							break;
+						case CURVE_BEZIER:
+							setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1);
+							setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale);
 					}
 					time = time2;
 					mix = mix2;
@@ -758,22 +758,22 @@ module spine {
 				let timeline = new TransformConstraintTimeline(frameCount, input.readInt(true), index);
 				let time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat(),
 					mixScaleX = input.readFloat(), mixScaleY = input.readFloat(), mixShearY = input.readFloat();
-				for (let frame = 0, bezier = 0;; frame++) {
+				for (let frame = 0, bezier = 0; ; frame++) {
 					timeline.setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY);
 					if (frame == frameLast) break;
 					let time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(), mixY2 = input.readFloat(),
 						mixScaleX2 = input.readFloat(), mixScaleY2 = input.readFloat(), mixShearY2 = input.readFloat();
 					switch (input.readByte()) {
-					case CURVE_STEPPED:
-						timeline.setStepped(frame);
-						break;
-					case CURVE_BEZIER:
-						setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1);
-						setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1);
-						setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1);
-						setBezier(input, timeline, bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1);
-						setBezier(input, timeline, bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1);
-						setBezier(input, timeline, bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1);
+						case CURVE_STEPPED:
+							timeline.setStepped(frame);
+							break;
+						case CURVE_BEZIER:
+							setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1);
+							setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1);
+							setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1);
+							setBezier(input, timeline, bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1);
+							setBezier(input, timeline, bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1);
+							setBezier(input, timeline, bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1);
 					}
 					time = time2;
 					mixRotate = mixRotate2;
@@ -792,39 +792,39 @@ module spine {
 				let data = skeletonData.pathConstraints[index];
 				for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 					switch (input.readByte()) {
-					case PATH_POSITION:
-						timelines
-							.push(readTimeline1(input, new PathConstraintPositionTimeline(input.readInt(true), input.readInt(true), index),
-								data.positionMode == PositionMode.Fixed ? scale : 1));
-						break;
-					case PATH_SPACING:
-						timelines
-							.push(readTimeline1(input, new PathConstraintSpacingTimeline(input.readInt(true), input.readInt(true), index),
-								data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1));
-						break;
-					case PATH_MIX:
-						let timeline = new PathConstraintMixTimeline(input.readInt(true), input.readInt(true), index);
-						let time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat();
-						for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) {
-							timeline.setFrame(frame, time, mixRotate, mixX, mixY);
-							if (frame == frameLast) break;
-							let time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(),
-								mixY2 = input.readFloat();
-							switch (input.readByte()) {
-							case CURVE_STEPPED:
-								timeline.setStepped(frame);
-								break;
-							case CURVE_BEZIER:
-								setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1);
-								setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1);
-								setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1);
+						case PATH_POSITION:
+							timelines
+								.push(readTimeline1(input, new PathConstraintPositionTimeline(input.readInt(true), input.readInt(true), index),
+									data.positionMode == PositionMode.Fixed ? scale : 1));
+							break;
+						case PATH_SPACING:
+							timelines
+								.push(readTimeline1(input, new PathConstraintSpacingTimeline(input.readInt(true), input.readInt(true), index),
+									data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1));
+							break;
+						case PATH_MIX:
+							let timeline = new PathConstraintMixTimeline(input.readInt(true), input.readInt(true), index);
+							let time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat();
+							for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) {
+								timeline.setFrame(frame, time, mixRotate, mixX, mixY);
+								if (frame == frameLast) break;
+								let time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(),
+									mixY2 = input.readFloat();
+								switch (input.readByte()) {
+									case CURVE_STEPPED:
+										timeline.setStepped(frame);
+										break;
+									case CURVE_BEZIER:
+										setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1);
+										setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1);
+										setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1);
+								}
+								time = time2;
+								mixRotate = mixRotate2;
+								mixX = mixX2;
+								mixY = mixY2;
 							}
-							time = time2;
-							mixRotate = mixRotate2;
-							mixX = mixX2;
-							mixY = mixY2;
-						}
-						timelines.push(timeline);
+							timelines.push(timeline);
 					}
 				}
 			}
@@ -847,7 +847,7 @@ module spine {
 						let timeline = new DeformTimeline(frameCount, bezierCount, slotIndex, attachment);
 
 						let time = input.readFloat();
-						for (let frame = 0, bezier = 0;; frame++) {
+						for (let frame = 0, bezier = 0; ; frame++) {
 							let deform;
 							let end = input.readInt(true);
 							if (end == 0)
@@ -872,7 +872,7 @@ module spine {
 							timeline.setFrame(frame, time, deform);
 							if (frame == frameLast) break;
 							let time2 = input.readFloat();
-							switch(input.readByte()) {
+							switch (input.readByte()) {
 								case CURVE_STEPPED:
 									timeline.setStepped(frame);
 									break;
@@ -946,30 +946,30 @@ module spine {
 	}
 
 	class BinaryInput {
-		constructor(data: Uint8Array, public strings = new Array<string>(), private index: number = 0, private buffer = new DataView(data.buffer)) {
+		constructor (data: Uint8Array, public strings = new Array<string>(), private index: number = 0, private buffer = new DataView(data.buffer)) {
 		}
 
-		readByte(): number {
+		readByte (): number {
 			return this.buffer.getInt8(this.index++);
 		}
 
-		readUnsignedByte(): number {
+		readUnsignedByte (): number {
 			return this.buffer.getUint8(this.index++);
 		}
 
-		readShort(): number {
+		readShort (): number {
 			let value = this.buffer.getInt16(this.index);
 			this.index += 2;
 			return value;
 		}
 
-		readInt32(): number {
-			 let value = this.buffer.getInt32(this.index)
-			 this.index += 4;
-			 return value;
+		readInt32 (): number {
+			let value = this.buffer.getInt32(this.index)
+			this.index += 4;
+			return value;
 		}
 
-		readInt(optimizePositive: boolean) {
+		readInt (optimizePositive: boolean) {
 			let b = this.readByte();
 			let result = b & 0x7F;
 			if ((b & 0x80) != 0) {
@@ -999,10 +999,10 @@ module spine {
 		readString (): string {
 			let byteCount = this.readInt(true);
 			switch (byteCount) {
-			case 0:
-				return null;
-			case 1:
-				return "";
+				case 0:
+					return null;
+				case 1:
+					return "";
 			}
 			byteCount--;
 			let chars = "";
@@ -1010,18 +1010,18 @@ module spine {
 			for (let i = 0; i < byteCount;) {
 				let b = this.readByte();
 				switch (b >> 4) {
-				case 12:
-				case 13:
-					chars += String.fromCharCode(((b & 0x1F) << 6 | this.readByte() & 0x3F));
-					i += 2;
-					break;
-				case 14:
-					chars += String.fromCharCode(((b & 0x0F) << 12 | (this.readByte() & 0x3F) << 6 | this.readByte() & 0x3F));
-					i += 3;
-					break;
-				default:
-					chars += String.fromCharCode(b);
-					i++;
+					case 12:
+					case 13:
+						chars += String.fromCharCode(((b & 0x1F) << 6 | this.readByte() & 0x3F));
+						i += 2;
+						break;
+					case 14:
+						chars += String.fromCharCode(((b & 0x0F) << 12 | (this.readByte() & 0x3F) << 6 | this.readByte() & 0x3F));
+						i += 3;
+						break;
+					default:
+						chars += String.fromCharCode(b);
+						i++;
 				}
 			}
 			return chars;
@@ -1054,23 +1054,23 @@ module spine {
 	}
 
 	class Vertices {
-		constructor(public bones: Array<number> = null, public vertices: Array<number> | Float32Array = null) { }
+		constructor (public bones: Array<number> = null, public vertices: Array<number> | Float32Array = null) { }
 	}
 
 	enum AttachmentType { Region, BoundingBox, Mesh, LinkedMesh, Path, Point, Clipping }
 
 	function readTimeline1 (input: BinaryInput, timeline: CurveTimeline1, scale: number): CurveTimeline1 {
 		let time = input.readFloat(), value = input.readFloat() * scale;
-		for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) {
+		for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) {
 			timeline.setFrame(frame, time, value);
 			if (frame == frameLast) break;
 			let time2 = input.readFloat(), value2 = input.readFloat() * scale;
 			switch (input.readByte()) {
-			case CURVE_STEPPED:
-				timeline.setStepped(frame);
-				break;
-			case CURVE_BEZIER:
-				setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale);
+				case CURVE_STEPPED:
+					timeline.setStepped(frame);
+					break;
+				case CURVE_BEZIER:
+					setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale);
 			}
 			time = time2;
 			value = value2;
@@ -1080,17 +1080,17 @@ module spine {
 
 	function readTimeline2 (input: BinaryInput, timeline: CurveTimeline2, scale: number): CurveTimeline2 {
 		let time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale;
-		for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) {
+		for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) {
 			timeline.setFrame(frame, time, value1, value2);
 			if (frame == frameLast) break;
 			let time2 = input.readFloat(), nvalue1 = input.readFloat() * scale, nvalue2 = input.readFloat() * scale;
 			switch (input.readByte()) {
-			case CURVE_STEPPED:
-				timeline.setStepped(frame);
-				break;
-			case CURVE_BEZIER:
-				setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale);
-				setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale);
+				case CURVE_STEPPED:
+					timeline.setStepped(frame);
+					break;
+				case CURVE_BEZIER:
+					setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale);
+					setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale);
 			}
 			time = time2;
 			value1 = nvalue1;
@@ -1104,29 +1104,29 @@ module spine {
 		timeline.setBezier(bezier, frame, value, time1, value1, input.readFloat(), input.readFloat() * scale, input.readFloat(), input.readFloat() * scale, time2, value2);
 	}
 
-	const  BONE_ROTATE = 0;
-	const  BONE_TRANSLATE = 1;
-	const  BONE_TRANSLATEX = 2;
-	const  BONE_TRANSLATEY = 3;
-	const  BONE_SCALE = 4;
-	const  BONE_SCALEX = 5;
-	const  BONE_SCALEY = 6;
-	const  BONE_SHEAR = 7;
-	const  BONE_SHEARX = 8;
-	const  BONE_SHEARY = 9;
-
-	const  SLOT_ATTACHMENT = 0;
-	const  SLOT_RGBA = 1;
-	const  SLOT_RGB = 2;
-	const  SLOT_RGBA2 = 3;
-	const  SLOT_RGB2 = 4;
-	const  SLOT_ALPHA = 5;
-
-	const  PATH_POSITION = 0;
-	const  PATH_SPACING = 1;
-	const  PATH_MIX = 2;
-
-	const  CURVE_LINEAR = 0;
-	const  CURVE_STEPPED = 1;
-	const  CURVE_BEZIER = 2;
+	const BONE_ROTATE = 0;
+	const BONE_TRANSLATE = 1;
+	const BONE_TRANSLATEX = 2;
+	const BONE_TRANSLATEY = 3;
+	const BONE_SCALE = 4;
+	const BONE_SCALEX = 5;
+	const BONE_SCALEY = 6;
+	const BONE_SHEAR = 7;
+	const BONE_SHEARX = 8;
+	const BONE_SHEARY = 9;
+
+	const SLOT_ATTACHMENT = 0;
+	const SLOT_RGBA = 1;
+	const SLOT_RGB = 2;
+	const SLOT_RGBA2 = 3;
+	const SLOT_RGB2 = 4;
+	const SLOT_ALPHA = 5;
+
+	const PATH_POSITION = 0;
+	const PATH_SPACING = 1;
+	const PATH_MIX = 2;
+
+	const CURVE_LINEAR = 0;
+	const CURVE_STEPPED = 1;
+	const CURVE_BEZIER = 2;
 }

+ 231 - 231
spine-ts/core/src/SkeletonBounds.ts

@@ -1,231 +1,231 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	/** Collects each visible {@link BoundingBoxAttachment} and computes the world vertices for its polygon. The polygon vertices are
-	 * provided along with convenience methods for doing hit detection. */
-	export class SkeletonBounds {
-
-		/** The left edge of the axis aligned bounding box. */
-		minX = 0;
-
-		/** The bottom edge of the axis aligned bounding box. */
-		minY = 0;
-
-		/** The right edge of the axis aligned bounding box. */
-		maxX = 0;
-
-		/** The top edge of the axis aligned bounding box. */
-		maxY = 0;
-
-		/** The visible bounding boxes. */
-		boundingBoxes = new Array<BoundingBoxAttachment>();
-
-		/** The world vertices for the bounding box polygons. */
-		polygons = new Array<ArrayLike<number>>();
-
-		private polygonPool = new Pool<ArrayLike<number>>(() => {
-			return Utils.newFloatArray(16);
-		});
-
-		/** Clears any previous polygons, finds all visible bounding box attachments, and computes the world vertices for each bounding
-		 * box's polygon.
-		 * @param updateAabb If true, the axis aligned bounding box containing all the polygons is computed. If false, the
-		 *           SkeletonBounds AABB methods will always return true. */
-		update (skeleton: Skeleton, updateAabb: boolean) {
-			if (!skeleton) throw new Error("skeleton cannot be null.");
-			let boundingBoxes = this.boundingBoxes;
-			let polygons = this.polygons;
-			let polygonPool = this.polygonPool;
-			let slots = skeleton.slots;
-			let slotCount = slots.length;
-
-			boundingBoxes.length = 0;
-			polygonPool.freeAll(polygons);
-			polygons.length = 0;
-
-			for (let i = 0; i < slotCount; i++) {
-				let slot = slots[i];
-				if (!slot.bone.active) continue;
-				let attachment = slot.getAttachment();
-				if (attachment instanceof BoundingBoxAttachment) {
-					let boundingBox = attachment as BoundingBoxAttachment;
-					boundingBoxes.push(boundingBox);
-
-					let polygon = polygonPool.obtain();
-					if (polygon.length != boundingBox.worldVerticesLength) {
-						polygon = Utils.newFloatArray(boundingBox.worldVerticesLength);
-					}
-					polygons.push(polygon);
-					boundingBox.computeWorldVertices(slot, 0, boundingBox.worldVerticesLength, polygon, 0, 2);
-				}
-			}
-
-			if (updateAabb) {
-				this.aabbCompute();
-			} else {
-				this.minX = Number.POSITIVE_INFINITY;
-				this.minY = Number.POSITIVE_INFINITY;
-				this.maxX = Number.NEGATIVE_INFINITY;
-				this.maxY = Number.NEGATIVE_INFINITY;
-			}
-		}
-
-		aabbCompute () {
-			let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
-			let polygons = this.polygons;
-			for (let i = 0, n = polygons.length; i < n; i++) {
-				let polygon = polygons[i];
-				let vertices = polygon;
-				for (let ii = 0, nn = polygon.length; ii < nn; ii += 2) {
-					let x = vertices[ii];
-					let 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 (x: number, y: number) {
-			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 (x1: number, y1: number, x2: number, y2: number) {
-			let minX = this.minX;
-			let minY = this.minY;
-			let maxX = this.maxX;
-			let maxY = this.maxY;
-			if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
-				return false;
-			let m = (y2 - y1) / (x2 - x1);
-			let y = m * (minX - x1) + y1;
-			if (y > minY && y < maxY) return true;
-			y = m * (maxX - x1) + y1;
-			if (y > minY && y < maxY) return true;
-			let 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 (bounds: SkeletonBounds) {
-			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 (x: number, y: number): BoundingBoxAttachment {
-			let polygons = this.polygons;
-			for (let i = 0, n = polygons.length; i < n; i++)
-				if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i];
-			return null;
-		}
-
-		/** Returns true if the polygon contains the point. */
-		containsPointPolygon (polygon: ArrayLike<number>, x: number, y: number) {
-			let vertices = polygon;
-			let nn = polygon.length;
-
-			let prevIndex = nn - 2;
-			let inside = false;
-			for (let ii = 0; ii < nn; ii += 2) {
-				let vertexY = vertices[ii + 1];
-				let prevY = vertices[prevIndex + 1];
-				if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
-					let vertexX = vertices[ii];
-					if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
-				}
-				prevIndex = ii;
-			}
-			return inside;
-		}
-
-		/** Returns the first bounding box attachment that contains any part of the line segment, or null. When doing many checks, it
-		 * is usually more efficient to only call this method if {@link #aabbIntersectsSegment()} returns
-		 * true. */
-		intersectsSegment (x1: number, y1: number, x2: number, y2: number) {
-			let polygons = this.polygons;
-			for (let i = 0, n = polygons.length; i < n; i++)
-				if (this.intersectsSegmentPolygon(polygons[i], x1, y1, x2, y2)) return this.boundingBoxes[i];
-			return null;
-		}
-
-		/** Returns true if the polygon contains any part of the line segment. */
-		intersectsSegmentPolygon (polygon: ArrayLike<number>, x1: number, y1: number, x2: number, y2: number) {
-			let vertices = polygon;
-			let nn = polygon.length;
-
-			let width12 = x1 - x2, height12 = y1 - y2;
-			let det1 = x1 * y2 - y1 * x2;
-			let x3 = vertices[nn - 2], y3 = vertices[nn - 1];
-			for (let ii = 0; ii < nn; ii += 2) {
-				let x4 = vertices[ii], y4 = vertices[ii + 1];
-				let det2 = x3 * y4 - y3 * x4;
-				let width34 = x3 - x4, height34 = y3 - y4;
-				let det3 = width12 * height34 - height12 * width34;
-				let x = (det1 * width34 - width12 * det2) / det3;
-				if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
-					let 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;
-		}
-
-		/** Returns the polygon for the specified bounding box, or null. */
-		getPolygon (boundingBox: BoundingBoxAttachment) {
-			if (!boundingBox) throw new Error("boundingBox cannot be null.");
-			let index = this.boundingBoxes.indexOf(boundingBox);
-			return index == -1 ? null : this.polygons[index];
-		}
-
-		/** The width of the axis aligned bounding box. */
-		getWidth () {
-			return this.maxX - this.minX;
-		}
-
-		/** The height of the axis aligned bounding box. */
-		getHeight () {
-			return this.maxY - this.minY;
-		}
-	}
-
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	/** Collects each visible {@link BoundingBoxAttachment} and computes the world vertices for its polygon. The polygon vertices are
+	 * provided along with convenience methods for doing hit detection. */
+	export class SkeletonBounds {
+
+		/** The left edge of the axis aligned bounding box. */
+		minX = 0;
+
+		/** The bottom edge of the axis aligned bounding box. */
+		minY = 0;
+
+		/** The right edge of the axis aligned bounding box. */
+		maxX = 0;
+
+		/** The top edge of the axis aligned bounding box. */
+		maxY = 0;
+
+		/** The visible bounding boxes. */
+		boundingBoxes = new Array<BoundingBoxAttachment>();
+
+		/** The world vertices for the bounding box polygons. */
+		polygons = new Array<ArrayLike<number>>();
+
+		private polygonPool = new Pool<ArrayLike<number>>(() => {
+			return Utils.newFloatArray(16);
+		});
+
+		/** Clears any previous polygons, finds all visible bounding box attachments, and computes the world vertices for each bounding
+		 * box's polygon.
+		 * @param updateAabb If true, the axis aligned bounding box containing all the polygons is computed. If false, the
+		 *           SkeletonBounds AABB methods will always return true. */
+		update (skeleton: Skeleton, updateAabb: boolean) {
+			if (!skeleton) throw new Error("skeleton cannot be null.");
+			let boundingBoxes = this.boundingBoxes;
+			let polygons = this.polygons;
+			let polygonPool = this.polygonPool;
+			let slots = skeleton.slots;
+			let slotCount = slots.length;
+
+			boundingBoxes.length = 0;
+			polygonPool.freeAll(polygons);
+			polygons.length = 0;
+
+			for (let i = 0; i < slotCount; i++) {
+				let slot = slots[i];
+				if (!slot.bone.active) continue;
+				let attachment = slot.getAttachment();
+				if (attachment instanceof BoundingBoxAttachment) {
+					let boundingBox = attachment as BoundingBoxAttachment;
+					boundingBoxes.push(boundingBox);
+
+					let polygon = polygonPool.obtain();
+					if (polygon.length != boundingBox.worldVerticesLength) {
+						polygon = Utils.newFloatArray(boundingBox.worldVerticesLength);
+					}
+					polygons.push(polygon);
+					boundingBox.computeWorldVertices(slot, 0, boundingBox.worldVerticesLength, polygon, 0, 2);
+				}
+			}
+
+			if (updateAabb) {
+				this.aabbCompute();
+			} else {
+				this.minX = Number.POSITIVE_INFINITY;
+				this.minY = Number.POSITIVE_INFINITY;
+				this.maxX = Number.NEGATIVE_INFINITY;
+				this.maxY = Number.NEGATIVE_INFINITY;
+			}
+		}
+
+		aabbCompute () {
+			let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
+			let polygons = this.polygons;
+			for (let i = 0, n = polygons.length; i < n; i++) {
+				let polygon = polygons[i];
+				let vertices = polygon;
+				for (let ii = 0, nn = polygon.length; ii < nn; ii += 2) {
+					let x = vertices[ii];
+					let 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 (x: number, y: number) {
+			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 (x1: number, y1: number, x2: number, y2: number) {
+			let minX = this.minX;
+			let minY = this.minY;
+			let maxX = this.maxX;
+			let maxY = this.maxY;
+			if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
+				return false;
+			let m = (y2 - y1) / (x2 - x1);
+			let y = m * (minX - x1) + y1;
+			if (y > minY && y < maxY) return true;
+			y = m * (maxX - x1) + y1;
+			if (y > minY && y < maxY) return true;
+			let 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 (bounds: SkeletonBounds) {
+			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 (x: number, y: number): BoundingBoxAttachment {
+			let polygons = this.polygons;
+			for (let i = 0, n = polygons.length; i < n; i++)
+				if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i];
+			return null;
+		}
+
+		/** Returns true if the polygon contains the point. */
+		containsPointPolygon (polygon: ArrayLike<number>, x: number, y: number) {
+			let vertices = polygon;
+			let nn = polygon.length;
+
+			let prevIndex = nn - 2;
+			let inside = false;
+			for (let ii = 0; ii < nn; ii += 2) {
+				let vertexY = vertices[ii + 1];
+				let prevY = vertices[prevIndex + 1];
+				if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
+					let vertexX = vertices[ii];
+					if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
+				}
+				prevIndex = ii;
+			}
+			return inside;
+		}
+
+		/** Returns the first bounding box attachment that contains any part of the line segment, or null. When doing many checks, it
+		 * is usually more efficient to only call this method if {@link #aabbIntersectsSegment()} returns
+		 * true. */
+		intersectsSegment (x1: number, y1: number, x2: number, y2: number) {
+			let polygons = this.polygons;
+			for (let i = 0, n = polygons.length; i < n; i++)
+				if (this.intersectsSegmentPolygon(polygons[i], x1, y1, x2, y2)) return this.boundingBoxes[i];
+			return null;
+		}
+
+		/** Returns true if the polygon contains any part of the line segment. */
+		intersectsSegmentPolygon (polygon: ArrayLike<number>, x1: number, y1: number, x2: number, y2: number) {
+			let vertices = polygon;
+			let nn = polygon.length;
+
+			let width12 = x1 - x2, height12 = y1 - y2;
+			let det1 = x1 * y2 - y1 * x2;
+			let x3 = vertices[nn - 2], y3 = vertices[nn - 1];
+			for (let ii = 0; ii < nn; ii += 2) {
+				let x4 = vertices[ii], y4 = vertices[ii + 1];
+				let det2 = x3 * y4 - y3 * x4;
+				let width34 = x3 - x4, height34 = y3 - y4;
+				let det3 = width12 * height34 - height12 * width34;
+				let x = (det1 * width34 - width12 * det2) / det3;
+				if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
+					let 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;
+		}
+
+		/** Returns the polygon for the specified bounding box, or null. */
+		getPolygon (boundingBox: BoundingBoxAttachment) {
+			if (!boundingBox) throw new Error("boundingBox cannot be null.");
+			let index = this.boundingBoxes.indexOf(boundingBox);
+			return index == -1 ? null : this.polygons[index];
+		}
+
+		/** The width of the axis aligned bounding box. */
+		getWidth () {
+			return this.maxX - this.minX;
+		}
+
+		/** The height of the axis aligned bounding box. */
+		getHeight () {
+			return this.maxY - this.minY;
+		}
+	}
+
+}

+ 1 - 1
spine-ts/core/src/SkeletonClipping.ts

@@ -250,7 +250,7 @@ module spine {
 
 			let clippingVertices = clippingArea;
 			let clippingVerticesLast = clippingArea.length - 4;
-			for (let i = 0;; i += 2) {
+			for (let i = 0; ; i += 2) {
 				let edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1];
 				let edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3];
 				let deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2;

+ 216 - 216
spine-ts/core/src/SkeletonData.ts

@@ -1,216 +1,216 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	/** Stores the setup pose and all of the stateless data for a skeleton.
-	 *
-	 * See [Data objects](http://esotericsoftware.com/spine-runtime-architecture#Data-objects) in the Spine Runtimes
-	 * Guide. */
-	export class SkeletonData {
-
-		/** The skeleton's name, which by default is the name of the skeleton data file, if possible. May be null. */
-		name: string;
-
-		/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
-		bones = new Array<BoneData>(); // Ordered parents first.
-
-		/** The skeleton's slots. */
-		slots = new Array<SlotData>(); // Setup pose draw order.
-		skins = new Array<Skin>();
-
-		/** The skeleton's default skin. By default this skin contains all attachments that were not in a skin in Spine.
-		 *
-		 * See {@link Skeleton#getAttachmentByName()}.
-		 * May be null. */
-		defaultSkin: Skin;
-
-		/** The skeleton's events. */
-		events = new Array<EventData>();
-
-		/** The skeleton's animations. */
-		animations = new Array<Animation>();
-
-		/** The skeleton's IK constraints. */
-		ikConstraints = new Array<IkConstraintData>();
-
-		/** The skeleton's transform constraints. */
-		transformConstraints = new Array<TransformConstraintData>();
-
-		/** The skeleton's path constraints. */
-		pathConstraints = new Array<PathConstraintData>();
-
-		/** The X coordinate of the skeleton's axis aligned bounding box in the setup pose. */
-		x: number;
-
-		/** The Y coordinate of the skeleton's axis aligned bounding box in the setup pose. */
-		y: number;
-
-		/** The width of the skeleton's axis aligned bounding box in the setup pose. */
-		width: number;
-
-		/** The height of the skeleton's axis aligned bounding box in the setup pose. */
-		height: number;
-
-		/** The Spine version used to export the skeleton data, or null. */
-		version: string;
-
-		/** The skeleton data hash. This value will change if any of the skeleton data has changed. May be null. */
-		hash: string;
-
-		// Nonessential
-		/** The dopesheet FPS in Spine. Available only when nonessential data was exported. */
-		fps = 0;
-
-		/** The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null. */
-		imagesPath: string;
-
-		/** The path to the audio directory as defined in Spine. Available only when nonessential data was exported. May be null. */
-		audioPath: string;
-
-		/** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
-		 * multiple times.
-		 * @returns May be null. */
-		findBone (boneName: string) {
-			if (!boneName) throw new Error("boneName cannot be null.");
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++) {
-				let bone = bones[i];
-				if (bone.name == boneName) return bone;
-			}
-			return null;
-		}
-
-		findBoneIndex (boneName: string) {
-			if (!boneName) throw new Error("boneName cannot be null.");
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++)
-				if (bones[i].name == boneName) return i;
-			return -1;
-		}
-
-		/** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
-		 * multiple times.
-		 * @returns May be null. */
-		findSlot (slotName: string) {
-			if (!slotName) throw new Error("slotName cannot be null.");
-			let slots = this.slots;
-			for (let i = 0, n = slots.length; i < n; i++) {
-				let slot = slots[i];
-				if (slot.name == slotName) return slot;
-			}
-			return null;
-		}
-
-		findSlotIndex (slotName: string) {
-			if (!slotName) throw new Error("slotName cannot be null.");
-			let slots = this.slots;
-			for (let i = 0, n = slots.length; i < n; i++)
-				if (slots[i].name == slotName) return i;
-			return -1;
-		}
-
-		/** Finds a skin by comparing each skin's name. It is more efficient to cache the results of this method than to call it
-		 * multiple times.
-		 * @returns May be null. */
-		findSkin (skinName: string) {
-			if (!skinName) throw new Error("skinName cannot be null.");
-			let skins = this.skins;
-			for (let i = 0, n = skins.length; i < n; i++) {
-				let skin = skins[i];
-				if (skin.name == skinName) return skin;
-			}
-			return null;
-		}
-
-		/** Finds an event by comparing each events's name. It is more efficient to cache the results of this method than to call it
-		 * multiple times.
-		 * @returns May be null. */
-		findEvent (eventDataName: string) {
-			if (!eventDataName) throw new Error("eventDataName cannot be null.");
-			let events = this.events;
-			for (let i = 0, n = events.length; i < n; i++) {
-				let event = events[i];
-				if (event.name == eventDataName) return event;
-			}
-			return null;
-		}
-
-		/** Finds an animation by comparing each animation's name. It is more efficient to cache the results of this method than to
-		 * call it multiple times.
-		 * @returns May be null. */
-		findAnimation (animationName: string) {
-			if (!animationName) throw new Error("animationName cannot be null.");
-			let animations = this.animations;
-			for (let i = 0, n = animations.length; i < n; i++) {
-				let animation = animations[i];
-				if (animation.name == animationName) return animation;
-			}
-			return null;
-		}
-
-		/** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
-		 * than to call it multiple times.
-		 * @return May be null. */
-		findIkConstraint (constraintName: string) {
-			if (!constraintName) throw new Error("constraintName cannot be null.");
-			let ikConstraints = this.ikConstraints;
-			for (let i = 0, n = ikConstraints.length; i < n; i++) {
-				let constraint = ikConstraints[i];
-				if (constraint.name == constraintName) return constraint;
-			}
-			return null;
-		}
-
-		/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
-		 * this method than to call it multiple times.
-		 * @return May be null. */
-		findTransformConstraint (constraintName: string) {
-			if (!constraintName) throw new Error("constraintName cannot be null.");
-			let transformConstraints = this.transformConstraints;
-			for (let i = 0, n = transformConstraints.length; i < n; i++) {
-				let constraint = transformConstraints[i];
-				if (constraint.name == constraintName) return constraint;
-			}
-			return null;
-		}
-
-		/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
-		 * than to call it multiple times.
-		 * @return May be null. */
-		findPathConstraint (constraintName: string) {
-			if (!constraintName) throw new Error("constraintName cannot be null.");
-			let pathConstraints = this.pathConstraints;
-			for (let i = 0, n = pathConstraints.length; i < n; i++) {
-				let constraint = pathConstraints[i];
-				if (constraint.name == constraintName) return constraint;
-			}
-			return null;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	/** Stores the setup pose and all of the stateless data for a skeleton.
+	 *
+	 * See [Data objects](http://esotericsoftware.com/spine-runtime-architecture#Data-objects) in the Spine Runtimes
+	 * Guide. */
+	export class SkeletonData {
+
+		/** The skeleton's name, which by default is the name of the skeleton data file, if possible. May be null. */
+		name: string;
+
+		/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
+		bones = new Array<BoneData>(); // Ordered parents first.
+
+		/** The skeleton's slots. */
+		slots = new Array<SlotData>(); // Setup pose draw order.
+		skins = new Array<Skin>();
+
+		/** The skeleton's default skin. By default this skin contains all attachments that were not in a skin in Spine.
+		 *
+		 * See {@link Skeleton#getAttachmentByName()}.
+		 * May be null. */
+		defaultSkin: Skin;
+
+		/** The skeleton's events. */
+		events = new Array<EventData>();
+
+		/** The skeleton's animations. */
+		animations = new Array<Animation>();
+
+		/** The skeleton's IK constraints. */
+		ikConstraints = new Array<IkConstraintData>();
+
+		/** The skeleton's transform constraints. */
+		transformConstraints = new Array<TransformConstraintData>();
+
+		/** The skeleton's path constraints. */
+		pathConstraints = new Array<PathConstraintData>();
+
+		/** The X coordinate of the skeleton's axis aligned bounding box in the setup pose. */
+		x: number;
+
+		/** The Y coordinate of the skeleton's axis aligned bounding box in the setup pose. */
+		y: number;
+
+		/** The width of the skeleton's axis aligned bounding box in the setup pose. */
+		width: number;
+
+		/** The height of the skeleton's axis aligned bounding box in the setup pose. */
+		height: number;
+
+		/** The Spine version used to export the skeleton data, or null. */
+		version: string;
+
+		/** The skeleton data hash. This value will change if any of the skeleton data has changed. May be null. */
+		hash: string;
+
+		// Nonessential
+		/** The dopesheet FPS in Spine. Available only when nonessential data was exported. */
+		fps = 0;
+
+		/** The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null. */
+		imagesPath: string;
+
+		/** The path to the audio directory as defined in Spine. Available only when nonessential data was exported. May be null. */
+		audioPath: string;
+
+		/** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
+		 * multiple times.
+		 * @returns May be null. */
+		findBone (boneName: string) {
+			if (!boneName) throw new Error("boneName cannot be null.");
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++) {
+				let bone = bones[i];
+				if (bone.name == boneName) return bone;
+			}
+			return null;
+		}
+
+		findBoneIndex (boneName: string) {
+			if (!boneName) throw new Error("boneName cannot be null.");
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++)
+				if (bones[i].name == boneName) return i;
+			return -1;
+		}
+
+		/** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
+		 * multiple times.
+		 * @returns May be null. */
+		findSlot (slotName: string) {
+			if (!slotName) throw new Error("slotName cannot be null.");
+			let slots = this.slots;
+			for (let i = 0, n = slots.length; i < n; i++) {
+				let slot = slots[i];
+				if (slot.name == slotName) return slot;
+			}
+			return null;
+		}
+
+		findSlotIndex (slotName: string) {
+			if (!slotName) throw new Error("slotName cannot be null.");
+			let slots = this.slots;
+			for (let i = 0, n = slots.length; i < n; i++)
+				if (slots[i].name == slotName) return i;
+			return -1;
+		}
+
+		/** Finds a skin by comparing each skin's name. It is more efficient to cache the results of this method than to call it
+		 * multiple times.
+		 * @returns May be null. */
+		findSkin (skinName: string) {
+			if (!skinName) throw new Error("skinName cannot be null.");
+			let skins = this.skins;
+			for (let i = 0, n = skins.length; i < n; i++) {
+				let skin = skins[i];
+				if (skin.name == skinName) return skin;
+			}
+			return null;
+		}
+
+		/** Finds an event by comparing each events's name. It is more efficient to cache the results of this method than to call it
+		 * multiple times.
+		 * @returns May be null. */
+		findEvent (eventDataName: string) {
+			if (!eventDataName) throw new Error("eventDataName cannot be null.");
+			let events = this.events;
+			for (let i = 0, n = events.length; i < n; i++) {
+				let event = events[i];
+				if (event.name == eventDataName) return event;
+			}
+			return null;
+		}
+
+		/** Finds an animation by comparing each animation's name. It is more efficient to cache the results of this method than to
+		 * call it multiple times.
+		 * @returns May be null. */
+		findAnimation (animationName: string) {
+			if (!animationName) throw new Error("animationName cannot be null.");
+			let animations = this.animations;
+			for (let i = 0, n = animations.length; i < n; i++) {
+				let animation = animations[i];
+				if (animation.name == animationName) return animation;
+			}
+			return null;
+		}
+
+		/** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
+		 * than to call it multiple times.
+		 * @return May be null. */
+		findIkConstraint (constraintName: string) {
+			if (!constraintName) throw new Error("constraintName cannot be null.");
+			let ikConstraints = this.ikConstraints;
+			for (let i = 0, n = ikConstraints.length; i < n; i++) {
+				let constraint = ikConstraints[i];
+				if (constraint.name == constraintName) return constraint;
+			}
+			return null;
+		}
+
+		/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
+		 * this method than to call it multiple times.
+		 * @return May be null. */
+		findTransformConstraint (constraintName: string) {
+			if (!constraintName) throw new Error("constraintName cannot be null.");
+			let transformConstraints = this.transformConstraints;
+			for (let i = 0, n = transformConstraints.length; i < n; i++) {
+				let constraint = transformConstraints[i];
+				if (constraint.name == constraintName) return constraint;
+			}
+			return null;
+		}
+
+		/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
+		 * than to call it multiple times.
+		 * @return May be null. */
+		findPathConstraint (constraintName: string) {
+			if (!constraintName) throw new Error("constraintName cannot be null.");
+			let pathConstraints = this.pathConstraints;
+			for (let i = 0, n = pathConstraints.length; i < n; i++) {
+				let constraint = pathConstraints[i];
+				if (constraint.name == constraintName) return constraint;
+			}
+			return null;
+		}
+	}
+}

+ 965 - 965
spine-ts/core/src/SkeletonJson.ts

@@ -1,965 +1,965 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Loads skeleton data in the Spine JSON format.
-	 *
-	 * See [Spine JSON format](http://esotericsoftware.com/spine-json-format) and
-	 * [JSON and binary data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the Spine
-	 * Runtimes Guide. */
-	export class SkeletonJson {
-		attachmentLoader: AttachmentLoader;
-
-		/** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at
-		 * runtime than were used in Spine.
-		 *
-		 * See [Scaling](http://esotericsoftware.com/spine-loading-skeleton-data#Scaling) in the Spine Runtimes Guide. */
-		scale = 1;
-		private linkedMeshes = new Array<LinkedMesh>();
-
-		constructor (attachmentLoader: AttachmentLoader) {
-			this.attachmentLoader = attachmentLoader;
-		}
-
-		readSkeletonData (json: string | any): SkeletonData {
-			let scale = this.scale;
-			let skeletonData = new SkeletonData();
-			let root = typeof(json) === "string" ? JSON.parse(json) : json;
-
-			// Skeleton
-			let skeletonMap = root.skeleton;
-			if (skeletonMap) {
-				skeletonData.hash = skeletonMap.hash;
-				skeletonData.version = skeletonMap.spine;
-				skeletonData.x = skeletonMap.x;
-				skeletonData.y = skeletonMap.y;
-				skeletonData.width = skeletonMap.width;
-				skeletonData.height = skeletonMap.height;
-				skeletonData.fps = skeletonMap.fps;
-				skeletonData.imagesPath = skeletonMap.images;
-			}
-
-			// Bones
-			if (root.bones) {
-				for (let i = 0; i < root.bones.length; i++) {
-					let boneMap = root.bones[i];
-
-					let parent: BoneData = null;
-					let parentName: string = getValue(boneMap, "parent", null);
-					if (parentName) parent = skeletonData.findBone(parentName);
-					let data = new BoneData(skeletonData.bones.length, boneMap.name, parent);
-					data.length = getValue(boneMap, "length", 0) * scale;
-					data.x = getValue(boneMap, "x", 0) * scale;
-					data.y = getValue(boneMap, "y", 0) * scale;
-					data.rotation = getValue(boneMap, "rotation", 0);
-					data.scaleX = getValue(boneMap, "scaleX", 1);
-					data.scaleY = getValue(boneMap, "scaleY", 1);
-					data.shearX = getValue(boneMap, "shearX", 0);
-					data.shearY = getValue(boneMap, "shearY", 0);
-					data.transformMode = Utils.enumValue(TransformMode, getValue(boneMap, "transform", "Normal"));
-					data.skinRequired = getValue(boneMap, "skin", false);
-
-					let color = getValue(boneMap, "color", null);
-					if (color) data.color.setFromString(color);
-
-					skeletonData.bones.push(data);
-				}
-			}
-
-			// Slots.
-			if (root.slots) {
-				for (let i = 0; i < root.slots.length; i++) {
-					let slotMap = root.slots[i];
-					let boneData = skeletonData.findBone(slotMap.bone);
-					let data = new SlotData(skeletonData.slots.length, slotMap.name, boneData);
-
-					let color: string = getValue(slotMap, "color", null);
-					if (color) data.color.setFromString(color);
-
-					let dark: string = getValue(slotMap, "dark", null);
-					if (dark) data.darkColor = Color.fromString(dark);
-
-					data.attachmentName = getValue(slotMap, "attachment", null);
-					data.blendMode = Utils.enumValue(BlendMode, getValue(slotMap, "blend", "normal"));
-					skeletonData.slots.push(data);
-				}
-			}
-
-			// IK constraints
-			if (root.ik) {
-				for (let i = 0; i < root.ik.length; i++) {
-					let constraintMap = root.ik[i];
-					let data = new IkConstraintData(constraintMap.name);
-					data.order = getValue(constraintMap, "order", 0);
-					data.skinRequired = getValue(constraintMap, "skin", false);
-
-					for (let ii = 0; ii < constraintMap.bones.length; ii++)
-						data.bones.push(skeletonData.findBone(constraintMap.bones[ii]));
-
-					data.target = skeletonData.findBone(constraintMap.target);
-
-					data.mix = getValue(constraintMap, "mix", 1);
-					data.softness = getValue(constraintMap, "softness", 0) * scale;
-					data.bendDirection = getValue(constraintMap, "bendPositive", true) ? 1 : -1;
-					data.compress = getValue(constraintMap, "compress", false);
-					data.stretch = getValue(constraintMap, "stretch", false);
-					data.uniform = getValue(constraintMap, "uniform", false);
-
-					skeletonData.ikConstraints.push(data);
-				}
-			}
-
-			// Transform constraints.
-			if (root.transform) {
-				for (let i = 0; i < root.transform.length; i++) {
-					let constraintMap = root.transform[i];
-					let data = new TransformConstraintData(constraintMap.name);
-					data.order = getValue(constraintMap, "order", 0);
-					data.skinRequired = getValue(constraintMap, "skin", false);
-
-					for (let ii = 0; ii < constraintMap.bones.length; ii++)
-						data.bones.push(skeletonData.findBone(constraintMap.bones[ii]));
-
-					let targetName: string = constraintMap.target;
-					data.target = skeletonData.findBone(targetName);
-
-					data.local = getValue(constraintMap, "local", false);
-					data.relative = getValue(constraintMap, "relative", false);
-					data.offsetRotation = getValue(constraintMap, "rotation", 0);
-					data.offsetX = getValue(constraintMap, "x", 0) * scale;
-					data.offsetY = getValue(constraintMap, "y", 0) * scale;
-					data.offsetScaleX = getValue(constraintMap, "scaleX", 0);
-					data.offsetScaleY = getValue(constraintMap, "scaleY", 0);
-					data.offsetShearY = getValue(constraintMap, "shearY", 0);
-
-					data.mixRotate = getValue(constraintMap, "mixRotate", 1);
-					data.mixX = getValue(constraintMap, "mixX", 1);
-					data.mixY = getValue(constraintMap, "mixY", data.mixX);
-					data.mixScaleX = getValue(constraintMap, "mixScaleX", 1);
-					data.mixScaleY = getValue(constraintMap, "mixScaleY", data.mixScaleX);
-					data.mixShearY = getValue(constraintMap, "mixShearY", 1);
-
-					skeletonData.transformConstraints.push(data);
-				}
-			}
-
-			// Path constraints.
-			if (root.path) {
-				for (let i = 0; i < root.path.length; i++) {
-					let constraintMap = root.path[i];
-					let data = new PathConstraintData(constraintMap.name);
-					data.order = getValue(constraintMap, "order", 0);
-					data.skinRequired = getValue(constraintMap, "skin", false);
-
-					for (let ii = 0; ii < constraintMap.bones.length; ii++)
-						data.bones.push(skeletonData.findBone(constraintMap.bones[ii]));
-
-					let targetName: string = constraintMap.target;
-					data.target = skeletonData.findSlot(targetName);
-
-					data.positionMode = Utils.enumValue(PositionMode, getValue(constraintMap, "positionMode", "Percent"));
-					data.spacingMode = Utils.enumValue(SpacingMode, getValue(constraintMap, "spacingMode", "Length"));
-					data.rotateMode = Utils.enumValue(RotateMode, getValue(constraintMap, "rotateMode", "Tangent"));
-					data.offsetRotation = getValue(constraintMap, "rotation", 0);
-					data.position = getValue(constraintMap, "position", 0);
-					if (data.positionMode == PositionMode.Fixed) data.position *= scale;
-					data.spacing = getValue(constraintMap, "spacing", 0);
-					if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
-					data.mixRotate = getValue(constraintMap, "mixRotate", 1);
-					data.mixX = getValue(constraintMap, "mixX", 1);
-					data.mixY = getValue(constraintMap, "mixY", data.mixX);
-
-					skeletonData.pathConstraints.push(data);
-				}
-			}
-
-			// Skins.
-			if (root.skins) {
-				for (let i = 0; i < root.skins.length; i++) {
-					let skinMap = root.skins[i]
-					let skin = new Skin(skinMap.name);
-
-					if (skinMap.bones) {
-						for (let ii = 0; ii < skinMap.bones.length; ii++)
-							skin.bones.push(skeletonData.findBone(skinMap.bones[ii]));
-					}
-
-					if (skinMap.ik) {
-						for (let ii = 0; ii < skinMap.ik.length; ii++)
-							skin.constraints.push(skeletonData.findIkConstraint(skinMap.ik[ii]));
-					}
-
-					if (skinMap.transform) {
-						for (let ii = 0; ii < skinMap.transform.length; ii++)
-							skin.constraints.push(skeletonData.findTransformConstraint(skinMap.transform[ii]));
-					}
-
-					if (skinMap.path) {
-						for (let ii = 0; ii < skinMap.path.length; ii++)
-							skin.constraints.push(skeletonData.findPathConstraint(skinMap.path[ii]));
-					}
-
-					for (let slotName in skinMap.attachments) {
-						let slot = skeletonData.findSlot(slotName);
-						let slotMap = skinMap.attachments[slotName];
-						for (let entryName in slotMap) {
-							let attachment = this.readAttachment(slotMap[entryName], skin, slot.index, entryName, skeletonData);
-							if (attachment) skin.setAttachment(slot.index, entryName, attachment);
-						}
-					}
-					skeletonData.skins.push(skin);
-					if (skin.name == "default") skeletonData.defaultSkin = skin;
-				}
-			}
-
-			// Linked meshes.
-			for (let i = 0, n = this.linkedMeshes.length; i < n; i++) {
-				let linkedMesh = this.linkedMeshes[i];
-				let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
-				let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
-				linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
-				linkedMesh.mesh.setParentMesh(<MeshAttachment> parent);
-				linkedMesh.mesh.updateUVs();
-			}
-			this.linkedMeshes.length = 0;
-
-			// Events.
-			if (root.events) {
-				for (let eventName in root.events) {
-					let eventMap = root.events[eventName];
-					let data = new EventData(eventName);
-					data.intValue = getValue(eventMap, "int", 0);
-					data.floatValue = getValue(eventMap, "float", 0);
-					data.stringValue = getValue(eventMap, "string", "");
-					data.audioPath = getValue(eventMap, "audio", null);
-					if (data.audioPath) {
-						data.volume = getValue(eventMap, "volume", 1);
-						data.balance = getValue(eventMap, "balance", 0);
-					}
-					skeletonData.events.push(data);
-				}
-			}
-
-			// Animations.
-			if (root.animations) {
-				for (let animationName in root.animations) {
-					let animationMap = root.animations[animationName];
-					this.readAnimation(animationMap, animationName, skeletonData);
-				}
-			}
-
-			return skeletonData;
-		}
-
-		readAttachment (map: any, skin: Skin, slotIndex: number, name: string, skeletonData: SkeletonData): Attachment {
-			let scale = this.scale;
-			name = getValue(map, "name", name);
-
-			switch (getValue(map, "type", "region")) {
-				case "region": {
-					let path = getValue(map, "path", name);
-					let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
-					if (!region) return null;
-					region.path = path;
-					region.x = getValue(map, "x", 0) * scale;
-					region.y = getValue(map, "y", 0) * scale;
-					region.scaleX = getValue(map, "scaleX", 1);
-					region.scaleY = getValue(map, "scaleY", 1);
-					region.rotation = getValue(map, "rotation", 0);
-					region.width = map.width * scale;
-					region.height = map.height * scale;
-
-					let color: string = getValue(map, "color", null);
-					if (color) region.color.setFromString(color);
-
-					region.updateOffset();
-					return region;
-				}
-				case "boundingbox": {
-					let box = this.attachmentLoader.newBoundingBoxAttachment(skin, name);
-					if (!box) return null;
-					this.readVertices(map, box, map.vertexCount << 1);
-					let color: string = getValue(map, "color", null);
-					if (color) box.color.setFromString(color);
-					return box;
-				}
-				case "mesh":
-				case "linkedmesh": {
-					let path = getValue(map, "path", name);
-					let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
-					if (!mesh) return null;
-					mesh.path = path;
-
-					let color = getValue(map, "color", null);
-					if (color) mesh.color.setFromString(color);
-
-					mesh.width = getValue(map, "width", 0) * scale;
-					mesh.height = getValue(map, "height", 0) * scale;
-
-					let parent: string = getValue(map, "parent", null);
-					if (parent) {
-						this.linkedMeshes.push(new LinkedMesh(mesh, <string> getValue(map, "skin", null), slotIndex, parent, getValue(map, "deform", true)));
-						return mesh;
-					}
-
-					let uvs: Array<number> = map.uvs;
-					this.readVertices(map, mesh, uvs.length);
-					mesh.triangles = map.triangles;
-					mesh.regionUVs = uvs;
-					mesh.updateUVs();
-
-					mesh.edges = getValue(map, "edges", null);
-					mesh.hullLength = getValue(map, "hull", 0) * 2;
-					return mesh;
-				}
-				case "path": {
-					let path = this.attachmentLoader.newPathAttachment(skin, name);
-					if (!path) return null;
-					path.closed = getValue(map, "closed", false);
-					path.constantSpeed = getValue(map, "constantSpeed", true);
-
-					let vertexCount = map.vertexCount;
-					this.readVertices(map, path, vertexCount << 1);
-
-					let lengths: Array<number> = Utils.newArray(vertexCount / 3, 0);
-					for (let i = 0; i < map.lengths.length; i++)
-						lengths[i] = map.lengths[i] * scale;
-					path.lengths = lengths;
-
-					let color: string = getValue(map, "color", null);
-					if (color) path.color.setFromString(color);
-					return path;
-				}
-				case "point": {
-					let point = this.attachmentLoader.newPointAttachment(skin, name);
-					if (!point) return null;
-					point.x = getValue(map, "x", 0) * scale;
-					point.y = getValue(map, "y", 0) * scale;
-					point.rotation = getValue(map, "rotation", 0);
-
-					let color = getValue(map, "color", null);
-					if (color) point.color.setFromString(color);
-					return point;
-				}
-				case "clipping": {
-					let clip = this.attachmentLoader.newClippingAttachment(skin, name);
-					if (!clip) return null;
-
-					let end = getValue(map, "end", null);
-					if (end) clip.endSlot = skeletonData.findSlot(end);
-
-					let vertexCount = map.vertexCount;
-					this.readVertices(map, clip, vertexCount << 1);
-
-					let color: string = getValue(map, "color", null);
-					if (color) clip.color.setFromString(color);
-					return clip;
-				}
-			}
-			return null;
-		}
-
-		readVertices (map: any, attachment: VertexAttachment, verticesLength: number) {
-			let scale = this.scale;
-			attachment.worldVerticesLength = verticesLength;
-			let vertices: Array<number> = map.vertices;
-			if (verticesLength == vertices.length) {
-				let scaledVertices = Utils.toFloatArray(vertices);
-				if (scale != 1) {
-					for (let i = 0, n = vertices.length; i < n; i++)
-						scaledVertices[i] *= scale;
-				}
-				attachment.vertices = scaledVertices;
-				return;
-			}
-			let weights = new Array<number>();
-			let bones = new Array<number>();
-			for (let i = 0, n = vertices.length; i < n;) {
-				let boneCount = vertices[i++];
-				bones.push(boneCount);
-				for (let nn = i + boneCount * 4; i < nn; i += 4) {
-					bones.push(vertices[i]);
-					weights.push(vertices[i + 1] * scale);
-					weights.push(vertices[i + 2] * scale);
-					weights.push(vertices[i + 3]);
-				}
-			}
-			attachment.bones = bones;
-			attachment.vertices = Utils.toFloatArray(weights);
-		}
-
-		readAnimation (map: any, name: string, skeletonData: SkeletonData) {
-			let scale = this.scale;
-			let timelines = new Array<Timeline>();
-
-			// Slot timelines.
-			if (map.slots) {
-				for (let slotName in map.slots) {
-					let slotMap = map.slots[slotName];
-					let slotIndex = skeletonData.findSlotIndex(slotName);
-					for (let timelineName in slotMap) {
-						let timelineMap = slotMap[timelineName];
-						if (!timelineMap) continue;
-						if (timelineName == "attachment") {
-							let timeline = new AttachmentTimeline(timelineMap.length, slotIndex);
-							for (let frame = 0; frame < timelineMap.length; frame++) {
-								let keyMap = timelineMap[frame];
-								timeline.setFrame(frame, getValue(keyMap, "time", 0), keyMap.name);
-							}
-							timelines.push(timeline);
-
-						} else if (timelineName == "rgba") {
-							let timeline = new RGBATimeline(timelineMap.length, timelineMap.length << 2, slotIndex);
-							let keyMap = timelineMap[0];
-							let time = getValue(keyMap, "time", 0);
-							let color = Color.fromString(keyMap.color);
-
-							for (let frame = 0, bezier = 0;; frame++) {
-								timeline.setFrame(frame, time, color.r, color.g, color.b, color.a);
-								let nextMap = timelineMap[frame + 1];
-								if (!nextMap)  {
-									timeline.shrink(bezier);
-									break;
-								}
-								let time2 = getValue(nextMap, "time", 0);
-								let newColor = Color.fromString(nextMap.color);
-								let curve = keyMap.curve;
-								if (curve) {
-									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1);
-								}
-								time = time2;
-								color = newColor;
-								keyMap = nextMap;
-							}
-
-							timelines.push(timeline);
-
-						} else if (timelineName == "rgb") {
-							let timeline = new RGBTimeline(timelineMap.length, timelineMap.length * 3, slotIndex);
-							let keyMap = timelineMap[0];
-							let time = getValue(keyMap, "time", 0);
-							let color = Color.fromString(keyMap.color);
-
-							for (let frame = 0, bezier = 0;; frame++) {
-								timeline.setFrame(frame, time, color.r, color.g, color.b);
-								let nextMap = timelineMap[frame + 1];
-								if (!nextMap)  {
-									timeline.shrink(bezier);
-									break;
-								}
-								let time2 = getValue(nextMap, "time", 0);
-								let newColor = Color.fromString(nextMap.color);
-								let curve = keyMap.curve;
-								if (curve) {
-									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1);
-								}
-								time = time2;
-								color = newColor;
-								keyMap = nextMap;
-							}
-
-							timelines.push(timeline);
-
-						} else if (timelineName == "alpha") {
-							timelines.push(readTimeline1(timelineMap, new AlphaTimeline(timelineMap.length, timelineMap.length, slotIndex), 0, 1));
-						} else if (timelineName == "rgba2") {
-							let timeline = new RGBA2Timeline(timelineMap.length, timelineMap.length * 7, slotIndex);
-
-							let keyMap = timelineMap[0];
-							let time = getValue(keyMap, "time", 0);
-							let color = Color.fromString(keyMap.light);
-							let color2 = Color.fromString(keyMap.dark);
-
-							for (let frame = 0, bezier = 0;; frame++) {
-								timeline.setFrame(frame, time, color.r, color.g, color.b, color.a, color2.r, color2.g, color2.b);
-								let nextMap = timelineMap[frame + 1];
-								if (!nextMap)  {
-									timeline.shrink(bezier);
-									break;
-								}
-								let time2 = getValue(nextMap, "time", 0);
-								let newColor = Color.fromString(nextMap.light);
-								let newColor2 = Color.fromString(nextMap.dark);
-								let curve = keyMap.curve;
-								if (curve) {
-									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.r, newColor2.r, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.g, newColor2.g, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, color2.b, newColor2.b, 1);
-								}
-								time = time2;
-								color = newColor;
-								color2 = newColor2;
-								keyMap = nextMap;
-							}
-
-							timelines.push(timeline);
-
-						} else if (timelineName == "rgb2") {
-							let timeline = new RGB2Timeline(timelineMap.length, timelineMap.length * 6, slotIndex);
-
-							let keyMap = timelineMap[0];
-							let time = getValue(keyMap, "time", 0);
-							let color = Color.fromString(keyMap.light);
-							let color2 = Color.fromString(keyMap.dark);
-
-							for (let frame = 0, bezier = 0;; frame++) {
-								timeline.setFrame(frame, time, color.r, color.g, color.b, color2.r, color2.g, color2.b);
-								let nextMap = timelineMap[frame + 1];
-								if (!nextMap)  {
-									timeline.shrink(bezier);
-									break;
-								}
-								let time2 = getValue(nextMap, "time", 0);
-								let newColor = Color.fromString(nextMap.light);
-								let newColor2 = Color.fromString(nextMap.dark);
-								let curve = keyMap.curve;
-								if (curve) {
-									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color2.r, newColor2.r, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.g, newColor2.g, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.b, newColor2.b, 1);
-								}
-								time = time2;
-								color = newColor;
-								color2 = newColor2;
-								keyMap = nextMap;
-							}
-
-							timelines.push(timeline);
-						}
-					}
-				}
-			}
-
-			// Bone timelines.
-			if (map.bones) {
-				for (let boneName in map.bones) {
-					let boneMap = map.bones[boneName];
-					let boneIndex = skeletonData.findBoneIndex(boneName);
-					for (let timelineName in boneMap) {
-						let timelineMap = boneMap[timelineName];
-						if (timelineMap.length == 0) continue;
-
-						if (timelineName === "rotate") {
-							timelines.push(readTimeline1(timelineMap, new RotateTimeline(timelineMap.length, timelineMap.length, boneIndex), 0, 1));
-						} else if (timelineName === "translate") {
-							let timeline = new TranslateTimeline(timelineMap.length, timelineMap.length << 1, boneIndex);
-							timelines.push(readTimeline2(timelineMap, timeline, "x", "y", 0, scale));
-						} else if (timelineName === "translatex") {
-							let timeline = new TranslateXTimeline(timelineMap.length, timelineMap.length, boneIndex);
-							timelines.push(readTimeline1(timelineMap, timeline, 0, scale));
-						} else if (timelineName === "translatey") {
-							let timeline = new TranslateYTimeline(timelineMap.length, timelineMap.length, boneIndex);
-							timelines.push(readTimeline1(timelineMap, timeline, 0, scale));
-						} else if (timelineName === "scale") {
-							let timeline = new ScaleTimeline(timelineMap.length, timelineMap.length << 1, boneIndex);
-							timelines.push(readTimeline2(timelineMap, timeline, "x", "y", 1, 1));
-						} else if (timelineName === "scalex") {
-							let timeline = new ScaleXTimeline(timelineMap.length, timelineMap.length, boneIndex);
-							timelines.push(readTimeline1(timelineMap, timeline, 1, 1));
-						} else if (timelineName === "scaley") {
-							let timeline = new ScaleYTimeline(timelineMap.length, timelineMap.length, boneIndex);
-							timelines.push(readTimeline1(timelineMap, timeline, 1, 1));
-						} else if (timelineName === "shear") {
-							let timeline = new ShearTimeline(timelineMap.length, timelineMap.length << 1, boneIndex);
-							timelines.push(readTimeline2(timelineMap, timeline, "x", "y", 0, 1));
-						} else if (timelineName === "shearx") {
-							let timeline = new ShearXTimeline(timelineMap.length, timelineMap.length, boneIndex);
-							timelines.push(readTimeline1(timelineMap, timeline, 0, 1));
-						} else if (timelineName === "sheary") {
-							let timeline = new ShearYTimeline(timelineMap.length, timelineMap.length, boneIndex);
-							timelines.push(readTimeline1(timelineMap, timeline, 0, 1));
-						}
-					}
-				}
-			}
-
-			// IK constraint timelines.
-			if (map.ik) {
-				for (let constraintName in map.ik) {
-					let constraintMap = map.ik[constraintName];
-					let keyMap = constraintMap[0];
-					if (!keyMap) continue;
-
-					let constraint = skeletonData.findIkConstraint(constraintName);
-					let constraintIndex = skeletonData.ikConstraints.indexOf(constraint);
-					let timeline = new IkConstraintTimeline(constraintMap.length, constraintMap.length << 1, constraintIndex);
-
-					let time = getValue(keyMap, "time", 0);
-					let mix = getValue(keyMap, "mix", 1);
-					let softness = getValue(keyMap, "softness", 0) * scale;
-
-					for (let frame = 0, bezier = 0;; frame++) {
-						timeline.setFrame(frame, time, mix, softness, getValue(keyMap, "bendPositive", true) ? 1 : -1, getValue(keyMap, "compress", false), getValue(keyMap, "stretch", false));
-						let nextMap = constraintMap[frame + 1];
-						if (!nextMap) {
-							timeline.shrink(bezier);
-							break;
-						}
-
-						let time2 = getValue(nextMap, "time", 0);
-						let mix2 = getValue(nextMap, "mix", 1);
-						let softness2 = getValue(nextMap, "softness", 0) * scale;
-						let curve = keyMap.curve;
-						if (curve) {
-							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1);
-							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, scale);
-						}
-
-						time = time2;
-						mix = mix2;
-						softness = softness2;
-						keyMap = nextMap;
-					}
-					timelines.push(timeline);
-				}
-			}
-
-			// Transform constraint timelines.
-			if (map.transform) {
-				for (let constraintName in map.transform) {
-					let timelineMap = map.transform[constraintName];
-					let keyMap = timelineMap[0];
-					if (!keyMap) continue;
-
-					let constraint = skeletonData.findTransformConstraint(constraintName);
-					let constraintIndex = skeletonData.transformConstraints.indexOf(constraint);
-					let timeline = new TransformConstraintTimeline(timelineMap.length, timelineMap.length << 2, constraintIndex);
-
-					let time = getValue(keyMap, "time", 0);
-					let mixRotate = getValue(keyMap, "mixRotate", 1);
-					let mixX = getValue(keyMap, "mixX", 1);
-					let mixY = getValue(keyMap, "mixY", mixX);
-					let mixScaleX = getValue(keyMap, "mixScaleX", 1);
-					let mixScaleY = getValue(keyMap, "mixScaleY", mixScaleX);
-					let mixShearY = getValue(keyMap, "mixShearY", 1);
-
-					for (let frame = 0, bezier = 0;; frame++) {
-						timeline.setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY);
-						let nextMap = timelineMap[frame + 1];
-						if (!nextMap) {
-							timeline.shrink(bezier);
-							break;
-						}
-
-						let time2 = getValue(nextMap, "time", 0);
-						let mixRotate2 = getValue(nextMap, "mixRotate", 1);
-						let mixX2 = getValue(nextMap, "mixX", 1);
-						let mixY2 = getValue(nextMap, "mixY", mixX2);
-						let mixScaleX2 = getValue(nextMap, "mixScaleX", 1);
-						let mixScaleY2 = getValue(nextMap, "mixScaleY", mixScaleX2);
-						let mixShearY2 = getValue(nextMap, "mixShearY", 1);
-						let curve = keyMap.curve;
-						if (curve) {
-							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1);
-							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1);
-							bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1);
-							bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1);
-							bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1);
-							bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1);
-						}
-
-						time = time2;
-						mixRotate = mixRotate2;
-						mixX = mixX2;
-						mixY = mixY2;
-						mixScaleX = mixScaleX2;
-						mixScaleY = mixScaleY2;
-						mixScaleX = mixScaleX2;
-						keyMap = nextMap;
-					}
-					timelines.push(timeline);
-				}
-			}
-
-			// Path constraint timelines.
-			if (map.path) {
-				for (let constraintName in map.path) {
-					let constraintMap = map.path[constraintName];
-					let constraint = skeletonData.findPathConstraint(constraintName);
-					let constraintIndex = skeletonData.pathConstraints.indexOf(constraint);
-					for (let timelineName in constraintMap) {
-						let timelineMap = constraintMap[timelineName];
-						let keyMap = timelineMap[0];
-						if (!keyMap) continue;
-
-						if (timelineName === "position") {
-							let timeline = new PathConstraintPositionTimeline(timelineMap.length, timelineMap.length, constraintIndex);
-							timelines.push(readTimeline1(timelineMap, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1));
-						} else if (timelineName === "spacing") {
-							let timeline = new PathConstraintSpacingTimeline(timelineMap.length, timelineMap.length, constraintIndex);
-							timelines.push(readTimeline1(timelineMap, timeline, 0, constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1));
-						} else if (timelineName === "mix") {
-							let timeline = new PathConstraintMixTimeline(timelineMap.size, timelineMap.size * 3, constraintIndex);
-							let time = getValue(keyMap, "time", 0);
-							let mixRotate = getValue(keyMap, "mixRotate", 1);
-							let mixX = getValue(keyMap, "mixX", 1);
-							let mixY = getValue(keyMap, "mixY", mixX);
-							for (let frame = 0, bezier = 0;; frame++) {
-								timeline.setFrame(frame, time, mixRotate, mixX, mixY);
-								let nextMap = timelineMap[frame + 1];
-								if (!nextMap) {
-									timeline.shrink(bezier);
-									break;
-								}
-								let time2 = getValue(nextMap, "time", 0);
-								let mixRotate2 = getValue(nextMap, "mixRotate", 1);
-								let mixX2 = getValue(nextMap, "mixX", 1);
-								let mixY2 = getValue(nextMap, "mixY", mixX2);
-								let curve = keyMap.curve;
-								if (curve) {
-									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1);
-									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1);
-								}
-								time = time2;
-								mixRotate = mixRotate2;
-								mixX = mixX2;
-								mixY = mixY2;
-								keyMap = nextMap;
-							}
-							timelines.push(timeline);
-						}
-					}
-				}
-			}
-
-			// Deform timelines.
-			if (map.deform) {
-				for (let deformName in map.deform) {
-					let deformMap = map.deform[deformName];
-					let skin = skeletonData.findSkin(deformName);
-					for (let slotName in deformMap) {
-						let slotMap = deformMap[slotName];
-						let slotIndex = skeletonData.findSlotIndex(slotName);
-						for (let timelineName in slotMap) {
-							let timelineMap = slotMap[timelineName];
-							let keyMap = timelineMap[0];
-							if (!keyMap) continue;
-
-							let attachment = <VertexAttachment>skin.getAttachment(slotIndex, timelineName);
-							let weighted = attachment.bones;
-							let vertices = attachment.vertices;
-							let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
-
-							let timeline = new DeformTimeline(timelineMap.length, timelineMap.length, slotIndex, attachment);
-							let time = getValue(keyMap, "time", 0);
-							for (let frame = 0, bezier = 0;; frame++) {
-								let deform: ArrayLike<number>;
-								let verticesValue: Array<Number> = getValue(keyMap, "vertices", null);
-								if (!verticesValue)
-									deform = weighted ? Utils.newFloatArray(deformLength) : vertices;
-								else {
-									deform = Utils.newFloatArray(deformLength);
-									let start = <number>getValue(keyMap, "offset", 0);
-									Utils.arrayCopy(verticesValue, 0, deform, start, verticesValue.length);
-									if (scale != 1) {
-										for (let i = start, n = i + verticesValue.length; i < n; i++)
-											deform[i] *= scale;
-									}
-									if (!weighted) {
-										for (let i = 0; i < deformLength; i++)
-											deform[i] += vertices[i];
-									}
-								}
-
-								timeline.setFrame(frame, time, deform);
-								let nextMap = timelineMap[frame + 1];
-								if (!nextMap) {
-									timeline.shrink(bezier);
-									break;
-								}
-								let time2 = getValue(nextMap, "time", 0);
-								let curve = keyMap.curve;
-								if (curve) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1);
-								time = time2;
-								keyMap = nextMap;
-							}
-							timelines.push(timeline);
-						}
-					}
-				}
-			}
-
-			// Draw order timelines.
-			if (map.drawOrder) {
-				let timeline = new DrawOrderTimeline(map.drawOrder.length);
-				let slotCount = skeletonData.slots.length;
-				let frame = 0;
-				for (let i = 0; i < map.drawOrder.length; i++, frame++) {
-					let drawOrderMap = map.drawOrder[i];
-					let drawOrder: Array<number> = null;
-					let offsets = getValue(drawOrderMap, "offsets", null);
-					if (offsets) {
-						drawOrder = Utils.newArray<number>(slotCount, -1);
-						let unchanged = Utils.newArray<number>(slotCount - offsets.length, 0);
-						let originalIndex = 0, unchangedIndex = 0;
-						for (let ii = 0; ii < offsets.length; ii++) {
-							let offsetMap = offsets[ii];
-							let slotIndex = skeletonData.findSlotIndex(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 (let ii = slotCount - 1; ii >= 0; ii--)
-							if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
-					}
-					timeline.setFrame(frame, getValue(drawOrderMap, "time", 0), drawOrder);
-				}
-				timelines.push(timeline);
-			}
-
-			// Event timelines.
-			if (map.events) {
-				let timeline = new EventTimeline(map.events.length);
-				let frame = 0;
-				for (let i = 0; i < map.events.length; i++, frame++) {
-					let eventMap = map.events[i];
-					let eventData = skeletonData.findEvent(eventMap.name);
-					let event = new Event(Utils.toSinglePrecision(getValue(eventMap, "time", 0)), eventData);
-					event.intValue = getValue(eventMap, "int", eventData.intValue);
-					event.floatValue = getValue(eventMap, "float", eventData.floatValue);
-					event.stringValue = getValue(eventMap, "string", eventData.stringValue);
-					if (event.data.audioPath) {
-						event.volume = getValue(eventMap, "volume", 1);
-						event.balance = getValue(eventMap, "balance", 0);
-					}
-					timeline.setFrame(frame, event);
-				}
-				timelines.push(timeline);
-			}
-
-			let duration = 0;
-			for (let i = 0, n = timelines.length; i < n; i++)
-				duration = Math.max(duration, timelines[i].getDuration());
-			skeletonData.animations.push(new Animation(name, timelines, duration));
-		}
-	}
-
-	class LinkedMesh {
-		parent: string; skin: string;
-		slotIndex: number;
-		mesh: MeshAttachment;
-		inheritDeform: boolean;
-
-		constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) {
-			this.mesh = mesh;
-			this.skin = skin;
-			this.slotIndex = slotIndex;
-			this.parent = parent;
-			this.inheritDeform = inheritDeform;
-		}
-	}
-
-	function readTimeline1 (keys: any[], timeline: CurveTimeline1, defaultValue: number, scale: number) {
-		let keyMap = keys[0];
-		let time = getValue(keyMap, "time", 0);
-		let value = getValue(keyMap, "value", defaultValue) * scale;
-		let bezier = 0;
-		for (let frame = 0;; frame++) {
-			timeline.setFrame(frame, time, value);
-			let nextMap = keys[frame + 1];
-			if (!nextMap) {
-				timeline.shrink(bezier);
-				return timeline;
-			}
-			let time2 = getValue(nextMap, "time", 0);
-			let value2 = getValue(nextMap, "value", defaultValue) * scale;
-			if (keyMap.curve) bezier = readCurve(keyMap.curve, timeline, bezier, frame, 0, time, time2, value, value2, scale);
-			time = time2;
-			value = value2;
-			keyMap = nextMap;
-		}
-	}
-
-	function readTimeline2 (keys: any[], timeline: CurveTimeline2, name1: string, name2: string, defaultValue: number, scale: number) {
-		let keyMap = keys[0];
-		let time = getValue(keyMap, "time", 0);
-		let value1 = getValue(keyMap, name1, defaultValue) * scale;
-		let value2 = getValue(keyMap, name2, defaultValue) * scale;
-		let bezier = 0;
-		for (let frame = 0;; frame++) {
-			timeline.setFrame(frame, time, value1, value2);
-			let nextMap = keys[frame + 1];
-			if (!nextMap) {
-				timeline.shrink(bezier);
-				return timeline;
-			}
-			let time2 = getValue(nextMap, "time", 0);
-			let nvalue1 = getValue(nextMap, name1, defaultValue) * scale;
-			let nvalue2 = getValue(nextMap, name2, defaultValue) * scale;
-			let curve = keyMap.curve;
-			if (curve) {
-				bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale);
-				bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale);
-			}
-			time = time2;
-			value1 = nvalue1;
-			value2 = nvalue2;
-			keyMap = nextMap;
-		}
-	}
-
-	function readCurve (curve: any, timeline: CurveTimeline, bezier: number, frame: number, value: number, time1: number, time2: number,
-		value1: number, value2: number, scale: number) {
-		if (curve == "stepped") {
-			timeline.setStepped(frame);
-			return bezier;
-		}
-		let i = value << 2;
-		let cx1 = curve[i];
-		let cy1 = curve[i + 1] * scale;
-		let cx2 = curve[i + 2];
-		let cy2 = curve[i + 3] * scale;
-		timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
-		return bezier + 1;
-	}
-
-	function getValue (map: any, property: string, defaultValue: any) {
-		return map[property] !== undefined ? map[property] : defaultValue;
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Loads skeleton data in the Spine JSON format.
+	 *
+	 * See [Spine JSON format](http://esotericsoftware.com/spine-json-format) and
+	 * [JSON and binary data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the Spine
+	 * Runtimes Guide. */
+	export class SkeletonJson {
+		attachmentLoader: AttachmentLoader;
+
+		/** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at
+		 * runtime than were used in Spine.
+		 *
+		 * See [Scaling](http://esotericsoftware.com/spine-loading-skeleton-data#Scaling) in the Spine Runtimes Guide. */
+		scale = 1;
+		private linkedMeshes = new Array<LinkedMesh>();
+
+		constructor (attachmentLoader: AttachmentLoader) {
+			this.attachmentLoader = attachmentLoader;
+		}
+
+		readSkeletonData (json: string | any): SkeletonData {
+			let scale = this.scale;
+			let skeletonData = new SkeletonData();
+			let root = typeof (json) === "string" ? JSON.parse(json) : json;
+
+			// Skeleton
+			let skeletonMap = root.skeleton;
+			if (skeletonMap) {
+				skeletonData.hash = skeletonMap.hash;
+				skeletonData.version = skeletonMap.spine;
+				skeletonData.x = skeletonMap.x;
+				skeletonData.y = skeletonMap.y;
+				skeletonData.width = skeletonMap.width;
+				skeletonData.height = skeletonMap.height;
+				skeletonData.fps = skeletonMap.fps;
+				skeletonData.imagesPath = skeletonMap.images;
+			}
+
+			// Bones
+			if (root.bones) {
+				for (let i = 0; i < root.bones.length; i++) {
+					let boneMap = root.bones[i];
+
+					let parent: BoneData = null;
+					let parentName: string = getValue(boneMap, "parent", null);
+					if (parentName) parent = skeletonData.findBone(parentName);
+					let data = new BoneData(skeletonData.bones.length, boneMap.name, parent);
+					data.length = getValue(boneMap, "length", 0) * scale;
+					data.x = getValue(boneMap, "x", 0) * scale;
+					data.y = getValue(boneMap, "y", 0) * scale;
+					data.rotation = getValue(boneMap, "rotation", 0);
+					data.scaleX = getValue(boneMap, "scaleX", 1);
+					data.scaleY = getValue(boneMap, "scaleY", 1);
+					data.shearX = getValue(boneMap, "shearX", 0);
+					data.shearY = getValue(boneMap, "shearY", 0);
+					data.transformMode = Utils.enumValue(TransformMode, getValue(boneMap, "transform", "Normal"));
+					data.skinRequired = getValue(boneMap, "skin", false);
+
+					let color = getValue(boneMap, "color", null);
+					if (color) data.color.setFromString(color);
+
+					skeletonData.bones.push(data);
+				}
+			}
+
+			// Slots.
+			if (root.slots) {
+				for (let i = 0; i < root.slots.length; i++) {
+					let slotMap = root.slots[i];
+					let boneData = skeletonData.findBone(slotMap.bone);
+					let data = new SlotData(skeletonData.slots.length, slotMap.name, boneData);
+
+					let color: string = getValue(slotMap, "color", null);
+					if (color) data.color.setFromString(color);
+
+					let dark: string = getValue(slotMap, "dark", null);
+					if (dark) data.darkColor = Color.fromString(dark);
+
+					data.attachmentName = getValue(slotMap, "attachment", null);
+					data.blendMode = Utils.enumValue(BlendMode, getValue(slotMap, "blend", "normal"));
+					skeletonData.slots.push(data);
+				}
+			}
+
+			// IK constraints
+			if (root.ik) {
+				for (let i = 0; i < root.ik.length; i++) {
+					let constraintMap = root.ik[i];
+					let data = new IkConstraintData(constraintMap.name);
+					data.order = getValue(constraintMap, "order", 0);
+					data.skinRequired = getValue(constraintMap, "skin", false);
+
+					for (let ii = 0; ii < constraintMap.bones.length; ii++)
+						data.bones.push(skeletonData.findBone(constraintMap.bones[ii]));
+
+					data.target = skeletonData.findBone(constraintMap.target);
+
+					data.mix = getValue(constraintMap, "mix", 1);
+					data.softness = getValue(constraintMap, "softness", 0) * scale;
+					data.bendDirection = getValue(constraintMap, "bendPositive", true) ? 1 : -1;
+					data.compress = getValue(constraintMap, "compress", false);
+					data.stretch = getValue(constraintMap, "stretch", false);
+					data.uniform = getValue(constraintMap, "uniform", false);
+
+					skeletonData.ikConstraints.push(data);
+				}
+			}
+
+			// Transform constraints.
+			if (root.transform) {
+				for (let i = 0; i < root.transform.length; i++) {
+					let constraintMap = root.transform[i];
+					let data = new TransformConstraintData(constraintMap.name);
+					data.order = getValue(constraintMap, "order", 0);
+					data.skinRequired = getValue(constraintMap, "skin", false);
+
+					for (let ii = 0; ii < constraintMap.bones.length; ii++)
+						data.bones.push(skeletonData.findBone(constraintMap.bones[ii]));
+
+					let targetName: string = constraintMap.target;
+					data.target = skeletonData.findBone(targetName);
+
+					data.local = getValue(constraintMap, "local", false);
+					data.relative = getValue(constraintMap, "relative", false);
+					data.offsetRotation = getValue(constraintMap, "rotation", 0);
+					data.offsetX = getValue(constraintMap, "x", 0) * scale;
+					data.offsetY = getValue(constraintMap, "y", 0) * scale;
+					data.offsetScaleX = getValue(constraintMap, "scaleX", 0);
+					data.offsetScaleY = getValue(constraintMap, "scaleY", 0);
+					data.offsetShearY = getValue(constraintMap, "shearY", 0);
+
+					data.mixRotate = getValue(constraintMap, "mixRotate", 1);
+					data.mixX = getValue(constraintMap, "mixX", 1);
+					data.mixY = getValue(constraintMap, "mixY", data.mixX);
+					data.mixScaleX = getValue(constraintMap, "mixScaleX", 1);
+					data.mixScaleY = getValue(constraintMap, "mixScaleY", data.mixScaleX);
+					data.mixShearY = getValue(constraintMap, "mixShearY", 1);
+
+					skeletonData.transformConstraints.push(data);
+				}
+			}
+
+			// Path constraints.
+			if (root.path) {
+				for (let i = 0; i < root.path.length; i++) {
+					let constraintMap = root.path[i];
+					let data = new PathConstraintData(constraintMap.name);
+					data.order = getValue(constraintMap, "order", 0);
+					data.skinRequired = getValue(constraintMap, "skin", false);
+
+					for (let ii = 0; ii < constraintMap.bones.length; ii++)
+						data.bones.push(skeletonData.findBone(constraintMap.bones[ii]));
+
+					let targetName: string = constraintMap.target;
+					data.target = skeletonData.findSlot(targetName);
+
+					data.positionMode = Utils.enumValue(PositionMode, getValue(constraintMap, "positionMode", "Percent"));
+					data.spacingMode = Utils.enumValue(SpacingMode, getValue(constraintMap, "spacingMode", "Length"));
+					data.rotateMode = Utils.enumValue(RotateMode, getValue(constraintMap, "rotateMode", "Tangent"));
+					data.offsetRotation = getValue(constraintMap, "rotation", 0);
+					data.position = getValue(constraintMap, "position", 0);
+					if (data.positionMode == PositionMode.Fixed) data.position *= scale;
+					data.spacing = getValue(constraintMap, "spacing", 0);
+					if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
+					data.mixRotate = getValue(constraintMap, "mixRotate", 1);
+					data.mixX = getValue(constraintMap, "mixX", 1);
+					data.mixY = getValue(constraintMap, "mixY", data.mixX);
+
+					skeletonData.pathConstraints.push(data);
+				}
+			}
+
+			// Skins.
+			if (root.skins) {
+				for (let i = 0; i < root.skins.length; i++) {
+					let skinMap = root.skins[i]
+					let skin = new Skin(skinMap.name);
+
+					if (skinMap.bones) {
+						for (let ii = 0; ii < skinMap.bones.length; ii++)
+							skin.bones.push(skeletonData.findBone(skinMap.bones[ii]));
+					}
+
+					if (skinMap.ik) {
+						for (let ii = 0; ii < skinMap.ik.length; ii++)
+							skin.constraints.push(skeletonData.findIkConstraint(skinMap.ik[ii]));
+					}
+
+					if (skinMap.transform) {
+						for (let ii = 0; ii < skinMap.transform.length; ii++)
+							skin.constraints.push(skeletonData.findTransformConstraint(skinMap.transform[ii]));
+					}
+
+					if (skinMap.path) {
+						for (let ii = 0; ii < skinMap.path.length; ii++)
+							skin.constraints.push(skeletonData.findPathConstraint(skinMap.path[ii]));
+					}
+
+					for (let slotName in skinMap.attachments) {
+						let slot = skeletonData.findSlot(slotName);
+						let slotMap = skinMap.attachments[slotName];
+						for (let entryName in slotMap) {
+							let attachment = this.readAttachment(slotMap[entryName], skin, slot.index, entryName, skeletonData);
+							if (attachment) skin.setAttachment(slot.index, entryName, attachment);
+						}
+					}
+					skeletonData.skins.push(skin);
+					if (skin.name == "default") skeletonData.defaultSkin = skin;
+				}
+			}
+
+			// Linked meshes.
+			for (let i = 0, n = this.linkedMeshes.length; i < n; i++) {
+				let linkedMesh = this.linkedMeshes[i];
+				let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
+				let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
+				linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
+				linkedMesh.mesh.setParentMesh(<MeshAttachment>parent);
+				linkedMesh.mesh.updateUVs();
+			}
+			this.linkedMeshes.length = 0;
+
+			// Events.
+			if (root.events) {
+				for (let eventName in root.events) {
+					let eventMap = root.events[eventName];
+					let data = new EventData(eventName);
+					data.intValue = getValue(eventMap, "int", 0);
+					data.floatValue = getValue(eventMap, "float", 0);
+					data.stringValue = getValue(eventMap, "string", "");
+					data.audioPath = getValue(eventMap, "audio", null);
+					if (data.audioPath) {
+						data.volume = getValue(eventMap, "volume", 1);
+						data.balance = getValue(eventMap, "balance", 0);
+					}
+					skeletonData.events.push(data);
+				}
+			}
+
+			// Animations.
+			if (root.animations) {
+				for (let animationName in root.animations) {
+					let animationMap = root.animations[animationName];
+					this.readAnimation(animationMap, animationName, skeletonData);
+				}
+			}
+
+			return skeletonData;
+		}
+
+		readAttachment (map: any, skin: Skin, slotIndex: number, name: string, skeletonData: SkeletonData): Attachment {
+			let scale = this.scale;
+			name = getValue(map, "name", name);
+
+			switch (getValue(map, "type", "region")) {
+				case "region": {
+					let path = getValue(map, "path", name);
+					let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
+					if (!region) return null;
+					region.path = path;
+					region.x = getValue(map, "x", 0) * scale;
+					region.y = getValue(map, "y", 0) * scale;
+					region.scaleX = getValue(map, "scaleX", 1);
+					region.scaleY = getValue(map, "scaleY", 1);
+					region.rotation = getValue(map, "rotation", 0);
+					region.width = map.width * scale;
+					region.height = map.height * scale;
+
+					let color: string = getValue(map, "color", null);
+					if (color) region.color.setFromString(color);
+
+					region.updateOffset();
+					return region;
+				}
+				case "boundingbox": {
+					let box = this.attachmentLoader.newBoundingBoxAttachment(skin, name);
+					if (!box) return null;
+					this.readVertices(map, box, map.vertexCount << 1);
+					let color: string = getValue(map, "color", null);
+					if (color) box.color.setFromString(color);
+					return box;
+				}
+				case "mesh":
+				case "linkedmesh": {
+					let path = getValue(map, "path", name);
+					let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
+					if (!mesh) return null;
+					mesh.path = path;
+
+					let color = getValue(map, "color", null);
+					if (color) mesh.color.setFromString(color);
+
+					mesh.width = getValue(map, "width", 0) * scale;
+					mesh.height = getValue(map, "height", 0) * scale;
+
+					let parent: string = getValue(map, "parent", null);
+					if (parent) {
+						this.linkedMeshes.push(new LinkedMesh(mesh, <string>getValue(map, "skin", null), slotIndex, parent, getValue(map, "deform", true)));
+						return mesh;
+					}
+
+					let uvs: Array<number> = map.uvs;
+					this.readVertices(map, mesh, uvs.length);
+					mesh.triangles = map.triangles;
+					mesh.regionUVs = uvs;
+					mesh.updateUVs();
+
+					mesh.edges = getValue(map, "edges", null);
+					mesh.hullLength = getValue(map, "hull", 0) * 2;
+					return mesh;
+				}
+				case "path": {
+					let path = this.attachmentLoader.newPathAttachment(skin, name);
+					if (!path) return null;
+					path.closed = getValue(map, "closed", false);
+					path.constantSpeed = getValue(map, "constantSpeed", true);
+
+					let vertexCount = map.vertexCount;
+					this.readVertices(map, path, vertexCount << 1);
+
+					let lengths: Array<number> = Utils.newArray(vertexCount / 3, 0);
+					for (let i = 0; i < map.lengths.length; i++)
+						lengths[i] = map.lengths[i] * scale;
+					path.lengths = lengths;
+
+					let color: string = getValue(map, "color", null);
+					if (color) path.color.setFromString(color);
+					return path;
+				}
+				case "point": {
+					let point = this.attachmentLoader.newPointAttachment(skin, name);
+					if (!point) return null;
+					point.x = getValue(map, "x", 0) * scale;
+					point.y = getValue(map, "y", 0) * scale;
+					point.rotation = getValue(map, "rotation", 0);
+
+					let color = getValue(map, "color", null);
+					if (color) point.color.setFromString(color);
+					return point;
+				}
+				case "clipping": {
+					let clip = this.attachmentLoader.newClippingAttachment(skin, name);
+					if (!clip) return null;
+
+					let end = getValue(map, "end", null);
+					if (end) clip.endSlot = skeletonData.findSlot(end);
+
+					let vertexCount = map.vertexCount;
+					this.readVertices(map, clip, vertexCount << 1);
+
+					let color: string = getValue(map, "color", null);
+					if (color) clip.color.setFromString(color);
+					return clip;
+				}
+			}
+			return null;
+		}
+
+		readVertices (map: any, attachment: VertexAttachment, verticesLength: number) {
+			let scale = this.scale;
+			attachment.worldVerticesLength = verticesLength;
+			let vertices: Array<number> = map.vertices;
+			if (verticesLength == vertices.length) {
+				let scaledVertices = Utils.toFloatArray(vertices);
+				if (scale != 1) {
+					for (let i = 0, n = vertices.length; i < n; i++)
+						scaledVertices[i] *= scale;
+				}
+				attachment.vertices = scaledVertices;
+				return;
+			}
+			let weights = new Array<number>();
+			let bones = new Array<number>();
+			for (let i = 0, n = vertices.length; i < n;) {
+				let boneCount = vertices[i++];
+				bones.push(boneCount);
+				for (let nn = i + boneCount * 4; i < nn; i += 4) {
+					bones.push(vertices[i]);
+					weights.push(vertices[i + 1] * scale);
+					weights.push(vertices[i + 2] * scale);
+					weights.push(vertices[i + 3]);
+				}
+			}
+			attachment.bones = bones;
+			attachment.vertices = Utils.toFloatArray(weights);
+		}
+
+		readAnimation (map: any, name: string, skeletonData: SkeletonData) {
+			let scale = this.scale;
+			let timelines = new Array<Timeline>();
+
+			// Slot timelines.
+			if (map.slots) {
+				for (let slotName in map.slots) {
+					let slotMap = map.slots[slotName];
+					let slotIndex = skeletonData.findSlotIndex(slotName);
+					for (let timelineName in slotMap) {
+						let timelineMap = slotMap[timelineName];
+						if (!timelineMap) continue;
+						if (timelineName == "attachment") {
+							let timeline = new AttachmentTimeline(timelineMap.length, slotIndex);
+							for (let frame = 0; frame < timelineMap.length; frame++) {
+								let keyMap = timelineMap[frame];
+								timeline.setFrame(frame, getValue(keyMap, "time", 0), keyMap.name);
+							}
+							timelines.push(timeline);
+
+						} else if (timelineName == "rgba") {
+							let timeline = new RGBATimeline(timelineMap.length, timelineMap.length << 2, slotIndex);
+							let keyMap = timelineMap[0];
+							let time = getValue(keyMap, "time", 0);
+							let color = Color.fromString(keyMap.color);
+
+							for (let frame = 0, bezier = 0; ; frame++) {
+								timeline.setFrame(frame, time, color.r, color.g, color.b, color.a);
+								let nextMap = timelineMap[frame + 1];
+								if (!nextMap) {
+									timeline.shrink(bezier);
+									break;
+								}
+								let time2 = getValue(nextMap, "time", 0);
+								let newColor = Color.fromString(nextMap.color);
+								let curve = keyMap.curve;
+								if (curve) {
+									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1);
+								}
+								time = time2;
+								color = newColor;
+								keyMap = nextMap;
+							}
+
+							timelines.push(timeline);
+
+						} else if (timelineName == "rgb") {
+							let timeline = new RGBTimeline(timelineMap.length, timelineMap.length * 3, slotIndex);
+							let keyMap = timelineMap[0];
+							let time = getValue(keyMap, "time", 0);
+							let color = Color.fromString(keyMap.color);
+
+							for (let frame = 0, bezier = 0; ; frame++) {
+								timeline.setFrame(frame, time, color.r, color.g, color.b);
+								let nextMap = timelineMap[frame + 1];
+								if (!nextMap) {
+									timeline.shrink(bezier);
+									break;
+								}
+								let time2 = getValue(nextMap, "time", 0);
+								let newColor = Color.fromString(nextMap.color);
+								let curve = keyMap.curve;
+								if (curve) {
+									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1);
+								}
+								time = time2;
+								color = newColor;
+								keyMap = nextMap;
+							}
+
+							timelines.push(timeline);
+
+						} else if (timelineName == "alpha") {
+							timelines.push(readTimeline1(timelineMap, new AlphaTimeline(timelineMap.length, timelineMap.length, slotIndex), 0, 1));
+						} else if (timelineName == "rgba2") {
+							let timeline = new RGBA2Timeline(timelineMap.length, timelineMap.length * 7, slotIndex);
+
+							let keyMap = timelineMap[0];
+							let time = getValue(keyMap, "time", 0);
+							let color = Color.fromString(keyMap.light);
+							let color2 = Color.fromString(keyMap.dark);
+
+							for (let frame = 0, bezier = 0; ; frame++) {
+								timeline.setFrame(frame, time, color.r, color.g, color.b, color.a, color2.r, color2.g, color2.b);
+								let nextMap = timelineMap[frame + 1];
+								if (!nextMap) {
+									timeline.shrink(bezier);
+									break;
+								}
+								let time2 = getValue(nextMap, "time", 0);
+								let newColor = Color.fromString(nextMap.light);
+								let newColor2 = Color.fromString(nextMap.dark);
+								let curve = keyMap.curve;
+								if (curve) {
+									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.r, newColor2.r, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.g, newColor2.g, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, color2.b, newColor2.b, 1);
+								}
+								time = time2;
+								color = newColor;
+								color2 = newColor2;
+								keyMap = nextMap;
+							}
+
+							timelines.push(timeline);
+
+						} else if (timelineName == "rgb2") {
+							let timeline = new RGB2Timeline(timelineMap.length, timelineMap.length * 6, slotIndex);
+
+							let keyMap = timelineMap[0];
+							let time = getValue(keyMap, "time", 0);
+							let color = Color.fromString(keyMap.light);
+							let color2 = Color.fromString(keyMap.dark);
+
+							for (let frame = 0, bezier = 0; ; frame++) {
+								timeline.setFrame(frame, time, color.r, color.g, color.b, color2.r, color2.g, color2.b);
+								let nextMap = timelineMap[frame + 1];
+								if (!nextMap) {
+									timeline.shrink(bezier);
+									break;
+								}
+								let time2 = getValue(nextMap, "time", 0);
+								let newColor = Color.fromString(nextMap.light);
+								let newColor2 = Color.fromString(nextMap.dark);
+								let curve = keyMap.curve;
+								if (curve) {
+									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color2.r, newColor2.r, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.g, newColor2.g, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.b, newColor2.b, 1);
+								}
+								time = time2;
+								color = newColor;
+								color2 = newColor2;
+								keyMap = nextMap;
+							}
+
+							timelines.push(timeline);
+						}
+					}
+				}
+			}
+
+			// Bone timelines.
+			if (map.bones) {
+				for (let boneName in map.bones) {
+					let boneMap = map.bones[boneName];
+					let boneIndex = skeletonData.findBoneIndex(boneName);
+					for (let timelineName in boneMap) {
+						let timelineMap = boneMap[timelineName];
+						if (timelineMap.length == 0) continue;
+
+						if (timelineName === "rotate") {
+							timelines.push(readTimeline1(timelineMap, new RotateTimeline(timelineMap.length, timelineMap.length, boneIndex), 0, 1));
+						} else if (timelineName === "translate") {
+							let timeline = new TranslateTimeline(timelineMap.length, timelineMap.length << 1, boneIndex);
+							timelines.push(readTimeline2(timelineMap, timeline, "x", "y", 0, scale));
+						} else if (timelineName === "translatex") {
+							let timeline = new TranslateXTimeline(timelineMap.length, timelineMap.length, boneIndex);
+							timelines.push(readTimeline1(timelineMap, timeline, 0, scale));
+						} else if (timelineName === "translatey") {
+							let timeline = new TranslateYTimeline(timelineMap.length, timelineMap.length, boneIndex);
+							timelines.push(readTimeline1(timelineMap, timeline, 0, scale));
+						} else if (timelineName === "scale") {
+							let timeline = new ScaleTimeline(timelineMap.length, timelineMap.length << 1, boneIndex);
+							timelines.push(readTimeline2(timelineMap, timeline, "x", "y", 1, 1));
+						} else if (timelineName === "scalex") {
+							let timeline = new ScaleXTimeline(timelineMap.length, timelineMap.length, boneIndex);
+							timelines.push(readTimeline1(timelineMap, timeline, 1, 1));
+						} else if (timelineName === "scaley") {
+							let timeline = new ScaleYTimeline(timelineMap.length, timelineMap.length, boneIndex);
+							timelines.push(readTimeline1(timelineMap, timeline, 1, 1));
+						} else if (timelineName === "shear") {
+							let timeline = new ShearTimeline(timelineMap.length, timelineMap.length << 1, boneIndex);
+							timelines.push(readTimeline2(timelineMap, timeline, "x", "y", 0, 1));
+						} else if (timelineName === "shearx") {
+							let timeline = new ShearXTimeline(timelineMap.length, timelineMap.length, boneIndex);
+							timelines.push(readTimeline1(timelineMap, timeline, 0, 1));
+						} else if (timelineName === "sheary") {
+							let timeline = new ShearYTimeline(timelineMap.length, timelineMap.length, boneIndex);
+							timelines.push(readTimeline1(timelineMap, timeline, 0, 1));
+						}
+					}
+				}
+			}
+
+			// IK constraint timelines.
+			if (map.ik) {
+				for (let constraintName in map.ik) {
+					let constraintMap = map.ik[constraintName];
+					let keyMap = constraintMap[0];
+					if (!keyMap) continue;
+
+					let constraint = skeletonData.findIkConstraint(constraintName);
+					let constraintIndex = skeletonData.ikConstraints.indexOf(constraint);
+					let timeline = new IkConstraintTimeline(constraintMap.length, constraintMap.length << 1, constraintIndex);
+
+					let time = getValue(keyMap, "time", 0);
+					let mix = getValue(keyMap, "mix", 1);
+					let softness = getValue(keyMap, "softness", 0) * scale;
+
+					for (let frame = 0, bezier = 0; ; frame++) {
+						timeline.setFrame(frame, time, mix, softness, getValue(keyMap, "bendPositive", true) ? 1 : -1, getValue(keyMap, "compress", false), getValue(keyMap, "stretch", false));
+						let nextMap = constraintMap[frame + 1];
+						if (!nextMap) {
+							timeline.shrink(bezier);
+							break;
+						}
+
+						let time2 = getValue(nextMap, "time", 0);
+						let mix2 = getValue(nextMap, "mix", 1);
+						let softness2 = getValue(nextMap, "softness", 0) * scale;
+						let curve = keyMap.curve;
+						if (curve) {
+							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, scale);
+						}
+
+						time = time2;
+						mix = mix2;
+						softness = softness2;
+						keyMap = nextMap;
+					}
+					timelines.push(timeline);
+				}
+			}
+
+			// Transform constraint timelines.
+			if (map.transform) {
+				for (let constraintName in map.transform) {
+					let timelineMap = map.transform[constraintName];
+					let keyMap = timelineMap[0];
+					if (!keyMap) continue;
+
+					let constraint = skeletonData.findTransformConstraint(constraintName);
+					let constraintIndex = skeletonData.transformConstraints.indexOf(constraint);
+					let timeline = new TransformConstraintTimeline(timelineMap.length, timelineMap.length << 2, constraintIndex);
+
+					let time = getValue(keyMap, "time", 0);
+					let mixRotate = getValue(keyMap, "mixRotate", 1);
+					let mixX = getValue(keyMap, "mixX", 1);
+					let mixY = getValue(keyMap, "mixY", mixX);
+					let mixScaleX = getValue(keyMap, "mixScaleX", 1);
+					let mixScaleY = getValue(keyMap, "mixScaleY", mixScaleX);
+					let mixShearY = getValue(keyMap, "mixShearY", 1);
+
+					for (let frame = 0, bezier = 0; ; frame++) {
+						timeline.setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY);
+						let nextMap = timelineMap[frame + 1];
+						if (!nextMap) {
+							timeline.shrink(bezier);
+							break;
+						}
+
+						let time2 = getValue(nextMap, "time", 0);
+						let mixRotate2 = getValue(nextMap, "mixRotate", 1);
+						let mixX2 = getValue(nextMap, "mixX", 1);
+						let mixY2 = getValue(nextMap, "mixY", mixX2);
+						let mixScaleX2 = getValue(nextMap, "mixScaleX", 1);
+						let mixScaleY2 = getValue(nextMap, "mixScaleY", mixScaleX2);
+						let mixShearY2 = getValue(nextMap, "mixShearY", 1);
+						let curve = keyMap.curve;
+						if (curve) {
+							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1);
+						}
+
+						time = time2;
+						mixRotate = mixRotate2;
+						mixX = mixX2;
+						mixY = mixY2;
+						mixScaleX = mixScaleX2;
+						mixScaleY = mixScaleY2;
+						mixScaleX = mixScaleX2;
+						keyMap = nextMap;
+					}
+					timelines.push(timeline);
+				}
+			}
+
+			// Path constraint timelines.
+			if (map.path) {
+				for (let constraintName in map.path) {
+					let constraintMap = map.path[constraintName];
+					let constraint = skeletonData.findPathConstraint(constraintName);
+					let constraintIndex = skeletonData.pathConstraints.indexOf(constraint);
+					for (let timelineName in constraintMap) {
+						let timelineMap = constraintMap[timelineName];
+						let keyMap = timelineMap[0];
+						if (!keyMap) continue;
+
+						if (timelineName === "position") {
+							let timeline = new PathConstraintPositionTimeline(timelineMap.length, timelineMap.length, constraintIndex);
+							timelines.push(readTimeline1(timelineMap, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1));
+						} else if (timelineName === "spacing") {
+							let timeline = new PathConstraintSpacingTimeline(timelineMap.length, timelineMap.length, constraintIndex);
+							timelines.push(readTimeline1(timelineMap, timeline, 0, constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1));
+						} else if (timelineName === "mix") {
+							let timeline = new PathConstraintMixTimeline(timelineMap.size, timelineMap.size * 3, constraintIndex);
+							let time = getValue(keyMap, "time", 0);
+							let mixRotate = getValue(keyMap, "mixRotate", 1);
+							let mixX = getValue(keyMap, "mixX", 1);
+							let mixY = getValue(keyMap, "mixY", mixX);
+							for (let frame = 0, bezier = 0; ; frame++) {
+								timeline.setFrame(frame, time, mixRotate, mixX, mixY);
+								let nextMap = timelineMap[frame + 1];
+								if (!nextMap) {
+									timeline.shrink(bezier);
+									break;
+								}
+								let time2 = getValue(nextMap, "time", 0);
+								let mixRotate2 = getValue(nextMap, "mixRotate", 1);
+								let mixX2 = getValue(nextMap, "mixX", 1);
+								let mixY2 = getValue(nextMap, "mixY", mixX2);
+								let curve = keyMap.curve;
+								if (curve) {
+									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1);
+									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1);
+								}
+								time = time2;
+								mixRotate = mixRotate2;
+								mixX = mixX2;
+								mixY = mixY2;
+								keyMap = nextMap;
+							}
+							timelines.push(timeline);
+						}
+					}
+				}
+			}
+
+			// Deform timelines.
+			if (map.deform) {
+				for (let deformName in map.deform) {
+					let deformMap = map.deform[deformName];
+					let skin = skeletonData.findSkin(deformName);
+					for (let slotName in deformMap) {
+						let slotMap = deformMap[slotName];
+						let slotIndex = skeletonData.findSlotIndex(slotName);
+						for (let timelineName in slotMap) {
+							let timelineMap = slotMap[timelineName];
+							let keyMap = timelineMap[0];
+							if (!keyMap) continue;
+
+							let attachment = <VertexAttachment>skin.getAttachment(slotIndex, timelineName);
+							let weighted = attachment.bones;
+							let vertices = attachment.vertices;
+							let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
+
+							let timeline = new DeformTimeline(timelineMap.length, timelineMap.length, slotIndex, attachment);
+							let time = getValue(keyMap, "time", 0);
+							for (let frame = 0, bezier = 0; ; frame++) {
+								let deform: ArrayLike<number>;
+								let verticesValue: Array<Number> = getValue(keyMap, "vertices", null);
+								if (!verticesValue)
+									deform = weighted ? Utils.newFloatArray(deformLength) : vertices;
+								else {
+									deform = Utils.newFloatArray(deformLength);
+									let start = <number>getValue(keyMap, "offset", 0);
+									Utils.arrayCopy(verticesValue, 0, deform, start, verticesValue.length);
+									if (scale != 1) {
+										for (let i = start, n = i + verticesValue.length; i < n; i++)
+											deform[i] *= scale;
+									}
+									if (!weighted) {
+										for (let i = 0; i < deformLength; i++)
+											deform[i] += vertices[i];
+									}
+								}
+
+								timeline.setFrame(frame, time, deform);
+								let nextMap = timelineMap[frame + 1];
+								if (!nextMap) {
+									timeline.shrink(bezier);
+									break;
+								}
+								let time2 = getValue(nextMap, "time", 0);
+								let curve = keyMap.curve;
+								if (curve) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1);
+								time = time2;
+								keyMap = nextMap;
+							}
+							timelines.push(timeline);
+						}
+					}
+				}
+			}
+
+			// Draw order timelines.
+			if (map.drawOrder) {
+				let timeline = new DrawOrderTimeline(map.drawOrder.length);
+				let slotCount = skeletonData.slots.length;
+				let frame = 0;
+				for (let i = 0; i < map.drawOrder.length; i++, frame++) {
+					let drawOrderMap = map.drawOrder[i];
+					let drawOrder: Array<number> = null;
+					let offsets = getValue(drawOrderMap, "offsets", null);
+					if (offsets) {
+						drawOrder = Utils.newArray<number>(slotCount, -1);
+						let unchanged = Utils.newArray<number>(slotCount - offsets.length, 0);
+						let originalIndex = 0, unchangedIndex = 0;
+						for (let ii = 0; ii < offsets.length; ii++) {
+							let offsetMap = offsets[ii];
+							let slotIndex = skeletonData.findSlotIndex(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 (let ii = slotCount - 1; ii >= 0; ii--)
+							if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
+					}
+					timeline.setFrame(frame, getValue(drawOrderMap, "time", 0), drawOrder);
+				}
+				timelines.push(timeline);
+			}
+
+			// Event timelines.
+			if (map.events) {
+				let timeline = new EventTimeline(map.events.length);
+				let frame = 0;
+				for (let i = 0; i < map.events.length; i++, frame++) {
+					let eventMap = map.events[i];
+					let eventData = skeletonData.findEvent(eventMap.name);
+					let event = new Event(Utils.toSinglePrecision(getValue(eventMap, "time", 0)), eventData);
+					event.intValue = getValue(eventMap, "int", eventData.intValue);
+					event.floatValue = getValue(eventMap, "float", eventData.floatValue);
+					event.stringValue = getValue(eventMap, "string", eventData.stringValue);
+					if (event.data.audioPath) {
+						event.volume = getValue(eventMap, "volume", 1);
+						event.balance = getValue(eventMap, "balance", 0);
+					}
+					timeline.setFrame(frame, event);
+				}
+				timelines.push(timeline);
+			}
+
+			let duration = 0;
+			for (let i = 0, n = timelines.length; i < n; i++)
+				duration = Math.max(duration, timelines[i].getDuration());
+			skeletonData.animations.push(new Animation(name, timelines, duration));
+		}
+	}
+
+	class LinkedMesh {
+		parent: string; skin: string;
+		slotIndex: number;
+		mesh: MeshAttachment;
+		inheritDeform: boolean;
+
+		constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) {
+			this.mesh = mesh;
+			this.skin = skin;
+			this.slotIndex = slotIndex;
+			this.parent = parent;
+			this.inheritDeform = inheritDeform;
+		}
+	}
+
+	function readTimeline1 (keys: any[], timeline: CurveTimeline1, defaultValue: number, scale: number) {
+		let keyMap = keys[0];
+		let time = getValue(keyMap, "time", 0);
+		let value = getValue(keyMap, "value", defaultValue) * scale;
+		let bezier = 0;
+		for (let frame = 0; ; frame++) {
+			timeline.setFrame(frame, time, value);
+			let nextMap = keys[frame + 1];
+			if (!nextMap) {
+				timeline.shrink(bezier);
+				return timeline;
+			}
+			let time2 = getValue(nextMap, "time", 0);
+			let value2 = getValue(nextMap, "value", defaultValue) * scale;
+			if (keyMap.curve) bezier = readCurve(keyMap.curve, timeline, bezier, frame, 0, time, time2, value, value2, scale);
+			time = time2;
+			value = value2;
+			keyMap = nextMap;
+		}
+	}
+
+	function readTimeline2 (keys: any[], timeline: CurveTimeline2, name1: string, name2: string, defaultValue: number, scale: number) {
+		let keyMap = keys[0];
+		let time = getValue(keyMap, "time", 0);
+		let value1 = getValue(keyMap, name1, defaultValue) * scale;
+		let value2 = getValue(keyMap, name2, defaultValue) * scale;
+		let bezier = 0;
+		for (let frame = 0; ; frame++) {
+			timeline.setFrame(frame, time, value1, value2);
+			let nextMap = keys[frame + 1];
+			if (!nextMap) {
+				timeline.shrink(bezier);
+				return timeline;
+			}
+			let time2 = getValue(nextMap, "time", 0);
+			let nvalue1 = getValue(nextMap, name1, defaultValue) * scale;
+			let nvalue2 = getValue(nextMap, name2, defaultValue) * scale;
+			let curve = keyMap.curve;
+			if (curve) {
+				bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale);
+				bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale);
+			}
+			time = time2;
+			value1 = nvalue1;
+			value2 = nvalue2;
+			keyMap = nextMap;
+		}
+	}
+
+	function readCurve (curve: any, timeline: CurveTimeline, bezier: number, frame: number, value: number, time1: number, time2: number,
+		value1: number, value2: number, scale: number) {
+		if (curve == "stepped") {
+			timeline.setStepped(frame);
+			return bezier;
+		}
+		let i = value << 2;
+		let cx1 = curve[i];
+		let cy1 = curve[i + 1] * scale;
+		let cx2 = curve[i + 2];
+		let cy2 = curve[i + 3] * scale;
+		timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
+		return bezier + 1;
+	}
+
+	function getValue (map: any, property: string, defaultValue: any) {
+		return map[property] !== undefined ? map[property] : defaultValue;
+	}
+}

+ 202 - 202
spine-ts/core/src/Skin.ts

@@ -1,202 +1,202 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	/** Stores an entry in the skin consisting of the slot index, name, and attachment **/
-	export class SkinEntry {
-		constructor(public slotIndex: number, public name: string, public attachment: Attachment) { }
-	}
-
-	/** Stores attachments by slot index and attachment name.
-	 *
-	 * See SkeletonData {@link SkeletonData#defaultSkin}, Skeleton {@link Skeleton#skin}, and
-	 * [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide. */
-	export class Skin {
-		/** The skin's name, which is unique across all skins in the skeleton. */
-		name: string;
-
-		attachments = new Array<Map<Attachment>>();
-		bones = Array<BoneData>();
-		constraints = new Array<ConstraintData>();
-
-		constructor (name: string) {
-			if (!name) throw new Error("name cannot be null.");
-			this.name = name;
-		}
-
-		/** Adds an attachment to the skin for the specified slot index and name. */
-		setAttachment (slotIndex: number, name: string, attachment: Attachment) {
-			if (!attachment) throw new Error("attachment cannot be null.");
-			let attachments = this.attachments;
-			if (slotIndex >= attachments.length) attachments.length = slotIndex + 1;
-			if (!attachments[slotIndex]) attachments[slotIndex] = { };
-			attachments[slotIndex][name] = attachment;
-		}
-
-		/** Adds all attachments, bones, and constraints from the specified skin to this skin. */
-		addSkin (skin: Skin) {
-			for(let i = 0; i < skin.bones.length; i++) {
-				let bone = skin.bones[i];
-				let contained = false;
-				for (let ii = 0; ii < this.bones.length; ii++) {
-					if (this.bones[ii] == bone) {
-						contained = true;
-						break;
-					}
-				}
-				if (!contained) this.bones.push(bone);
-			}
-
-			for(let i = 0; i < skin.constraints.length; i++) {
-				let constraint = skin.constraints[i];
-				let contained = false;
-				for (let ii = 0; ii < this.constraints.length; ii++) {
-					if (this.constraints[ii] == constraint) {
-						contained = true;
-						break;
-					}
-				}
-				if (!contained) this.constraints.push(constraint);
-			}
-
-			let attachments = skin.getAttachments();
-			for (let i = 0; i < attachments.length; i++) {
-				var attachment = attachments[i];
-				this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment);
-			}
-		}
-
-		/** Adds all bones and constraints and copies of all attachments from the specified skin to this skin. Mesh attachments are not
-		 * copied, instead a new linked mesh is created. The attachment copies can be modified without affecting the originals. */
-		copySkin (skin: Skin) {
-			for(let i = 0; i < skin.bones.length; i++) {
-				let bone = skin.bones[i];
-				let contained = false;
-				for (let ii = 0; ii < this.bones.length; ii++) {
-					if (this.bones[ii] == bone) {
-						contained = true;
-						break;
-					}
-				}
-				if (!contained) this.bones.push(bone);
-			}
-
-			for(let i = 0; i < skin.constraints.length; i++) {
-				let constraint = skin.constraints[i];
-				let contained = false;
-				for (let ii = 0; ii < this.constraints.length; ii++) {
-					if (this.constraints[ii] == constraint) {
-						contained = true;
-						break;
-					}
-				}
-				if (!contained) this.constraints.push(constraint);
-			}
-
-			let attachments = skin.getAttachments();
-			for (let i = 0; i < attachments.length; i++) {
-				var attachment = attachments[i];
-				if (!attachment.attachment) continue;
-				if (attachment.attachment instanceof MeshAttachment) {
-					attachment.attachment = attachment.attachment.newLinkedMesh();
-					this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment);
-				} else {
-					attachment.attachment = attachment.attachment.copy();
-					this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment);
-				}
-			}
-		}
-
-		/** Returns the attachment for the specified slot index and name, or null. */
-		getAttachment (slotIndex: number, name: string): Attachment {
-			let dictionary = this.attachments[slotIndex];
-			return dictionary ? dictionary[name] : null;
-		}
-
-		/** Removes the attachment in the skin for the specified slot index and name, if any. */
-		removeAttachment (slotIndex: number, name: string) {
-			let dictionary = this.attachments[slotIndex];
-			if (dictionary) dictionary[name] = null;
-		}
-
-		/** Returns all attachments in this skin. */
-		getAttachments (): Array<SkinEntry> {
-			let entries = new Array<SkinEntry>();
-			for (var i = 0; i < this.attachments.length; i++) {
-				let slotAttachments = this.attachments[i];
-				if (slotAttachments) {
-					for (let name in slotAttachments) {
-						let attachment = slotAttachments[name];
-						if (attachment) entries.push(new SkinEntry(i, name, attachment));
-					}
-				}
-			}
-			return entries;
-		}
-
-		/** Returns all attachments in this skin for the specified slot index. */
-		getAttachmentsForSlot (slotIndex: number, attachments: Array<SkinEntry>) {
-			let slotAttachments = this.attachments[slotIndex];
-			if (slotAttachments) {
-				for (let name in slotAttachments) {
-					let attachment = slotAttachments[name];
-					if (attachment) attachments.push(new SkinEntry(slotIndex, name, attachment));
-				}
-			}
-		}
-
-		/** Clears all attachments, bones, and constraints. */
-		clear () {
-			this.attachments.length = 0;
-			this.bones.length = 0;
-			this.constraints.length = 0;
-		}
-
-		/** Attach each attachment in this skin if the corresponding attachment in the old skin is currently attached. */
-		attachAll (skeleton: Skeleton, oldSkin: Skin) {
-			let slotIndex = 0;
-			for (let i = 0; i < skeleton.slots.length; i++) {
-				let slot = skeleton.slots[i];
-				let slotAttachment = slot.getAttachment();
-				if (slotAttachment && slotIndex < oldSkin.attachments.length) {
-					let dictionary = oldSkin.attachments[slotIndex];
-					for (let key in dictionary) {
-						let skinAttachment:Attachment = dictionary[key];
-						if (slotAttachment == skinAttachment) {
-							let attachment = this.getAttachment(slotIndex, key);
-							if (attachment) slot.setAttachment(attachment);
-							break;
-						}
-					}
-				}
-				slotIndex++;
-			}
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	/** Stores an entry in the skin consisting of the slot index, name, and attachment **/
+	export class SkinEntry {
+		constructor (public slotIndex: number, public name: string, public attachment: Attachment) { }
+	}
+
+	/** Stores attachments by slot index and attachment name.
+	 *
+	 * See SkeletonData {@link SkeletonData#defaultSkin}, Skeleton {@link Skeleton#skin}, and
+	 * [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide. */
+	export class Skin {
+		/** The skin's name, which is unique across all skins in the skeleton. */
+		name: string;
+
+		attachments = new Array<Map<Attachment>>();
+		bones = Array<BoneData>();
+		constraints = new Array<ConstraintData>();
+
+		constructor (name: string) {
+			if (!name) throw new Error("name cannot be null.");
+			this.name = name;
+		}
+
+		/** Adds an attachment to the skin for the specified slot index and name. */
+		setAttachment (slotIndex: number, name: string, attachment: Attachment) {
+			if (!attachment) throw new Error("attachment cannot be null.");
+			let attachments = this.attachments;
+			if (slotIndex >= attachments.length) attachments.length = slotIndex + 1;
+			if (!attachments[slotIndex]) attachments[slotIndex] = {};
+			attachments[slotIndex][name] = attachment;
+		}
+
+		/** Adds all attachments, bones, and constraints from the specified skin to this skin. */
+		addSkin (skin: Skin) {
+			for (let i = 0; i < skin.bones.length; i++) {
+				let bone = skin.bones[i];
+				let contained = false;
+				for (let ii = 0; ii < this.bones.length; ii++) {
+					if (this.bones[ii] == bone) {
+						contained = true;
+						break;
+					}
+				}
+				if (!contained) this.bones.push(bone);
+			}
+
+			for (let i = 0; i < skin.constraints.length; i++) {
+				let constraint = skin.constraints[i];
+				let contained = false;
+				for (let ii = 0; ii < this.constraints.length; ii++) {
+					if (this.constraints[ii] == constraint) {
+						contained = true;
+						break;
+					}
+				}
+				if (!contained) this.constraints.push(constraint);
+			}
+
+			let attachments = skin.getAttachments();
+			for (let i = 0; i < attachments.length; i++) {
+				var attachment = attachments[i];
+				this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment);
+			}
+		}
+
+		/** Adds all bones and constraints and copies of all attachments from the specified skin to this skin. Mesh attachments are not
+		 * copied, instead a new linked mesh is created. The attachment copies can be modified without affecting the originals. */
+		copySkin (skin: Skin) {
+			for (let i = 0; i < skin.bones.length; i++) {
+				let bone = skin.bones[i];
+				let contained = false;
+				for (let ii = 0; ii < this.bones.length; ii++) {
+					if (this.bones[ii] == bone) {
+						contained = true;
+						break;
+					}
+				}
+				if (!contained) this.bones.push(bone);
+			}
+
+			for (let i = 0; i < skin.constraints.length; i++) {
+				let constraint = skin.constraints[i];
+				let contained = false;
+				for (let ii = 0; ii < this.constraints.length; ii++) {
+					if (this.constraints[ii] == constraint) {
+						contained = true;
+						break;
+					}
+				}
+				if (!contained) this.constraints.push(constraint);
+			}
+
+			let attachments = skin.getAttachments();
+			for (let i = 0; i < attachments.length; i++) {
+				var attachment = attachments[i];
+				if (!attachment.attachment) continue;
+				if (attachment.attachment instanceof MeshAttachment) {
+					attachment.attachment = attachment.attachment.newLinkedMesh();
+					this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment);
+				} else {
+					attachment.attachment = attachment.attachment.copy();
+					this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment);
+				}
+			}
+		}
+
+		/** Returns the attachment for the specified slot index and name, or null. */
+		getAttachment (slotIndex: number, name: string): Attachment {
+			let dictionary = this.attachments[slotIndex];
+			return dictionary ? dictionary[name] : null;
+		}
+
+		/** Removes the attachment in the skin for the specified slot index and name, if any. */
+		removeAttachment (slotIndex: number, name: string) {
+			let dictionary = this.attachments[slotIndex];
+			if (dictionary) dictionary[name] = null;
+		}
+
+		/** Returns all attachments in this skin. */
+		getAttachments (): Array<SkinEntry> {
+			let entries = new Array<SkinEntry>();
+			for (var i = 0; i < this.attachments.length; i++) {
+				let slotAttachments = this.attachments[i];
+				if (slotAttachments) {
+					for (let name in slotAttachments) {
+						let attachment = slotAttachments[name];
+						if (attachment) entries.push(new SkinEntry(i, name, attachment));
+					}
+				}
+			}
+			return entries;
+		}
+
+		/** Returns all attachments in this skin for the specified slot index. */
+		getAttachmentsForSlot (slotIndex: number, attachments: Array<SkinEntry>) {
+			let slotAttachments = this.attachments[slotIndex];
+			if (slotAttachments) {
+				for (let name in slotAttachments) {
+					let attachment = slotAttachments[name];
+					if (attachment) attachments.push(new SkinEntry(slotIndex, name, attachment));
+				}
+			}
+		}
+
+		/** Clears all attachments, bones, and constraints. */
+		clear () {
+			this.attachments.length = 0;
+			this.bones.length = 0;
+			this.constraints.length = 0;
+		}
+
+		/** Attach each attachment in this skin if the corresponding attachment in the old skin is currently attached. */
+		attachAll (skeleton: Skeleton, oldSkin: Skin) {
+			let slotIndex = 0;
+			for (let i = 0; i < skeleton.slots.length; i++) {
+				let slot = skeleton.slots[i];
+				let slotAttachment = slot.getAttachment();
+				if (slotAttachment && slotIndex < oldSkin.attachments.length) {
+					let dictionary = oldSkin.attachments[slotIndex];
+					for (let key in dictionary) {
+						let skinAttachment: Attachment = dictionary[key];
+						if (slotAttachment == skinAttachment) {
+							let attachment = this.getAttachment(slotIndex, key);
+							if (attachment) slot.setAttachment(attachment);
+							break;
+						}
+					}
+				}
+				slotIndex++;
+			}
+		}
+	}
+}

+ 118 - 118
spine-ts/core/src/Slot.ts

@@ -1,118 +1,118 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store
-	 * state for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared
-	 * across multiple skeletons. */
-	export class Slot {
-		/** The slot's setup pose data. */
-		data: SlotData;
-
-		/** The bone this slot belongs to. */
-		bone: Bone;
-
-		/** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two
-		 * color tinting. */
-		color: Color;
-
-		/** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
-		 * color's alpha is not used. */
-		darkColor: Color;
-
-		attachment: Attachment;
-
-		private attachmentTime: number;
-
-		attachmentState: number;
-
-		/** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
-		 * weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
-		 *
-		 * See {@link VertexAttachment#computeWorldVertices()} and {@link DeformTimeline}. */
-		deform = new Array<number>();
-
-		constructor (data: SlotData, bone: Bone) {
-			if (!data) throw new Error("data cannot be null.");
-			if (!bone) throw new Error("bone cannot be null.");
-			this.data = data;
-			this.bone = bone;
-			this.color = new Color();
-			this.darkColor = !data.darkColor ? null : new Color();
-			this.setToSetupPose();
-		}
-
-		/** The skeleton this slot belongs to. */
-		getSkeleton (): Skeleton {
-			return this.bone.skeleton;
-		}
-
-		/** The current attachment for the slot, or null if the slot has no attachment. */
-		getAttachment (): Attachment {
-			return this.attachment;
-		}
-
-		/** Sets the slot's attachment and, if the attachment changed, resets {@link #attachmentTime} and clears the {@link #deform}.
-		 * The deform is not cleared if the old attachment has the same {@link VertexAttachment#getDeformAttachment()} as the specified
-		 * attachment.
-		 * @param attachment May be null. */
-		setAttachment (attachment: Attachment) {
-			if (this.attachment == attachment) return;
-			if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment)
-			|| (<VertexAttachment>attachment).deformAttachment != (<VertexAttachment>this.attachment).deformAttachment) {
-				this.deform.length = 0;
-			}
-			this.attachment = attachment;
-			this.attachmentTime = this.bone.skeleton.time;
-		}
-
-		setAttachmentTime (time: number) {
-			this.attachmentTime = this.bone.skeleton.time - time;
-		}
-
-		/** The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton
-		 * {@link Skeleton#time}. */
-		getAttachmentTime (): number {
-			return this.bone.skeleton.time - this.attachmentTime;
-		}
-
-		/** Sets this slot to the setup pose. */
-		setToSetupPose () {
-			this.color.setFromColor(this.data.color);
-			if (this.darkColor) this.darkColor.setFromColor(this.data.darkColor);
-			if (!this.data.attachmentName)
-				this.attachment = null;
-			else {
-				this.attachment = null;
-				this.setAttachment(this.bone.skeleton.getAttachment(this.data.index, this.data.attachmentName));
-			}
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store
+	 * state for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared
+	 * across multiple skeletons. */
+	export class Slot {
+		/** The slot's setup pose data. */
+		data: SlotData;
+
+		/** The bone this slot belongs to. */
+		bone: Bone;
+
+		/** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two
+		 * color tinting. */
+		color: Color;
+
+		/** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
+		 * color's alpha is not used. */
+		darkColor: Color;
+
+		attachment: Attachment;
+
+		private attachmentTime: number;
+
+		attachmentState: number;
+
+		/** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
+		 * weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
+		 *
+		 * See {@link VertexAttachment#computeWorldVertices()} and {@link DeformTimeline}. */
+		deform = new Array<number>();
+
+		constructor (data: SlotData, bone: Bone) {
+			if (!data) throw new Error("data cannot be null.");
+			if (!bone) throw new Error("bone cannot be null.");
+			this.data = data;
+			this.bone = bone;
+			this.color = new Color();
+			this.darkColor = !data.darkColor ? null : new Color();
+			this.setToSetupPose();
+		}
+
+		/** The skeleton this slot belongs to. */
+		getSkeleton (): Skeleton {
+			return this.bone.skeleton;
+		}
+
+		/** The current attachment for the slot, or null if the slot has no attachment. */
+		getAttachment (): Attachment {
+			return this.attachment;
+		}
+
+		/** Sets the slot's attachment and, if the attachment changed, resets {@link #attachmentTime} and clears the {@link #deform}.
+		 * The deform is not cleared if the old attachment has the same {@link VertexAttachment#getDeformAttachment()} as the specified
+		 * attachment.
+		 * @param attachment May be null. */
+		setAttachment (attachment: Attachment) {
+			if (this.attachment == attachment) return;
+			if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment)
+				|| (<VertexAttachment>attachment).deformAttachment != (<VertexAttachment>this.attachment).deformAttachment) {
+				this.deform.length = 0;
+			}
+			this.attachment = attachment;
+			this.attachmentTime = this.bone.skeleton.time;
+		}
+
+		setAttachmentTime (time: number) {
+			this.attachmentTime = this.bone.skeleton.time - time;
+		}
+
+		/** The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton
+		 * {@link Skeleton#time}. */
+		getAttachmentTime (): number {
+			return this.bone.skeleton.time - this.attachmentTime;
+		}
+
+		/** Sets this slot to the setup pose. */
+		setToSetupPose () {
+			this.color.setFromColor(this.data.color);
+			if (this.darkColor) this.darkColor.setFromColor(this.data.darkColor);
+			if (!this.data.attachmentName)
+				this.attachment = null;
+			else {
+				this.attachment = null;
+				this.setAttachment(this.bone.skeleton.getAttachment(this.data.index, this.data.attachmentName));
+			}
+		}
+	}
+}

+ 69 - 69
spine-ts/core/src/SlotData.ts

@@ -1,69 +1,69 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the setup pose for a {@link Slot}. */
-	export class SlotData {
-		/** The index of the slot in {@link Skeleton#getSlots()}. */
-		index: number;
-
-		/** The name of the slot, which is unique across all slots in the skeleton. */
-		name: string;
-
-		/** The bone this slot belongs to. */
-		boneData: BoneData;
-
-		/** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two
-		 * color tinting. */
-		color = new Color(1, 1, 1, 1);
-
-		/** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
-		 * color's alpha is not used. */
-		darkColor: Color;
-
-		/** The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. */
-		attachmentName: string;
-
-		/** The blend mode for drawing the slot's attachment. */
-		blendMode: BlendMode;
-
-		constructor (index: number, name: string, boneData: BoneData) {
-			if (index < 0) throw new Error("index must be >= 0.");
-			if (!name) throw new Error("name cannot be null.");
-			if (!boneData) throw new Error("boneData cannot be null.");
-			this.index = index;
-			this.name = name;
-			this.boneData = boneData;
-		}
-	}
-
-	/** Determines how images are blended with existing pixels when drawn. */
-	export enum BlendMode { Normal, Additive, Multiply, Screen }
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the setup pose for a {@link Slot}. */
+	export class SlotData {
+		/** The index of the slot in {@link Skeleton#getSlots()}. */
+		index: number;
+
+		/** The name of the slot, which is unique across all slots in the skeleton. */
+		name: string;
+
+		/** The bone this slot belongs to. */
+		boneData: BoneData;
+
+		/** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two
+		 * color tinting. */
+		color = new Color(1, 1, 1, 1);
+
+		/** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
+		 * color's alpha is not used. */
+		darkColor: Color;
+
+		/** The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. */
+		attachmentName: string;
+
+		/** The blend mode for drawing the slot's attachment. */
+		blendMode: BlendMode;
+
+		constructor (index: number, name: string, boneData: BoneData) {
+			if (index < 0) throw new Error("index must be >= 0.");
+			if (!name) throw new Error("name cannot be null.");
+			if (!boneData) throw new Error("boneData cannot be null.");
+			this.index = index;
+			this.name = name;
+			this.boneData = boneData;
+		}
+	}
+
+	/** Determines how images are blended with existing pixels when drawn. */
+	export enum BlendMode { Normal, Additive, Multiply, Screen }
+}

+ 78 - 78
spine-ts/core/src/Texture.ts

@@ -1,78 +1,78 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	export abstract class Texture {
-		protected _image: HTMLImageElement | ImageBitmap;
-
-		constructor (image: HTMLImageElement | ImageBitmap) {
-			this._image = image;
-		}
-
-		getImage (): HTMLImageElement | ImageBitmap {
-			return this._image;
-		}
-
-		abstract setFilters (minFilter: TextureFilter, magFilter: TextureFilter): void;
-		abstract setWraps (uWrap: TextureWrap, vWrap: TextureWrap): void;
-		abstract dispose (): void;
-	}
-
-	export enum TextureFilter {
-		Nearest = 9728, // WebGLRenderingContext.NEAREST
-		Linear = 9729, // WebGLRenderingContext.LINEAR
-		MipMap = 9987, // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR
-		MipMapNearestNearest = 9984, // WebGLRenderingContext.NEAREST_MIPMAP_NEAREST
-		MipMapLinearNearest = 9985, // WebGLRenderingContext.LINEAR_MIPMAP_NEAREST
-		MipMapNearestLinear = 9986, // WebGLRenderingContext.NEAREST_MIPMAP_LINEAR
-		MipMapLinearLinear = 9987 // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR
-	}
-
-	export enum TextureWrap {
-		MirroredRepeat = 33648, // WebGLRenderingContext.MIRRORED_REPEAT
-		ClampToEdge = 33071, // WebGLRenderingContext.CLAMP_TO_EDGE
-		Repeat = 10497 // WebGLRenderingContext.REPEAT
-	}
-
-	export class TextureRegion {
-		renderObject: any;
-		u = 0; v = 0;
-		u2 = 0; v2 = 0;
-		width = 0; height = 0;
-		degrees = 0;
-		offsetX = 0; offsetY = 0;
-		originalWidth = 0; originalHeight = 0;
-	}
-
-	export class FakeTexture extends Texture {
-		setFilters(minFilter: TextureFilter, magFilter: TextureFilter) { }
-		setWraps(uWrap: TextureWrap, vWrap: TextureWrap) { }
-		dispose() { }
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	export abstract class Texture {
+		protected _image: HTMLImageElement | ImageBitmap;
+
+		constructor (image: HTMLImageElement | ImageBitmap) {
+			this._image = image;
+		}
+
+		getImage (): HTMLImageElement | ImageBitmap {
+			return this._image;
+		}
+
+		abstract setFilters (minFilter: TextureFilter, magFilter: TextureFilter): void;
+		abstract setWraps (uWrap: TextureWrap, vWrap: TextureWrap): void;
+		abstract dispose (): void;
+	}
+
+	export enum TextureFilter {
+		Nearest = 9728, // WebGLRenderingContext.NEAREST
+		Linear = 9729, // WebGLRenderingContext.LINEAR
+		MipMap = 9987, // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR
+		MipMapNearestNearest = 9984, // WebGLRenderingContext.NEAREST_MIPMAP_NEAREST
+		MipMapLinearNearest = 9985, // WebGLRenderingContext.LINEAR_MIPMAP_NEAREST
+		MipMapNearestLinear = 9986, // WebGLRenderingContext.NEAREST_MIPMAP_LINEAR
+		MipMapLinearLinear = 9987 // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR
+	}
+
+	export enum TextureWrap {
+		MirroredRepeat = 33648, // WebGLRenderingContext.MIRRORED_REPEAT
+		ClampToEdge = 33071, // WebGLRenderingContext.CLAMP_TO_EDGE
+		Repeat = 10497 // WebGLRenderingContext.REPEAT
+	}
+
+	export class TextureRegion {
+		renderObject: any;
+		u = 0; v = 0;
+		u2 = 0; v2 = 0;
+		width = 0; height = 0;
+		degrees = 0;
+		offsetX = 0; offsetY = 0;
+		originalWidth = 0; originalHeight = 0;
+	}
+
+	export class FakeTexture extends Texture {
+		setFilters (minFilter: TextureFilter, magFilter: TextureFilter) { }
+		setWraps (uWrap: TextureWrap, vWrap: TextureWrap) { }
+		dispose () { }
+	}
+}

+ 264 - 264
spine-ts/core/src/TextureAtlas.ts

@@ -1,264 +1,264 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	export class TextureAtlas implements Disposable {
-		pages = new Array<TextureAtlasPage>();
-		regions = new Array<TextureAtlasRegion>();
-
-		constructor (atlasText: string) {
-			let reader = new TextureAtlasReader(atlasText);
-			let entry = new Array<string>(4);
-			let page: TextureAtlasPage = null;
-			let region: TextureAtlasRegion = null;
-
-			let pageFields: Map<Function> = {};
-			pageFields["size"] = () => {
-				page.width = parseInt(entry[1]);
-				page.height = parseInt(entry[2]);
-			};
-			pageFields["format"] = () => {
-				// page.format = Format[tuple[0]]; we don't need format in WebGL
-			};
-			pageFields["filter"] = () => {
-				page.minFilter = Utils.enumValue(TextureFilter, entry[1]);
-				page.magFilter = Utils.enumValue(TextureFilter, entry[2]);
-			};
-			pageFields["repeat"] = () => {
-				if (entry[1].indexOf('x') != -1) page.uWrap = TextureWrap.Repeat;
-				if (entry[1].indexOf('y') != -1) page.vWrap = TextureWrap.Repeat;
-			};
-			pageFields["pma"] = () => {
-				page.pma = entry[1] == "true";
-			};
-
-			var regionFields: Map<Function> = {};
-			regionFields["xy"] = () => { // Deprecated, use bounds.
-				region.x = parseInt(entry[1]);
-				region.y = parseInt(entry[2]);
-			};
-			regionFields["size"] = () => { // Deprecated, use bounds.
-				region.width = parseInt(entry[1]);
-				region.height = parseInt(entry[2]);
-			};
-			regionFields["bounds"] = () => {
-				region.x = parseInt(entry[1]);
-				region.y = parseInt(entry[2]);
-				region.width = parseInt(entry[3]);
-				region.height = parseInt(entry[4]);
-			};
-			regionFields["offset"] = () => { // Deprecated, use offsets.
-				region.offsetX = parseInt(entry[1]);
-				region.offsetY = parseInt(entry[2]);
-			};
-			regionFields["orig"] = () => { // Deprecated, use offsets.
-				region.originalWidth = parseInt(entry[1]);
-				region.originalHeight = parseInt(entry[2]);
-			};
-			regionFields["offsets"] = () => {
-				region.offsetX = parseInt(entry[1]);
-				region.offsetY = parseInt(entry[2]);
-				region.originalWidth = parseInt(entry[3]);
-				region.originalHeight = parseInt(entry[4]);
-			};
-			regionFields["rotate"] = () => {
-				let value = entry[1];
-				if (value == "true")
-					region.degrees = 90;
-				else if (value != "false")
-					region.degrees = parseInt(value);
-			};
-			regionFields["index"] = () => {
-				region.index = parseInt(entry[1]);
-			};
-
-			let line = reader.readLine();
-			// Ignore empty lines before first entry.
-			while (line && line.trim().length == 0)
-				line = reader.readLine();
-			// Header entries.
-			while (true) {
-				if (!line || line.trim().length == 0) break;
-				if (reader.readEntry(entry, line) == 0) break; // Silently ignore all header fields.
-				line = reader.readLine();
-			}
-
-			// Page and region entries.
-			let names: string[] = null;
-			let values: number[][] = null;
-			while (true) {
-				if (line === null) break;
-				if (line.trim().length == 0) {
-					page = null;
-					line = reader.readLine();
-				} else if (!page) {
-					page = new TextureAtlasPage();
-					page.name = line.trim();
-					while (true) {
-						if (reader.readEntry(entry, line = reader.readLine()) == 0) break;
-						let field: Function = pageFields[entry[0]];
-						if (field) field();
-					}
-					this.pages.push(page);
-				} else {
-					region = new TextureAtlasRegion();
-
-					region.page = page;
-					region.name = line;
-					while (true) {
-						let count = reader.readEntry(entry, line = reader.readLine());
-						if (count == 0) break;
-						let field: Function = regionFields[entry[0]];
-						if (field)
-							field();
-						else {
-							if (!names) {
-								names = [];
-								values = [];
-							}
-							names.push(entry[0]);
-							let entryValues: number[] = [];
-							for (let i = 0; i < count; i++)
-								entryValues.push(parseInt(entry[i + 1]));
-							values.push(entryValues);
-						}
-					}
-					if (region.originalWidth == 0 && region.originalHeight == 0) {
-						region.originalWidth = region.width;
-						region.originalHeight = region.height;
-					}
-					if (names && names.length > 0) {
-						region.names = names;
-						region.values = values;
-						names = null;
-						values = null;
-					}
-					region.u = region.x / page.width;
-					region.v = region.y / page.height;
-					if (region.degrees == 90) {
-						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;
-					}
-					this.regions.push(region);
-				}
-			}
-		}
-
-		findRegion (name: string): TextureAtlasRegion {
-			for (let i = 0; i < this.regions.length; i++) {
-				if (this.regions[i].name == name) {
-					return this.regions[i];
-				}
-			}
-			return null;
-		}
-
-		setTextures (assetManager: AssetManager, pathPrefix: string = "") {
-			for (let page of this.pages)
-				page.setTexture(assetManager.get(pathPrefix + page.name));
-		}
-
-		dispose () {
-			for (let i = 0; i < this.pages.length; i++) {
-				this.pages[i].texture.dispose();
-			}
-		}
-	}
-
-	class TextureAtlasReader {
-		lines: Array<string>;
-		index: number = 0;
-
-		constructor (text: string) {
-			this.lines = text.split(/\r\n|\r|\n/);
-		}
-
-		readLine (): string {
-			if (this.index >= this.lines.length)
-				return null;
-			return this.lines[this.index++];
-		}
-
-		readEntry (entry: string[], line: string): number {
-			if (!line) return 0;
-			line = line.trim();
-			if (line.length == 0) return 0;
-
-			let colon = line.indexOf(':');
-			if (colon == -1) return 0;
-			entry[0] = line.substr(0, colon).trim();
-			for (let i = 1, lastMatch = colon + 1;; i++) {
-				let comma = line.indexOf(',', lastMatch);
-				if (comma == -1) {
-					entry[i] = line.substr(lastMatch).trim();
-					return i;
-				}
-				entry[i] = line.substr(lastMatch, comma - lastMatch).trim();
-				lastMatch = comma + 1;
-				if (i == 4) return 4;
-			}
-		}
-	}
-
-	export class TextureAtlasPage {
-		name: string;
-		minFilter: TextureFilter = TextureFilter.Nearest;
-		magFilter: TextureFilter = TextureFilter.Nearest;
-		uWrap: TextureWrap = TextureWrap.ClampToEdge;
-		vWrap: TextureWrap = TextureWrap.ClampToEdge;
-		texture: Texture;
-		width: number;
-		height: number;
-		pma: boolean;
-
-		setTexture (texture: Texture) {
-			this.texture = texture;
-			texture.setFilters(this.minFilter, this.magFilter);
-			texture.setWraps(this.uWrap, this.vWrap);
-		}
-	}
-
-	export class TextureAtlasRegion extends TextureRegion {
-		page: TextureAtlasPage;
-		name: string;
-		x: number;
-		y: number;
-		offsetX: number;
-		offsetY: number;
-		originalWidth: number;
-		originalHeight: number;
-		index: number;
-		degrees: number;
-		names: string[];
-		values: number[][];
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	export class TextureAtlas implements Disposable {
+		pages = new Array<TextureAtlasPage>();
+		regions = new Array<TextureAtlasRegion>();
+
+		constructor (atlasText: string) {
+			let reader = new TextureAtlasReader(atlasText);
+			let entry = new Array<string>(4);
+			let page: TextureAtlasPage = null;
+			let region: TextureAtlasRegion = null;
+
+			let pageFields: Map<Function> = {};
+			pageFields["size"] = () => {
+				page.width = parseInt(entry[1]);
+				page.height = parseInt(entry[2]);
+			};
+			pageFields["format"] = () => {
+				// page.format = Format[tuple[0]]; we don't need format in WebGL
+			};
+			pageFields["filter"] = () => {
+				page.minFilter = Utils.enumValue(TextureFilter, entry[1]);
+				page.magFilter = Utils.enumValue(TextureFilter, entry[2]);
+			};
+			pageFields["repeat"] = () => {
+				if (entry[1].indexOf('x') != -1) page.uWrap = TextureWrap.Repeat;
+				if (entry[1].indexOf('y') != -1) page.vWrap = TextureWrap.Repeat;
+			};
+			pageFields["pma"] = () => {
+				page.pma = entry[1] == "true";
+			};
+
+			var regionFields: Map<Function> = {};
+			regionFields["xy"] = () => { // Deprecated, use bounds.
+				region.x = parseInt(entry[1]);
+				region.y = parseInt(entry[2]);
+			};
+			regionFields["size"] = () => { // Deprecated, use bounds.
+				region.width = parseInt(entry[1]);
+				region.height = parseInt(entry[2]);
+			};
+			regionFields["bounds"] = () => {
+				region.x = parseInt(entry[1]);
+				region.y = parseInt(entry[2]);
+				region.width = parseInt(entry[3]);
+				region.height = parseInt(entry[4]);
+			};
+			regionFields["offset"] = () => { // Deprecated, use offsets.
+				region.offsetX = parseInt(entry[1]);
+				region.offsetY = parseInt(entry[2]);
+			};
+			regionFields["orig"] = () => { // Deprecated, use offsets.
+				region.originalWidth = parseInt(entry[1]);
+				region.originalHeight = parseInt(entry[2]);
+			};
+			regionFields["offsets"] = () => {
+				region.offsetX = parseInt(entry[1]);
+				region.offsetY = parseInt(entry[2]);
+				region.originalWidth = parseInt(entry[3]);
+				region.originalHeight = parseInt(entry[4]);
+			};
+			regionFields["rotate"] = () => {
+				let value = entry[1];
+				if (value == "true")
+					region.degrees = 90;
+				else if (value != "false")
+					region.degrees = parseInt(value);
+			};
+			regionFields["index"] = () => {
+				region.index = parseInt(entry[1]);
+			};
+
+			let line = reader.readLine();
+			// Ignore empty lines before first entry.
+			while (line && line.trim().length == 0)
+				line = reader.readLine();
+			// Header entries.
+			while (true) {
+				if (!line || line.trim().length == 0) break;
+				if (reader.readEntry(entry, line) == 0) break; // Silently ignore all header fields.
+				line = reader.readLine();
+			}
+
+			// Page and region entries.
+			let names: string[] = null;
+			let values: number[][] = null;
+			while (true) {
+				if (line === null) break;
+				if (line.trim().length == 0) {
+					page = null;
+					line = reader.readLine();
+				} else if (!page) {
+					page = new TextureAtlasPage();
+					page.name = line.trim();
+					while (true) {
+						if (reader.readEntry(entry, line = reader.readLine()) == 0) break;
+						let field: Function = pageFields[entry[0]];
+						if (field) field();
+					}
+					this.pages.push(page);
+				} else {
+					region = new TextureAtlasRegion();
+
+					region.page = page;
+					region.name = line;
+					while (true) {
+						let count = reader.readEntry(entry, line = reader.readLine());
+						if (count == 0) break;
+						let field: Function = regionFields[entry[0]];
+						if (field)
+							field();
+						else {
+							if (!names) {
+								names = [];
+								values = [];
+							}
+							names.push(entry[0]);
+							let entryValues: number[] = [];
+							for (let i = 0; i < count; i++)
+								entryValues.push(parseInt(entry[i + 1]));
+							values.push(entryValues);
+						}
+					}
+					if (region.originalWidth == 0 && region.originalHeight == 0) {
+						region.originalWidth = region.width;
+						region.originalHeight = region.height;
+					}
+					if (names && names.length > 0) {
+						region.names = names;
+						region.values = values;
+						names = null;
+						values = null;
+					}
+					region.u = region.x / page.width;
+					region.v = region.y / page.height;
+					if (region.degrees == 90) {
+						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;
+					}
+					this.regions.push(region);
+				}
+			}
+		}
+
+		findRegion (name: string): TextureAtlasRegion {
+			for (let i = 0; i < this.regions.length; i++) {
+				if (this.regions[i].name == name) {
+					return this.regions[i];
+				}
+			}
+			return null;
+		}
+
+		setTextures (assetManager: AssetManager, pathPrefix: string = "") {
+			for (let page of this.pages)
+				page.setTexture(assetManager.get(pathPrefix + page.name));
+		}
+
+		dispose () {
+			for (let i = 0; i < this.pages.length; i++) {
+				this.pages[i].texture.dispose();
+			}
+		}
+	}
+
+	class TextureAtlasReader {
+		lines: Array<string>;
+		index: number = 0;
+
+		constructor (text: string) {
+			this.lines = text.split(/\r\n|\r|\n/);
+		}
+
+		readLine (): string {
+			if (this.index >= this.lines.length)
+				return null;
+			return this.lines[this.index++];
+		}
+
+		readEntry (entry: string[], line: string): number {
+			if (!line) return 0;
+			line = line.trim();
+			if (line.length == 0) return 0;
+
+			let colon = line.indexOf(':');
+			if (colon == -1) return 0;
+			entry[0] = line.substr(0, colon).trim();
+			for (let i = 1, lastMatch = colon + 1; ; i++) {
+				let comma = line.indexOf(',', lastMatch);
+				if (comma == -1) {
+					entry[i] = line.substr(lastMatch).trim();
+					return i;
+				}
+				entry[i] = line.substr(lastMatch, comma - lastMatch).trim();
+				lastMatch = comma + 1;
+				if (i == 4) return 4;
+			}
+		}
+	}
+
+	export class TextureAtlasPage {
+		name: string;
+		minFilter: TextureFilter = TextureFilter.Nearest;
+		magFilter: TextureFilter = TextureFilter.Nearest;
+		uWrap: TextureWrap = TextureWrap.ClampToEdge;
+		vWrap: TextureWrap = TextureWrap.ClampToEdge;
+		texture: Texture;
+		width: number;
+		height: number;
+		pma: boolean;
+
+		setTexture (texture: Texture) {
+			this.texture = texture;
+			texture.setFilters(this.minFilter, this.magFilter);
+			texture.setWraps(this.uWrap, this.vWrap);
+		}
+	}
+
+	export class TextureAtlasRegion extends TextureRegion {
+		page: TextureAtlasPage;
+		name: string;
+		x: number;
+		y: number;
+		offsetX: number;
+		offsetY: number;
+		originalWidth: number;
+		originalHeight: number;
+		index: number;
+		degrees: number;
+		names: string[];
+		values: number[][];
+	}
+}

+ 279 - 279
spine-ts/core/src/TransformConstraint.ts

@@ -1,279 +1,279 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained
-	 * bones to match that of the target bone.
-	 *
-	 * See [Transform constraints](http://esotericsoftware.com/spine-transform-constraints) in the Spine User Guide. */
-	export class TransformConstraint implements Updatable {
-
-		/** The transform constraint's setup pose data. */
-		data: TransformConstraintData;
-
-		/** The bones that will be modified by this transform constraint. */
-		bones: Array<Bone>;
-
-		/** The target bone whose world transform will be copied to the constrained bones. */
-		target: Bone;
-
-		mixRotate = 0; mixX = 0; mixY = 0; mixScaleX = 0; mixScaleY = 0; mixShearY = 0;
-
-		temp = new Vector2();
-		active = false;
-
-		constructor (data: TransformConstraintData, skeleton: Skeleton) {
-			if (!data) throw new Error("data cannot be null.");
-			if (!skeleton) throw new Error("skeleton cannot be null.");
-			this.data = data;
-			this.mixRotate = data.mixRotate;
-			this.mixX = data.mixX;
-			this.mixY = data.mixY;
-			this.mixScaleX = data.mixScaleX;
-			this.mixScaleY = data.mixScaleY;
-			this.mixShearY = data.mixShearY;
-			this.bones = new Array<Bone>();
-			for (let i = 0; i < data.bones.length; i++)
-				this.bones.push(skeleton.findBone(data.bones[i].name));
-			this.target = skeleton.findBone(data.target.name);
-		}
-
-		isActive () {
-			return this.active;
-		}
-
-		update () {
-			if (this.mixRotate == 0 && this.mixX == 0 && this.mixY == 0 && this.mixScaleX == 0 && this.mixScaleX == 0 && this.mixShearY == 0) return;
-
-			if (this.data.local) {
-				if (this.data.relative)
-					this.applyRelativeLocal();
-				else
-					this.applyAbsoluteLocal();
-			} else {
-				if (this.data.relative)
-					this.applyRelativeWorld();
-				else
-					this.applyAbsoluteWorld();
-			}
-		}
-
-		applyAbsoluteWorld () {
-			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
-			mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
-			let translate = mixX != 0 || mixY != 0;
-
-			let target = this.target;
-			let ta = target.a, tb = target.b, tc = target.c, td = target.d;
-			let degRadReflect = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad;
-			let offsetRotation = this.data.offsetRotation * degRadReflect;
-			let offsetShearY = this.data.offsetShearY * degRadReflect;
-
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++) {
-				let bone = bones[i];
-
-				if (mixRotate != 0) {
-					let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-					let r = Math.atan2(tc, ta) - Math.atan2(c, a) + offsetRotation;
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) //
-						r += MathUtils.PI2;
-					r *= mixRotate;
-					let cos = Math.cos(r), sin = Math.sin(r);
-					bone.a = cos * a - sin * c;
-					bone.b = cos * b - sin * d;
-					bone.c = sin * a + cos * c;
-					bone.d = sin * b + cos * d;
-				}
-
-				if (translate) {
-					let temp = this.temp;
-					target.localToWorld(temp.set(this.data.offsetX, this.data.offsetY));
-					bone.worldX += (temp.x - bone.worldX) * mixX;
-					bone.worldY += (temp.y - bone.worldY) * mixY;
-				}
-
-				if (mixScaleX != 0) {
-					let s = Math.sqrt(bone.a * bone.a + bone.c * bone.c);
-					if (s != 0) s = (s + (Math.sqrt(ta * ta + tc * tc) - s + this.data.offsetScaleX) * mixScaleX) / s;
-					bone.a *= s;
-					bone.c *= s;
-				}
-				if (mixScaleY != 0) {
-					let s = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
-					if (s != 0) s = (s + (Math.sqrt(tb * tb + td * td) - s + this.data.offsetScaleY) * mixScaleY) / s;
-					bone.b *= s;
-					bone.d *= s;
-				}
-
-				if (mixShearY > 0) {
-					let b = bone.b, d = bone.d;
-					let by = Math.atan2(d, b);
-					let r = Math.atan2(td, tb) - Math.atan2(tc, ta) - (by - Math.atan2(bone.c, bone.a));
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) //
-						r += MathUtils.PI2;
-					r = by + (r + offsetShearY) * mixShearY;
-					let s = Math.sqrt(b * b + d * d);
-					bone.b = Math.cos(r) * s;
-					bone.d = Math.sin(r) * s;
-				}
-
-				bone.updateAppliedTransform();
-			}
-		}
-
-		applyRelativeWorld () {
-			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
-			mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
-			let translate = mixX != 0 || mixY != 0;
-
-			let target = this.target;
-			let ta = target.a, tb = target.b, tc = target.c, td = target.d;
-			let degRadReflect = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad;
-			let offsetRotation = this.data.offsetRotation * degRadReflect, offsetShearY = this.data.offsetShearY * degRadReflect;
-
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++) {
-				let bone = bones[i];
-
-				if (mixRotate != 0) {
-					let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-					let r = Math.atan2(tc, ta) + offsetRotation;
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) //
-						r += MathUtils.PI2;
-					r *= mixRotate;
-					let cos = Math.cos(r), sin = Math.sin(r);
-					bone.a = cos * a - sin * c;
-					bone.b = cos * b - sin * d;
-					bone.c = sin * a + cos * c;
-					bone.d = sin * b + cos * d;
-				}
-
-				if (translate) {
-					let temp = this.temp;
-					target.localToWorld(temp.set(this.data.offsetX, this.data.offsetY));
-					bone.worldX += temp.x * mixX;
-					bone.worldY += temp.y * mixY;
-				}
-
-				if (mixScaleX != 0) {
-					let s = (Math.sqrt(ta * ta + tc * tc) - 1 + this.data.offsetScaleX) * mixScaleX + 1;
-					bone.a *= s;
-					bone.c *= s;
-				}
-				if (mixScaleY != 0) {
-					let s = (Math.sqrt(tb * tb + td * td) - 1 + this.data.offsetScaleY) * mixScaleY + 1;
-					bone.b *= s;
-					bone.d *= s;
-				}
-
-				if (mixShearY > 0) {
-					let r = Math.atan2(td, tb) - Math.atan2(tc, ta);
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) //
-						r += MathUtils.PI2;
-					let b = bone.b, d = bone.d;
-					r = Math.atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY;
-					let s = Math.sqrt(b * b + d * d);
-					bone.b = Math.cos(r) * s;
-					bone.d = Math.sin(r) * s;
-				}
-
-				bone.updateAppliedTransform();
-			}
-		}
-
-		applyAbsoluteLocal () {
-			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
-			mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
-
-			let target = this.target;
-
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++) {
-				let bone = bones[i];
-
-				let rotation = bone.arotation;
-				if (mixRotate != 0) {
-					let r = target.arotation - rotation + this.data.offsetRotation;
-					r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360;
-					rotation += r * mixRotate;
-				}
-
-				let x = bone.ax, y = bone.ay;
-				x += (target.ax - x + this.data.offsetX) * mixX;
-				y += (target.ay - y + this.data.offsetY) * mixY;
-
-				let scaleX = bone.ascaleX, scaleY = bone.ascaleY;
-				if (mixScaleX != 0 && scaleX != 0)
-					scaleX = (scaleX + (target.ascaleX - scaleX + this.data.offsetScaleX) * mixScaleX) / scaleX;
-				if (mixScaleY != 0 && scaleY != 0)
-					scaleY = (scaleY + (target.ascaleY - scaleY + this.data.offsetScaleY) * mixScaleY) / scaleY;
-
-				let shearY = bone.ashearY;
-				if (mixShearY != 0) {
-					let r = target.ashearY - shearY + this.data.offsetShearY;
-					r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360;
-					shearY += r * mixShearY;
-				}
-
-				bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
-			}
-		}
-
-		applyRelativeLocal () {
-			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
-			mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
-
-			let target = this.target;
-
-			let bones = this.bones;
-			for (let i = 0, n = bones.length; i < n; i++) {
-				let bone = bones[i];
-
-				let rotation = bone.arotation + (target.arotation + this.data.offsetRotation) * mixRotate;
-				let x = bone.ax + (target.ax + this.data.offsetX) * mixX;
-				let y = bone.ay + (target.ay + this.data.offsetY) * mixY;
-				let scaleX = (bone.ascaleX * ((target.ascaleX - 1 + this.data.offsetScaleX) * mixScaleX) + 1);
-				let scaleY = (bone.ascaleY * ((target.ascaleY - 1 + this.data.offsetScaleY) * mixScaleY) + 1);
-				let shearY = bone.ashearY + (target.ashearY + this.data.offsetShearY) * mixShearY;
-
-				bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
-			}
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained
+	 * bones to match that of the target bone.
+	 *
+	 * See [Transform constraints](http://esotericsoftware.com/spine-transform-constraints) in the Spine User Guide. */
+	export class TransformConstraint implements Updatable {
+
+		/** The transform constraint's setup pose data. */
+		data: TransformConstraintData;
+
+		/** The bones that will be modified by this transform constraint. */
+		bones: Array<Bone>;
+
+		/** The target bone whose world transform will be copied to the constrained bones. */
+		target: Bone;
+
+		mixRotate = 0; mixX = 0; mixY = 0; mixScaleX = 0; mixScaleY = 0; mixShearY = 0;
+
+		temp = new Vector2();
+		active = false;
+
+		constructor (data: TransformConstraintData, skeleton: Skeleton) {
+			if (!data) throw new Error("data cannot be null.");
+			if (!skeleton) throw new Error("skeleton cannot be null.");
+			this.data = data;
+			this.mixRotate = data.mixRotate;
+			this.mixX = data.mixX;
+			this.mixY = data.mixY;
+			this.mixScaleX = data.mixScaleX;
+			this.mixScaleY = data.mixScaleY;
+			this.mixShearY = data.mixShearY;
+			this.bones = new Array<Bone>();
+			for (let i = 0; i < data.bones.length; i++)
+				this.bones.push(skeleton.findBone(data.bones[i].name));
+			this.target = skeleton.findBone(data.target.name);
+		}
+
+		isActive () {
+			return this.active;
+		}
+
+		update () {
+			if (this.mixRotate == 0 && this.mixX == 0 && this.mixY == 0 && this.mixScaleX == 0 && this.mixScaleX == 0 && this.mixShearY == 0) return;
+
+			if (this.data.local) {
+				if (this.data.relative)
+					this.applyRelativeLocal();
+				else
+					this.applyAbsoluteLocal();
+			} else {
+				if (this.data.relative)
+					this.applyRelativeWorld();
+				else
+					this.applyAbsoluteWorld();
+			}
+		}
+
+		applyAbsoluteWorld () {
+			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
+				mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
+			let translate = mixX != 0 || mixY != 0;
+
+			let target = this.target;
+			let ta = target.a, tb = target.b, tc = target.c, td = target.d;
+			let degRadReflect = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad;
+			let offsetRotation = this.data.offsetRotation * degRadReflect;
+			let offsetShearY = this.data.offsetShearY * degRadReflect;
+
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++) {
+				let bone = bones[i];
+
+				if (mixRotate != 0) {
+					let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+					let r = Math.atan2(tc, ta) - Math.atan2(c, a) + offsetRotation;
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) //
+						r += MathUtils.PI2;
+					r *= mixRotate;
+					let cos = Math.cos(r), sin = Math.sin(r);
+					bone.a = cos * a - sin * c;
+					bone.b = cos * b - sin * d;
+					bone.c = sin * a + cos * c;
+					bone.d = sin * b + cos * d;
+				}
+
+				if (translate) {
+					let temp = this.temp;
+					target.localToWorld(temp.set(this.data.offsetX, this.data.offsetY));
+					bone.worldX += (temp.x - bone.worldX) * mixX;
+					bone.worldY += (temp.y - bone.worldY) * mixY;
+				}
+
+				if (mixScaleX != 0) {
+					let s = Math.sqrt(bone.a * bone.a + bone.c * bone.c);
+					if (s != 0) s = (s + (Math.sqrt(ta * ta + tc * tc) - s + this.data.offsetScaleX) * mixScaleX) / s;
+					bone.a *= s;
+					bone.c *= s;
+				}
+				if (mixScaleY != 0) {
+					let s = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
+					if (s != 0) s = (s + (Math.sqrt(tb * tb + td * td) - s + this.data.offsetScaleY) * mixScaleY) / s;
+					bone.b *= s;
+					bone.d *= s;
+				}
+
+				if (mixShearY > 0) {
+					let b = bone.b, d = bone.d;
+					let by = Math.atan2(d, b);
+					let r = Math.atan2(td, tb) - Math.atan2(tc, ta) - (by - Math.atan2(bone.c, bone.a));
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) //
+						r += MathUtils.PI2;
+					r = by + (r + offsetShearY) * mixShearY;
+					let s = Math.sqrt(b * b + d * d);
+					bone.b = Math.cos(r) * s;
+					bone.d = Math.sin(r) * s;
+				}
+
+				bone.updateAppliedTransform();
+			}
+		}
+
+		applyRelativeWorld () {
+			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
+				mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
+			let translate = mixX != 0 || mixY != 0;
+
+			let target = this.target;
+			let ta = target.a, tb = target.b, tc = target.c, td = target.d;
+			let degRadReflect = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad;
+			let offsetRotation = this.data.offsetRotation * degRadReflect, offsetShearY = this.data.offsetShearY * degRadReflect;
+
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++) {
+				let bone = bones[i];
+
+				if (mixRotate != 0) {
+					let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+					let r = Math.atan2(tc, ta) + offsetRotation;
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) //
+						r += MathUtils.PI2;
+					r *= mixRotate;
+					let cos = Math.cos(r), sin = Math.sin(r);
+					bone.a = cos * a - sin * c;
+					bone.b = cos * b - sin * d;
+					bone.c = sin * a + cos * c;
+					bone.d = sin * b + cos * d;
+				}
+
+				if (translate) {
+					let temp = this.temp;
+					target.localToWorld(temp.set(this.data.offsetX, this.data.offsetY));
+					bone.worldX += temp.x * mixX;
+					bone.worldY += temp.y * mixY;
+				}
+
+				if (mixScaleX != 0) {
+					let s = (Math.sqrt(ta * ta + tc * tc) - 1 + this.data.offsetScaleX) * mixScaleX + 1;
+					bone.a *= s;
+					bone.c *= s;
+				}
+				if (mixScaleY != 0) {
+					let s = (Math.sqrt(tb * tb + td * td) - 1 + this.data.offsetScaleY) * mixScaleY + 1;
+					bone.b *= s;
+					bone.d *= s;
+				}
+
+				if (mixShearY > 0) {
+					let r = Math.atan2(td, tb) - Math.atan2(tc, ta);
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) //
+						r += MathUtils.PI2;
+					let b = bone.b, d = bone.d;
+					r = Math.atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY;
+					let s = Math.sqrt(b * b + d * d);
+					bone.b = Math.cos(r) * s;
+					bone.d = Math.sin(r) * s;
+				}
+
+				bone.updateAppliedTransform();
+			}
+		}
+
+		applyAbsoluteLocal () {
+			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
+				mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
+
+			let target = this.target;
+
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++) {
+				let bone = bones[i];
+
+				let rotation = bone.arotation;
+				if (mixRotate != 0) {
+					let r = target.arotation - rotation + this.data.offsetRotation;
+					r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360;
+					rotation += r * mixRotate;
+				}
+
+				let x = bone.ax, y = bone.ay;
+				x += (target.ax - x + this.data.offsetX) * mixX;
+				y += (target.ay - y + this.data.offsetY) * mixY;
+
+				let scaleX = bone.ascaleX, scaleY = bone.ascaleY;
+				if (mixScaleX != 0 && scaleX != 0)
+					scaleX = (scaleX + (target.ascaleX - scaleX + this.data.offsetScaleX) * mixScaleX) / scaleX;
+				if (mixScaleY != 0 && scaleY != 0)
+					scaleY = (scaleY + (target.ascaleY - scaleY + this.data.offsetScaleY) * mixScaleY) / scaleY;
+
+				let shearY = bone.ashearY;
+				if (mixShearY != 0) {
+					let r = target.ashearY - shearY + this.data.offsetShearY;
+					r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360;
+					shearY += r * mixShearY;
+				}
+
+				bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
+			}
+		}
+
+		applyRelativeLocal () {
+			let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
+				mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
+
+			let target = this.target;
+
+			let bones = this.bones;
+			for (let i = 0, n = bones.length; i < n; i++) {
+				let bone = bones[i];
+
+				let rotation = bone.arotation + (target.arotation + this.data.offsetRotation) * mixRotate;
+				let x = bone.ax + (target.ax + this.data.offsetX) * mixX;
+				let y = bone.ay + (target.ay + this.data.offsetY) * mixY;
+				let scaleX = (bone.ascaleX * ((target.ascaleX - 1 + this.data.offsetScaleX) * mixScaleX) + 1);
+				let scaleY = (bone.ascaleY * ((target.ascaleY - 1 + this.data.offsetScaleY) * mixScaleY) + 1);
+				let shearY = bone.ashearY + (target.ashearY + this.data.offsetShearY) * mixShearY;
+
+				bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
+			}
+		}
+	}
+}

+ 75 - 75
spine-ts/core/src/TransformConstraintData.ts

@@ -1,75 +1,75 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** Stores the setup pose for a {@link TransformConstraint}.
-	 *
-	 * See [Transform constraints](http://esotericsoftware.com/spine-transform-constraints) in the Spine User Guide. */
-	export class TransformConstraintData extends ConstraintData {
-
-		/** The bones that will be modified by this transform constraint. */
-		bones = new Array<BoneData>();
-
-		/** The target bone whose world transform will be copied to the constrained bones. */
-		target: BoneData;
-
-		mixRotate = 0;
-		mixX = 0;
-		mixY = 0;
-		mixScaleX = 0;
-		mixScaleY = 0;
-		mixShearY = 0;
-
-		/** An offset added to the constrained bone rotation. */
-		offsetRotation = 0;
-
-		/** An offset added to the constrained bone X translation. */
-		offsetX = 0;
-
-		/** An offset added to the constrained bone Y translation. */
-		offsetY = 0;
-
-		/** An offset added to the constrained bone scaleX. */
-		offsetScaleX = 0;
-
-		/** An offset added to the constrained bone scaleY. */
-		offsetScaleY = 0;
-
-		/** An offset added to the constrained bone shearY. */
-		offsetShearY = 0;
-
-		relative = false;
-		local = false;
-
-		constructor (name: string) {
-			super(name, 0, false);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** Stores the setup pose for a {@link TransformConstraint}.
+	 *
+	 * See [Transform constraints](http://esotericsoftware.com/spine-transform-constraints) in the Spine User Guide. */
+	export class TransformConstraintData extends ConstraintData {
+
+		/** The bones that will be modified by this transform constraint. */
+		bones = new Array<BoneData>();
+
+		/** The target bone whose world transform will be copied to the constrained bones. */
+		target: BoneData;
+
+		mixRotate = 0;
+		mixX = 0;
+		mixY = 0;
+		mixScaleX = 0;
+		mixScaleY = 0;
+		mixShearY = 0;
+
+		/** An offset added to the constrained bone rotation. */
+		offsetRotation = 0;
+
+		/** An offset added to the constrained bone X translation. */
+		offsetX = 0;
+
+		/** An offset added to the constrained bone Y translation. */
+		offsetY = 0;
+
+		/** An offset added to the constrained bone scaleX. */
+		offsetScaleX = 0;
+
+		/** An offset added to the constrained bone scaleY. */
+		offsetScaleY = 0;
+
+		/** An offset added to the constrained bone shearY. */
+		offsetShearY = 0;
+
+		relative = false;
+		local = false;
+
+		constructor (name: string) {
+			super(name, 0, false);
+		}
+	}
+}

+ 1 - 1
spine-ts/core/src/Triangulator.ts

@@ -120,7 +120,7 @@ module spine {
 			return triangles;
 		}
 
-		decompose (verticesArray: Array<number>, triangles: Array<number>) : Array<Array<number>> {
+		decompose (verticesArray: Array<number>, triangles: Array<number>): Array<Array<number>> {
 			let vertices = verticesArray;
 			let convexPolygons = this.convexPolygons;
 			this.polygonPool.freeAll(convexPolygons);

+ 42 - 42
spine-ts/core/src/Updatable.ts

@@ -1,42 +1,42 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** The interface for items updated by {@link Skeleton#updateWorldTransform()}. */
-	export interface Updatable {
-		update(): void;
-
-		/** Returns false when this item has not been updated because a skin is required and the {@link Skeleton#skin active skin}
-		 * does not contain this item.
-		 * @see Skin#getBones()
-		 * @see Skin#getConstraints() */
-		isActive(): boolean;
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** The interface for items updated by {@link Skeleton#updateWorldTransform()}. */
+	export interface Updatable {
+		update (): void;
+
+		/** Returns false when this item has not been updated because a skin is required and the {@link Skeleton#skin active skin}
+		 * does not contain this item.
+		 * @see Skin#getBones()
+		 * @see Skin#getConstraints() */
+		isActive (): boolean;
+	}
+}

+ 462 - 462
spine-ts/core/src/Utils.ts

@@ -1,462 +1,462 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	export interface Map<T> {
-		[key: string]: T;
-	}
-
-	export class IntSet {
-		array = new Array<number>();
-
-		add (value: number): boolean {
-			let contains = this.contains(value);
-			this.array[value | 0] = value | 0;
-			return !contains;
-		}
-
-		contains (value: number) {
-			return this.array[value | 0] != undefined;
-		}
-
-		remove (value: number) {
-			this.array[value | 0] = undefined;
-		}
-
-		clear () {
-			this.array.length = 0;
-		}
-	}
-
-	export class StringSet {
-		entries: Map<boolean> = {};
-		size = 0;
-
-		add (value: string): boolean {
-			let contains = this.entries[value];
-			this.entries[value] = true;
-			if (!contains) {
-				this.size++;
-				return true;
-			}
-			return false;
-		}
-
-		addAll (values: string[]): boolean {
-			let oldSize = this.size;
-			for (var i = 0, n = values.length; i < n; i++)
-				this.add(values[i]);
-			return oldSize != this.size;
-		}
-
-		contains (value: string) {
-			return this.entries[value];
-		}
-
-		clear () {
-			this.entries = {};
-			this.size = 0;
-		}
-	}
-
-	export interface Disposable {
-		dispose (): void;
-	}
-
-	export interface Restorable {
-		restore (): void;
-	}
-
-	export class Color {
-		public static WHITE = new Color(1, 1, 1, 1);
-		public static RED = new Color(1, 0, 0, 1);
-		public static GREEN = new Color(0, 1, 0, 1);
-		public static BLUE = new Color(0, 0, 1, 1);
-		public static MAGENTA = new Color(1, 0, 1, 1);
-
-		constructor (public r: number = 0, public g: number = 0, public b: number = 0, public a: number = 0) {
-		}
-
-		set (r: number, g: number, b: number, a: number) {
-			this.r = r;
-			this.g = g;
-			this.b = b;
-			this.a = a;
-			return this.clamp();
-		}
-
-		setFromColor (c: Color) {
-			this.r = c.r;
-			this.g = c.g;
-			this.b = c.b;
-			this.a = c.a;
-			return this;
-		}
-
-		setFromString (hex: string) {
-			hex = hex.charAt(0) == '#' ? hex.substr(1) : hex;
-			this.r = parseInt(hex.substr(0, 2), 16) / 255;
-			this.g = parseInt(hex.substr(2, 2), 16) / 255;
-			this.b = parseInt(hex.substr(4, 2), 16) / 255;
-			this.a = hex.length != 8 ? 1 : parseInt(hex.substr(6, 2), 16) / 255;
-			return this;
-		}
-
-		add (r: number, g: number, b: number, a: number) {
-			this.r += r;
-			this.g += g;
-			this.b += b;
-			this.a += a;
-			return this.clamp();
-		}
-
-		clamp () {
-			if (this.r < 0) this.r = 0;
-			else if (this.r > 1) this.r = 1;
-
-			if (this.g < 0) this.g = 0;
-			else if (this.g > 1) this.g = 1;
-
-			if (this.b < 0) this.b = 0;
-			else if (this.b > 1) this.b = 1;
-
-			if (this.a < 0) this.a = 0;
-			else if (this.a > 1) this.a = 1;
-			return this;
-		}
-
-		static rgba8888ToColor(color: Color, value: number) {
-			color.r = ((value & 0xff000000) >>> 24) / 255;
-			color.g = ((value & 0x00ff0000) >>> 16) / 255;
-			color.b = ((value & 0x0000ff00) >>> 8) / 255;
-			color.a = ((value & 0x000000ff)) / 255;
-		}
-
-		static rgb888ToColor (color: Color, value: number) {
-			color.r = ((value & 0x00ff0000) >>> 16) / 255;
-			color.g = ((value & 0x0000ff00) >>> 8) / 255;
-			color.b = ((value & 0x000000ff)) / 255;
-		}
-
-		static fromString (hex : string) : Color {
-			return new Color().setFromString(hex);
-		}
-	}
-
-	export class MathUtils {
-		static PI = 3.1415927;
-		static PI2 = MathUtils.PI * 2;
-		static radiansToDegrees = 180 / MathUtils.PI;
-		static radDeg = MathUtils.radiansToDegrees;
-		static degreesToRadians = MathUtils.PI / 180;
-		static degRad = MathUtils.degreesToRadians;
-
-		static clamp (value: number, min: number, max: number) {
-			if (value < min) return min;
-			if (value > max) return max;
-			return value;
-		}
-
-		static cosDeg (degrees: number) {
-			return Math.cos(degrees * MathUtils.degRad);
-		}
-
-		static sinDeg (degrees: number) {
-			return Math.sin(degrees * MathUtils.degRad);
-		}
-
-		static signum (value: number): number {
-			return value > 0 ? 1 : value < 0 ? -1 : 0;
-		}
-
-		static toInt (x: number) {
-			return x > 0 ? Math.floor(x) : Math.ceil(x);
-		}
-
-		static cbrt (x: number) {
-			let y = Math.pow(Math.abs(x), 1/3);
-			return x < 0 ? -y : y;
-		}
-
-		static randomTriangular (min: number, max: number): number {
-			return MathUtils.randomTriangularWith(min, max, (min + max) * 0.5);
-		}
-
-		static randomTriangularWith (min: number, max: number, mode: number): number {
-			let u = Math.random();
-			let d = max - min;
-			if (u <= (mode - min) / d) return min + Math.sqrt(u * d * (mode - min));
-			return max - Math.sqrt((1 - u) * d * (max - mode));
-		}
-
-		static isPowerOfTwo(value: number) {
-			return value && (value & (value - 1)) === 0;
-		}
-	}
-
-	export abstract class Interpolation {
-		protected abstract applyInternal (a: number): number;
-		apply(start: number, end: number, a: number): number {
-			return start + (end - start) * this.applyInternal(a);
-		}
-	}
-
-	export class Pow extends Interpolation {
-		protected power = 2;
-
-		constructor (power: number) {
-			super();
-			this.power = power;
-		}
-
-		applyInternal (a: number): number {
-			if (a <= 0.5) return Math.pow(a * 2, this.power) / 2;
-			return Math.pow((a - 1) * 2, this.power) / (this.power % 2 == 0 ? -2 : 2) + 1;
-		}
-	}
-
-	export class PowOut extends Pow {
-		constructor (power: number) {
-			super(power);
-		}
-
-		applyInternal (a: number) : number {
-			return Math.pow(a - 1, this.power) * (this.power % 2 == 0 ? -1 : 1) + 1;
-		}
-	}
-
-	export class Utils {
-		static SUPPORTS_TYPED_ARRAYS = typeof(Float32Array) !== "undefined";
-
-		static arrayCopy<T> (source: ArrayLike<T>, sourceStart: number, dest: ArrayLike<T>, destStart: number, numElements: number) {
-			for (let i = sourceStart, j = destStart; i < sourceStart + numElements; i++, j++) {
-				dest[j] = source[i];
-			}
-		}
-
-		static arrayFill<T> (array: ArrayLike<T>, fromIndex: number, toIndex: number, value: T) {
-			for (let i = fromIndex; i < toIndex; i++)
-				array[i] = value;
-		}
-
-		static setArraySize<T> (array: Array<T>, size: number, value: any = 0): Array<T> {
-			let oldSize = array.length;
-			if (oldSize == size) return array;
-			array.length = size;
-			if (oldSize < size) {
-				for (let i = oldSize; i < size; i++) array[i] = value;
-			}
-			return array;
-		}
-
-		static ensureArrayCapacity<T> (array: Array<T>, size: number, value: any = 0): Array<T> {
-			if (array.length >= size) return array;
-			return Utils.setArraySize(array, size, value);
-		}
-
-		static newArray<T> (size: number, defaultValue: T): Array<T> {
-			let array = new Array<T>(size);
-			for (let i = 0; i < size; i++) array[i] = defaultValue;
-			return array;
-		}
-
-		static newFloatArray (size: number): ArrayLike<number> {
-			if (Utils.SUPPORTS_TYPED_ARRAYS)
-				return new Float32Array(size)
-			else {
-				let array = new Array<number>(size);
-				for (let i = 0; i < array.length; i++) array[i] = 0;
-				return array;
-			}
-		}
-
-		static newShortArray (size: number): ArrayLike<number> {
-			if (Utils.SUPPORTS_TYPED_ARRAYS)
-				return new Int16Array(size)
-			else {
-				let array = new Array<number>(size);
-				for (let i = 0; i < array.length; i++) array[i] = 0;
-				return array;
-			}
-		}
-
-		static toFloatArray (array: Array<number>) {
-			return Utils.SUPPORTS_TYPED_ARRAYS ? new Float32Array(array) : array;
-		}
-
-		static toSinglePrecision (value: number) {
-			return Utils.SUPPORTS_TYPED_ARRAYS ? Math.fround(value) : value;
-		}
-
-		// This function is used to fix WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
-		static webkit602BugfixHelper (alpha: number, blend: MixBlend) {
-		}
-
-		static contains<T> (array: Array<T>, element: T, identity = true) {
-			for (var i = 0; i < array.length; i++)
-				if (array[i] == element) return true;
-			return false;
-		}
-
-		static enumValue (type: any, name: string) {
-			return type[name[0].toUpperCase() + name.slice(1)];
-		}
-	}
-
-	export class DebugUtils {
-		static logBones(skeleton: Skeleton) {
-			for (let i = 0; i < skeleton.bones.length; i++) {
-				let bone = skeleton.bones[i];
-				console.log(bone.data.name + ", " + bone.a + ", " + bone.b + ", " + bone.c + ", " + bone.d + ", " + bone.worldX + ", " + bone.worldY);
-			}
-		}
-	}
-
-	export class Pool<T> {
-		private items = new Array<T>();
-		private instantiator: () => T;
-
-		constructor (instantiator: () => T) {
-			this.instantiator = instantiator;
-		}
-
-		obtain () {
-			return this.items.length > 0 ? this.items.pop() : this.instantiator();
-		}
-
-		free (item: T) {
-			if ((item as any).reset) (item as any).reset();
-			this.items.push(item);
-		}
-
-		freeAll (items: ArrayLike<T>) {
-			for (let i = 0; i < items.length; i++)
-				this.free(items[i]);
-		}
-
-		clear () {
-			this.items.length = 0;
-		}
-	}
-
-	export class Vector2 {
-		constructor (public x = 0, public y = 0) {
-		}
-
-		set (x: number, y: number): Vector2 {
-			this.x = x;
-			this.y = y;
-			return this;
-		}
-
-		length () {
-			let x = this.x;
-			let y = this.y;
-			return Math.sqrt(x * x + y * y);
-		}
-
-		normalize () {
-			let len = this.length();
-			if (len != 0) {
-				this.x /= len;
-				this.y /= len;
-			}
-			return this;
-		}
-	}
-
-	export class TimeKeeper {
-		maxDelta = 0.064;
-		framesPerSecond = 0;
-		delta = 0;
-		totalTime = 0;
-
-		private lastTime = Date.now() / 1000;
-		private frameCount = 0;
-		private frameTime = 0;
-
-		update () {
-			let now = Date.now() / 1000;
-			this.delta = now - this.lastTime;
-			this.frameTime += this.delta;
-			this.totalTime += this.delta;
-			if (this.delta > this.maxDelta) this.delta = this.maxDelta;
-			this.lastTime = now;
-
-			this.frameCount++;
-			if (this.frameTime > 1) {
-				this.framesPerSecond = this.frameCount / this.frameTime;
-				this.frameTime = 0;
-				this.frameCount = 0;
-			}
-		}
-	}
-
-	export interface ArrayLike<T> {
-		length: number;
-		[n: number]: T;
-	}
-
-	export class WindowedMean {
-		values: Array<number>;
-		addedValues = 0;
-		lastValue = 0;
-		mean = 0;
-		dirty = true;
-
-		constructor (windowSize: number = 32) {
-			this.values = new Array<number>(windowSize);
-		}
-
-		hasEnoughData () {
-			return this.addedValues >= this.values.length;
-		}
-
-		addValue (value: number) {
-			if (this.addedValues < this.values.length) this.addedValues++;
-			this.values[this.lastValue++] = value;
-			if (this.lastValue > this.values.length - 1) this.lastValue = 0;
-			this.dirty = true;
-		}
-
-		getMean () {
-			if (this.hasEnoughData()) {
-				if (this.dirty) {
-					let mean = 0;
-					for (let i = 0; i < this.values.length; i++)
-						mean += this.values[i];
-					this.mean = mean / this.values.length;
-					this.dirty = false;
-				}
-				return this.mean;
-			}
-			return 0;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	export interface Map<T> {
+		[key: string]: T;
+	}
+
+	export class IntSet {
+		array = new Array<number>();
+
+		add (value: number): boolean {
+			let contains = this.contains(value);
+			this.array[value | 0] = value | 0;
+			return !contains;
+		}
+
+		contains (value: number) {
+			return this.array[value | 0] != undefined;
+		}
+
+		remove (value: number) {
+			this.array[value | 0] = undefined;
+		}
+
+		clear () {
+			this.array.length = 0;
+		}
+	}
+
+	export class StringSet {
+		entries: Map<boolean> = {};
+		size = 0;
+
+		add (value: string): boolean {
+			let contains = this.entries[value];
+			this.entries[value] = true;
+			if (!contains) {
+				this.size++;
+				return true;
+			}
+			return false;
+		}
+
+		addAll (values: string[]): boolean {
+			let oldSize = this.size;
+			for (var i = 0, n = values.length; i < n; i++)
+				this.add(values[i]);
+			return oldSize != this.size;
+		}
+
+		contains (value: string) {
+			return this.entries[value];
+		}
+
+		clear () {
+			this.entries = {};
+			this.size = 0;
+		}
+	}
+
+	export interface Disposable {
+		dispose (): void;
+	}
+
+	export interface Restorable {
+		restore (): void;
+	}
+
+	export class Color {
+		public static WHITE = new Color(1, 1, 1, 1);
+		public static RED = new Color(1, 0, 0, 1);
+		public static GREEN = new Color(0, 1, 0, 1);
+		public static BLUE = new Color(0, 0, 1, 1);
+		public static MAGENTA = new Color(1, 0, 1, 1);
+
+		constructor (public r: number = 0, public g: number = 0, public b: number = 0, public a: number = 0) {
+		}
+
+		set (r: number, g: number, b: number, a: number) {
+			this.r = r;
+			this.g = g;
+			this.b = b;
+			this.a = a;
+			return this.clamp();
+		}
+
+		setFromColor (c: Color) {
+			this.r = c.r;
+			this.g = c.g;
+			this.b = c.b;
+			this.a = c.a;
+			return this;
+		}
+
+		setFromString (hex: string) {
+			hex = hex.charAt(0) == '#' ? hex.substr(1) : hex;
+			this.r = parseInt(hex.substr(0, 2), 16) / 255;
+			this.g = parseInt(hex.substr(2, 2), 16) / 255;
+			this.b = parseInt(hex.substr(4, 2), 16) / 255;
+			this.a = hex.length != 8 ? 1 : parseInt(hex.substr(6, 2), 16) / 255;
+			return this;
+		}
+
+		add (r: number, g: number, b: number, a: number) {
+			this.r += r;
+			this.g += g;
+			this.b += b;
+			this.a += a;
+			return this.clamp();
+		}
+
+		clamp () {
+			if (this.r < 0) this.r = 0;
+			else if (this.r > 1) this.r = 1;
+
+			if (this.g < 0) this.g = 0;
+			else if (this.g > 1) this.g = 1;
+
+			if (this.b < 0) this.b = 0;
+			else if (this.b > 1) this.b = 1;
+
+			if (this.a < 0) this.a = 0;
+			else if (this.a > 1) this.a = 1;
+			return this;
+		}
+
+		static rgba8888ToColor (color: Color, value: number) {
+			color.r = ((value & 0xff000000) >>> 24) / 255;
+			color.g = ((value & 0x00ff0000) >>> 16) / 255;
+			color.b = ((value & 0x0000ff00) >>> 8) / 255;
+			color.a = ((value & 0x000000ff)) / 255;
+		}
+
+		static rgb888ToColor (color: Color, value: number) {
+			color.r = ((value & 0x00ff0000) >>> 16) / 255;
+			color.g = ((value & 0x0000ff00) >>> 8) / 255;
+			color.b = ((value & 0x000000ff)) / 255;
+		}
+
+		static fromString (hex: string): Color {
+			return new Color().setFromString(hex);
+		}
+	}
+
+	export class MathUtils {
+		static PI = 3.1415927;
+		static PI2 = MathUtils.PI * 2;
+		static radiansToDegrees = 180 / MathUtils.PI;
+		static radDeg = MathUtils.radiansToDegrees;
+		static degreesToRadians = MathUtils.PI / 180;
+		static degRad = MathUtils.degreesToRadians;
+
+		static clamp (value: number, min: number, max: number) {
+			if (value < min) return min;
+			if (value > max) return max;
+			return value;
+		}
+
+		static cosDeg (degrees: number) {
+			return Math.cos(degrees * MathUtils.degRad);
+		}
+
+		static sinDeg (degrees: number) {
+			return Math.sin(degrees * MathUtils.degRad);
+		}
+
+		static signum (value: number): number {
+			return value > 0 ? 1 : value < 0 ? -1 : 0;
+		}
+
+		static toInt (x: number) {
+			return x > 0 ? Math.floor(x) : Math.ceil(x);
+		}
+
+		static cbrt (x: number) {
+			let y = Math.pow(Math.abs(x), 1 / 3);
+			return x < 0 ? -y : y;
+		}
+
+		static randomTriangular (min: number, max: number): number {
+			return MathUtils.randomTriangularWith(min, max, (min + max) * 0.5);
+		}
+
+		static randomTriangularWith (min: number, max: number, mode: number): number {
+			let u = Math.random();
+			let d = max - min;
+			if (u <= (mode - min) / d) return min + Math.sqrt(u * d * (mode - min));
+			return max - Math.sqrt((1 - u) * d * (max - mode));
+		}
+
+		static isPowerOfTwo (value: number) {
+			return value && (value & (value - 1)) === 0;
+		}
+	}
+
+	export abstract class Interpolation {
+		protected abstract applyInternal (a: number): number;
+		apply (start: number, end: number, a: number): number {
+			return start + (end - start) * this.applyInternal(a);
+		}
+	}
+
+	export class Pow extends Interpolation {
+		protected power = 2;
+
+		constructor (power: number) {
+			super();
+			this.power = power;
+		}
+
+		applyInternal (a: number): number {
+			if (a <= 0.5) return Math.pow(a * 2, this.power) / 2;
+			return Math.pow((a - 1) * 2, this.power) / (this.power % 2 == 0 ? -2 : 2) + 1;
+		}
+	}
+
+	export class PowOut extends Pow {
+		constructor (power: number) {
+			super(power);
+		}
+
+		applyInternal (a: number): number {
+			return Math.pow(a - 1, this.power) * (this.power % 2 == 0 ? -1 : 1) + 1;
+		}
+	}
+
+	export class Utils {
+		static SUPPORTS_TYPED_ARRAYS = typeof (Float32Array) !== "undefined";
+
+		static arrayCopy<T> (source: ArrayLike<T>, sourceStart: number, dest: ArrayLike<T>, destStart: number, numElements: number) {
+			for (let i = sourceStart, j = destStart; i < sourceStart + numElements; i++, j++) {
+				dest[j] = source[i];
+			}
+		}
+
+		static arrayFill<T> (array: ArrayLike<T>, fromIndex: number, toIndex: number, value: T) {
+			for (let i = fromIndex; i < toIndex; i++)
+				array[i] = value;
+		}
+
+		static setArraySize<T> (array: Array<T>, size: number, value: any = 0): Array<T> {
+			let oldSize = array.length;
+			if (oldSize == size) return array;
+			array.length = size;
+			if (oldSize < size) {
+				for (let i = oldSize; i < size; i++) array[i] = value;
+			}
+			return array;
+		}
+
+		static ensureArrayCapacity<T> (array: Array<T>, size: number, value: any = 0): Array<T> {
+			if (array.length >= size) return array;
+			return Utils.setArraySize(array, size, value);
+		}
+
+		static newArray<T> (size: number, defaultValue: T): Array<T> {
+			let array = new Array<T>(size);
+			for (let i = 0; i < size; i++) array[i] = defaultValue;
+			return array;
+		}
+
+		static newFloatArray (size: number): ArrayLike<number> {
+			if (Utils.SUPPORTS_TYPED_ARRAYS)
+				return new Float32Array(size)
+			else {
+				let array = new Array<number>(size);
+				for (let i = 0; i < array.length; i++) array[i] = 0;
+				return array;
+			}
+		}
+
+		static newShortArray (size: number): ArrayLike<number> {
+			if (Utils.SUPPORTS_TYPED_ARRAYS)
+				return new Int16Array(size)
+			else {
+				let array = new Array<number>(size);
+				for (let i = 0; i < array.length; i++) array[i] = 0;
+				return array;
+			}
+		}
+
+		static toFloatArray (array: Array<number>) {
+			return Utils.SUPPORTS_TYPED_ARRAYS ? new Float32Array(array) : array;
+		}
+
+		static toSinglePrecision (value: number) {
+			return Utils.SUPPORTS_TYPED_ARRAYS ? Math.fround(value) : value;
+		}
+
+		// This function is used to fix WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
+		static webkit602BugfixHelper (alpha: number, blend: MixBlend) {
+		}
+
+		static contains<T> (array: Array<T>, element: T, identity = true) {
+			for (var i = 0; i < array.length; i++)
+				if (array[i] == element) return true;
+			return false;
+		}
+
+		static enumValue (type: any, name: string) {
+			return type[name[0].toUpperCase() + name.slice(1)];
+		}
+	}
+
+	export class DebugUtils {
+		static logBones (skeleton: Skeleton) {
+			for (let i = 0; i < skeleton.bones.length; i++) {
+				let bone = skeleton.bones[i];
+				console.log(bone.data.name + ", " + bone.a + ", " + bone.b + ", " + bone.c + ", " + bone.d + ", " + bone.worldX + ", " + bone.worldY);
+			}
+		}
+	}
+
+	export class Pool<T> {
+		private items = new Array<T>();
+		private instantiator: () => T;
+
+		constructor (instantiator: () => T) {
+			this.instantiator = instantiator;
+		}
+
+		obtain () {
+			return this.items.length > 0 ? this.items.pop() : this.instantiator();
+		}
+
+		free (item: T) {
+			if ((item as any).reset) (item as any).reset();
+			this.items.push(item);
+		}
+
+		freeAll (items: ArrayLike<T>) {
+			for (let i = 0; i < items.length; i++)
+				this.free(items[i]);
+		}
+
+		clear () {
+			this.items.length = 0;
+		}
+	}
+
+	export class Vector2 {
+		constructor (public x = 0, public y = 0) {
+		}
+
+		set (x: number, y: number): Vector2 {
+			this.x = x;
+			this.y = y;
+			return this;
+		}
+
+		length () {
+			let x = this.x;
+			let y = this.y;
+			return Math.sqrt(x * x + y * y);
+		}
+
+		normalize () {
+			let len = this.length();
+			if (len != 0) {
+				this.x /= len;
+				this.y /= len;
+			}
+			return this;
+		}
+	}
+
+	export class TimeKeeper {
+		maxDelta = 0.064;
+		framesPerSecond = 0;
+		delta = 0;
+		totalTime = 0;
+
+		private lastTime = Date.now() / 1000;
+		private frameCount = 0;
+		private frameTime = 0;
+
+		update () {
+			let now = Date.now() / 1000;
+			this.delta = now - this.lastTime;
+			this.frameTime += this.delta;
+			this.totalTime += this.delta;
+			if (this.delta > this.maxDelta) this.delta = this.maxDelta;
+			this.lastTime = now;
+
+			this.frameCount++;
+			if (this.frameTime > 1) {
+				this.framesPerSecond = this.frameCount / this.frameTime;
+				this.frameTime = 0;
+				this.frameCount = 0;
+			}
+		}
+	}
+
+	export interface ArrayLike<T> {
+		length: number;
+		[n: number]: T;
+	}
+
+	export class WindowedMean {
+		values: Array<number>;
+		addedValues = 0;
+		lastValue = 0;
+		mean = 0;
+		dirty = true;
+
+		constructor (windowSize: number = 32) {
+			this.values = new Array<number>(windowSize);
+		}
+
+		hasEnoughData () {
+			return this.addedValues >= this.values.length;
+		}
+
+		addValue (value: number) {
+			if (this.addedValues < this.values.length) this.addedValues++;
+			this.values[this.lastValue++] = value;
+			if (this.lastValue > this.values.length - 1) this.lastValue = 0;
+			this.dirty = true;
+		}
+
+		getMean () {
+			if (this.hasEnoughData()) {
+				if (this.dirty) {
+					let mean = 0;
+					for (let i = 0; i < this.values.length; i++)
+						mean += this.values[i];
+					this.mean = mean / this.values.length;
+					this.dirty = false;
+				}
+				return this.mean;
+			}
+			return 0;
+		}
+	}
+}

+ 3 - 3
spine-ts/core/src/VertexEffect.ts

@@ -29,8 +29,8 @@
 
 module spine {
 	export interface VertexEffect {
-		begin(skeleton: Skeleton): void;
-		transform(position: Vector2, uv: Vector2, light: Color, dark: Color): void;
-		end(): void;
+		begin (skeleton: Skeleton): void;
+		transform (position: Vector2, uv: Vector2, light: Color, dark: Color): void;
+		end (): void;
 	}
 }

+ 159 - 159
spine-ts/core/src/attachments/Attachment.ts

@@ -1,159 +1,159 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	/** The base class for all attachments. */
-	export abstract class Attachment {
-		name: string;
-
-		constructor (name: string) {
-			if (!name) throw new Error("name cannot be null.");
-			this.name = name;
-		}
-
-		abstract copy (): Attachment;
-	}
-
-	/** Base class for an attachment with vertices that are transformed by one or more bones and can be deformed by a slot's
-	 * {@link Slot#deform}. */
-	export abstract class VertexAttachment extends Attachment {
-		private static nextID = 0;
-
-		/** The unique ID for this attachment. */
-		id = VertexAttachment.nextID++;
-
-		/** The bones which affect the {@link #getVertices()}. The array entries are, for each vertex, the number of bones affecting
-		 * the vertex followed by that many bone indices, which is the index of the bone in {@link Skeleton#bones}. Will be null
-		 * if this attachment has no weights. */
-		bones: Array<number>;
-
-		/** The vertex positions in the bone's coordinate system. For a non-weighted attachment, the values are `x,y`
-		 * entries for each vertex. For a weighted attachment, the values are `x,y,weight` entries for each bone affecting
-		 * each vertex. */
-		vertices: ArrayLike<number>;
-
-		/** The maximum number of world vertex values that can be output by
-		 * {@link #computeWorldVertices()} using the `count` parameter. */
-		worldVerticesLength = 0;
-
-		/** Deform keys for the deform attachment are also applied to this attachment. May be null if no deform keys should be applied. */
-		deformAttachment: VertexAttachment = this;
-
-		constructor (name: string) {
-			super(name);
-		}
-
-		/** Transforms the attachment's local {@link #vertices} to world coordinates. If the slot's {@link Slot#deform} is
-		 * not empty, it is used to deform the vertices.
-		 *
-		 * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
-		 * Runtimes Guide.
-		 * @param start The index of the first {@link #vertices} value to transform. Each vertex has 2 values, x and y.
-		 * @param count The number of world vertex values to output. Must be <= {@link #worldVerticesLength} - `start`.
-		 * @param worldVertices The output world vertices. Must have a length >= `offset` + `count` *
-		 *           `stride` / 2.
-		 * @param offset The `worldVertices` index to begin writing values.
-		 * @param stride The number of `worldVertices` entries between the value pairs written. */
-		computeWorldVertices (slot: Slot, start: number, count: number, worldVertices: ArrayLike<number>, offset: number, stride: number) {
-			count = offset + (count >> 1) * stride;
-			let skeleton = slot.bone.skeleton;
-			let deformArray = slot.deform;
-			let vertices = this.vertices;
-			let bones = this.bones;
-			if (!bones) {
-				if (deformArray.length > 0) vertices = deformArray;
-				let bone = slot.bone;
-				let x = bone.worldX;
-				let y = bone.worldY;
-				let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-				for (let v = start, w = offset; w < count; v += 2, w += stride) {
-					let vx = vertices[v], vy = vertices[v + 1];
-					worldVertices[w] = vx * a + vy * b + x;
-					worldVertices[w + 1] = vx * c + vy * d + y;
-				}
-				return;
-			}
-			let v = 0, skip = 0;
-			for (let i = 0; i < start; i += 2) {
-				let n = bones[v];
-				v += n + 1;
-				skip += n;
-			}
-			let skeletonBones = skeleton.bones;
-			if (deformArray.length == 0) {
-				for (let w = offset, b = skip * 3; w < count; w += stride) {
-					let wx = 0, wy = 0;
-					let n = bones[v++];
-					n += v;
-					for (; v < n; v++, b += 3) {
-						let bone = skeletonBones[bones[v]];
-						let vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
-						wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
-						wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
-					}
-					worldVertices[w] = wx;
-					worldVertices[w + 1] = wy;
-				}
-			} else {
-				let deform = deformArray;
-				for (let w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
-					let wx = 0, wy = 0;
-					let n = bones[v++];
-					n += v;
-					for (; v < n; v++, b += 3, f += 2) {
-						let bone = skeletonBones[bones[v]];
-						let vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
-						wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
-						wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
-					}
-					worldVertices[w] = wx;
-					worldVertices[w + 1] = wy;
-				}
-			}
-		}
-
-		/** Does not copy id (generated) or name (set on construction). **/
-		copyTo (attachment: VertexAttachment) {
-			if (this.bones) {
-				attachment.bones = new Array<number>(this.bones.length);
-				Utils.arrayCopy(this.bones, 0, attachment.bones, 0, this.bones.length);
-			} else
-				attachment.bones = null;
-
-			if (this.vertices) {
-				attachment.vertices = Utils.newFloatArray(this.vertices.length);
-				Utils.arrayCopy(this.vertices, 0, attachment.vertices, 0, this.vertices.length);
-			} else
-				attachment.vertices = null;
-
-			attachment.worldVerticesLength = this.worldVerticesLength;
-			attachment.deformAttachment = this.deformAttachment;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	/** The base class for all attachments. */
+	export abstract class Attachment {
+		name: string;
+
+		constructor (name: string) {
+			if (!name) throw new Error("name cannot be null.");
+			this.name = name;
+		}
+
+		abstract copy (): Attachment;
+	}
+
+	/** Base class for an attachment with vertices that are transformed by one or more bones and can be deformed by a slot's
+	 * {@link Slot#deform}. */
+	export abstract class VertexAttachment extends Attachment {
+		private static nextID = 0;
+
+		/** The unique ID for this attachment. */
+		id = VertexAttachment.nextID++;
+
+		/** The bones which affect the {@link #getVertices()}. The array entries are, for each vertex, the number of bones affecting
+		 * the vertex followed by that many bone indices, which is the index of the bone in {@link Skeleton#bones}. Will be null
+		 * if this attachment has no weights. */
+		bones: Array<number>;
+
+		/** The vertex positions in the bone's coordinate system. For a non-weighted attachment, the values are `x,y`
+		 * entries for each vertex. For a weighted attachment, the values are `x,y,weight` entries for each bone affecting
+		 * each vertex. */
+		vertices: ArrayLike<number>;
+
+		/** The maximum number of world vertex values that can be output by
+		 * {@link #computeWorldVertices()} using the `count` parameter. */
+		worldVerticesLength = 0;
+
+		/** Deform keys for the deform attachment are also applied to this attachment. May be null if no deform keys should be applied. */
+		deformAttachment: VertexAttachment = this;
+
+		constructor (name: string) {
+			super(name);
+		}
+
+		/** Transforms the attachment's local {@link #vertices} to world coordinates. If the slot's {@link Slot#deform} is
+		 * not empty, it is used to deform the vertices.
+		 *
+		 * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
+		 * Runtimes Guide.
+		 * @param start The index of the first {@link #vertices} value to transform. Each vertex has 2 values, x and y.
+		 * @param count The number of world vertex values to output. Must be <= {@link #worldVerticesLength} - `start`.
+		 * @param worldVertices The output world vertices. Must have a length >= `offset` + `count` *
+		 *           `stride` / 2.
+		 * @param offset The `worldVertices` index to begin writing values.
+		 * @param stride The number of `worldVertices` entries between the value pairs written. */
+		computeWorldVertices (slot: Slot, start: number, count: number, worldVertices: ArrayLike<number>, offset: number, stride: number) {
+			count = offset + (count >> 1) * stride;
+			let skeleton = slot.bone.skeleton;
+			let deformArray = slot.deform;
+			let vertices = this.vertices;
+			let bones = this.bones;
+			if (!bones) {
+				if (deformArray.length > 0) vertices = deformArray;
+				let bone = slot.bone;
+				let x = bone.worldX;
+				let y = bone.worldY;
+				let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+				for (let v = start, w = offset; w < count; v += 2, w += stride) {
+					let vx = vertices[v], vy = vertices[v + 1];
+					worldVertices[w] = vx * a + vy * b + x;
+					worldVertices[w + 1] = vx * c + vy * d + y;
+				}
+				return;
+			}
+			let v = 0, skip = 0;
+			for (let i = 0; i < start; i += 2) {
+				let n = bones[v];
+				v += n + 1;
+				skip += n;
+			}
+			let skeletonBones = skeleton.bones;
+			if (deformArray.length == 0) {
+				for (let w = offset, b = skip * 3; w < count; w += stride) {
+					let wx = 0, wy = 0;
+					let n = bones[v++];
+					n += v;
+					for (; v < n; v++, b += 3) {
+						let bone = skeletonBones[bones[v]];
+						let vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
+						wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
+						wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
+					}
+					worldVertices[w] = wx;
+					worldVertices[w + 1] = wy;
+				}
+			} else {
+				let deform = deformArray;
+				for (let w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
+					let wx = 0, wy = 0;
+					let n = bones[v++];
+					n += v;
+					for (; v < n; v++, b += 3, f += 2) {
+						let bone = skeletonBones[bones[v]];
+						let vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
+						wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
+						wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
+					}
+					worldVertices[w] = wx;
+					worldVertices[w + 1] = wy;
+				}
+			}
+		}
+
+		/** Does not copy id (generated) or name (set on construction). **/
+		copyTo (attachment: VertexAttachment) {
+			if (this.bones) {
+				attachment.bones = new Array<number>(this.bones.length);
+				Utils.arrayCopy(this.bones, 0, attachment.bones, 0, this.bones.length);
+			} else
+				attachment.bones = null;
+
+			if (this.vertices) {
+				attachment.vertices = Utils.newFloatArray(this.vertices.length);
+				Utils.arrayCopy(this.vertices, 0, attachment.vertices, 0, this.vertices.length);
+			} else
+				attachment.vertices = null;
+
+			attachment.worldVerticesLength = this.worldVerticesLength;
+			attachment.deformAttachment = this.deformAttachment;
+		}
+	}
+}

+ 55 - 55
spine-ts/core/src/attachments/AttachmentLoader.ts

@@ -1,55 +1,55 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** The interface which can be implemented to customize creating and populating attachments.
-	 *
-	 * See [Loading skeleton data](http://esotericsoftware.com/spine-loading-skeleton-data#AttachmentLoader) in the Spine
-	 * Runtimes Guide. */
-	export interface AttachmentLoader {
-		/** @return May be null to not load an attachment. */
-		newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment;
-
-		/** @return May be null to not load an attachment. */
-		newMeshAttachment (skin: Skin, name: string, path: string) : MeshAttachment;
-
-		/** @return May be null to not load an attachment. */
-		newBoundingBoxAttachment (skin: Skin, name: string) : BoundingBoxAttachment;
-
-		/** @return May be null to not load an attachment */
-		newPathAttachment(skin: Skin, name: string): PathAttachment;
-
-		/** @return May be null to not load an attachment */
-		newPointAttachment(skin: Skin, name: string): PointAttachment;
-
-		/** @return May be null to not load an attachment */
-		newClippingAttachment(skin: Skin, name: string): ClippingAttachment;
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** The interface which can be implemented to customize creating and populating attachments.
+	 *
+	 * See [Loading skeleton data](http://esotericsoftware.com/spine-loading-skeleton-data#AttachmentLoader) in the Spine
+	 * Runtimes Guide. */
+	export interface AttachmentLoader {
+		/** @return May be null to not load an attachment. */
+		newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment;
+
+		/** @return May be null to not load an attachment. */
+		newMeshAttachment (skin: Skin, name: string, path: string): MeshAttachment;
+
+		/** @return May be null to not load an attachment. */
+		newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment;
+
+		/** @return May be null to not load an attachment */
+		newPathAttachment (skin: Skin, name: string): PathAttachment;
+
+		/** @return May be null to not load an attachment */
+		newPointAttachment (skin: Skin, name: string): PointAttachment;
+
+		/** @return May be null to not load an attachment */
+		newClippingAttachment (skin: Skin, name: string): ClippingAttachment;
+	}
+}

+ 50 - 50
spine-ts/core/src/attachments/BoundingBoxAttachment.ts

@@ -1,50 +1,50 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	/** An attachment with vertices that make up a polygon. Can be used for hit detection, creating physics bodies, spawning particle
-	 * effects, and more.
-	 *
-	 * See {@link SkeletonBounds} and [Bounding Boxes](http://esotericsoftware.com/spine-bounding-boxes) in the Spine User
-	 * Guide. */
-	export class BoundingBoxAttachment extends VertexAttachment {
-		color = new Color(1, 1, 1, 1);
-
-		constructor (name: string) {
-			super(name);
-		}
-
-		copy (): Attachment {
-			let copy = new BoundingBoxAttachment(this.name);
-			this.copyTo(copy);
-			copy.color.setFromColor(this.color);
-			return copy;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	/** An attachment with vertices that make up a polygon. Can be used for hit detection, creating physics bodies, spawning particle
+	 * effects, and more.
+	 *
+	 * See {@link SkeletonBounds} and [Bounding Boxes](http://esotericsoftware.com/spine-bounding-boxes) in the Spine User
+	 * Guide. */
+	export class BoundingBoxAttachment extends VertexAttachment {
+		color = new Color(1, 1, 1, 1);
+
+		constructor (name: string) {
+			super(name);
+		}
+
+		copy (): Attachment {
+			let copy = new BoundingBoxAttachment(this.name);
+			this.copyTo(copy);
+			copy.color.setFromColor(this.color);
+			return copy;
+		}
+	}
+}

+ 198 - 198
spine-ts/core/src/attachments/MeshAttachment.ts

@@ -1,198 +1,198 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-	/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not
-	 * supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
-	 *
-	 * See [Mesh attachments](http://esotericsoftware.com/spine-meshes) in the Spine User Guide. */
-	export class MeshAttachment extends VertexAttachment {
-		region: TextureRegion;
-
-		/** The name of the texture region for this attachment. */
-		path: string;
-
-		/** The UV pair for each vertex, normalized within the texture region. */
-		regionUVs: ArrayLike<number>;
-
-		/** The UV pair for each vertex, normalized within the entire texture.
-		 *
-		 * See {@link #updateUVs}. */
-		uvs: ArrayLike<number>;
-
-		/** Triplets of vertex indices which describe the mesh's triangulation. */
-		triangles: Array<number>;
-
-		/** The color to tint the mesh. */
-		color = new Color(1, 1, 1, 1);
-
-		/** The width of the mesh's image. Available only when nonessential data was exported. */
-		width: number;
-
-		/** The height of the mesh's image. Available only when nonessential data was exported. */
-		height: number;
-
-		/** The number of entries at the beginning of {@link #vertices} that make up the mesh hull. */
-		hullLength: number;
-
-		/** Vertex index pairs describing edges for controling triangulation. Mesh triangles will never cross edges. Only available if
-		 * nonessential data was exported. Triangulation is not performed at runtime. */
-		edges: Array<number>;
-
-		private parentMesh: MeshAttachment;
-		tempColor = new Color(0, 0, 0, 0);
-
-		constructor (name: string) {
-			super(name);
-		}
-
-		/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region UVs or
-		 * region. */
-		updateUVs () {
-			let regionUVs = this.regionUVs;
-			if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
-			let uvs = this.uvs;
-			let n = this.uvs.length;
-			let u = this.region.u, v = this.region.v, width = 0, height = 0;
-			if (this.region instanceof TextureAtlasRegion) {
-				let region = this.region, image = region.page.texture.getImage();
-				let textureWidth = image.width, textureHeight = image.height;
-				switch(region.degrees) {
-				case 90:
-					u -= (region.originalHeight - region.offsetY - region.height) / textureWidth;
-					v -= (region.originalWidth - region.offsetX - region.width) / textureHeight;
-					width = region.originalHeight / textureWidth;
-					height = region.originalWidth / textureHeight;
-					for (let i = 0; i < n; i += 2) {
-						uvs[i] = u + regionUVs[i + 1] * width;
-						uvs[i + 1] = v + (1 - regionUVs[i]) * height;
-					}
-					return;
-				case 180:
-					u -= (region.originalWidth - region.offsetX - region.width) / textureWidth;
-					v -= region.offsetY / textureHeight;
-					width = region.originalWidth / textureWidth;
-					height = region.originalHeight / textureHeight;
-					for (let i = 0; i < n; i += 2) {
-						uvs[i] = u + (1 - regionUVs[i]) * width;
-						uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height;
-					}
-					return;
-				case 270:
-					u -= region.offsetY / textureWidth;
-					v -= region.offsetX / textureHeight;
-					width = region.originalHeight / textureWidth;
-					height = region.originalWidth / textureHeight;
-					for (let i = 0; i < n; i += 2) {
-						uvs[i] = u + (1 - regionUVs[i + 1]) * width;
-						uvs[i + 1] = v + regionUVs[i] * height;
-					}
-					return;
-				}
-				u -= region.offsetX / textureWidth;
-				v -= (region.originalHeight - region.offsetY - region.height) / textureHeight;
-				width = region.originalWidth / textureWidth;
-				height = region.originalHeight / textureHeight;
-			} else if (!this.region) {
-				u = v = 0;
-				width = height = 1;
-			} else {
-				width = this.region.u2 - u;
-				height = this.region.v2 - v;
-			}
-
-			for (let i = 0; i < n; i += 2) {
-				uvs[i] = u + regionUVs[i] * width;
-				uvs[i + 1] = v + regionUVs[i + 1] * height;
-			}
-		}
-
-		/** The parent mesh if this is a linked mesh, else null. A linked mesh shares the {@link #bones}, {@link #vertices},
-		 * {@link #regionUVs}, {@link #triangles}, {@link #hullLength}, {@link #edges}, {@link #width}, and {@link #height} with the
-		 * parent mesh, but may have a different {@link #name} or {@link #path} (and therefore a different texture). */
-		getParentMesh () {
-			return this.parentMesh;
-		}
-
-		/** @param parentMesh May be null. */
-		setParentMesh (parentMesh: MeshAttachment) {
-			this.parentMesh = parentMesh;
-			if (parentMesh) {
-				this.bones = parentMesh.bones;
-				this.vertices = parentMesh.vertices;
-				this.worldVerticesLength = parentMesh.worldVerticesLength;
-				this.regionUVs = parentMesh.regionUVs;
-				this.triangles = parentMesh.triangles;
-				this.hullLength = parentMesh.hullLength;
-				this.worldVerticesLength = parentMesh.worldVerticesLength
-			}
-		}
-
-		copy (): Attachment {
-			if (this.parentMesh) return this.newLinkedMesh();
-
-			let copy = new MeshAttachment(this.name);
-			copy.region = this.region;
-			copy.path = this.path;
-			copy.color.setFromColor(this.color);
-
-			this.copyTo(copy);
-			copy.regionUVs = new Array<number>(this.regionUVs.length);
-			Utils.arrayCopy(this.regionUVs, 0, copy.regionUVs, 0, this.regionUVs.length);
-			copy.uvs = new Array<number>(this.uvs.length);
-			Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, this.uvs.length);
-			copy.triangles = new Array<number>(this.triangles.length);
-			Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length);
-			copy.hullLength = this.hullLength;
-
-			// Nonessential.
-			if (this.edges) {
-				copy.edges = new Array<number>(this.edges.length);
-				Utils.arrayCopy(this.edges, 0, copy.edges, 0, this.edges.length);
-			}
-			copy.width = this.width;
-			copy.height = this.height;
-
-			return copy;
-		}
-
-		/** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/
-		newLinkedMesh (): MeshAttachment {
-			let copy = new MeshAttachment(this.name);
-			copy.region = this.region;
-			copy.path = this.path;
-			copy.color.setFromColor(this.color);
-			copy.deformAttachment = this.deformAttachment;
-			copy.setParentMesh(this.parentMesh ? this.parentMesh : this);
-			copy.updateUVs();
-			return copy;
-		}
-	}
-
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+	/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not
+	 * supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
+	 *
+	 * See [Mesh attachments](http://esotericsoftware.com/spine-meshes) in the Spine User Guide. */
+	export class MeshAttachment extends VertexAttachment {
+		region: TextureRegion;
+
+		/** The name of the texture region for this attachment. */
+		path: string;
+
+		/** The UV pair for each vertex, normalized within the texture region. */
+		regionUVs: ArrayLike<number>;
+
+		/** The UV pair for each vertex, normalized within the entire texture.
+		 *
+		 * See {@link #updateUVs}. */
+		uvs: ArrayLike<number>;
+
+		/** Triplets of vertex indices which describe the mesh's triangulation. */
+		triangles: Array<number>;
+
+		/** The color to tint the mesh. */
+		color = new Color(1, 1, 1, 1);
+
+		/** The width of the mesh's image. Available only when nonessential data was exported. */
+		width: number;
+
+		/** The height of the mesh's image. Available only when nonessential data was exported. */
+		height: number;
+
+		/** The number of entries at the beginning of {@link #vertices} that make up the mesh hull. */
+		hullLength: number;
+
+		/** Vertex index pairs describing edges for controling triangulation. Mesh triangles will never cross edges. Only available if
+		 * nonessential data was exported. Triangulation is not performed at runtime. */
+		edges: Array<number>;
+
+		private parentMesh: MeshAttachment;
+		tempColor = new Color(0, 0, 0, 0);
+
+		constructor (name: string) {
+			super(name);
+		}
+
+		/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region UVs or
+		 * region. */
+		updateUVs () {
+			let regionUVs = this.regionUVs;
+			if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
+			let uvs = this.uvs;
+			let n = this.uvs.length;
+			let u = this.region.u, v = this.region.v, width = 0, height = 0;
+			if (this.region instanceof TextureAtlasRegion) {
+				let region = this.region, image = region.page.texture.getImage();
+				let textureWidth = image.width, textureHeight = image.height;
+				switch (region.degrees) {
+					case 90:
+						u -= (region.originalHeight - region.offsetY - region.height) / textureWidth;
+						v -= (region.originalWidth - region.offsetX - region.width) / textureHeight;
+						width = region.originalHeight / textureWidth;
+						height = region.originalWidth / textureHeight;
+						for (let i = 0; i < n; i += 2) {
+							uvs[i] = u + regionUVs[i + 1] * width;
+							uvs[i + 1] = v + (1 - regionUVs[i]) * height;
+						}
+						return;
+					case 180:
+						u -= (region.originalWidth - region.offsetX - region.width) / textureWidth;
+						v -= region.offsetY / textureHeight;
+						width = region.originalWidth / textureWidth;
+						height = region.originalHeight / textureHeight;
+						for (let i = 0; i < n; i += 2) {
+							uvs[i] = u + (1 - regionUVs[i]) * width;
+							uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height;
+						}
+						return;
+					case 270:
+						u -= region.offsetY / textureWidth;
+						v -= region.offsetX / textureHeight;
+						width = region.originalHeight / textureWidth;
+						height = region.originalWidth / textureHeight;
+						for (let i = 0; i < n; i += 2) {
+							uvs[i] = u + (1 - regionUVs[i + 1]) * width;
+							uvs[i + 1] = v + regionUVs[i] * height;
+						}
+						return;
+				}
+				u -= region.offsetX / textureWidth;
+				v -= (region.originalHeight - region.offsetY - region.height) / textureHeight;
+				width = region.originalWidth / textureWidth;
+				height = region.originalHeight / textureHeight;
+			} else if (!this.region) {
+				u = v = 0;
+				width = height = 1;
+			} else {
+				width = this.region.u2 - u;
+				height = this.region.v2 - v;
+			}
+
+			for (let i = 0; i < n; i += 2) {
+				uvs[i] = u + regionUVs[i] * width;
+				uvs[i + 1] = v + regionUVs[i + 1] * height;
+			}
+		}
+
+		/** The parent mesh if this is a linked mesh, else null. A linked mesh shares the {@link #bones}, {@link #vertices},
+		 * {@link #regionUVs}, {@link #triangles}, {@link #hullLength}, {@link #edges}, {@link #width}, and {@link #height} with the
+		 * parent mesh, but may have a different {@link #name} or {@link #path} (and therefore a different texture). */
+		getParentMesh () {
+			return this.parentMesh;
+		}
+
+		/** @param parentMesh May be null. */
+		setParentMesh (parentMesh: MeshAttachment) {
+			this.parentMesh = parentMesh;
+			if (parentMesh) {
+				this.bones = parentMesh.bones;
+				this.vertices = parentMesh.vertices;
+				this.worldVerticesLength = parentMesh.worldVerticesLength;
+				this.regionUVs = parentMesh.regionUVs;
+				this.triangles = parentMesh.triangles;
+				this.hullLength = parentMesh.hullLength;
+				this.worldVerticesLength = parentMesh.worldVerticesLength
+			}
+		}
+
+		copy (): Attachment {
+			if (this.parentMesh) return this.newLinkedMesh();
+
+			let copy = new MeshAttachment(this.name);
+			copy.region = this.region;
+			copy.path = this.path;
+			copy.color.setFromColor(this.color);
+
+			this.copyTo(copy);
+			copy.regionUVs = new Array<number>(this.regionUVs.length);
+			Utils.arrayCopy(this.regionUVs, 0, copy.regionUVs, 0, this.regionUVs.length);
+			copy.uvs = new Array<number>(this.uvs.length);
+			Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, this.uvs.length);
+			copy.triangles = new Array<number>(this.triangles.length);
+			Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length);
+			copy.hullLength = this.hullLength;
+
+			// Nonessential.
+			if (this.edges) {
+				copy.edges = new Array<number>(this.edges.length);
+				Utils.arrayCopy(this.edges, 0, copy.edges, 0, this.edges.length);
+			}
+			copy.width = this.width;
+			copy.height = this.height;
+
+			return copy;
+		}
+
+		/** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/
+		newLinkedMesh (): MeshAttachment {
+			let copy = new MeshAttachment(this.name);
+			copy.region = this.region;
+			copy.path = this.path;
+			copy.color.setFromColor(this.color);
+			copy.deformAttachment = this.deformAttachment;
+			copy.setParentMesh(this.parentMesh ? this.parentMesh : this);
+			copy.updateUVs();
+			return copy;
+		}
+	}
+
+}

+ 66 - 66
spine-ts/core/src/attachments/PathAttachment.ts

@@ -1,66 +1,66 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** An attachment whose vertices make up a composite Bezier curve.
-	 *
-	 * See {@link PathConstraint} and [Paths](http://esotericsoftware.com/spine-paths) in the Spine User Guide. */
-	export class PathAttachment extends VertexAttachment {
-
-		/** The lengths along the path in the setup pose from the start of the path to the end of each Bezier curve. */
-		lengths: Array<number>;
-
-		/** If true, the start and end knots are connected. */
-		closed = false;
-
-		/** If true, additional calculations are performed to make calculating positions along the path more accurate. If false, fewer
-		 * calculations are performed but calculating positions along the path is less accurate. */
-		constantSpeed = false;
-
-		/** The color of the path as it was in Spine. Available only when nonessential data was exported. Paths are not usually
-		 * rendered at runtime. */
-		color = new Color(1, 1, 1, 1);
-
-		constructor (name: string) {
-			super(name);
-		}
-
-		copy (): Attachment {
-			let copy = new PathAttachment(this.name);
-			this.copyTo(copy);
-			copy.lengths = new Array<number>(this.lengths.length);
-			Utils.arrayCopy(this.lengths, 0, copy.lengths, 0, this.lengths.length);
-			copy.closed = closed;
-			copy.constantSpeed = this.constantSpeed;
-			copy.color.setFromColor(this.color);
-			return copy;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** An attachment whose vertices make up a composite Bezier curve.
+	 *
+	 * See {@link PathConstraint} and [Paths](http://esotericsoftware.com/spine-paths) in the Spine User Guide. */
+	export class PathAttachment extends VertexAttachment {
+
+		/** The lengths along the path in the setup pose from the start of the path to the end of each Bezier curve. */
+		lengths: Array<number>;
+
+		/** If true, the start and end knots are connected. */
+		closed = false;
+
+		/** If true, additional calculations are performed to make calculating positions along the path more accurate. If false, fewer
+		 * calculations are performed but calculating positions along the path is less accurate. */
+		constantSpeed = false;
+
+		/** The color of the path as it was in Spine. Available only when nonessential data was exported. Paths are not usually
+		 * rendered at runtime. */
+		color = new Color(1, 1, 1, 1);
+
+		constructor (name: string) {
+			super(name);
+		}
+
+		copy (): Attachment {
+			let copy = new PathAttachment(this.name);
+			this.copyTo(copy);
+			copy.lengths = new Array<number>(this.lengths.length);
+			Utils.arrayCopy(this.lengths, 0, copy.lengths, 0, this.lengths.length);
+			copy.closed = closed;
+			copy.constantSpeed = this.constantSpeed;
+			copy.color.setFromColor(this.color);
+			return copy;
+		}
+	}
+}

+ 226 - 226
spine-ts/core/src/attachments/RegionAttachment.ts

@@ -1,226 +1,226 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine {
-
-	/** An attachment that displays a textured quadrilateral.
-	 *
-	 * See [Region attachments](http://esotericsoftware.com/spine-regions) in the Spine User Guide. */
-	export class RegionAttachment extends Attachment {
-		/** The local x translation. */
-		x = 0;
-
-		/** The local y translation. */
-		y = 0;
-
-		/** The local scaleX. */
-		scaleX = 1;
-
-		/** The local scaleY. */
-		scaleY = 1;
-
-		/** The local rotation. */
-		rotation = 0;
-
-		/** The width of the region attachment in Spine. */
-		width = 0;
-
-		/** The height of the region attachment in Spine. */
-		height = 0;
-
-		/** The color to tint the region attachment. */
-		color = new Color(1, 1, 1, 1);
-
-		/** The name of the texture region for this attachment. */
-		path: string;
-
-		rendererObject: any;
-		region: TextureRegion;
-
-		/** For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.
-		 *
-		 * See {@link #updateOffset()}. */
-		offset = Utils.newFloatArray(8);
-
-		uvs = Utils.newFloatArray(8);
-
-		tempColor = new Color(1, 1, 1, 1);
-
-		constructor (name:string) {
-			super(name);
-		}
-
-		/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
-		updateOffset () : void {
-			let region = this.region;
-			let regionScaleX = this.width / this.region.originalWidth * this.scaleX;
-			let regionScaleY = this.height / this.region.originalHeight * this.scaleY;
-			let localX = -this.width / 2 * this.scaleX + this.region.offsetX * regionScaleX;
-			let localY = -this.height / 2 * this.scaleY + this.region.offsetY * regionScaleY;
-			let localX2 = localX + this.region.width * regionScaleX;
-			let localY2 = localY + this.region.height * regionScaleY;
-			let radians = this.rotation * Math.PI / 180;
-			let cos = Math.cos(radians);
-			let sin = Math.sin(radians);
-			let x = this.x, y = this.y;
-			let localXCos = localX * cos + x;
-			let localXSin = localX * sin;
-			let localYCos = localY * cos + y;
-			let localYSin = localY * sin;
-			let localX2Cos = localX2 * cos + x;
-			let localX2Sin = localX2 * sin;
-			let localY2Cos = localY2 * cos + y;
-			let localY2Sin = localY2 * sin;
-			let offset = this.offset;
-			offset[0] = localXCos - localYSin;
-			offset[1] = localYCos + localXSin;
-			offset[2] = localXCos - localY2Sin;
-			offset[3] = localY2Cos + localXSin;
-			offset[4] = localX2Cos - localY2Sin;
-			offset[5] = localY2Cos + localX2Sin;
-			offset[6] = localX2Cos - localYSin;
-			offset[7] = localYCos + localX2Sin;
-		}
-
-		setRegion (region: TextureRegion) : void {
-			this.region = region;
-			let uvs = this.uvs;
-			if (region.degrees == 90) {
-				uvs[2] = region.u;
-				uvs[3] = region.v2;
-				uvs[4] = region.u;
-				uvs[5] = region.v;
-				uvs[6] = region.u2;
-				uvs[7] = region.v;
-				uvs[0] = region.u2;
-				uvs[1] = region.v2;
-			} else {
-				uvs[0] = region.u;
-				uvs[1] = region.v2;
-				uvs[2] = region.u;
-				uvs[3] = region.v;
-				uvs[4] = region.u2;
-				uvs[5] = region.v;
-				uvs[6] = region.u2;
-				uvs[7] = region.v2;
-			}
-		}
-
-		/** Transforms the attachment's four vertices to world coordinates.
-		 *
-		 * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
-		 * Runtimes Guide.
-		 * @param worldVertices The output world vertices. Must have a length >= `offset` + 8.
-		 * @param offset The `worldVertices` index to begin writing values.
-		 * @param stride The number of `worldVertices` entries between the value pairs written. */
-		computeWorldVertices (bone: Bone, worldVertices: ArrayLike<number>, offset: number, stride: number) {
-			let vertexOffset = this.offset;
-			let x = bone.worldX, y = bone.worldY;
-			let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-			let offsetX = 0, offsetY = 0;
-
-			offsetX = vertexOffset[0];
-			offsetY = vertexOffset[1];
-			worldVertices[offset] = offsetX * a + offsetY * b + x; // br
-			worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
-			offset += stride;
-
-			offsetX = vertexOffset[2];
-			offsetY = vertexOffset[3];
-			worldVertices[offset] = offsetX * a + offsetY * b + x; // bl
-			worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
-			offset += stride;
-
-			offsetX = vertexOffset[4];
-			offsetY = vertexOffset[5];
-			worldVertices[offset] = offsetX * a + offsetY * b + x; // ul
-			worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
-			offset += stride;
-
-			offsetX = vertexOffset[6];
-			offsetY = vertexOffset[7];
-			worldVertices[offset] = offsetX * a + offsetY * b + x; // ur
-			worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
-		}
-
-		copy (): Attachment {
-			let copy = new RegionAttachment(this.name);
-			copy.region = this.region;
-			copy.rendererObject = this.rendererObject;
-			copy.path = this.path;
-			copy.x = this.x;
-			copy.y = this.y;
-			copy.scaleX = this.scaleX;
-			copy.scaleY = this.scaleY;
-			copy.rotation = this.rotation;
-			copy.width = this.width;
-			copy.height = this.height;
-			Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, 8);
-			Utils.arrayCopy(this.offset, 0, copy.offset, 0, 8);
-			copy.color.setFromColor(this.color);
-			return copy;
-		}
-
-		static X1 = 0;
-		static Y1 = 1;
-		static C1R = 2;
-		static C1G = 3;
-		static C1B = 4;
-		static C1A = 5;
-		static U1 = 6;
-		static V1 = 7;
-
-		static X2 = 8;
-		static Y2 = 9;
-		static C2R = 10;
-		static C2G = 11;
-		static C2B = 12;
-		static C2A = 13;
-		static U2 = 14;
-		static V2 = 15;
-
-		static X3 = 16;
-		static Y3 = 17;
-		static C3R = 18;
-		static C3G = 19;
-		static C3B = 20;
-		static C3A = 21;
-		static U3 = 22;
-		static V3 = 23;
-
-		static X4 = 24;
-		static Y4 = 25;
-		static C4R = 26;
-		static C4G = 27;
-		static C4B = 28;
-		static C4A = 29;
-		static U4 = 30;
-		static V4 = 31;
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine {
+
+	/** An attachment that displays a textured quadrilateral.
+	 *
+	 * See [Region attachments](http://esotericsoftware.com/spine-regions) in the Spine User Guide. */
+	export class RegionAttachment extends Attachment {
+		/** The local x translation. */
+		x = 0;
+
+		/** The local y translation. */
+		y = 0;
+
+		/** The local scaleX. */
+		scaleX = 1;
+
+		/** The local scaleY. */
+		scaleY = 1;
+
+		/** The local rotation. */
+		rotation = 0;
+
+		/** The width of the region attachment in Spine. */
+		width = 0;
+
+		/** The height of the region attachment in Spine. */
+		height = 0;
+
+		/** The color to tint the region attachment. */
+		color = new Color(1, 1, 1, 1);
+
+		/** The name of the texture region for this attachment. */
+		path: string;
+
+		rendererObject: any;
+		region: TextureRegion;
+
+		/** For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.
+		 *
+		 * See {@link #updateOffset()}. */
+		offset = Utils.newFloatArray(8);
+
+		uvs = Utils.newFloatArray(8);
+
+		tempColor = new Color(1, 1, 1, 1);
+
+		constructor (name: string) {
+			super(name);
+		}
+
+		/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
+		updateOffset (): void {
+			let region = this.region;
+			let regionScaleX = this.width / this.region.originalWidth * this.scaleX;
+			let regionScaleY = this.height / this.region.originalHeight * this.scaleY;
+			let localX = -this.width / 2 * this.scaleX + this.region.offsetX * regionScaleX;
+			let localY = -this.height / 2 * this.scaleY + this.region.offsetY * regionScaleY;
+			let localX2 = localX + this.region.width * regionScaleX;
+			let localY2 = localY + this.region.height * regionScaleY;
+			let radians = this.rotation * Math.PI / 180;
+			let cos = Math.cos(radians);
+			let sin = Math.sin(radians);
+			let x = this.x, y = this.y;
+			let localXCos = localX * cos + x;
+			let localXSin = localX * sin;
+			let localYCos = localY * cos + y;
+			let localYSin = localY * sin;
+			let localX2Cos = localX2 * cos + x;
+			let localX2Sin = localX2 * sin;
+			let localY2Cos = localY2 * cos + y;
+			let localY2Sin = localY2 * sin;
+			let offset = this.offset;
+			offset[0] = localXCos - localYSin;
+			offset[1] = localYCos + localXSin;
+			offset[2] = localXCos - localY2Sin;
+			offset[3] = localY2Cos + localXSin;
+			offset[4] = localX2Cos - localY2Sin;
+			offset[5] = localY2Cos + localX2Sin;
+			offset[6] = localX2Cos - localYSin;
+			offset[7] = localYCos + localX2Sin;
+		}
+
+		setRegion (region: TextureRegion): void {
+			this.region = region;
+			let uvs = this.uvs;
+			if (region.degrees == 90) {
+				uvs[2] = region.u;
+				uvs[3] = region.v2;
+				uvs[4] = region.u;
+				uvs[5] = region.v;
+				uvs[6] = region.u2;
+				uvs[7] = region.v;
+				uvs[0] = region.u2;
+				uvs[1] = region.v2;
+			} else {
+				uvs[0] = region.u;
+				uvs[1] = region.v2;
+				uvs[2] = region.u;
+				uvs[3] = region.v;
+				uvs[4] = region.u2;
+				uvs[5] = region.v;
+				uvs[6] = region.u2;
+				uvs[7] = region.v2;
+			}
+		}
+
+		/** Transforms the attachment's four vertices to world coordinates.
+		 *
+		 * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
+		 * Runtimes Guide.
+		 * @param worldVertices The output world vertices. Must have a length >= `offset` + 8.
+		 * @param offset The `worldVertices` index to begin writing values.
+		 * @param stride The number of `worldVertices` entries between the value pairs written. */
+		computeWorldVertices (bone: Bone, worldVertices: ArrayLike<number>, offset: number, stride: number) {
+			let vertexOffset = this.offset;
+			let x = bone.worldX, y = bone.worldY;
+			let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+			let offsetX = 0, offsetY = 0;
+
+			offsetX = vertexOffset[0];
+			offsetY = vertexOffset[1];
+			worldVertices[offset] = offsetX * a + offsetY * b + x; // br
+			worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
+			offset += stride;
+
+			offsetX = vertexOffset[2];
+			offsetY = vertexOffset[3];
+			worldVertices[offset] = offsetX * a + offsetY * b + x; // bl
+			worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
+			offset += stride;
+
+			offsetX = vertexOffset[4];
+			offsetY = vertexOffset[5];
+			worldVertices[offset] = offsetX * a + offsetY * b + x; // ul
+			worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
+			offset += stride;
+
+			offsetX = vertexOffset[6];
+			offsetY = vertexOffset[7];
+			worldVertices[offset] = offsetX * a + offsetY * b + x; // ur
+			worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
+		}
+
+		copy (): Attachment {
+			let copy = new RegionAttachment(this.name);
+			copy.region = this.region;
+			copy.rendererObject = this.rendererObject;
+			copy.path = this.path;
+			copy.x = this.x;
+			copy.y = this.y;
+			copy.scaleX = this.scaleX;
+			copy.scaleY = this.scaleY;
+			copy.rotation = this.rotation;
+			copy.width = this.width;
+			copy.height = this.height;
+			Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, 8);
+			Utils.arrayCopy(this.offset, 0, copy.offset, 0, 8);
+			copy.color.setFromColor(this.color);
+			return copy;
+		}
+
+		static X1 = 0;
+		static Y1 = 1;
+		static C1R = 2;
+		static C1G = 3;
+		static C1B = 4;
+		static C1A = 5;
+		static U1 = 6;
+		static V1 = 7;
+
+		static X2 = 8;
+		static Y2 = 9;
+		static C2R = 10;
+		static C2G = 11;
+		static C2B = 12;
+		static C2A = 13;
+		static U2 = 14;
+		static V2 = 15;
+
+		static X3 = 16;
+		static Y3 = 17;
+		static C3R = 18;
+		static C3G = 19;
+		static C3B = 20;
+		static C3A = 21;
+		static U3 = 22;
+		static V3 = 23;
+
+		static X4 = 24;
+		static Y4 = 25;
+		static C4R = 26;
+		static C4G = 27;
+		static C4B = 28;
+		static C4A = 29;
+		static U4 = 30;
+		static V4 = 31;
+	}
+}

+ 1 - 1
spine-ts/core/src/polyfills.ts

@@ -28,7 +28,7 @@
  *****************************************************************************/
 
 interface Math {
-	fround(n: number): number;
+	fround (n: number): number;
 }
 
 (() => {

+ 3 - 3
spine-ts/core/src/vertexeffects/JitterEffect.ts

@@ -37,15 +37,15 @@ module spine {
 			this.jitterY = jitterY;
 		}
 
-		begin(skeleton: Skeleton): void {
+		begin (skeleton: Skeleton): void {
 		}
 
-		transform(position: Vector2, uv: Vector2, light: Color, dark: Color): void {
+		transform (position: Vector2, uv: Vector2, light: Color, dark: Color): void {
 			position.x += MathUtils.randomTriangular(-this.jitterX, this.jitterY);
 			position.y += MathUtils.randomTriangular(-this.jitterX, this.jitterY);
 		}
 
-		end(): void {
+		end (): void {
 		}
 	}
 }

+ 3 - 3
spine-ts/core/src/vertexeffects/SwirlEffect.ts

@@ -41,12 +41,12 @@ module spine {
 			this.radius = radius;
 		}
 
-		begin(skeleton: Skeleton): void {
+		begin (skeleton: Skeleton): void {
 			this.worldX = skeleton.x + this.centerX;
 			this.worldY = skeleton.y + this.centerY;
 		}
 
-		transform(position: Vector2, uv: Vector2, light: Color, dark: Color): void {
+		transform (position: Vector2, uv: Vector2, light: Color, dark: Color): void {
 			let radAngle = this.angle * MathUtils.degreesToRadians;
 			let x = position.x - this.worldX;
 			let y = position.y - this.worldY;
@@ -60,7 +60,7 @@ module spine {
 			}
 		}
 
-		end(): void {
+		end (): void {
 		}
 	}
 }

+ 8 - 8
spine-ts/player/src/Player.ts

@@ -519,7 +519,7 @@ module spine {
 			if (!controlBones.length && !config.showControls) return;
 			let selectedBones = this.selectedBones = new Array<Bone>(controlBones.length);
 			let canvas = this.canvas;
-			let target:Bone = null;
+			let target: Bone = null;
 			let offset = new spine.Vector2();
 			let coords = new spine.webgl.Vector3();
 			let mouse = new spine.webgl.Vector3();
@@ -528,10 +528,10 @@ module spine {
 			let renderer = this.sceneRenderer;
 
 			let closest = function (x: number, y: number): Bone {
- 				mouse.set(x, canvas.clientHeight - y, 0)
+				mouse.set(x, canvas.clientHeight - y, 0)
 				offset.x = offset.y = 0;
 				let bestDistance = 24, index = 0;
-				let best:Bone;
+				let best: Bone;
 				for (let i = 0; i < controlBones.length; i++) {
 					selectedBones[i] = null;
 					let bone = skeleton.findBone(controlBones[i]);
@@ -789,9 +789,9 @@ module spine {
 					// Determine the viewport.
 					let viewport = this.viewport;
 					viewport.x = this.currentViewport.x - (this.currentViewport.padLeft as number),
-					viewport.y = this.currentViewport.y - (this.currentViewport.padBottom as number),
-					viewport.width = this.currentViewport.width + (this.currentViewport.padLeft as number) + (this.currentViewport.padRight as number),
-					viewport.height = this.currentViewport.height + (this.currentViewport.padBottom as number) + (this.currentViewport.padTop as number)
+						viewport.y = this.currentViewport.y - (this.currentViewport.padBottom as number),
+						viewport.width = this.currentViewport.width + (this.currentViewport.padLeft as number) + (this.currentViewport.padRight as number),
+						viewport.height = this.currentViewport.height + (this.currentViewport.padBottom as number) + (this.currentViewport.padTop as number)
 
 					if (this.previousViewport) {
 						let transitionAlpha = (performance.now() - this.viewportTransitionStart) / 1000 / config.viewport.transitionTime;
@@ -1070,7 +1070,7 @@ module spine {
 		private enabled = false;
 		public change: (value: boolean) => void;
 
-		constructor (private text: string) {}
+		constructor (private text: string) { }
 
 		create (): HTMLElement {
 			this.switch = createElement(/*html*/`
@@ -1108,7 +1108,7 @@ module spine {
 
 		create (): HTMLElement {
 			this.slider = createElement(/*html*/`
-<div class="spine-player-slider ${this.big ? "big": ""}">
+<div class="spine-player-slider ${this.big ? "big" : ""}">
 	<div class="spine-player-slider-value"></div>
 	<!--<div class="spine-player-slider-knob"></div>-->
 </div>`);

+ 7 - 7
spine-ts/player/src/PlayerEditor.ts

@@ -27,12 +27,12 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-declare function CodeMirror(el: Element, config: any): void;
+declare function CodeMirror (el: Element, config: any): void;
 
 module spine {
 	export class SpinePlayerEditor {
 		private static DEFAULT_CODE =
-		`
+			`
 <script src="https://esotericsoftware.com/files/spine-player/4.0/spine-player.js"></script>
 <link rel="stylesheet" href="https://esotericsoftware.com/files/spine-player/4.0/spine-player.css">
 
@@ -47,7 +47,7 @@ new spine.SpinePlayer("player-container", {
 		`.trim();
 
 		private prefix: string =
-`<html>
+			`<html>
 <head>
 <style>
 body { margin: 0px; }
@@ -91,21 +91,21 @@ body { margin: 0px; }
 			})
 		}
 
-		setPreAndPostfix(prefix: string, postfix: string) {
+		setPreAndPostfix (prefix: string, postfix: string) {
 			this.prefix = prefix;
 			this.postfix = postfix;
 			this.startPlayer()
 		}
 
-		setCode(code: string) {
+		setCode (code: string) {
 			this.code.setValue(code);
 			this.startPlayer();
 		}
 
 		private timerId = 0;
-		startPlayer() {
+		startPlayer () {
 			clearTimeout(this.timerId);
-			this.timerId = setTimeout( () => {
+			this.timerId = setTimeout(() => {
 				let code = this.code.getDoc().getValue();
 				code = this.prefix + code + this.postfix;
 				code = window.btoa(code);

+ 38 - 38
spine-ts/threejs/src/AssetManager.ts

@@ -1,38 +1,38 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.threejs {
-	export class AssetManager extends spine.AssetManager {
-		constructor (pathPrefix: string = "", downloader: Downloader = null) {
-			super((image: HTMLImageElement) => {
-				return new ThreeJsTexture(image);
-			}, pathPrefix, downloader);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.threejs {
+	export class AssetManager extends spine.AssetManager {
+		constructor (pathPrefix: string = "", downloader: Downloader = null) {
+			super((image: HTMLImageElement) => {
+				return new ThreeJsTexture(image);
+			}, pathPrefix, downloader);
+		}
+	}
+}

+ 125 - 125
spine-ts/threejs/src/MeshBatcher.ts

@@ -1,125 +1,125 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.threejs {
-	export class MeshBatcher extends THREE.Mesh {
-		private static VERTEX_SIZE = 9;
-		private vertexBuffer: THREE.InterleavedBuffer;
-		private vertices: Float32Array;
-		private verticesLength = 0;
-		private indices: Uint16Array;
-		private indicesLength = 0;
-
-		constructor (maxVertices: number = 10920, materialCustomizer: SkeletonMeshMaterialParametersCustomizer = (parameters) => { }) {
-			super();
-			if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
-			let vertices = this.vertices = new Float32Array(maxVertices * MeshBatcher.VERTEX_SIZE);
-			let indices = this.indices = new Uint16Array(maxVertices * 3);
-			let geo = new THREE.BufferGeometry();
-			let vertexBuffer = this.vertexBuffer = new THREE.InterleavedBuffer(vertices, MeshBatcher.VERTEX_SIZE);
-			vertexBuffer.usage = WebGLRenderingContext.DYNAMIC_DRAW;
-			geo.setAttribute("position", new THREE.InterleavedBufferAttribute(vertexBuffer, 3, 0, false));
-			geo.setAttribute("color", new THREE.InterleavedBufferAttribute(vertexBuffer, 4, 3, false));
-			geo.setAttribute("uv", new THREE.InterleavedBufferAttribute(vertexBuffer, 2, 7, false));
-			geo.setIndex(new THREE.BufferAttribute(indices, 1));
-			geo.getIndex().usage = WebGLRenderingContext.DYNAMIC_DRAW;
-			geo.drawRange.start = 0;
-			geo.drawRange.count = 0;
-			this.geometry = geo;
-			this.material = new SkeletonMeshMaterial(materialCustomizer);
-		}
-
-		dispose () {
-			this.geometry.dispose();
-			if (this.material instanceof THREE.Material)
-				this.material.dispose();
-			else if (this.material) {
-				for (let i = 0; i < this.material.length; i++) {
-					let material = this.material[i];
-					if (material instanceof THREE.Material)
-						material.dispose();
-				}
-			}
-		}
-
-		clear () {
-			let geo = (<THREE.BufferGeometry>this.geometry);
-			geo.drawRange.start = 0;
-			geo.drawRange.count = 0;
-			(<SkeletonMeshMaterial>this.material).uniforms.map.value = null;
-		}
-
-		begin () {
-			this.verticesLength = 0;
-			this.indicesLength = 0;
-		}
-
-		canBatch(verticesLength: number, indicesLength: number) {
-			if (this.indicesLength + indicesLength >= this.indices.byteLength / 2) return false;
-			if (this.verticesLength + verticesLength >= this.vertices.byteLength / 2) return false;
-			return true;
-		}
-
-		batch (vertices: ArrayLike<number>, verticesLength: number, indices: ArrayLike<number>, indicesLength: number, z: number = 0) {
-			let indexStart = this.verticesLength / MeshBatcher.VERTEX_SIZE;
-			let vertexBuffer = this.vertices;
-			let i = this.verticesLength;
-			let j = 0;
-			for (;j < verticesLength;) {
-				vertexBuffer[i++] = vertices[j++];
-				vertexBuffer[i++] = vertices[j++];
-				vertexBuffer[i++] = z;
-				vertexBuffer[i++] = vertices[j++];
-				vertexBuffer[i++] = vertices[j++];
-				vertexBuffer[i++] = vertices[j++];
-				vertexBuffer[i++] = vertices[j++];
-				vertexBuffer[i++] = vertices[j++];
-				vertexBuffer[i++] = vertices[j++];
-			}
-			this.verticesLength = i;
-
-			let indicesArray = this.indices;
-			for (i = this.indicesLength, j = 0; j < indicesLength; i++, j++)
-				indicesArray[i] = indices[j] + indexStart;
-			this.indicesLength += indicesLength;
-		}
-
-		end () {
-			this.vertexBuffer.needsUpdate = this.verticesLength > 0;
-			this.vertexBuffer.updateRange.offset = 0;
-			this.vertexBuffer.updateRange.count = this.verticesLength;
-			let geo = (<THREE.BufferGeometry>this.geometry);
-			geo.getIndex().needsUpdate = this.indicesLength > 0;
-			geo.getIndex().updateRange.offset = 0;
-			geo.getIndex().updateRange.count = this.indicesLength;
-			geo.drawRange.start = 0;
-			geo.drawRange.count = this.indicesLength;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.threejs {
+	export class MeshBatcher extends THREE.Mesh {
+		private static VERTEX_SIZE = 9;
+		private vertexBuffer: THREE.InterleavedBuffer;
+		private vertices: Float32Array;
+		private verticesLength = 0;
+		private indices: Uint16Array;
+		private indicesLength = 0;
+
+		constructor (maxVertices: number = 10920, materialCustomizer: SkeletonMeshMaterialParametersCustomizer = (parameters) => { }) {
+			super();
+			if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
+			let vertices = this.vertices = new Float32Array(maxVertices * MeshBatcher.VERTEX_SIZE);
+			let indices = this.indices = new Uint16Array(maxVertices * 3);
+			let geo = new THREE.BufferGeometry();
+			let vertexBuffer = this.vertexBuffer = new THREE.InterleavedBuffer(vertices, MeshBatcher.VERTEX_SIZE);
+			vertexBuffer.usage = WebGLRenderingContext.DYNAMIC_DRAW;
+			geo.setAttribute("position", new THREE.InterleavedBufferAttribute(vertexBuffer, 3, 0, false));
+			geo.setAttribute("color", new THREE.InterleavedBufferAttribute(vertexBuffer, 4, 3, false));
+			geo.setAttribute("uv", new THREE.InterleavedBufferAttribute(vertexBuffer, 2, 7, false));
+			geo.setIndex(new THREE.BufferAttribute(indices, 1));
+			geo.getIndex().usage = WebGLRenderingContext.DYNAMIC_DRAW;
+			geo.drawRange.start = 0;
+			geo.drawRange.count = 0;
+			this.geometry = geo;
+			this.material = new SkeletonMeshMaterial(materialCustomizer);
+		}
+
+		dispose () {
+			this.geometry.dispose();
+			if (this.material instanceof THREE.Material)
+				this.material.dispose();
+			else if (this.material) {
+				for (let i = 0; i < this.material.length; i++) {
+					let material = this.material[i];
+					if (material instanceof THREE.Material)
+						material.dispose();
+				}
+			}
+		}
+
+		clear () {
+			let geo = (<THREE.BufferGeometry>this.geometry);
+			geo.drawRange.start = 0;
+			geo.drawRange.count = 0;
+			(<SkeletonMeshMaterial>this.material).uniforms.map.value = null;
+		}
+
+		begin () {
+			this.verticesLength = 0;
+			this.indicesLength = 0;
+		}
+
+		canBatch (verticesLength: number, indicesLength: number) {
+			if (this.indicesLength + indicesLength >= this.indices.byteLength / 2) return false;
+			if (this.verticesLength + verticesLength >= this.vertices.byteLength / 2) return false;
+			return true;
+		}
+
+		batch (vertices: ArrayLike<number>, verticesLength: number, indices: ArrayLike<number>, indicesLength: number, z: number = 0) {
+			let indexStart = this.verticesLength / MeshBatcher.VERTEX_SIZE;
+			let vertexBuffer = this.vertices;
+			let i = this.verticesLength;
+			let j = 0;
+			for (; j < verticesLength;) {
+				vertexBuffer[i++] = vertices[j++];
+				vertexBuffer[i++] = vertices[j++];
+				vertexBuffer[i++] = z;
+				vertexBuffer[i++] = vertices[j++];
+				vertexBuffer[i++] = vertices[j++];
+				vertexBuffer[i++] = vertices[j++];
+				vertexBuffer[i++] = vertices[j++];
+				vertexBuffer[i++] = vertices[j++];
+				vertexBuffer[i++] = vertices[j++];
+			}
+			this.verticesLength = i;
+
+			let indicesArray = this.indices;
+			for (i = this.indicesLength, j = 0; j < indicesLength; i++, j++)
+				indicesArray[i] = indices[j] + indexStart;
+			this.indicesLength += indicesLength;
+		}
+
+		end () {
+			this.vertexBuffer.needsUpdate = this.verticesLength > 0;
+			this.vertexBuffer.updateRange.offset = 0;
+			this.vertexBuffer.updateRange.count = this.verticesLength;
+			let geo = (<THREE.BufferGeometry>this.geometry);
+			geo.getIndex().needsUpdate = this.indicesLength > 0;
+			geo.getIndex().updateRange.offset = 0;
+			geo.getIndex().updateRange.count = this.indicesLength;
+			geo.drawRange.start = 0;
+			geo.drawRange.count = this.indicesLength;
+		}
+	}
+}

+ 322 - 322
spine-ts/threejs/src/SkeletonMesh.ts

@@ -1,322 +1,322 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.threejs {
-	export interface SkeletonMeshMaterialParametersCustomizer {
-		(materialParameters: THREE.ShaderMaterialParameters): void;
-	}
-
-	export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
-		constructor (customizer: SkeletonMeshMaterialParametersCustomizer) {
-			let vertexShader = `
-				attribute vec4 color;
-				varying vec2 vUv;
-				varying vec4 vColor;
-				void main() {
-					vUv = uv;
-					vColor = color;
-					gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
-				}
-			`;
-			let fragmentShader = `
-				uniform sampler2D map;
-				varying vec2 vUv;
-				varying vec4 vColor;
-				void main(void) {
-					gl_FragColor = texture2D(map, vUv)*vColor;
-				}
-			`;
-
-			let parameters: THREE.ShaderMaterialParameters = {
-				uniforms: {
-					map: { type: "t", value: null }
-				},
-				vertexShader: vertexShader,
-				fragmentShader: fragmentShader,
-				side: THREE.DoubleSide,
-				transparent: true,
-				alphaTest: 0.5
-			};
-			customizer(parameters);
-			super(parameters);
-		};
-	}
-
-	export class SkeletonMesh extends THREE.Object3D {
-		tempPos: Vector2 = new Vector2();
-		tempUv: Vector2 = new Vector2();
-		tempLight = new Color();
-		tempDark = new Color();
-		skeleton: Skeleton;
-		state: AnimationState;
-		zOffset: number = 0.1;
-		vertexEffect: VertexEffect;
-
-		private batches = new Array<MeshBatcher>();
-		private nextBatchIndex = 0;
-		private clipper: SkeletonClipping = new SkeletonClipping();
-
-		static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
-		static VERTEX_SIZE = 2 + 2 + 4;
-
-		private vertices = Utils.newFloatArray(1024);
-		private tempColor = new Color();
-		private materialCustomizer: SkeletonMeshMaterialParametersCustomizer;
-
-		constructor (skeletonData: SkeletonData, materialCustomizer: SkeletonMeshMaterialParametersCustomizer = (parameters) => { }) {
-			super();
-			this.materialCustomizer = materialCustomizer;
-			this.skeleton = new Skeleton(skeletonData);
-			let animData = new AnimationStateData(skeletonData);
-			this.state = new AnimationState(animData);
-		}
-
-		update(deltaTime: number) {
-			let state = this.state;
-			let skeleton = this.skeleton;
-
-			state.update(deltaTime);
-			state.apply(skeleton);
-			skeleton.updateWorldTransform();
-
-			this.updateGeometry();
-		}
-
-		dispose () {
-			for (var i = 0; i < this.batches.length; i++) {
-				this.batches[i].dispose();
-			}
-		}
-
-		private clearBatches () {
-			for (var i = 0; i < this.batches.length; i++) {
-				this.batches[i].clear();
-				this.batches[i].visible = false;
-			}
-			this.nextBatchIndex = 0;
-		}
-
-		private nextBatch () {
-			if (this.batches.length == this.nextBatchIndex) {
-				let batch = new MeshBatcher(10920, this.materialCustomizer);
-				this.add(batch);
-				this.batches.push(batch);
-			}
-			let batch = this.batches[this.nextBatchIndex++];
-			batch.visible = true;
-			return batch;
-		}
-
-		private updateGeometry() {
-			this.clearBatches();
-
-			let tempPos = this.tempPos;
-			let tempUv = this.tempUv;
-			let tempLight = this.tempLight;
-			let tempDark = this.tempDark;
-
-			var numVertices = 0;
-			var verticesLength = 0;
-			var indicesLength = 0;
-
-			let blendMode: BlendMode = null;
-			let clipper = this.clipper;
-
-			let vertices: ArrayLike<number> = this.vertices;
-			let triangles: Array<number> = null;
-			let uvs: ArrayLike<number> = null;
-			let drawOrder = this.skeleton.drawOrder;
-			let batch = this.nextBatch();
-			batch.begin();
-			let z = 0;
-			let zOffset = this.zOffset;
-			for (let i = 0, n = drawOrder.length; i < n; i++) {
-				let vertexSize = clipper.isClipping() ? 2 : SkeletonMesh.VERTEX_SIZE;
-				let slot = drawOrder[i];
-				if (!slot.bone.active) {
-					clipper.clipEndWithSlot(slot);
-					continue;
-				}
-				let attachment = slot.getAttachment();
-				let attachmentColor: Color = null;
-				let texture: ThreeJsTexture = null;
-				let numFloats = 0;
-				if (attachment instanceof RegionAttachment) {
-					let region = <RegionAttachment>attachment;
-					attachmentColor = region.color;
-					vertices = this.vertices;
-					numFloats = vertexSize * 4;
-					region.computeWorldVertices(slot.bone, vertices, 0, vertexSize);
-					triangles = SkeletonMesh.QUAD_TRIANGLES;
-					uvs = region.uvs;
-					texture = <ThreeJsTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
-				} else if (attachment instanceof MeshAttachment) {
-					let mesh = <MeshAttachment>attachment;
-					attachmentColor = mesh.color;
-					vertices = this.vertices;
-					numFloats = (mesh.worldVerticesLength >> 1) * vertexSize;
-					if (numFloats > vertices.length) {
-						vertices = this.vertices = spine.Utils.newFloatArray(numFloats);
-					}
-					mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, vertexSize);
-					triangles = mesh.triangles;
-					uvs = mesh.uvs;
-					texture = <ThreeJsTexture>(<TextureAtlasRegion>mesh.region.renderObject).page.texture;
-				} else if (attachment instanceof ClippingAttachment) {
-					let clip = <ClippingAttachment>(attachment);
-					clipper.clipStart(slot, clip);
-					continue;
-				} else {
-					clipper.clipEndWithSlot(slot);
-					continue;
-				}
-
-				if (texture) {
-					let skeleton = slot.bone.skeleton;
-					let skeletonColor = skeleton.color;
-					let slotColor = slot.color;
-					let alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
-					let color = this.tempColor;
-					color.set(skeletonColor.r * slotColor.r * attachmentColor.r,
-							skeletonColor.g * slotColor.g * attachmentColor.g,
-							skeletonColor.b * slotColor.b * attachmentColor.b,
-							alpha);
-
-					let finalVertices: ArrayLike<number>;
-					let finalVerticesLength: number;
-					let finalIndices: ArrayLike<number>;
-					let finalIndicesLength: number;
-
-					if (clipper.isClipping()) {
-						clipper.clipTriangles(vertices, numFloats, triangles, triangles.length, uvs, color, null, false);
-						let clippedVertices = clipper.clippedVertices;
-						let clippedTriangles = clipper.clippedTriangles;
-						if (this.vertexEffect) {
-							let vertexEffect = this.vertexEffect;
-							let verts = clippedVertices;
-							for (let v = 0, n = clippedVertices.length; v < n; v += vertexSize) {
-								tempPos.x = verts[v];
-								tempPos.y = verts[v + 1];
-								tempLight.setFromColor(color);
-								tempDark.set(0, 0, 0, 0);
-								tempUv.x = verts[v + 6];
-								tempUv.y = verts[v + 7];
-								vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
-								verts[v] = tempPos.x;
-								verts[v + 1] = tempPos.y;
-								verts[v + 2] = tempLight.r;
-								verts[v + 3] = tempLight.g;
-								verts[v + 4] = tempLight.b;
-								verts[v + 5] = tempLight.a;
-								verts[v + 6] = tempUv.x;
-								verts[v + 7] = tempUv.y;
-							}
-						}
-						finalVertices = clippedVertices;
-						finalVerticesLength = clippedVertices.length;
-						finalIndices = clippedTriangles;
-						finalIndicesLength = clippedTriangles.length;
-					} else {
-						let verts = vertices;
-						if (this.vertexEffect) {
-							let vertexEffect = this.vertexEffect;
-							for (let v = 0, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) {
-								tempPos.x = verts[v];
-								tempPos.y = verts[v + 1];
-								tempLight.setFromColor(color);
-								tempDark.set(0, 0, 0, 0);
-								tempUv.x = uvs[u];
-								tempUv.y = uvs[u + 1];
-								vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
-								verts[v] = tempPos.x;
-								verts[v + 1] = tempPos.y;
-								verts[v + 2] = tempLight.r;
-								verts[v + 3] = tempLight.g;
-								verts[v + 4] = tempLight.b;
-								verts[v + 5] = tempLight.a;
-								verts[v + 6] = tempUv.x;
-								verts[v + 7] = tempUv.y;
-							}
-						} else {
-							for (let v = 2, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) {
-								verts[v] = color.r;
-								verts[v + 1] = color.g;
-								verts[v + 2] = color.b;
-								verts[v + 3] = color.a;
-								verts[v + 4] = uvs[u];
-								verts[v + 5] = uvs[u + 1];
-							}
-						}
-						finalVertices = vertices;
-						finalVerticesLength = numFloats;
-						finalIndices = triangles;
-						finalIndicesLength = triangles.length;
-					}
-
-					if (finalVerticesLength == 0 || finalIndicesLength == 0) {
-						clipper.clipEndWithSlot(slot);
-						continue;
-					}
-
-					// Start new batch if this one can't hold vertices/indices
-					if (!batch.canBatch(finalVerticesLength, finalIndicesLength)) {
-						batch.end();
-						batch = this.nextBatch();
-						batch.begin();
-					}
-
-					// FIXME per slot blending would require multiple material support
-					//let slotBlendMode = slot.data.blendMode;
-					//if (slotBlendMode != blendMode) {
-					//	blendMode = slotBlendMode;
-					//	batcher.setBlendMode(getSourceGLBlendMode(this._gl, blendMode, premultipliedAlpha), getDestGLBlendMode(this._gl, blendMode));
-					//}
-
-					let batchMaterial = <SkeletonMeshMaterial>batch.material;
-					if (!batchMaterial.uniforms.map.value) batchMaterial.uniforms.map.value = texture.texture;
-					if (batchMaterial.uniforms.map.value != texture.texture) {
-						batch.end();
-						batch = this.nextBatch();
-						batch.begin();
-						batchMaterial = <SkeletonMeshMaterial>batch.material;
-						batchMaterial.uniforms.map.value = texture.texture;
-					}
-					batchMaterial.needsUpdate = true;
-
-					batch.batch(finalVertices, finalVerticesLength, finalIndices, finalIndicesLength, z);
-					z += zOffset;
-				}
-
-				clipper.clipEndWithSlot(slot);
-			}
-			clipper.clipEnd();
-			batch.end();
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.threejs {
+	export interface SkeletonMeshMaterialParametersCustomizer {
+		(materialParameters: THREE.ShaderMaterialParameters): void;
+	}
+
+	export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
+		constructor (customizer: SkeletonMeshMaterialParametersCustomizer) {
+			let vertexShader = `
+				attribute vec4 color;
+				varying vec2 vUv;
+				varying vec4 vColor;
+				void main() {
+					vUv = uv;
+					vColor = color;
+					gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
+				}
+			`;
+			let fragmentShader = `
+				uniform sampler2D map;
+				varying vec2 vUv;
+				varying vec4 vColor;
+				void main(void) {
+					gl_FragColor = texture2D(map, vUv)*vColor;
+				}
+			`;
+
+			let parameters: THREE.ShaderMaterialParameters = {
+				uniforms: {
+					map: { type: "t", value: null }
+				},
+				vertexShader: vertexShader,
+				fragmentShader: fragmentShader,
+				side: THREE.DoubleSide,
+				transparent: true,
+				alphaTest: 0.5
+			};
+			customizer(parameters);
+			super(parameters);
+		};
+	}
+
+	export class SkeletonMesh extends THREE.Object3D {
+		tempPos: Vector2 = new Vector2();
+		tempUv: Vector2 = new Vector2();
+		tempLight = new Color();
+		tempDark = new Color();
+		skeleton: Skeleton;
+		state: AnimationState;
+		zOffset: number = 0.1;
+		vertexEffect: VertexEffect;
+
+		private batches = new Array<MeshBatcher>();
+		private nextBatchIndex = 0;
+		private clipper: SkeletonClipping = new SkeletonClipping();
+
+		static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
+		static VERTEX_SIZE = 2 + 2 + 4;
+
+		private vertices = Utils.newFloatArray(1024);
+		private tempColor = new Color();
+		private materialCustomizer: SkeletonMeshMaterialParametersCustomizer;
+
+		constructor (skeletonData: SkeletonData, materialCustomizer: SkeletonMeshMaterialParametersCustomizer = (parameters) => { }) {
+			super();
+			this.materialCustomizer = materialCustomizer;
+			this.skeleton = new Skeleton(skeletonData);
+			let animData = new AnimationStateData(skeletonData);
+			this.state = new AnimationState(animData);
+		}
+
+		update (deltaTime: number) {
+			let state = this.state;
+			let skeleton = this.skeleton;
+
+			state.update(deltaTime);
+			state.apply(skeleton);
+			skeleton.updateWorldTransform();
+
+			this.updateGeometry();
+		}
+
+		dispose () {
+			for (var i = 0; i < this.batches.length; i++) {
+				this.batches[i].dispose();
+			}
+		}
+
+		private clearBatches () {
+			for (var i = 0; i < this.batches.length; i++) {
+				this.batches[i].clear();
+				this.batches[i].visible = false;
+			}
+			this.nextBatchIndex = 0;
+		}
+
+		private nextBatch () {
+			if (this.batches.length == this.nextBatchIndex) {
+				let batch = new MeshBatcher(10920, this.materialCustomizer);
+				this.add(batch);
+				this.batches.push(batch);
+			}
+			let batch = this.batches[this.nextBatchIndex++];
+			batch.visible = true;
+			return batch;
+		}
+
+		private updateGeometry () {
+			this.clearBatches();
+
+			let tempPos = this.tempPos;
+			let tempUv = this.tempUv;
+			let tempLight = this.tempLight;
+			let tempDark = this.tempDark;
+
+			var numVertices = 0;
+			var verticesLength = 0;
+			var indicesLength = 0;
+
+			let blendMode: BlendMode = null;
+			let clipper = this.clipper;
+
+			let vertices: ArrayLike<number> = this.vertices;
+			let triangles: Array<number> = null;
+			let uvs: ArrayLike<number> = null;
+			let drawOrder = this.skeleton.drawOrder;
+			let batch = this.nextBatch();
+			batch.begin();
+			let z = 0;
+			let zOffset = this.zOffset;
+			for (let i = 0, n = drawOrder.length; i < n; i++) {
+				let vertexSize = clipper.isClipping() ? 2 : SkeletonMesh.VERTEX_SIZE;
+				let slot = drawOrder[i];
+				if (!slot.bone.active) {
+					clipper.clipEndWithSlot(slot);
+					continue;
+				}
+				let attachment = slot.getAttachment();
+				let attachmentColor: Color = null;
+				let texture: ThreeJsTexture = null;
+				let numFloats = 0;
+				if (attachment instanceof RegionAttachment) {
+					let region = <RegionAttachment>attachment;
+					attachmentColor = region.color;
+					vertices = this.vertices;
+					numFloats = vertexSize * 4;
+					region.computeWorldVertices(slot.bone, vertices, 0, vertexSize);
+					triangles = SkeletonMesh.QUAD_TRIANGLES;
+					uvs = region.uvs;
+					texture = <ThreeJsTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
+				} else if (attachment instanceof MeshAttachment) {
+					let mesh = <MeshAttachment>attachment;
+					attachmentColor = mesh.color;
+					vertices = this.vertices;
+					numFloats = (mesh.worldVerticesLength >> 1) * vertexSize;
+					if (numFloats > vertices.length) {
+						vertices = this.vertices = spine.Utils.newFloatArray(numFloats);
+					}
+					mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, vertexSize);
+					triangles = mesh.triangles;
+					uvs = mesh.uvs;
+					texture = <ThreeJsTexture>(<TextureAtlasRegion>mesh.region.renderObject).page.texture;
+				} else if (attachment instanceof ClippingAttachment) {
+					let clip = <ClippingAttachment>(attachment);
+					clipper.clipStart(slot, clip);
+					continue;
+				} else {
+					clipper.clipEndWithSlot(slot);
+					continue;
+				}
+
+				if (texture) {
+					let skeleton = slot.bone.skeleton;
+					let skeletonColor = skeleton.color;
+					let slotColor = slot.color;
+					let alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
+					let color = this.tempColor;
+					color.set(skeletonColor.r * slotColor.r * attachmentColor.r,
+						skeletonColor.g * slotColor.g * attachmentColor.g,
+						skeletonColor.b * slotColor.b * attachmentColor.b,
+						alpha);
+
+					let finalVertices: ArrayLike<number>;
+					let finalVerticesLength: number;
+					let finalIndices: ArrayLike<number>;
+					let finalIndicesLength: number;
+
+					if (clipper.isClipping()) {
+						clipper.clipTriangles(vertices, numFloats, triangles, triangles.length, uvs, color, null, false);
+						let clippedVertices = clipper.clippedVertices;
+						let clippedTriangles = clipper.clippedTriangles;
+						if (this.vertexEffect) {
+							let vertexEffect = this.vertexEffect;
+							let verts = clippedVertices;
+							for (let v = 0, n = clippedVertices.length; v < n; v += vertexSize) {
+								tempPos.x = verts[v];
+								tempPos.y = verts[v + 1];
+								tempLight.setFromColor(color);
+								tempDark.set(0, 0, 0, 0);
+								tempUv.x = verts[v + 6];
+								tempUv.y = verts[v + 7];
+								vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
+								verts[v] = tempPos.x;
+								verts[v + 1] = tempPos.y;
+								verts[v + 2] = tempLight.r;
+								verts[v + 3] = tempLight.g;
+								verts[v + 4] = tempLight.b;
+								verts[v + 5] = tempLight.a;
+								verts[v + 6] = tempUv.x;
+								verts[v + 7] = tempUv.y;
+							}
+						}
+						finalVertices = clippedVertices;
+						finalVerticesLength = clippedVertices.length;
+						finalIndices = clippedTriangles;
+						finalIndicesLength = clippedTriangles.length;
+					} else {
+						let verts = vertices;
+						if (this.vertexEffect) {
+							let vertexEffect = this.vertexEffect;
+							for (let v = 0, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) {
+								tempPos.x = verts[v];
+								tempPos.y = verts[v + 1];
+								tempLight.setFromColor(color);
+								tempDark.set(0, 0, 0, 0);
+								tempUv.x = uvs[u];
+								tempUv.y = uvs[u + 1];
+								vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
+								verts[v] = tempPos.x;
+								verts[v + 1] = tempPos.y;
+								verts[v + 2] = tempLight.r;
+								verts[v + 3] = tempLight.g;
+								verts[v + 4] = tempLight.b;
+								verts[v + 5] = tempLight.a;
+								verts[v + 6] = tempUv.x;
+								verts[v + 7] = tempUv.y;
+							}
+						} else {
+							for (let v = 2, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) {
+								verts[v] = color.r;
+								verts[v + 1] = color.g;
+								verts[v + 2] = color.b;
+								verts[v + 3] = color.a;
+								verts[v + 4] = uvs[u];
+								verts[v + 5] = uvs[u + 1];
+							}
+						}
+						finalVertices = vertices;
+						finalVerticesLength = numFloats;
+						finalIndices = triangles;
+						finalIndicesLength = triangles.length;
+					}
+
+					if (finalVerticesLength == 0 || finalIndicesLength == 0) {
+						clipper.clipEndWithSlot(slot);
+						continue;
+					}
+
+					// Start new batch if this one can't hold vertices/indices
+					if (!batch.canBatch(finalVerticesLength, finalIndicesLength)) {
+						batch.end();
+						batch = this.nextBatch();
+						batch.begin();
+					}
+
+					// FIXME per slot blending would require multiple material support
+					//let slotBlendMode = slot.data.blendMode;
+					//if (slotBlendMode != blendMode) {
+					//	blendMode = slotBlendMode;
+					//	batcher.setBlendMode(getSourceGLBlendMode(this._gl, blendMode, premultipliedAlpha), getDestGLBlendMode(this._gl, blendMode));
+					//}
+
+					let batchMaterial = <SkeletonMeshMaterial>batch.material;
+					if (!batchMaterial.uniforms.map.value) batchMaterial.uniforms.map.value = texture.texture;
+					if (batchMaterial.uniforms.map.value != texture.texture) {
+						batch.end();
+						batch = this.nextBatch();
+						batch.begin();
+						batchMaterial = <SkeletonMeshMaterial>batch.material;
+						batchMaterial.uniforms.map.value = texture.texture;
+					}
+					batchMaterial.needsUpdate = true;
+
+					batch.batch(finalVertices, finalVerticesLength, finalIndices, finalIndicesLength, z);
+					z += zOffset;
+				}
+
+				clipper.clipEndWithSlot(slot);
+			}
+			clipper.clipEnd();
+			batch.end();
+		}
+	}
+}

+ 72 - 72
spine-ts/threejs/src/ThreeJsTexture.ts

@@ -1,72 +1,72 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.threejs {
-	export class ThreeJsTexture extends Texture {
-		texture: THREE.Texture;
-
-		constructor (image: HTMLImageElement) {
-			super(image);
-			this.texture = new THREE.Texture(image);
-			this.texture.flipY = false;
-			this.texture.needsUpdate = true;
-		}
-
-		setFilters (minFilter: TextureFilter, magFilter: TextureFilter) {
-			this.texture.minFilter = ThreeJsTexture.toThreeJsTextureFilter(minFilter);
-			this.texture.magFilter = ThreeJsTexture.toThreeJsTextureFilter(magFilter);
-		}
-
-		setWraps (uWrap: TextureWrap, vWrap: TextureWrap) {
-			this.texture.wrapS = ThreeJsTexture.toThreeJsTextureWrap(uWrap);
-			this.texture.wrapT = ThreeJsTexture.toThreeJsTextureWrap(vWrap);
-		}
-
-		dispose () {
-			this.texture.dispose();
-		}
-
-		static toThreeJsTextureFilter(filter: TextureFilter) {
-			if (filter === TextureFilter.Linear) return THREE.LinearFilter;
-			else if (filter === TextureFilter.MipMap) return THREE.LinearMipMapLinearFilter; // also includes TextureFilter.MipMapLinearLinear
-			else if (filter === TextureFilter.MipMapLinearNearest) return THREE.LinearMipMapNearestFilter;
-			else if (filter === TextureFilter.MipMapNearestLinear) return THREE.NearestMipMapLinearFilter;
-			else if (filter === TextureFilter.MipMapNearestNearest) return THREE.NearestMipMapNearestFilter;
-			else if (filter === TextureFilter.Nearest) return THREE.NearestFilter;
-			else throw new Error("Unknown texture filter: " + filter);
-		}
-
-		static toThreeJsTextureWrap(wrap: TextureWrap) {
-			if (wrap === TextureWrap.ClampToEdge) return THREE.ClampToEdgeWrapping;
-			else if (wrap === TextureWrap.MirroredRepeat) return THREE.MirroredRepeatWrapping;
-			else if (wrap === TextureWrap.Repeat) return THREE.RepeatWrapping;
-			else throw new Error("Unknown texture wrap: " + wrap);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.threejs {
+	export class ThreeJsTexture extends Texture {
+		texture: THREE.Texture;
+
+		constructor (image: HTMLImageElement) {
+			super(image);
+			this.texture = new THREE.Texture(image);
+			this.texture.flipY = false;
+			this.texture.needsUpdate = true;
+		}
+
+		setFilters (minFilter: TextureFilter, magFilter: TextureFilter) {
+			this.texture.minFilter = ThreeJsTexture.toThreeJsTextureFilter(minFilter);
+			this.texture.magFilter = ThreeJsTexture.toThreeJsTextureFilter(magFilter);
+		}
+
+		setWraps (uWrap: TextureWrap, vWrap: TextureWrap) {
+			this.texture.wrapS = ThreeJsTexture.toThreeJsTextureWrap(uWrap);
+			this.texture.wrapT = ThreeJsTexture.toThreeJsTextureWrap(vWrap);
+		}
+
+		dispose () {
+			this.texture.dispose();
+		}
+
+		static toThreeJsTextureFilter (filter: TextureFilter) {
+			if (filter === TextureFilter.Linear) return THREE.LinearFilter;
+			else if (filter === TextureFilter.MipMap) return THREE.LinearMipMapLinearFilter; // also includes TextureFilter.MipMapLinearLinear
+			else if (filter === TextureFilter.MipMapLinearNearest) return THREE.LinearMipMapNearestFilter;
+			else if (filter === TextureFilter.MipMapNearestLinear) return THREE.NearestMipMapLinearFilter;
+			else if (filter === TextureFilter.MipMapNearestNearest) return THREE.NearestMipMapNearestFilter;
+			else if (filter === TextureFilter.Nearest) return THREE.NearestFilter;
+			else throw new Error("Unknown texture filter: " + filter);
+		}
+
+		static toThreeJsTextureWrap (wrap: TextureWrap) {
+			if (wrap === TextureWrap.ClampToEdge) return THREE.ClampToEdgeWrapping;
+			else if (wrap === TextureWrap.MirroredRepeat) return THREE.MirroredRepeatWrapping;
+			else if (wrap === TextureWrap.Repeat) return THREE.RepeatWrapping;
+			else throw new Error("Unknown texture wrap: " + wrap);
+		}
+	}
+}

+ 38 - 38
spine-ts/webgl/src/AssetManager.ts

@@ -1,38 +1,38 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class AssetManager extends spine.AssetManager {
-		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = null) {
-			super((image: HTMLImageElement | ImageBitmap) => {
-				return new spine.webgl.GLTexture(context, image);
-			}, pathPrefix, downloader);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class AssetManager extends spine.AssetManager {
+		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = null) {
+			super((image: HTMLImageElement | ImageBitmap) => {
+				return new spine.webgl.GLTexture(context, image);
+			}, pathPrefix, downloader);
+		}
+	}
+}

+ 88 - 88
spine-ts/webgl/src/Camera.ts

@@ -1,88 +1,88 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class OrthoCamera {
-		position = new Vector3(0, 0, 0);
-		direction = new Vector3(0, 0, -1);
-		up = new Vector3(0, 1, 0);
-		near = 0;
-		far = 100;
-		zoom = 1;
-		viewportWidth = 0;
-		viewportHeight = 0;
-		projectionView = new Matrix4();
-		inverseProjectionView = new Matrix4();
-		projection = new Matrix4();
-		view = new Matrix4();
-
-		constructor (viewportWidth: number, viewportHeight: number) {
-			this.viewportWidth = viewportWidth;
-			this.viewportHeight = viewportHeight;
-			this.update();
-		}
-
-		update () {
-			let projection = this.projection;
-			let view = this.view;
-			let projectionView = this.projectionView;
-			let inverseProjectionView = this.inverseProjectionView;
-			let zoom = this.zoom, viewportWidth = this.viewportWidth, viewportHeight = this.viewportHeight;
-			projection.ortho(zoom * (-viewportWidth / 2), zoom * (viewportWidth / 2),
-				zoom * (-viewportHeight / 2), zoom * (viewportHeight / 2),
-				this.near, this.far);
-			view.lookAt(this.position, this.direction, this.up);
-			projectionView.set(projection.values);
-			projectionView.multiply(view);
-			inverseProjectionView.set(projectionView.values).invert();
-		}
-
-		screenToWorld (screenCoords: Vector3, screenWidth: number, screenHeight: number) {
-			let x = screenCoords.x, y = screenHeight - screenCoords.y - 1;
-			screenCoords.x = (2 * x) / screenWidth - 1;
-			screenCoords.y = (2 * y) / screenHeight - 1;
-			screenCoords.z = (2 * screenCoords.z) - 1;
-			screenCoords.project(this.inverseProjectionView);
-			return screenCoords;
-		}
-
-		worldToScreen (worldCoords: Vector3, screenWidth: number, screenHeight: number) {
-			worldCoords.project(this.projectionView);
-			worldCoords.x = screenWidth * (worldCoords.x + 1) / 2;
-			worldCoords.y = screenHeight * (worldCoords.y + 1) / 2;
-			worldCoords.z = (worldCoords.z + 1) / 2;
-			return worldCoords;
-		}
-
-		setViewport(viewportWidth: number, viewportHeight: number) {
-			this.viewportWidth = viewportWidth;
-			this.viewportHeight = viewportHeight;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class OrthoCamera {
+		position = new Vector3(0, 0, 0);
+		direction = new Vector3(0, 0, -1);
+		up = new Vector3(0, 1, 0);
+		near = 0;
+		far = 100;
+		zoom = 1;
+		viewportWidth = 0;
+		viewportHeight = 0;
+		projectionView = new Matrix4();
+		inverseProjectionView = new Matrix4();
+		projection = new Matrix4();
+		view = new Matrix4();
+
+		constructor (viewportWidth: number, viewportHeight: number) {
+			this.viewportWidth = viewportWidth;
+			this.viewportHeight = viewportHeight;
+			this.update();
+		}
+
+		update () {
+			let projection = this.projection;
+			let view = this.view;
+			let projectionView = this.projectionView;
+			let inverseProjectionView = this.inverseProjectionView;
+			let zoom = this.zoom, viewportWidth = this.viewportWidth, viewportHeight = this.viewportHeight;
+			projection.ortho(zoom * (-viewportWidth / 2), zoom * (viewportWidth / 2),
+				zoom * (-viewportHeight / 2), zoom * (viewportHeight / 2),
+				this.near, this.far);
+			view.lookAt(this.position, this.direction, this.up);
+			projectionView.set(projection.values);
+			projectionView.multiply(view);
+			inverseProjectionView.set(projectionView.values).invert();
+		}
+
+		screenToWorld (screenCoords: Vector3, screenWidth: number, screenHeight: number) {
+			let x = screenCoords.x, y = screenHeight - screenCoords.y - 1;
+			screenCoords.x = (2 * x) / screenWidth - 1;
+			screenCoords.y = (2 * y) / screenHeight - 1;
+			screenCoords.z = (2 * screenCoords.z) - 1;
+			screenCoords.project(this.inverseProjectionView);
+			return screenCoords;
+		}
+
+		worldToScreen (worldCoords: Vector3, screenWidth: number, screenHeight: number) {
+			worldCoords.project(this.projectionView);
+			worldCoords.x = screenWidth * (worldCoords.x + 1) / 2;
+			worldCoords.y = screenHeight * (worldCoords.y + 1) / 2;
+			worldCoords.z = (worldCoords.z + 1) / 2;
+			return worldCoords;
+		}
+
+		setViewport (viewportWidth: number, viewportHeight: number) {
+			this.viewportWidth = viewportWidth;
+			this.viewportHeight = viewportHeight;
+		}
+	}
+}

+ 111 - 111
spine-ts/webgl/src/GLTexture.ts

@@ -1,111 +1,111 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class GLTexture extends Texture implements Disposable, Restorable {
-		context: ManagedWebGLRenderingContext;
-		private texture: WebGLTexture = null;
-		private boundUnit = 0;
-		private useMipMaps = false;
-
-		public static DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL = false;
-
-		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, image: HTMLImageElement | ImageBitmap, useMipMaps: boolean = false) {
-			super(image);
-			this.context = context instanceof ManagedWebGLRenderingContext? context : new ManagedWebGLRenderingContext(context);
-			this.useMipMaps = useMipMaps;
-			this.restore();
-			this.context.addRestorable(this);
-		}
-
-		setFilters (minFilter: TextureFilter, magFilter: TextureFilter) {
-			let gl = this.context.gl;
-			this.bind();
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, GLTexture.validateMagFilter(magFilter));
-		}
-
-		static validateMagFilter (magFilter: TextureFilter) {
-			switch (magFilter) {
-				case TextureFilter.MipMap:
-				case TextureFilter.MipMapLinearLinear:
-				case TextureFilter.MipMapLinearNearest:
-				case TextureFilter.MipMapNearestLinear:
-				case TextureFilter.MipMapNearestNearest:
-					return TextureFilter.Linear;
-				default:
-					return magFilter;
-			}
-		}
-
-		setWraps (uWrap: TextureWrap, vWrap: TextureWrap) {
-			let gl = this.context.gl;
-			this.bind();
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, uWrap);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, vWrap);
-		}
-
-		update (useMipMaps: boolean) {
-			let gl = this.context.gl;
-			if (!this.texture) this.texture = this.context.gl.createTexture();
-			this.bind();
-			if (GLTexture.DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
-			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._image);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, useMipMaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-			if (useMipMaps) gl.generateMipmap(gl.TEXTURE_2D);
-		}
-
-		restore () {
-			this.texture = null;
-			this.update(this.useMipMaps);
-		}
-
-		bind (unit: number = 0) {
-			let gl = this.context.gl;
-			this.boundUnit = unit;
-			gl.activeTexture(gl.TEXTURE0 + unit);
-			gl.bindTexture(gl.TEXTURE_2D, this.texture);
-		}
-
-		unbind () {
-			let gl = this.context.gl;
-			gl.activeTexture(gl.TEXTURE0 + this.boundUnit);
-			gl.bindTexture(gl.TEXTURE_2D, null);
-		}
-
-		dispose () {
-			this.context.removeRestorable(this);
-			let gl = this.context.gl;
-			gl.deleteTexture(this.texture);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class GLTexture extends Texture implements Disposable, Restorable {
+		context: ManagedWebGLRenderingContext;
+		private texture: WebGLTexture = null;
+		private boundUnit = 0;
+		private useMipMaps = false;
+
+		public static DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL = false;
+
+		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, image: HTMLImageElement | ImageBitmap, useMipMaps: boolean = false) {
+			super(image);
+			this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
+			this.useMipMaps = useMipMaps;
+			this.restore();
+			this.context.addRestorable(this);
+		}
+
+		setFilters (minFilter: TextureFilter, magFilter: TextureFilter) {
+			let gl = this.context.gl;
+			this.bind();
+			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
+			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, GLTexture.validateMagFilter(magFilter));
+		}
+
+		static validateMagFilter (magFilter: TextureFilter) {
+			switch (magFilter) {
+				case TextureFilter.MipMap:
+				case TextureFilter.MipMapLinearLinear:
+				case TextureFilter.MipMapLinearNearest:
+				case TextureFilter.MipMapNearestLinear:
+				case TextureFilter.MipMapNearestNearest:
+					return TextureFilter.Linear;
+				default:
+					return magFilter;
+			}
+		}
+
+		setWraps (uWrap: TextureWrap, vWrap: TextureWrap) {
+			let gl = this.context.gl;
+			this.bind();
+			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, uWrap);
+			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, vWrap);
+		}
+
+		update (useMipMaps: boolean) {
+			let gl = this.context.gl;
+			if (!this.texture) this.texture = this.context.gl.createTexture();
+			this.bind();
+			if (GLTexture.DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
+			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._image);
+			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, useMipMaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
+			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+			if (useMipMaps) gl.generateMipmap(gl.TEXTURE_2D);
+		}
+
+		restore () {
+			this.texture = null;
+			this.update(this.useMipMaps);
+		}
+
+		bind (unit: number = 0) {
+			let gl = this.context.gl;
+			this.boundUnit = unit;
+			gl.activeTexture(gl.TEXTURE0 + unit);
+			gl.bindTexture(gl.TEXTURE_2D, this.texture);
+		}
+
+		unbind () {
+			let gl = this.context.gl;
+			gl.activeTexture(gl.TEXTURE0 + this.boundUnit);
+			gl.bindTexture(gl.TEXTURE_2D, null);
+		}
+
+		dispose () {
+			this.context.removeRestorable(this);
+			let gl = this.context.gl;
+			gl.deleteTexture(this.texture);
+		}
+	}
+}

+ 233 - 233
spine-ts/webgl/src/Input.ts

@@ -1,233 +1,233 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class Input {
-		element: HTMLElement;
-		lastX = 0;
-		lastY = 0;
-		buttonDown = false;
-		currTouch: Touch = null;
-		private listeners = new Array<InputListener>();
-
-		touchesPool = new Pool<spine.webgl.Touch>(() => {
-			return new spine.webgl.Touch(0, 0, 0);
-		});
-
-		constructor (element: HTMLElement) {
-			this.element = element;
-			this.setupCallbacks(element);
-		}
-
-		private setupCallbacks(element: HTMLElement) {
-			let mouseDown = (ev: UIEvent) => {
-				if (ev instanceof MouseEvent) {
-					let rect = element.getBoundingClientRect();
-					let x = ev.clientX - rect.left;
-					let y = ev.clientY - rect.top;
-
-					let listeners = this.listeners;
-					for (let i = 0; i < listeners.length; i++)
-						if (listeners[i].down) listeners[i].down(x, y);
-
-					this.lastX = x;
-					this.lastY = y;
-					this.buttonDown = true;
-
-					document.addEventListener("mousemove", mouseMove);
-					document.addEventListener("mouseup", mouseUp);
-				}
-			}
-
-			let mouseMove = (ev: UIEvent) => {
-				if (ev instanceof MouseEvent) {
-					let rect = element.getBoundingClientRect();
-					let x = ev.clientX - rect.left;
-					let y = ev.clientY - rect.top;
-
-					let listeners = this.listeners;
-					for (let i = 0; i < listeners.length; i++) {
-						if (this.buttonDown) {
-							if (listeners[i].dragged) listeners[i].dragged(x, y);
-						} else {
-							if (listeners[i].moved) listeners[i].moved(x, y);
-						}
-					}
-
-					this.lastX = x;
-					this.lastY = y;
-				}
-			};
-
-			let mouseUp = (ev: UIEvent) => {
-				if (ev instanceof MouseEvent) {
-					let rect = element.getBoundingClientRect();
-					let x = ev.clientX - rect.left;
-					let y = ev.clientY - rect.top;
-
-					let listeners = this.listeners;
-					for (let i = 0; i < listeners.length; i++)
-						if (listeners[i].up) listeners[i].up(x, y);
-
-					this.lastX = x;
-					this.lastY = y;
-					this.buttonDown = false;
-					document.removeEventListener("mousemove", mouseMove);
-					document.removeEventListener("mouseup", mouseUp);
-				}
-			}
-
-			element.addEventListener("mousedown", mouseDown, true);
-			element.addEventListener("mousemove", mouseMove, true);
-			element.addEventListener("mouseup", mouseUp, true);
-			element.addEventListener("touchstart", (ev: TouchEvent) => {
-				if (!this.currTouch) {
-					var touches = ev.changedTouches;
-					for (var i = 0; i < touches.length; i++) {
-						var touch = touches[i];
-						let rect = element.getBoundingClientRect();
-						let x = touch.clientX - rect.left;
-						let y = touch.clientY - rect.top;
-						this.currTouch = this.touchesPool.obtain();
-						this.currTouch.identifier = touch.identifier;
-						this.currTouch.x = x;
-						this.currTouch.y = y;
-						break;
-					}
-
-					let listeners = this.listeners;
-					for (let i = 0; i < listeners.length; i++) {
-						if (listeners[i].down) listeners[i].down(this.currTouch.x, this.currTouch.y);
-					}
-
-					this.lastX = this.currTouch.x;
-					this.lastY = this.currTouch.y;
-					this.buttonDown = true;
-				}
-				ev.preventDefault();
-			}, false);
-			element.addEventListener("touchend", (ev: TouchEvent) => {
-				if (this.currTouch) {
-					var touches = ev.changedTouches;
-					for (var i = 0; i < touches.length; i++) {
-						var touch = touches[i];
-						if (this.currTouch.identifier === touch.identifier) {
-							let rect = element.getBoundingClientRect();
-							let x = this.currTouch.x = touch.clientX - rect.left;
-							let y = this.currTouch.y = touch.clientY - rect.top;
-							this.touchesPool.free(this.currTouch);
-							let listeners = this.listeners;
-							for (let i = 0; i < listeners.length; i++) {
-								if (listeners[i].up) listeners[i].up(x, y);
-							}
-
-							this.lastX = x;
-							this.lastY = y;
-							this.buttonDown = false;
-							this.currTouch = null;
-							break;
-						}
-					}
-				}
-				ev.preventDefault();
-			}, false);
-			element.addEventListener("touchcancel", (ev: TouchEvent) => {
-				if (this.currTouch) {
-					var touches = ev.changedTouches;
-					for (var i = 0; i < touches.length; i++) {
-						var touch = touches[i];
-						if (this.currTouch.identifier === touch.identifier) {
-							let rect = element.getBoundingClientRect();
-							let x = this.currTouch.x = touch.clientX - rect.left;
-							let y = this.currTouch.y = touch.clientY - rect.top;
-							this.touchesPool.free(this.currTouch);
-							let listeners = this.listeners;
-							for (let i = 0; i < listeners.length; i++) {
-								if (listeners[i].up) listeners[i].up(x, y);
-							}
-
-							this.lastX = x;
-							this.lastY = y;
-							this.buttonDown = false;
-							this.currTouch = null;
-							break;
-						}
-					}
-				}
-				ev.preventDefault();
-			}, false);
-			element.addEventListener("touchmove", (ev: TouchEvent) => {
-				if (this.currTouch) {
-					var touches = ev.changedTouches;
-					for (var i = 0; i < touches.length; i++) {
-						var touch = touches[i];
-						if (this.currTouch.identifier === touch.identifier) {
-							let rect = element.getBoundingClientRect();
-							let x = touch.clientX - rect.left;
-							let y = touch.clientY - rect.top;
-
-							let listeners = this.listeners;
-							for (let i = 0; i < listeners.length; i++) {
-								if (listeners[i].dragged) listeners[i].dragged(x, y);
-							}
-
-							this.lastX = this.currTouch.x = x;
-							this.lastY = this.currTouch.y = y;
-							break;
-						}
-					}
-				}
-				ev.preventDefault();
-			}, false);
-		}
-
-		addListener(listener: InputListener) {
-			this.listeners.push(listener);
-		}
-
-		removeListener(listener: InputListener) {
-			let idx = this.listeners.indexOf(listener);
-			if (idx > -1) {
-				this.listeners.splice(idx, 1);
-			}
-		}
-	}
-
-	export class Touch {
-		constructor(public identifier: number, public x: number, public y: number) {
-		}
-	}
-
-	export interface InputListener {
-		down(x: number, y: number): void;
-		up(x: number, y: number): void;
-		moved(x: number, y: number): void;
-		dragged(x: number, y: number): void;
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class Input {
+		element: HTMLElement;
+		lastX = 0;
+		lastY = 0;
+		buttonDown = false;
+		currTouch: Touch = null;
+		private listeners = new Array<InputListener>();
+
+		touchesPool = new Pool<spine.webgl.Touch>(() => {
+			return new spine.webgl.Touch(0, 0, 0);
+		});
+
+		constructor (element: HTMLElement) {
+			this.element = element;
+			this.setupCallbacks(element);
+		}
+
+		private setupCallbacks (element: HTMLElement) {
+			let mouseDown = (ev: UIEvent) => {
+				if (ev instanceof MouseEvent) {
+					let rect = element.getBoundingClientRect();
+					let x = ev.clientX - rect.left;
+					let y = ev.clientY - rect.top;
+
+					let listeners = this.listeners;
+					for (let i = 0; i < listeners.length; i++)
+						if (listeners[i].down) listeners[i].down(x, y);
+
+					this.lastX = x;
+					this.lastY = y;
+					this.buttonDown = true;
+
+					document.addEventListener("mousemove", mouseMove);
+					document.addEventListener("mouseup", mouseUp);
+				}
+			}
+
+			let mouseMove = (ev: UIEvent) => {
+				if (ev instanceof MouseEvent) {
+					let rect = element.getBoundingClientRect();
+					let x = ev.clientX - rect.left;
+					let y = ev.clientY - rect.top;
+
+					let listeners = this.listeners;
+					for (let i = 0; i < listeners.length; i++) {
+						if (this.buttonDown) {
+							if (listeners[i].dragged) listeners[i].dragged(x, y);
+						} else {
+							if (listeners[i].moved) listeners[i].moved(x, y);
+						}
+					}
+
+					this.lastX = x;
+					this.lastY = y;
+				}
+			};
+
+			let mouseUp = (ev: UIEvent) => {
+				if (ev instanceof MouseEvent) {
+					let rect = element.getBoundingClientRect();
+					let x = ev.clientX - rect.left;
+					let y = ev.clientY - rect.top;
+
+					let listeners = this.listeners;
+					for (let i = 0; i < listeners.length; i++)
+						if (listeners[i].up) listeners[i].up(x, y);
+
+					this.lastX = x;
+					this.lastY = y;
+					this.buttonDown = false;
+					document.removeEventListener("mousemove", mouseMove);
+					document.removeEventListener("mouseup", mouseUp);
+				}
+			}
+
+			element.addEventListener("mousedown", mouseDown, true);
+			element.addEventListener("mousemove", mouseMove, true);
+			element.addEventListener("mouseup", mouseUp, true);
+			element.addEventListener("touchstart", (ev: TouchEvent) => {
+				if (!this.currTouch) {
+					var touches = ev.changedTouches;
+					for (var i = 0; i < touches.length; i++) {
+						var touch = touches[i];
+						let rect = element.getBoundingClientRect();
+						let x = touch.clientX - rect.left;
+						let y = touch.clientY - rect.top;
+						this.currTouch = this.touchesPool.obtain();
+						this.currTouch.identifier = touch.identifier;
+						this.currTouch.x = x;
+						this.currTouch.y = y;
+						break;
+					}
+
+					let listeners = this.listeners;
+					for (let i = 0; i < listeners.length; i++) {
+						if (listeners[i].down) listeners[i].down(this.currTouch.x, this.currTouch.y);
+					}
+
+					this.lastX = this.currTouch.x;
+					this.lastY = this.currTouch.y;
+					this.buttonDown = true;
+				}
+				ev.preventDefault();
+			}, false);
+			element.addEventListener("touchend", (ev: TouchEvent) => {
+				if (this.currTouch) {
+					var touches = ev.changedTouches;
+					for (var i = 0; i < touches.length; i++) {
+						var touch = touches[i];
+						if (this.currTouch.identifier === touch.identifier) {
+							let rect = element.getBoundingClientRect();
+							let x = this.currTouch.x = touch.clientX - rect.left;
+							let y = this.currTouch.y = touch.clientY - rect.top;
+							this.touchesPool.free(this.currTouch);
+							let listeners = this.listeners;
+							for (let i = 0; i < listeners.length; i++) {
+								if (listeners[i].up) listeners[i].up(x, y);
+							}
+
+							this.lastX = x;
+							this.lastY = y;
+							this.buttonDown = false;
+							this.currTouch = null;
+							break;
+						}
+					}
+				}
+				ev.preventDefault();
+			}, false);
+			element.addEventListener("touchcancel", (ev: TouchEvent) => {
+				if (this.currTouch) {
+					var touches = ev.changedTouches;
+					for (var i = 0; i < touches.length; i++) {
+						var touch = touches[i];
+						if (this.currTouch.identifier === touch.identifier) {
+							let rect = element.getBoundingClientRect();
+							let x = this.currTouch.x = touch.clientX - rect.left;
+							let y = this.currTouch.y = touch.clientY - rect.top;
+							this.touchesPool.free(this.currTouch);
+							let listeners = this.listeners;
+							for (let i = 0; i < listeners.length; i++) {
+								if (listeners[i].up) listeners[i].up(x, y);
+							}
+
+							this.lastX = x;
+							this.lastY = y;
+							this.buttonDown = false;
+							this.currTouch = null;
+							break;
+						}
+					}
+				}
+				ev.preventDefault();
+			}, false);
+			element.addEventListener("touchmove", (ev: TouchEvent) => {
+				if (this.currTouch) {
+					var touches = ev.changedTouches;
+					for (var i = 0; i < touches.length; i++) {
+						var touch = touches[i];
+						if (this.currTouch.identifier === touch.identifier) {
+							let rect = element.getBoundingClientRect();
+							let x = touch.clientX - rect.left;
+							let y = touch.clientY - rect.top;
+
+							let listeners = this.listeners;
+							for (let i = 0; i < listeners.length; i++) {
+								if (listeners[i].dragged) listeners[i].dragged(x, y);
+							}
+
+							this.lastX = this.currTouch.x = x;
+							this.lastY = this.currTouch.y = y;
+							break;
+						}
+					}
+				}
+				ev.preventDefault();
+			}, false);
+		}
+
+		addListener (listener: InputListener) {
+			this.listeners.push(listener);
+		}
+
+		removeListener (listener: InputListener) {
+			let idx = this.listeners.indexOf(listener);
+			if (idx > -1) {
+				this.listeners.splice(idx, 1);
+			}
+		}
+	}
+
+	export class Touch {
+		constructor (public identifier: number, public x: number, public y: number) {
+		}
+	}
+
+	export interface InputListener {
+		down (x: number, y: number): void;
+		up (x: number, y: number): void;
+		moved (x: number, y: number): void;
+		dragged (x: number, y: number): void;
+	}
+}

文件差异内容过多而无法显示
+ 0 - 122
spine-ts/webgl/src/LoadingScreen.ts


+ 340 - 340
spine-ts/webgl/src/Matrix4.ts

@@ -1,340 +1,340 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export const M00 = 0;
-	export const M01 = 4;
-	export const M02 = 8;
-	export const M03 = 12;
-	export const M10 = 1;
-	export const M11 = 5;
-	export const M12 = 9;
-	export const M13 = 13;
-	export const M20 = 2;
-	export const M21 = 6;
-	export const M22 = 10;
-	export const M23 = 14;
-	export const M30 = 3;
-	export const M31 = 7;
-	export const M32 = 11;
-	export const M33 = 15;
-
-	export class Matrix4 {
-		temp: Float32Array = new Float32Array(16);
-		values: Float32Array = new Float32Array(16);
-
-		private static xAxis: Vector3 = null;
-		private static yAxis: Vector3 = null;
-		private static zAxis: Vector3 = null;
-		private static tmpMatrix = new Matrix4();
-
-		constructor () {
-			let v = this.values;
-			v[M00] = 1;
-			v[M11] = 1;
-			v[M22] = 1;
-			v[M33] = 1;
-		}
-
-		set (values: ArrayLike<number>): Matrix4 {
-			this.values.set(values);
-			return this;
-		}
-
-		transpose (): Matrix4 {
-			let t = this.temp;
-			let v = this.values;
-			t[M00] = v[M00];
-			t[M01] = v[M10];
-			t[M02] = v[M20];
-			t[M03] = v[M30];
-			t[M10] = v[M01];
-			t[M11] = v[M11];
-			t[M12] = v[M21];
-			t[M13] = v[M31];
-			t[M20] = v[M02];
-			t[M21] = v[M12];
-			t[M22] = v[M22];
-			t[M23] = v[M32];
-			t[M30] = v[M03];
-			t[M31] = v[M13];
-			t[M32] = v[M23];
-			t[M33] = v[M33];
-			return this.set(t);
-		}
-
-		identity (): Matrix4 {
-			let v = this.values;
-			v[M00] = 1;
-			v[M01] = 0;
-			v[M02] = 0;
-			v[M03] = 0;
-			v[M10] = 0;
-			v[M11] = 1;
-			v[M12] = 0;
-			v[M13] = 0;
-			v[M20] = 0;
-			v[M21] = 0;
-			v[M22] = 1;
-			v[M23] = 0;
-			v[M30] = 0;
-			v[M31] = 0;
-			v[M32] = 0;
-			v[M33] = 1;
-			return this;
-		}
-
-		invert (): Matrix4 {
-			let v = this.values;
-			let t = this.temp;
-			let l_det = v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03]
-				+ v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03]
-				- v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13]
-				- v[M00] * v[M31] * v[M22] * v[M13] - v[M20] * v[M01] * v[M32] * v[M13] + v[M00] * v[M21] * v[M32] * v[M13]
-				+ v[M30] * v[M11] * v[M02] * v[M23] - v[M10] * v[M31] * v[M02] * v[M23] - v[M30] * v[M01] * v[M12] * v[M23]
-				+ v[M00] * v[M31] * v[M12] * v[M23] + v[M10] * v[M01] * v[M32] * v[M23] - v[M00] * v[M11] * v[M32] * v[M23]
-				- v[M20] * v[M11] * v[M02] * v[M33] + v[M10] * v[M21] * v[M02] * v[M33] + v[M20] * v[M01] * v[M12] * v[M33]
-				- v[M00] * v[M21] * v[M12] * v[M33] - v[M10] * v[M01] * v[M22] * v[M33] + v[M00] * v[M11] * v[M22] * v[M33];
-			if (l_det == 0) throw new Error("non-invertible matrix");
-			let inv_det = 1.0 / l_det;
-			t[M00] = v[M12] * v[M23] * v[M31] - v[M13] * v[M22] * v[M31] + v[M13] * v[M21] * v[M32]
-				- v[M11] * v[M23] * v[M32] - v[M12] * v[M21] * v[M33] + v[M11] * v[M22] * v[M33];
-			t[M01] = v[M03] * v[M22] * v[M31] - v[M02] * v[M23] * v[M31] - v[M03] * v[M21] * v[M32]
-				+ v[M01] * v[M23] * v[M32] + v[M02] * v[M21] * v[M33] - v[M01] * v[M22] * v[M33];
-			t[M02] = v[M02] * v[M13] * v[M31] - v[M03] * v[M12] * v[M31] + v[M03] * v[M11] * v[M32]
-				- v[M01] * v[M13] * v[M32] - v[M02] * v[M11] * v[M33] + v[M01] * v[M12] * v[M33];
-			t[M03] = v[M03] * v[M12] * v[M21] - v[M02] * v[M13] * v[M21] - v[M03] * v[M11] * v[M22]
-				+ v[M01] * v[M13] * v[M22] + v[M02] * v[M11] * v[M23] - v[M01] * v[M12] * v[M23];
-			t[M10] = v[M13] * v[M22] * v[M30] - v[M12] * v[M23] * v[M30] - v[M13] * v[M20] * v[M32]
-				+ v[M10] * v[M23] * v[M32] + v[M12] * v[M20] * v[M33] - v[M10] * v[M22] * v[M33];
-			t[M11] = v[M02] * v[M23] * v[M30] - v[M03] * v[M22] * v[M30] + v[M03] * v[M20] * v[M32]
-				- v[M00] * v[M23] * v[M32] - v[M02] * v[M20] * v[M33] + v[M00] * v[M22] * v[M33];
-			t[M12] = v[M03] * v[M12] * v[M30] - v[M02] * v[M13] * v[M30] - v[M03] * v[M10] * v[M32]
-				+ v[M00] * v[M13] * v[M32] + v[M02] * v[M10] * v[M33] - v[M00] * v[M12] * v[M33];
-			t[M13] = v[M02] * v[M13] * v[M20] - v[M03] * v[M12] * v[M20] + v[M03] * v[M10] * v[M22]
-				- v[M00] * v[M13] * v[M22] - v[M02] * v[M10] * v[M23] + v[M00] * v[M12] * v[M23];
-			t[M20] = v[M11] * v[M23] * v[M30] - v[M13] * v[M21] * v[M30] + v[M13] * v[M20] * v[M31]
-				- v[M10] * v[M23] * v[M31] - v[M11] * v[M20] * v[M33] + v[M10] * v[M21] * v[M33];
-			t[M21] = v[M03] * v[M21] * v[M30] - v[M01] * v[M23] * v[M30] - v[M03] * v[M20] * v[M31]
-				+ v[M00] * v[M23] * v[M31] + v[M01] * v[M20] * v[M33] - v[M00] * v[M21] * v[M33];
-			t[M22] = v[M01] * v[M13] * v[M30] - v[M03] * v[M11] * v[M30] + v[M03] * v[M10] * v[M31]
-				- v[M00] * v[M13] * v[M31] - v[M01] * v[M10] * v[M33] + v[M00] * v[M11] * v[M33];
-			t[M23] = v[M03] * v[M11] * v[M20] - v[M01] * v[M13] * v[M20] - v[M03] * v[M10] * v[M21]
-				+ v[M00] * v[M13] * v[M21] + v[M01] * v[M10] * v[M23] - v[M00] * v[M11] * v[M23];
-			t[M30] = v[M12] * v[M21] * v[M30] - v[M11] * v[M22] * v[M30] - v[M12] * v[M20] * v[M31]
-				+ v[M10] * v[M22] * v[M31] + v[M11] * v[M20] * v[M32] - v[M10] * v[M21] * v[M32];
-			t[M31] = v[M01] * v[M22] * v[M30] - v[M02] * v[M21] * v[M30] + v[M02] * v[M20] * v[M31]
-				- v[M00] * v[M22] * v[M31] - v[M01] * v[M20] * v[M32] + v[M00] * v[M21] * v[M32];
-			t[M32] = v[M02] * v[M11] * v[M30] - v[M01] * v[M12] * v[M30] - v[M02] * v[M10] * v[M31]
-				+ v[M00] * v[M12] * v[M31] + v[M01] * v[M10] * v[M32] - v[M00] * v[M11] * v[M32];
-			t[M33] = v[M01] * v[M12] * v[M20] - v[M02] * v[M11] * v[M20] + v[M02] * v[M10] * v[M21]
-				- v[M00] * v[M12] * v[M21] - v[M01] * v[M10] * v[M22] + v[M00] * v[M11] * v[M22];
-			v[M00] = t[M00] * inv_det;
-			v[M01] = t[M01] * inv_det;
-			v[M02] = t[M02] * inv_det;
-			v[M03] = t[M03] * inv_det;
-			v[M10] = t[M10] * inv_det;
-			v[M11] = t[M11] * inv_det;
-			v[M12] = t[M12] * inv_det;
-			v[M13] = t[M13] * inv_det;
-			v[M20] = t[M20] * inv_det;
-			v[M21] = t[M21] * inv_det;
-			v[M22] = t[M22] * inv_det;
-			v[M23] = t[M23] * inv_det;
-			v[M30] = t[M30] * inv_det;
-			v[M31] = t[M31] * inv_det;
-			v[M32] = t[M32] * inv_det;
-			v[M33] = t[M33] * inv_det;
-			return this;
-		}
-
-		determinant (): number {
-			let v = this.values;
-			return v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03]
-				+ v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03]
-				- v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13]
-				- v[M00] * v[M31] * v[M22] * v[M13] - v[M20] * v[M01] * v[M32] * v[M13] + v[M00] * v[M21] * v[M32] * v[M13]
-				+ v[M30] * v[M11] * v[M02] * v[M23] - v[M10] * v[M31] * v[M02] * v[M23] - v[M30] * v[M01] * v[M12] * v[M23]
-				+ v[M00] * v[M31] * v[M12] * v[M23] + v[M10] * v[M01] * v[M32] * v[M23] - v[M00] * v[M11] * v[M32] * v[M23]
-				- v[M20] * v[M11] * v[M02] * v[M33] + v[M10] * v[M21] * v[M02] * v[M33] + v[M20] * v[M01] * v[M12] * v[M33]
-				- v[M00] * v[M21] * v[M12] * v[M33] - v[M10] * v[M01] * v[M22] * v[M33] + v[M00] * v[M11] * v[M22] * v[M33];
-		}
-
-		translate (x: number, y: number, z: number): Matrix4 {
-			let v = this.values;
-			v[M03] += x;
-			v[M13] += y;
-			v[M23] += z;
-			return this;
-		}
-
-		copy (): Matrix4 {
-			return new Matrix4().set(this.values);
-		}
-
-		projection (near: number, far: number, fovy: number, aspectRatio: number): Matrix4 {
-			this.identity();
-			let l_fd = (1.0 / Math.tan((fovy * (Math.PI / 180)) / 2.0));
-			let l_a1 = (far + near) / (near - far);
-			let l_a2 = (2 * far * near) / (near - far);
-			let v = this.values;
-			v[M00] = l_fd / aspectRatio;
-			v[M10] = 0;
-			v[M20] = 0;
-			v[M30] = 0;
-			v[M01] = 0;
-			v[M11] = l_fd;
-			v[M21] = 0;
-			v[M31] = 0;
-			v[M02] = 0;
-			v[M12] = 0;
-			v[M22] = l_a1;
-			v[M32] = -1;
-			v[M03] = 0;
-			v[M13] = 0;
-			v[M23] = l_a2;
-			v[M33] = 0;
-			return this;
-		}
-
-		ortho2d (x: number, y: number, width: number, height: number): Matrix4 {
-			return this.ortho(x, x + width, y, y + height, 0, 1);
-		}
-
-		ortho (left: number, right: number, bottom: number, top: number, near: number, far: number): Matrix4 {
-			this.identity();
-			let x_orth = 2 / (right - left);
-			let y_orth = 2 / (top - bottom);
-			let z_orth = -2 / (far - near);
-
-			let tx = -(right + left) / (right - left);
-			let ty = -(top + bottom) / (top - bottom);
-			let tz = -(far + near) / (far - near);
-
-			let v = this.values;
-			v[M00] = x_orth;
-			v[M10] = 0;
-			v[M20] = 0;
-			v[M30] = 0;
-			v[M01] = 0;
-			v[M11] = y_orth;
-			v[M21] = 0;
-			v[M31] = 0;
-			v[M02] = 0;
-			v[M12] = 0;
-			v[M22] = z_orth;
-			v[M32] = 0;
-			v[M03] = tx;
-			v[M13] = ty;
-			v[M23] = tz;
-			v[M33] = 1;
-			return this;
-		}
-
-		multiply (matrix: Matrix4): Matrix4 {
-			let t = this.temp;
-			let v = this.values;
-			let m = matrix.values;
-			t[M00] = v[M00] * m[M00] + v[M01] * m[M10] + v[M02] * m[M20] + v[M03] * m[M30];
-			t[M01] = v[M00] * m[M01] + v[M01] * m[M11] + v[M02] * m[M21] + v[M03] * m[M31];
-			t[M02] = v[M00] * m[M02] + v[M01] * m[M12] + v[M02] * m[M22] + v[M03] * m[M32];
-			t[M03] = v[M00] * m[M03] + v[M01] * m[M13] + v[M02] * m[M23] + v[M03] * m[M33];
-			t[M10] = v[M10] * m[M00] + v[M11] * m[M10] + v[M12] * m[M20] + v[M13] * m[M30];
-			t[M11] = v[M10] * m[M01] + v[M11] * m[M11] + v[M12] * m[M21] + v[M13] * m[M31];
-			t[M12] = v[M10] * m[M02] + v[M11] * m[M12] + v[M12] * m[M22] + v[M13] * m[M32];
-			t[M13] = v[M10] * m[M03] + v[M11] * m[M13] + v[M12] * m[M23] + v[M13] * m[M33];
-			t[M20] = v[M20] * m[M00] + v[M21] * m[M10] + v[M22] * m[M20] + v[M23] * m[M30];
-			t[M21] = v[M20] * m[M01] + v[M21] * m[M11] + v[M22] * m[M21] + v[M23] * m[M31];
-			t[M22] = v[M20] * m[M02] + v[M21] * m[M12] + v[M22] * m[M22] + v[M23] * m[M32];
-			t[M23] = v[M20] * m[M03] + v[M21] * m[M13] + v[M22] * m[M23] + v[M23] * m[M33];
-			t[M30] = v[M30] * m[M00] + v[M31] * m[M10] + v[M32] * m[M20] + v[M33] * m[M30];
-			t[M31] = v[M30] * m[M01] + v[M31] * m[M11] + v[M32] * m[M21] + v[M33] * m[M31];
-			t[M32] = v[M30] * m[M02] + v[M31] * m[M12] + v[M32] * m[M22] + v[M33] * m[M32];
-			t[M33] = v[M30] * m[M03] + v[M31] * m[M13] + v[M32] * m[M23] + v[M33] * m[M33];
-			return this.set(this.temp);
-		}
-
-		multiplyLeft (matrix: Matrix4): Matrix4 {
-			let t = this.temp;
-			let v = this.values;
-			let m = matrix.values;
-			t[M00] = m[M00] * v[M00] + m[M01] * v[M10] + m[M02] * v[M20] + m[M03] * v[M30];
-			t[M01] = m[M00] * v[M01] + m[M01] * v[M11] + m[M02] * v[M21] + m[M03] * v[M31];
-			t[M02] = m[M00] * v[M02] + m[M01] * v[M12] + m[M02] * v[M22] + m[M03] * v[M32];
-			t[M03] = m[M00] * v[M03] + m[M01] * v[M13] + m[M02] * v[M23] + m[M03] * v[M33];
-			t[M10] = m[M10] * v[M00] + m[M11] * v[M10] + m[M12] * v[M20] + m[M13] * v[M30];
-			t[M11] = m[M10] * v[M01] + m[M11] * v[M11] + m[M12] * v[M21] + m[M13] * v[M31];
-			t[M12] = m[M10] * v[M02] + m[M11] * v[M12] + m[M12] * v[M22] + m[M13] * v[M32];
-			t[M13] = m[M10] * v[M03] + m[M11] * v[M13] + m[M12] * v[M23] + m[M13] * v[M33];
-			t[M20] = m[M20] * v[M00] + m[M21] * v[M10] + m[M22] * v[M20] + m[M23] * v[M30];
-			t[M21] = m[M20] * v[M01] + m[M21] * v[M11] + m[M22] * v[M21] + m[M23] * v[M31];
-			t[M22] = m[M20] * v[M02] + m[M21] * v[M12] + m[M22] * v[M22] + m[M23] * v[M32];
-			t[M23] = m[M20] * v[M03] + m[M21] * v[M13] + m[M22] * v[M23] + m[M23] * v[M33];
-			t[M30] = m[M30] * v[M00] + m[M31] * v[M10] + m[M32] * v[M20] + m[M33] * v[M30];
-			t[M31] = m[M30] * v[M01] + m[M31] * v[M11] + m[M32] * v[M21] + m[M33] * v[M31];
-			t[M32] = m[M30] * v[M02] + m[M31] * v[M12] + m[M32] * v[M22] + m[M33] * v[M32];
-			t[M33] = m[M30] * v[M03] + m[M31] * v[M13] + m[M32] * v[M23] + m[M33] * v[M33];
-			return this.set(this.temp);
-		}
-
-		lookAt (position: Vector3, direction: Vector3, up: Vector3) {
-			Matrix4.initTemps();
-			let xAxis = Matrix4.xAxis, yAxis = Matrix4.yAxis, zAxis = Matrix4.zAxis;
-			zAxis.setFrom(direction).normalize();
-			xAxis.setFrom(direction).normalize();
-			xAxis.cross(up).normalize();
-			yAxis.setFrom(xAxis).cross(zAxis).normalize();
-			this.identity();
-			let val = this.values;
-			val[M00] = xAxis.x;
-			val[M01] = xAxis.y;
-			val[M02] = xAxis.z;
-			val[M10] = yAxis.x;
-			val[M11] = yAxis.y;
-			val[M12] = yAxis.z;
-			val[M20] = -zAxis.x;
-			val[M21] = -zAxis.y;
-			val[M22] = -zAxis.z;
-
-			Matrix4.tmpMatrix.identity();
-			Matrix4.tmpMatrix.values[M03] = -position.x;
-			Matrix4.tmpMatrix.values[M13] = -position.y;
-			Matrix4.tmpMatrix.values[M23] = -position.z;
-			this.multiply(Matrix4.tmpMatrix)
-
-			return this;
-		}
-
-		static initTemps() {
-			if (Matrix4.xAxis === null) Matrix4.xAxis = new Vector3();
-			if (Matrix4.yAxis === null) Matrix4.yAxis = new Vector3();
-			if (Matrix4.zAxis === null) Matrix4.zAxis = new Vector3();
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export const M00 = 0;
+	export const M01 = 4;
+	export const M02 = 8;
+	export const M03 = 12;
+	export const M10 = 1;
+	export const M11 = 5;
+	export const M12 = 9;
+	export const M13 = 13;
+	export const M20 = 2;
+	export const M21 = 6;
+	export const M22 = 10;
+	export const M23 = 14;
+	export const M30 = 3;
+	export const M31 = 7;
+	export const M32 = 11;
+	export const M33 = 15;
+
+	export class Matrix4 {
+		temp: Float32Array = new Float32Array(16);
+		values: Float32Array = new Float32Array(16);
+
+		private static xAxis: Vector3 = null;
+		private static yAxis: Vector3 = null;
+		private static zAxis: Vector3 = null;
+		private static tmpMatrix = new Matrix4();
+
+		constructor () {
+			let v = this.values;
+			v[M00] = 1;
+			v[M11] = 1;
+			v[M22] = 1;
+			v[M33] = 1;
+		}
+
+		set (values: ArrayLike<number>): Matrix4 {
+			this.values.set(values);
+			return this;
+		}
+
+		transpose (): Matrix4 {
+			let t = this.temp;
+			let v = this.values;
+			t[M00] = v[M00];
+			t[M01] = v[M10];
+			t[M02] = v[M20];
+			t[M03] = v[M30];
+			t[M10] = v[M01];
+			t[M11] = v[M11];
+			t[M12] = v[M21];
+			t[M13] = v[M31];
+			t[M20] = v[M02];
+			t[M21] = v[M12];
+			t[M22] = v[M22];
+			t[M23] = v[M32];
+			t[M30] = v[M03];
+			t[M31] = v[M13];
+			t[M32] = v[M23];
+			t[M33] = v[M33];
+			return this.set(t);
+		}
+
+		identity (): Matrix4 {
+			let v = this.values;
+			v[M00] = 1;
+			v[M01] = 0;
+			v[M02] = 0;
+			v[M03] = 0;
+			v[M10] = 0;
+			v[M11] = 1;
+			v[M12] = 0;
+			v[M13] = 0;
+			v[M20] = 0;
+			v[M21] = 0;
+			v[M22] = 1;
+			v[M23] = 0;
+			v[M30] = 0;
+			v[M31] = 0;
+			v[M32] = 0;
+			v[M33] = 1;
+			return this;
+		}
+
+		invert (): Matrix4 {
+			let v = this.values;
+			let t = this.temp;
+			let l_det = v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03]
+				+ v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03]
+				- v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13]
+				- v[M00] * v[M31] * v[M22] * v[M13] - v[M20] * v[M01] * v[M32] * v[M13] + v[M00] * v[M21] * v[M32] * v[M13]
+				+ v[M30] * v[M11] * v[M02] * v[M23] - v[M10] * v[M31] * v[M02] * v[M23] - v[M30] * v[M01] * v[M12] * v[M23]
+				+ v[M00] * v[M31] * v[M12] * v[M23] + v[M10] * v[M01] * v[M32] * v[M23] - v[M00] * v[M11] * v[M32] * v[M23]
+				- v[M20] * v[M11] * v[M02] * v[M33] + v[M10] * v[M21] * v[M02] * v[M33] + v[M20] * v[M01] * v[M12] * v[M33]
+				- v[M00] * v[M21] * v[M12] * v[M33] - v[M10] * v[M01] * v[M22] * v[M33] + v[M00] * v[M11] * v[M22] * v[M33];
+			if (l_det == 0) throw new Error("non-invertible matrix");
+			let inv_det = 1.0 / l_det;
+			t[M00] = v[M12] * v[M23] * v[M31] - v[M13] * v[M22] * v[M31] + v[M13] * v[M21] * v[M32]
+				- v[M11] * v[M23] * v[M32] - v[M12] * v[M21] * v[M33] + v[M11] * v[M22] * v[M33];
+			t[M01] = v[M03] * v[M22] * v[M31] - v[M02] * v[M23] * v[M31] - v[M03] * v[M21] * v[M32]
+				+ v[M01] * v[M23] * v[M32] + v[M02] * v[M21] * v[M33] - v[M01] * v[M22] * v[M33];
+			t[M02] = v[M02] * v[M13] * v[M31] - v[M03] * v[M12] * v[M31] + v[M03] * v[M11] * v[M32]
+				- v[M01] * v[M13] * v[M32] - v[M02] * v[M11] * v[M33] + v[M01] * v[M12] * v[M33];
+			t[M03] = v[M03] * v[M12] * v[M21] - v[M02] * v[M13] * v[M21] - v[M03] * v[M11] * v[M22]
+				+ v[M01] * v[M13] * v[M22] + v[M02] * v[M11] * v[M23] - v[M01] * v[M12] * v[M23];
+			t[M10] = v[M13] * v[M22] * v[M30] - v[M12] * v[M23] * v[M30] - v[M13] * v[M20] * v[M32]
+				+ v[M10] * v[M23] * v[M32] + v[M12] * v[M20] * v[M33] - v[M10] * v[M22] * v[M33];
+			t[M11] = v[M02] * v[M23] * v[M30] - v[M03] * v[M22] * v[M30] + v[M03] * v[M20] * v[M32]
+				- v[M00] * v[M23] * v[M32] - v[M02] * v[M20] * v[M33] + v[M00] * v[M22] * v[M33];
+			t[M12] = v[M03] * v[M12] * v[M30] - v[M02] * v[M13] * v[M30] - v[M03] * v[M10] * v[M32]
+				+ v[M00] * v[M13] * v[M32] + v[M02] * v[M10] * v[M33] - v[M00] * v[M12] * v[M33];
+			t[M13] = v[M02] * v[M13] * v[M20] - v[M03] * v[M12] * v[M20] + v[M03] * v[M10] * v[M22]
+				- v[M00] * v[M13] * v[M22] - v[M02] * v[M10] * v[M23] + v[M00] * v[M12] * v[M23];
+			t[M20] = v[M11] * v[M23] * v[M30] - v[M13] * v[M21] * v[M30] + v[M13] * v[M20] * v[M31]
+				- v[M10] * v[M23] * v[M31] - v[M11] * v[M20] * v[M33] + v[M10] * v[M21] * v[M33];
+			t[M21] = v[M03] * v[M21] * v[M30] - v[M01] * v[M23] * v[M30] - v[M03] * v[M20] * v[M31]
+				+ v[M00] * v[M23] * v[M31] + v[M01] * v[M20] * v[M33] - v[M00] * v[M21] * v[M33];
+			t[M22] = v[M01] * v[M13] * v[M30] - v[M03] * v[M11] * v[M30] + v[M03] * v[M10] * v[M31]
+				- v[M00] * v[M13] * v[M31] - v[M01] * v[M10] * v[M33] + v[M00] * v[M11] * v[M33];
+			t[M23] = v[M03] * v[M11] * v[M20] - v[M01] * v[M13] * v[M20] - v[M03] * v[M10] * v[M21]
+				+ v[M00] * v[M13] * v[M21] + v[M01] * v[M10] * v[M23] - v[M00] * v[M11] * v[M23];
+			t[M30] = v[M12] * v[M21] * v[M30] - v[M11] * v[M22] * v[M30] - v[M12] * v[M20] * v[M31]
+				+ v[M10] * v[M22] * v[M31] + v[M11] * v[M20] * v[M32] - v[M10] * v[M21] * v[M32];
+			t[M31] = v[M01] * v[M22] * v[M30] - v[M02] * v[M21] * v[M30] + v[M02] * v[M20] * v[M31]
+				- v[M00] * v[M22] * v[M31] - v[M01] * v[M20] * v[M32] + v[M00] * v[M21] * v[M32];
+			t[M32] = v[M02] * v[M11] * v[M30] - v[M01] * v[M12] * v[M30] - v[M02] * v[M10] * v[M31]
+				+ v[M00] * v[M12] * v[M31] + v[M01] * v[M10] * v[M32] - v[M00] * v[M11] * v[M32];
+			t[M33] = v[M01] * v[M12] * v[M20] - v[M02] * v[M11] * v[M20] + v[M02] * v[M10] * v[M21]
+				- v[M00] * v[M12] * v[M21] - v[M01] * v[M10] * v[M22] + v[M00] * v[M11] * v[M22];
+			v[M00] = t[M00] * inv_det;
+			v[M01] = t[M01] * inv_det;
+			v[M02] = t[M02] * inv_det;
+			v[M03] = t[M03] * inv_det;
+			v[M10] = t[M10] * inv_det;
+			v[M11] = t[M11] * inv_det;
+			v[M12] = t[M12] * inv_det;
+			v[M13] = t[M13] * inv_det;
+			v[M20] = t[M20] * inv_det;
+			v[M21] = t[M21] * inv_det;
+			v[M22] = t[M22] * inv_det;
+			v[M23] = t[M23] * inv_det;
+			v[M30] = t[M30] * inv_det;
+			v[M31] = t[M31] * inv_det;
+			v[M32] = t[M32] * inv_det;
+			v[M33] = t[M33] * inv_det;
+			return this;
+		}
+
+		determinant (): number {
+			let v = this.values;
+			return v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03]
+				+ v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03]
+				- v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13]
+				- v[M00] * v[M31] * v[M22] * v[M13] - v[M20] * v[M01] * v[M32] * v[M13] + v[M00] * v[M21] * v[M32] * v[M13]
+				+ v[M30] * v[M11] * v[M02] * v[M23] - v[M10] * v[M31] * v[M02] * v[M23] - v[M30] * v[M01] * v[M12] * v[M23]
+				+ v[M00] * v[M31] * v[M12] * v[M23] + v[M10] * v[M01] * v[M32] * v[M23] - v[M00] * v[M11] * v[M32] * v[M23]
+				- v[M20] * v[M11] * v[M02] * v[M33] + v[M10] * v[M21] * v[M02] * v[M33] + v[M20] * v[M01] * v[M12] * v[M33]
+				- v[M00] * v[M21] * v[M12] * v[M33] - v[M10] * v[M01] * v[M22] * v[M33] + v[M00] * v[M11] * v[M22] * v[M33];
+		}
+
+		translate (x: number, y: number, z: number): Matrix4 {
+			let v = this.values;
+			v[M03] += x;
+			v[M13] += y;
+			v[M23] += z;
+			return this;
+		}
+
+		copy (): Matrix4 {
+			return new Matrix4().set(this.values);
+		}
+
+		projection (near: number, far: number, fovy: number, aspectRatio: number): Matrix4 {
+			this.identity();
+			let l_fd = (1.0 / Math.tan((fovy * (Math.PI / 180)) / 2.0));
+			let l_a1 = (far + near) / (near - far);
+			let l_a2 = (2 * far * near) / (near - far);
+			let v = this.values;
+			v[M00] = l_fd / aspectRatio;
+			v[M10] = 0;
+			v[M20] = 0;
+			v[M30] = 0;
+			v[M01] = 0;
+			v[M11] = l_fd;
+			v[M21] = 0;
+			v[M31] = 0;
+			v[M02] = 0;
+			v[M12] = 0;
+			v[M22] = l_a1;
+			v[M32] = -1;
+			v[M03] = 0;
+			v[M13] = 0;
+			v[M23] = l_a2;
+			v[M33] = 0;
+			return this;
+		}
+
+		ortho2d (x: number, y: number, width: number, height: number): Matrix4 {
+			return this.ortho(x, x + width, y, y + height, 0, 1);
+		}
+
+		ortho (left: number, right: number, bottom: number, top: number, near: number, far: number): Matrix4 {
+			this.identity();
+			let x_orth = 2 / (right - left);
+			let y_orth = 2 / (top - bottom);
+			let z_orth = -2 / (far - near);
+
+			let tx = -(right + left) / (right - left);
+			let ty = -(top + bottom) / (top - bottom);
+			let tz = -(far + near) / (far - near);
+
+			let v = this.values;
+			v[M00] = x_orth;
+			v[M10] = 0;
+			v[M20] = 0;
+			v[M30] = 0;
+			v[M01] = 0;
+			v[M11] = y_orth;
+			v[M21] = 0;
+			v[M31] = 0;
+			v[M02] = 0;
+			v[M12] = 0;
+			v[M22] = z_orth;
+			v[M32] = 0;
+			v[M03] = tx;
+			v[M13] = ty;
+			v[M23] = tz;
+			v[M33] = 1;
+			return this;
+		}
+
+		multiply (matrix: Matrix4): Matrix4 {
+			let t = this.temp;
+			let v = this.values;
+			let m = matrix.values;
+			t[M00] = v[M00] * m[M00] + v[M01] * m[M10] + v[M02] * m[M20] + v[M03] * m[M30];
+			t[M01] = v[M00] * m[M01] + v[M01] * m[M11] + v[M02] * m[M21] + v[M03] * m[M31];
+			t[M02] = v[M00] * m[M02] + v[M01] * m[M12] + v[M02] * m[M22] + v[M03] * m[M32];
+			t[M03] = v[M00] * m[M03] + v[M01] * m[M13] + v[M02] * m[M23] + v[M03] * m[M33];
+			t[M10] = v[M10] * m[M00] + v[M11] * m[M10] + v[M12] * m[M20] + v[M13] * m[M30];
+			t[M11] = v[M10] * m[M01] + v[M11] * m[M11] + v[M12] * m[M21] + v[M13] * m[M31];
+			t[M12] = v[M10] * m[M02] + v[M11] * m[M12] + v[M12] * m[M22] + v[M13] * m[M32];
+			t[M13] = v[M10] * m[M03] + v[M11] * m[M13] + v[M12] * m[M23] + v[M13] * m[M33];
+			t[M20] = v[M20] * m[M00] + v[M21] * m[M10] + v[M22] * m[M20] + v[M23] * m[M30];
+			t[M21] = v[M20] * m[M01] + v[M21] * m[M11] + v[M22] * m[M21] + v[M23] * m[M31];
+			t[M22] = v[M20] * m[M02] + v[M21] * m[M12] + v[M22] * m[M22] + v[M23] * m[M32];
+			t[M23] = v[M20] * m[M03] + v[M21] * m[M13] + v[M22] * m[M23] + v[M23] * m[M33];
+			t[M30] = v[M30] * m[M00] + v[M31] * m[M10] + v[M32] * m[M20] + v[M33] * m[M30];
+			t[M31] = v[M30] * m[M01] + v[M31] * m[M11] + v[M32] * m[M21] + v[M33] * m[M31];
+			t[M32] = v[M30] * m[M02] + v[M31] * m[M12] + v[M32] * m[M22] + v[M33] * m[M32];
+			t[M33] = v[M30] * m[M03] + v[M31] * m[M13] + v[M32] * m[M23] + v[M33] * m[M33];
+			return this.set(this.temp);
+		}
+
+		multiplyLeft (matrix: Matrix4): Matrix4 {
+			let t = this.temp;
+			let v = this.values;
+			let m = matrix.values;
+			t[M00] = m[M00] * v[M00] + m[M01] * v[M10] + m[M02] * v[M20] + m[M03] * v[M30];
+			t[M01] = m[M00] * v[M01] + m[M01] * v[M11] + m[M02] * v[M21] + m[M03] * v[M31];
+			t[M02] = m[M00] * v[M02] + m[M01] * v[M12] + m[M02] * v[M22] + m[M03] * v[M32];
+			t[M03] = m[M00] * v[M03] + m[M01] * v[M13] + m[M02] * v[M23] + m[M03] * v[M33];
+			t[M10] = m[M10] * v[M00] + m[M11] * v[M10] + m[M12] * v[M20] + m[M13] * v[M30];
+			t[M11] = m[M10] * v[M01] + m[M11] * v[M11] + m[M12] * v[M21] + m[M13] * v[M31];
+			t[M12] = m[M10] * v[M02] + m[M11] * v[M12] + m[M12] * v[M22] + m[M13] * v[M32];
+			t[M13] = m[M10] * v[M03] + m[M11] * v[M13] + m[M12] * v[M23] + m[M13] * v[M33];
+			t[M20] = m[M20] * v[M00] + m[M21] * v[M10] + m[M22] * v[M20] + m[M23] * v[M30];
+			t[M21] = m[M20] * v[M01] + m[M21] * v[M11] + m[M22] * v[M21] + m[M23] * v[M31];
+			t[M22] = m[M20] * v[M02] + m[M21] * v[M12] + m[M22] * v[M22] + m[M23] * v[M32];
+			t[M23] = m[M20] * v[M03] + m[M21] * v[M13] + m[M22] * v[M23] + m[M23] * v[M33];
+			t[M30] = m[M30] * v[M00] + m[M31] * v[M10] + m[M32] * v[M20] + m[M33] * v[M30];
+			t[M31] = m[M30] * v[M01] + m[M31] * v[M11] + m[M32] * v[M21] + m[M33] * v[M31];
+			t[M32] = m[M30] * v[M02] + m[M31] * v[M12] + m[M32] * v[M22] + m[M33] * v[M32];
+			t[M33] = m[M30] * v[M03] + m[M31] * v[M13] + m[M32] * v[M23] + m[M33] * v[M33];
+			return this.set(this.temp);
+		}
+
+		lookAt (position: Vector3, direction: Vector3, up: Vector3) {
+			Matrix4.initTemps();
+			let xAxis = Matrix4.xAxis, yAxis = Matrix4.yAxis, zAxis = Matrix4.zAxis;
+			zAxis.setFrom(direction).normalize();
+			xAxis.setFrom(direction).normalize();
+			xAxis.cross(up).normalize();
+			yAxis.setFrom(xAxis).cross(zAxis).normalize();
+			this.identity();
+			let val = this.values;
+			val[M00] = xAxis.x;
+			val[M01] = xAxis.y;
+			val[M02] = xAxis.z;
+			val[M10] = yAxis.x;
+			val[M11] = yAxis.y;
+			val[M12] = yAxis.z;
+			val[M20] = -zAxis.x;
+			val[M21] = -zAxis.y;
+			val[M22] = -zAxis.z;
+
+			Matrix4.tmpMatrix.identity();
+			Matrix4.tmpMatrix.values[M03] = -position.x;
+			Matrix4.tmpMatrix.values[M13] = -position.y;
+			Matrix4.tmpMatrix.values[M23] = -position.z;
+			this.multiply(Matrix4.tmpMatrix)
+
+			return this;
+		}
+
+		static initTemps () {
+			if (Matrix4.xAxis === null) Matrix4.xAxis = new Vector3();
+			if (Matrix4.yAxis === null) Matrix4.yAxis = new Vector3();
+			if (Matrix4.zAxis === null) Matrix4.zAxis = new Vector3();
+		}
+	}
+}

+ 208 - 208
spine-ts/webgl/src/Mesh.ts

@@ -1,208 +1,208 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class Mesh implements Disposable, Restorable {
-		private context: ManagedWebGLRenderingContext;
-		private vertices:Float32Array;
-		private verticesBuffer: WebGLBuffer;
-		private verticesLength = 0;
-		private dirtyVertices = false;
-		private indices:Uint16Array;
-		private indicesBuffer: WebGLBuffer;
-		private indicesLength = 0;
-		private dirtyIndices = false;
-		private elementsPerVertex = 0;
-
-		getAttributes (): VertexAttribute[] { return this.attributes; }
-
-		maxVertices (): number { return this.vertices.length / this.elementsPerVertex; }
-		numVertices (): number { return this.verticesLength / this.elementsPerVertex; }
-		setVerticesLength (length: number) {
-			this.dirtyVertices = true;
-			this.verticesLength = length;
-		}
-		getVertices (): Float32Array { return this.vertices; }
-
-		maxIndices (): number { return this.indices.length; }
-		numIndices (): number { return this.indicesLength; }
-		setIndicesLength (length: number) {
-			this.dirtyIndices = true;
-			this.indicesLength = length;
-		}
-		getIndices (): Uint16Array { return this.indices };
-
-		getVertexSizeInFloats (): number {
-			let size = 0;
-			for (var i = 0; i < this.attributes.length; i++) {
-				let attribute = this.attributes[i];
-				size += attribute.numElements;
-			}
-			return size;
-		}
-
-		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, private attributes: VertexAttribute[], maxVertices: number, maxIndices: number) {
-			this.context = context instanceof ManagedWebGLRenderingContext? context : new ManagedWebGLRenderingContext(context);
-			this.elementsPerVertex = 0;
-			for (let i = 0; i < attributes.length; i++) {
-				this.elementsPerVertex += attributes[i].numElements;
-			}
-			this.vertices = new Float32Array(maxVertices * this.elementsPerVertex);
-			this.indices = new Uint16Array(maxIndices);
-			this.context.addRestorable(this);
-		}
-
-		setVertices (vertices: Array<number>) {
-			this.dirtyVertices = true;
-			if (vertices.length > this.vertices.length) throw Error("Mesh can't store more than " + this.maxVertices() + " vertices");
-			this.vertices.set(vertices, 0);
-			this.verticesLength = vertices.length;
-		}
-
-		setIndices (indices: Array<number>) {
-			this.dirtyIndices = true;
-			if (indices.length > this.indices.length) throw Error("Mesh can't store more than " + this.maxIndices() + " indices");
-			this.indices.set(indices, 0);
-			this.indicesLength = indices.length;
-		}
-
-		draw (shader: Shader, primitiveType: number) {
-			this.drawWithOffset(shader, primitiveType, 0, this.indicesLength > 0? this.indicesLength: this.verticesLength / this.elementsPerVertex);
-		}
-
-		drawWithOffset (shader: Shader, primitiveType: number, offset: number, count: number) {
-			let gl = this.context.gl;
-			if (this.dirtyVertices || this.dirtyIndices) this.update();
-			this.bind(shader);
-			if (this.indicesLength > 0) {
-				gl.drawElements(primitiveType, count, gl.UNSIGNED_SHORT, offset * 2);
-			} else {
-				gl.drawArrays(primitiveType, offset, count);
-			}
-			this.unbind(shader);
-		}
-
-		bind (shader: Shader) {
-			let gl = this.context.gl;
-			gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesBuffer);
-			let offset = 0;
-			for (let i = 0; i < this.attributes.length; i++) {
-				let attrib = this.attributes[i];
-				let location = shader.getAttributeLocation(attrib.name);
-				gl.enableVertexAttribArray(location);
-				gl.vertexAttribPointer(location, attrib.numElements, gl.FLOAT, false, this.elementsPerVertex * 4, offset * 4);
-				offset += attrib.numElements;
-			}
-			if (this.indicesLength > 0) gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
-		}
-
-		unbind (shader: Shader) {
-			let gl = this.context.gl;
-			for (let i = 0; i < this.attributes.length; i++) {
-				let attrib = this.attributes[i];
-				let location = shader.getAttributeLocation(attrib.name);
-				gl.disableVertexAttribArray(location);
-			}
-			gl.bindBuffer(gl.ARRAY_BUFFER, null);
-			if (this.indicesLength > 0) gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
-		}
-
-		private update () {
-			let gl = this.context.gl;
-			if (this.dirtyVertices) {
-				if (!this.verticesBuffer) {
-					this.verticesBuffer = gl.createBuffer();
-				}
-				gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesBuffer);
-				gl.bufferData(gl.ARRAY_BUFFER, this.vertices.subarray(0, this.verticesLength), gl.DYNAMIC_DRAW);
-				this.dirtyVertices = false;
-			}
-
-			if (this.dirtyIndices) {
-				if (!this.indicesBuffer) {
-					this.indicesBuffer = gl.createBuffer();
-				}
-				gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
-				gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices.subarray(0, this.indicesLength), gl.DYNAMIC_DRAW);
-				this.dirtyIndices = false;
-			}
-		}
-
-		restore () {
-			this.verticesBuffer = null;
-			this.indicesBuffer = null;
-			this.update();
-		}
-
-		dispose () {
-			this.context.removeRestorable(this);
-			let gl = this.context.gl;
-			gl.deleteBuffer(this.verticesBuffer);
-			gl.deleteBuffer(this.indicesBuffer);
-		}
-	}
-
-	export class VertexAttribute {
-		constructor (public name: string, public type: VertexAttributeType, public numElements: number) { }
-	}
-
-	export class Position2Attribute extends VertexAttribute {
-		constructor () {
-			super(Shader.POSITION, VertexAttributeType.Float, 2);
-		}
-	}
-
-	export class Position3Attribute extends VertexAttribute {
-		constructor () {
-			super(Shader.POSITION, VertexAttributeType.Float, 3);
-		}
-	}
-
-	export class TexCoordAttribute extends VertexAttribute {
-		constructor (unit: number = 0) {
-			super(Shader.TEXCOORDS + (unit == 0 ? "" : unit), VertexAttributeType.Float, 2);
-		}
-	}
-
-	export class ColorAttribute extends VertexAttribute {
-		constructor () {
-			super(Shader.COLOR, VertexAttributeType.Float, 4);
-		}
-	}
-
-	export class Color2Attribute extends VertexAttribute {
-		constructor () {
-			super(Shader.COLOR2, VertexAttributeType.Float, 4);
-		}
-	}
-
-	export enum VertexAttributeType {
-		Float
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class Mesh implements Disposable, Restorable {
+		private context: ManagedWebGLRenderingContext;
+		private vertices: Float32Array;
+		private verticesBuffer: WebGLBuffer;
+		private verticesLength = 0;
+		private dirtyVertices = false;
+		private indices: Uint16Array;
+		private indicesBuffer: WebGLBuffer;
+		private indicesLength = 0;
+		private dirtyIndices = false;
+		private elementsPerVertex = 0;
+
+		getAttributes (): VertexAttribute[] { return this.attributes; }
+
+		maxVertices (): number { return this.vertices.length / this.elementsPerVertex; }
+		numVertices (): number { return this.verticesLength / this.elementsPerVertex; }
+		setVerticesLength (length: number) {
+			this.dirtyVertices = true;
+			this.verticesLength = length;
+		}
+		getVertices (): Float32Array { return this.vertices; }
+
+		maxIndices (): number { return this.indices.length; }
+		numIndices (): number { return this.indicesLength; }
+		setIndicesLength (length: number) {
+			this.dirtyIndices = true;
+			this.indicesLength = length;
+		}
+		getIndices (): Uint16Array { return this.indices };
+
+		getVertexSizeInFloats (): number {
+			let size = 0;
+			for (var i = 0; i < this.attributes.length; i++) {
+				let attribute = this.attributes[i];
+				size += attribute.numElements;
+			}
+			return size;
+		}
+
+		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, private attributes: VertexAttribute[], maxVertices: number, maxIndices: number) {
+			this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
+			this.elementsPerVertex = 0;
+			for (let i = 0; i < attributes.length; i++) {
+				this.elementsPerVertex += attributes[i].numElements;
+			}
+			this.vertices = new Float32Array(maxVertices * this.elementsPerVertex);
+			this.indices = new Uint16Array(maxIndices);
+			this.context.addRestorable(this);
+		}
+
+		setVertices (vertices: Array<number>) {
+			this.dirtyVertices = true;
+			if (vertices.length > this.vertices.length) throw Error("Mesh can't store more than " + this.maxVertices() + " vertices");
+			this.vertices.set(vertices, 0);
+			this.verticesLength = vertices.length;
+		}
+
+		setIndices (indices: Array<number>) {
+			this.dirtyIndices = true;
+			if (indices.length > this.indices.length) throw Error("Mesh can't store more than " + this.maxIndices() + " indices");
+			this.indices.set(indices, 0);
+			this.indicesLength = indices.length;
+		}
+
+		draw (shader: Shader, primitiveType: number) {
+			this.drawWithOffset(shader, primitiveType, 0, this.indicesLength > 0 ? this.indicesLength : this.verticesLength / this.elementsPerVertex);
+		}
+
+		drawWithOffset (shader: Shader, primitiveType: number, offset: number, count: number) {
+			let gl = this.context.gl;
+			if (this.dirtyVertices || this.dirtyIndices) this.update();
+			this.bind(shader);
+			if (this.indicesLength > 0) {
+				gl.drawElements(primitiveType, count, gl.UNSIGNED_SHORT, offset * 2);
+			} else {
+				gl.drawArrays(primitiveType, offset, count);
+			}
+			this.unbind(shader);
+		}
+
+		bind (shader: Shader) {
+			let gl = this.context.gl;
+			gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesBuffer);
+			let offset = 0;
+			for (let i = 0; i < this.attributes.length; i++) {
+				let attrib = this.attributes[i];
+				let location = shader.getAttributeLocation(attrib.name);
+				gl.enableVertexAttribArray(location);
+				gl.vertexAttribPointer(location, attrib.numElements, gl.FLOAT, false, this.elementsPerVertex * 4, offset * 4);
+				offset += attrib.numElements;
+			}
+			if (this.indicesLength > 0) gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
+		}
+
+		unbind (shader: Shader) {
+			let gl = this.context.gl;
+			for (let i = 0; i < this.attributes.length; i++) {
+				let attrib = this.attributes[i];
+				let location = shader.getAttributeLocation(attrib.name);
+				gl.disableVertexAttribArray(location);
+			}
+			gl.bindBuffer(gl.ARRAY_BUFFER, null);
+			if (this.indicesLength > 0) gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
+		}
+
+		private update () {
+			let gl = this.context.gl;
+			if (this.dirtyVertices) {
+				if (!this.verticesBuffer) {
+					this.verticesBuffer = gl.createBuffer();
+				}
+				gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesBuffer);
+				gl.bufferData(gl.ARRAY_BUFFER, this.vertices.subarray(0, this.verticesLength), gl.DYNAMIC_DRAW);
+				this.dirtyVertices = false;
+			}
+
+			if (this.dirtyIndices) {
+				if (!this.indicesBuffer) {
+					this.indicesBuffer = gl.createBuffer();
+				}
+				gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
+				gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices.subarray(0, this.indicesLength), gl.DYNAMIC_DRAW);
+				this.dirtyIndices = false;
+			}
+		}
+
+		restore () {
+			this.verticesBuffer = null;
+			this.indicesBuffer = null;
+			this.update();
+		}
+
+		dispose () {
+			this.context.removeRestorable(this);
+			let gl = this.context.gl;
+			gl.deleteBuffer(this.verticesBuffer);
+			gl.deleteBuffer(this.indicesBuffer);
+		}
+	}
+
+	export class VertexAttribute {
+		constructor (public name: string, public type: VertexAttributeType, public numElements: number) { }
+	}
+
+	export class Position2Attribute extends VertexAttribute {
+		constructor () {
+			super(Shader.POSITION, VertexAttributeType.Float, 2);
+		}
+	}
+
+	export class Position3Attribute extends VertexAttribute {
+		constructor () {
+			super(Shader.POSITION, VertexAttributeType.Float, 3);
+		}
+	}
+
+	export class TexCoordAttribute extends VertexAttribute {
+		constructor (unit: number = 0) {
+			super(Shader.TEXCOORDS + (unit == 0 ? "" : unit), VertexAttributeType.Float, 2);
+		}
+	}
+
+	export class ColorAttribute extends VertexAttribute {
+		constructor () {
+			super(Shader.COLOR, VertexAttributeType.Float, 4);
+		}
+	}
+
+	export class Color2Attribute extends VertexAttribute {
+		constructor () {
+			super(Shader.COLOR2, VertexAttributeType.Float, 4);
+		}
+	}
+
+	export enum VertexAttributeType {
+		Float
+	}
+}

+ 133 - 133
spine-ts/webgl/src/PolygonBatcher.ts

@@ -1,133 +1,133 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class PolygonBatcher implements Disposable {
-		private context: ManagedWebGLRenderingContext;
-		private drawCalls: number;
-		private isDrawing = false;
-		private mesh: Mesh;
-		private shader: Shader = null;
-		private lastTexture: GLTexture = null;
-		private verticesLength = 0;
-		private indicesLength = 0;
-		private srcColorBlend: number;
-		private srcAlphaBlend: number;
-		private dstBlend: number;
-
-		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, twoColorTint: boolean = true, maxVertices: number = 10920) {
-			if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
-			this.context = context instanceof ManagedWebGLRenderingContext? context : new ManagedWebGLRenderingContext(context);
-			let attributes = twoColorTint ?
-					[new Position2Attribute(), new ColorAttribute(), new TexCoordAttribute(), new Color2Attribute()] :
-					[new Position2Attribute(), new ColorAttribute(), new TexCoordAttribute()];
-			this.mesh = new Mesh(context, attributes, maxVertices, maxVertices * 3);
-			let gl = this.context.gl;
-			this.srcColorBlend = gl.SRC_ALPHA;
-			this.srcAlphaBlend = gl.ONE;
-			this.dstBlend = gl.ONE_MINUS_SRC_ALPHA;
-		}
-
-		begin (shader: Shader) {
-			if (this.isDrawing) throw new Error("PolygonBatch is already drawing. Call PolygonBatch.end() before calling PolygonBatch.begin()");
-			this.drawCalls = 0;
-			this.shader = shader;
-			this.lastTexture = null;
-			this.isDrawing = true;
-
-			let gl = this.context.gl;
-			gl.enable(gl.BLEND);
-			gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend);
-		}
-
-		setBlendMode (srcColorBlend: number, srcAlphaBlend: number, dstBlend: number) {
-			this.srcColorBlend = srcColorBlend;
-			this.srcAlphaBlend = srcAlphaBlend;
-			this.dstBlend = dstBlend;
-			if (this.isDrawing) {
-				this.flush();
-				let gl = this.context.gl;
-				gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend);
-			}
-		}
-
-		draw (texture: GLTexture, vertices: ArrayLike<number>, indices: Array<number>) {
-			if (texture != this.lastTexture) {
-				this.flush();
-				this.lastTexture = texture;
-			} else if (this.verticesLength + vertices.length > this.mesh.getVertices().length ||
-					this.indicesLength + indices.length > this.mesh.getIndices().length) {
-				this.flush();
-			}
-
-			let indexStart = this.mesh.numVertices();
-			this.mesh.getVertices().set(vertices, this.verticesLength);
-			this.verticesLength += vertices.length;
-			this.mesh.setVerticesLength(this.verticesLength)
-
-			let indicesArray = this.mesh.getIndices();
-			for (let i = this.indicesLength, j = 0; j < indices.length; i++, j++)
-				indicesArray[i] = indices[j] + indexStart;
-			this.indicesLength += indices.length;
-			this.mesh.setIndicesLength(this.indicesLength);
-		}
-
-		flush () {
-			if (this.verticesLength == 0) return;
-
-			this.lastTexture.bind();
-			this.mesh.draw(this.shader, this.context.gl.TRIANGLES);
-
-			this.verticesLength = 0;
-			this.indicesLength = 0;
-			this.mesh.setVerticesLength(0);
-			this.mesh.setIndicesLength(0);
-			this.drawCalls++;
-		}
-
-		end () {
-			if (!this.isDrawing) throw new Error("PolygonBatch is not drawing. Call PolygonBatch.begin() before calling PolygonBatch.end()");
-			if (this.verticesLength > 0 || this.indicesLength > 0) this.flush();
-			this.shader = null;
-			this.lastTexture = null;
-			this.isDrawing = false;
-
-			let gl = this.context.gl;
-			gl.disable(gl.BLEND);
-		}
-
-		getDrawCalls () {
-			return this.drawCalls;
-		}
-
-		dispose () {
-			this.mesh.dispose();
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class PolygonBatcher implements Disposable {
+		private context: ManagedWebGLRenderingContext;
+		private drawCalls: number;
+		private isDrawing = false;
+		private mesh: Mesh;
+		private shader: Shader = null;
+		private lastTexture: GLTexture = null;
+		private verticesLength = 0;
+		private indicesLength = 0;
+		private srcColorBlend: number;
+		private srcAlphaBlend: number;
+		private dstBlend: number;
+
+		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, twoColorTint: boolean = true, maxVertices: number = 10920) {
+			if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
+			this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
+			let attributes = twoColorTint ?
+				[new Position2Attribute(), new ColorAttribute(), new TexCoordAttribute(), new Color2Attribute()] :
+				[new Position2Attribute(), new ColorAttribute(), new TexCoordAttribute()];
+			this.mesh = new Mesh(context, attributes, maxVertices, maxVertices * 3);
+			let gl = this.context.gl;
+			this.srcColorBlend = gl.SRC_ALPHA;
+			this.srcAlphaBlend = gl.ONE;
+			this.dstBlend = gl.ONE_MINUS_SRC_ALPHA;
+		}
+
+		begin (shader: Shader) {
+			if (this.isDrawing) throw new Error("PolygonBatch is already drawing. Call PolygonBatch.end() before calling PolygonBatch.begin()");
+			this.drawCalls = 0;
+			this.shader = shader;
+			this.lastTexture = null;
+			this.isDrawing = true;
+
+			let gl = this.context.gl;
+			gl.enable(gl.BLEND);
+			gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend);
+		}
+
+		setBlendMode (srcColorBlend: number, srcAlphaBlend: number, dstBlend: number) {
+			this.srcColorBlend = srcColorBlend;
+			this.srcAlphaBlend = srcAlphaBlend;
+			this.dstBlend = dstBlend;
+			if (this.isDrawing) {
+				this.flush();
+				let gl = this.context.gl;
+				gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend);
+			}
+		}
+
+		draw (texture: GLTexture, vertices: ArrayLike<number>, indices: Array<number>) {
+			if (texture != this.lastTexture) {
+				this.flush();
+				this.lastTexture = texture;
+			} else if (this.verticesLength + vertices.length > this.mesh.getVertices().length ||
+				this.indicesLength + indices.length > this.mesh.getIndices().length) {
+				this.flush();
+			}
+
+			let indexStart = this.mesh.numVertices();
+			this.mesh.getVertices().set(vertices, this.verticesLength);
+			this.verticesLength += vertices.length;
+			this.mesh.setVerticesLength(this.verticesLength)
+
+			let indicesArray = this.mesh.getIndices();
+			for (let i = this.indicesLength, j = 0; j < indices.length; i++, j++)
+				indicesArray[i] = indices[j] + indexStart;
+			this.indicesLength += indices.length;
+			this.mesh.setIndicesLength(this.indicesLength);
+		}
+
+		flush () {
+			if (this.verticesLength == 0) return;
+
+			this.lastTexture.bind();
+			this.mesh.draw(this.shader, this.context.gl.TRIANGLES);
+
+			this.verticesLength = 0;
+			this.indicesLength = 0;
+			this.mesh.setVerticesLength(0);
+			this.mesh.setIndicesLength(0);
+			this.drawCalls++;
+		}
+
+		end () {
+			if (!this.isDrawing) throw new Error("PolygonBatch is not drawing. Call PolygonBatch.begin() before calling PolygonBatch.end()");
+			if (this.verticesLength > 0 || this.indicesLength > 0) this.flush();
+			this.shader = null;
+			this.lastTexture = null;
+			this.isDrawing = false;
+
+			let gl = this.context.gl;
+			gl.disable(gl.BLEND);
+		}
+
+		getDrawCalls () {
+			return this.drawCalls;
+		}
+
+		dispose () {
+			this.mesh.dispose();
+		}
+	}
+}

+ 506 - 506
spine-ts/webgl/src/SceneRenderer.ts

@@ -1,506 +1,506 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	const quad = [
-		0, 0, 1, 1, 1, 1, 0, 0,
-		0, 0, 1, 1, 1, 1, 0, 0,
-		0, 0, 1, 1, 1, 1, 0, 0,
-		0, 0, 1, 1, 1, 1, 0, 0,
-	];
-	const QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
-	const WHITE = new Color(1, 1, 1, 1);
-
-	export class SceneRenderer implements Disposable {
-		context: ManagedWebGLRenderingContext;
-		canvas: HTMLCanvasElement;
-		camera: OrthoCamera;
-		batcher: PolygonBatcher;
-		private twoColorTint = false;
-		private batcherShader: Shader;
-		private shapes: ShapeRenderer;
-		private shapesShader: Shader;
-		private activeRenderer: PolygonBatcher | ShapeRenderer | SkeletonDebugRenderer;
-		skeletonRenderer: SkeletonRenderer;
-		skeletonDebugRenderer: SkeletonDebugRenderer;
-
-		constructor (canvas: HTMLCanvasElement, context: ManagedWebGLRenderingContext | WebGLRenderingContext, twoColorTint: boolean = true) {
-			this.canvas = canvas;
-			this.context = context instanceof ManagedWebGLRenderingContext? context : new ManagedWebGLRenderingContext(context);
-			this.twoColorTint = twoColorTint;
-			this.camera = new OrthoCamera(canvas.width, canvas.height);
-			this.batcherShader = twoColorTint ? Shader.newTwoColoredTextured(this.context) : Shader.newColoredTextured(this.context);
-			this.batcher = new PolygonBatcher(this.context, twoColorTint);
-			this.shapesShader = Shader.newColored(this.context);
-			this.shapes = new ShapeRenderer(this.context);
-			this.skeletonRenderer = new SkeletonRenderer(this.context, twoColorTint);
-			this.skeletonDebugRenderer = new SkeletonDebugRenderer(this.context);
-		}
-
-		begin () {
-			this.camera.update();
-			this.enableRenderer(this.batcher);
-		}
-
-		drawSkeleton (skeleton: Skeleton, premultipliedAlpha = false, slotRangeStart = -1, slotRangeEnd = -1) {
-			this.enableRenderer(this.batcher);
-			this.skeletonRenderer.premultipliedAlpha = premultipliedAlpha;
-			this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd);
-		}
-
-		drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones: Array<string> = null) {
-			this.enableRenderer(this.shapes);
-			this.skeletonDebugRenderer.premultipliedAlpha = premultipliedAlpha;
-			this.skeletonDebugRenderer.draw(this.shapes, skeleton, ignoredBones);
-		}
-
-		drawTexture (texture: GLTexture, x: number, y: number, width: number, height: number, color: Color = null) {
-			this.enableRenderer(this.batcher);
-			if (color === null) color = WHITE;
-			var i = 0;
-			quad[i++] = x;
-			quad[i++] = y;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = 0;
-			quad[i++] = 1;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x + width;
-			quad[i++] = y;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = 1;
-			quad[i++] = 1;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x + width;
-			quad[i++] = y + height;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = 1;
-			quad[i++] = 0;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x;
-			quad[i++] = y + height;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = 0;
-			quad[i++] = 0;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i] = 0;
-			}
-			this.batcher.draw(texture, quad, QUAD_TRIANGLES);
-		}
-
-		drawTextureUV (texture: GLTexture, x: number, y: number, width: number, height: number, u: number, v: number, u2: number, v2: number, color: Color = null) {
-			this.enableRenderer(this.batcher);
-			if (color === null) color = WHITE;
-			var i = 0;
-			quad[i++] = x;
-			quad[i++] = y;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = u;
-			quad[i++] = v;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x + width;
-			quad[i++] = y;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = u2;
-			quad[i++] = v;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x + width;
-			quad[i++] = y + height;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = u2;
-			quad[i++] = v2;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x;
-			quad[i++] = y + height;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = u;
-			quad[i++] = v2;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i] = 0;
-			}
-			this.batcher.draw(texture, quad, QUAD_TRIANGLES);
-		}
-
-		drawTextureRotated (texture: GLTexture, x: number, y: number, width: number, height: number, pivotX: number, pivotY: number, angle: number, color: Color = null) {
-			this.enableRenderer(this.batcher);
-			if (color === null) color = WHITE;
-
-			// bottom left and top right corner points relative to origin
-			let worldOriginX = x + pivotX;
-			let worldOriginY = y + pivotY;
-			let fx = -pivotX;
-			let fy = -pivotY;
-			let fx2 = width - pivotX;
-			let fy2 = height - pivotY;
-
-			// construct corner points, start from top left and go counter clockwise
-			let p1x = fx;
-			let p1y = fy;
-			let p2x = fx;
-			let p2y = fy2;
-			let p3x = fx2;
-			let p3y = fy2;
-			let p4x = fx2;
-			let p4y = fy;
-
-			let x1 = 0;
-			let y1 = 0;
-			let x2 = 0;
-			let y2 = 0;
-			let x3 = 0;
-			let y3 = 0;
-			let x4 = 0;
-			let y4 = 0;
-
-			// rotate
-			if (angle != 0) {
-				let cos = MathUtils.cosDeg(angle);
-				let sin = MathUtils.sinDeg(angle);
-
-				x1 = cos * p1x - sin * p1y;
-				y1 = sin * p1x + cos * p1y;
-
-				x4 = cos * p2x - sin * p2y;
-				y4 = sin * p2x + cos * p2y;
-
-				x3 = cos * p3x - sin * p3y;
-				y3 = sin * p3x + cos * p3y;
-
-				x2 = x3 + (x1 - x4);
-				y2 = y3 + (y1 - y4);
-			} else {
-				x1 = p1x;
-				y1 = p1y;
-
-				x4 = p2x;
-				y4 = p2y;
-
-				x3 = p3x;
-				y3 = p3y;
-
-				x2 = p4x;
-				y2 = p4y;
-			}
-
-			x1 += worldOriginX;
-			y1 += worldOriginY;
-			x2 += worldOriginX;
-			y2 += worldOriginY;
-			x3 += worldOriginX;
-			y3 += worldOriginY;
-			x4 += worldOriginX;
-			y4 += worldOriginY;
-
-			var i = 0;
-			quad[i++] = x1;
-			quad[i++] = y1;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = 0;
-			quad[i++] = 1;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x2;
-			quad[i++] = y2;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = 1;
-			quad[i++] = 1;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x3;
-			quad[i++] = y3;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = 1;
-			quad[i++] = 0;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x4;
-			quad[i++] = y4;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = 0;
-			quad[i++] = 0;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i] = 0;
-			}
-			this.batcher.draw(texture, quad, QUAD_TRIANGLES);
-		}
-
-		drawRegion (region: TextureAtlasRegion, x: number, y: number, width: number, height: number, color: Color = null) {
-			this.enableRenderer(this.batcher);
-			if (color === null) color = WHITE;
-			var i = 0;
-			quad[i++] = x;
-			quad[i++] = y;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = region.u;
-			quad[i++] = region.v2;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x + width;
-			quad[i++] = y;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = region.u2;
-			quad[i++] = region.v2;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x + width;
-			quad[i++] = y + height;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = region.u2;
-			quad[i++] = region.v;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-			}
-			quad[i++] = x;
-			quad[i++] = y + height;
-			quad[i++] = color.r;
-			quad[i++] = color.g;
-			quad[i++] = color.b;
-			quad[i++] = color.a;
-			quad[i++] = region.u;
-			quad[i++] = region.v;
-			if (this.twoColorTint) {
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i++] = 0;
-				quad[i] = 0;
-			}
-			this.batcher.draw(<GLTexture>region.page.texture, quad, QUAD_TRIANGLES);
-		}
-
-		line (x: number, y: number, x2: number, y2: number, color: Color = null, color2: Color = null) {
-			this.enableRenderer(this.shapes);
-			this.shapes.line(x, y, x2, y2, color);
-		}
-
-		triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color: Color = null, color2: Color = null, color3: Color = null) {
-			this.enableRenderer(this.shapes);
-			this.shapes.triangle(filled, x, y, x2, y2, x3, y3, color, color2, color3);
-		}
-
-		quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: Color = null, color2: Color = null, color3: Color = null, color4: Color = null) {
-			this.enableRenderer(this.shapes);
-			this.shapes.quad(filled, x, y, x2, y2, x3, y3, x4, y4, color, color2, color3, color4);
-		}
-
-		rect (filled: boolean, x: number, y: number, width: number, height: number, color: Color = null) {
-			this.enableRenderer(this.shapes);
-			this.shapes.rect(filled, x, y, width, height, color);
-		}
-
-		rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color: Color = null) {
-			this.enableRenderer(this.shapes);
-			this.shapes.rectLine(filled, x1, y1, x2, y2, width, color);
-		}
-
-		polygon (polygonVertices: ArrayLike<number>, offset: number, count: number, color: Color = null) {
-			this.enableRenderer(this.shapes);
-			this.shapes.polygon(polygonVertices, offset, count, color);
-		}
-
-		circle (filled: boolean, x: number, y: number, radius: number, color: Color = null, segments: number = 0) {
-			this.enableRenderer(this.shapes);
-			this.shapes.circle(filled, x, y, radius, color, segments);
-		}
-
-		curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color: Color = null) {
-			this.enableRenderer(this.shapes);
-			this.shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments, color);
-		}
-
-		end () {
-			if (this.activeRenderer === this.batcher) this.batcher.end();
-			else if (this.activeRenderer === this.shapes) this.shapes.end();
-			this.activeRenderer = null;
-		}
-
-		resize (resizeMode: ResizeMode) {
-			let canvas = this.canvas;
-			var dpr = window.devicePixelRatio || 1;
-			var w = Math.round(canvas.clientWidth * dpr);
-			var h = Math.round(canvas.clientHeight * dpr);
- 
-			if (canvas.width != w || canvas.height != h) {
-				canvas.width = w;
-				canvas.height = h;
-			}
-			this.context.gl.viewport(0, 0, canvas.width, canvas.height);
-
-			// Nothing to do for stretch, we simply apply the viewport size of the camera.
-			if (resizeMode === ResizeMode.Expand)
-				this.camera.setViewport(w, h);
-			else if (resizeMode === ResizeMode.Fit) {
-				let sourceWidth = canvas.width, sourceHeight = canvas.height;
-				let targetWidth = this.camera.viewportWidth, targetHeight = this.camera.viewportHeight;
-				let targetRatio = targetHeight / targetWidth;
-				let sourceRatio = sourceHeight / sourceWidth;
-				let scale = targetRatio < sourceRatio ? targetWidth / sourceWidth : targetHeight / sourceHeight;
-				this.camera.setViewport(sourceWidth * scale, sourceHeight * scale);
-			}
-			this.camera.update();
-		}
-
-		private enableRenderer(renderer: PolygonBatcher | ShapeRenderer | SkeletonDebugRenderer) {
-			if (this.activeRenderer === renderer) return;
-			this.end();
-			if (renderer instanceof PolygonBatcher) {
-				this.batcherShader.bind();
-				this.batcherShader.setUniform4x4f(Shader.MVP_MATRIX, this.camera.projectionView.values);
-				this.batcherShader.setUniformi("u_texture", 0);
-				this.batcher.begin(this.batcherShader);
-				this.activeRenderer = this.batcher;
-			} else if (renderer instanceof ShapeRenderer) {
-				this.shapesShader.bind();
-				this.shapesShader.setUniform4x4f(Shader.MVP_MATRIX, this.camera.projectionView.values);
-				this.shapes.begin(this.shapesShader);
-				this.activeRenderer = this.shapes;
-			} else
-				this.activeRenderer = this.skeletonDebugRenderer;
-		}
-
-		dispose () {
-			this.batcher.dispose();
-			this.batcherShader.dispose();
-			this.shapes.dispose();
-			this.shapesShader.dispose();
-			this.skeletonDebugRenderer.dispose();
-		}
-	}
-
-	export enum ResizeMode {
-		Stretch,
-		Expand,
-		Fit
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	const quad = [
+		0, 0, 1, 1, 1, 1, 0, 0,
+		0, 0, 1, 1, 1, 1, 0, 0,
+		0, 0, 1, 1, 1, 1, 0, 0,
+		0, 0, 1, 1, 1, 1, 0, 0,
+	];
+	const QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
+	const WHITE = new Color(1, 1, 1, 1);
+
+	export class SceneRenderer implements Disposable {
+		context: ManagedWebGLRenderingContext;
+		canvas: HTMLCanvasElement;
+		camera: OrthoCamera;
+		batcher: PolygonBatcher;
+		private twoColorTint = false;
+		private batcherShader: Shader;
+		private shapes: ShapeRenderer;
+		private shapesShader: Shader;
+		private activeRenderer: PolygonBatcher | ShapeRenderer | SkeletonDebugRenderer;
+		skeletonRenderer: SkeletonRenderer;
+		skeletonDebugRenderer: SkeletonDebugRenderer;
+
+		constructor (canvas: HTMLCanvasElement, context: ManagedWebGLRenderingContext | WebGLRenderingContext, twoColorTint: boolean = true) {
+			this.canvas = canvas;
+			this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
+			this.twoColorTint = twoColorTint;
+			this.camera = new OrthoCamera(canvas.width, canvas.height);
+			this.batcherShader = twoColorTint ? Shader.newTwoColoredTextured(this.context) : Shader.newColoredTextured(this.context);
+			this.batcher = new PolygonBatcher(this.context, twoColorTint);
+			this.shapesShader = Shader.newColored(this.context);
+			this.shapes = new ShapeRenderer(this.context);
+			this.skeletonRenderer = new SkeletonRenderer(this.context, twoColorTint);
+			this.skeletonDebugRenderer = new SkeletonDebugRenderer(this.context);
+		}
+
+		begin () {
+			this.camera.update();
+			this.enableRenderer(this.batcher);
+		}
+
+		drawSkeleton (skeleton: Skeleton, premultipliedAlpha = false, slotRangeStart = -1, slotRangeEnd = -1) {
+			this.enableRenderer(this.batcher);
+			this.skeletonRenderer.premultipliedAlpha = premultipliedAlpha;
+			this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd);
+		}
+
+		drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones: Array<string> = null) {
+			this.enableRenderer(this.shapes);
+			this.skeletonDebugRenderer.premultipliedAlpha = premultipliedAlpha;
+			this.skeletonDebugRenderer.draw(this.shapes, skeleton, ignoredBones);
+		}
+
+		drawTexture (texture: GLTexture, x: number, y: number, width: number, height: number, color: Color = null) {
+			this.enableRenderer(this.batcher);
+			if (color === null) color = WHITE;
+			var i = 0;
+			quad[i++] = x;
+			quad[i++] = y;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = 0;
+			quad[i++] = 1;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x + width;
+			quad[i++] = y;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = 1;
+			quad[i++] = 1;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x + width;
+			quad[i++] = y + height;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = 1;
+			quad[i++] = 0;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x;
+			quad[i++] = y + height;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = 0;
+			quad[i++] = 0;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i] = 0;
+			}
+			this.batcher.draw(texture, quad, QUAD_TRIANGLES);
+		}
+
+		drawTextureUV (texture: GLTexture, x: number, y: number, width: number, height: number, u: number, v: number, u2: number, v2: number, color: Color = null) {
+			this.enableRenderer(this.batcher);
+			if (color === null) color = WHITE;
+			var i = 0;
+			quad[i++] = x;
+			quad[i++] = y;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = u;
+			quad[i++] = v;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x + width;
+			quad[i++] = y;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = u2;
+			quad[i++] = v;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x + width;
+			quad[i++] = y + height;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = u2;
+			quad[i++] = v2;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x;
+			quad[i++] = y + height;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = u;
+			quad[i++] = v2;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i] = 0;
+			}
+			this.batcher.draw(texture, quad, QUAD_TRIANGLES);
+		}
+
+		drawTextureRotated (texture: GLTexture, x: number, y: number, width: number, height: number, pivotX: number, pivotY: number, angle: number, color: Color = null) {
+			this.enableRenderer(this.batcher);
+			if (color === null) color = WHITE;
+
+			// bottom left and top right corner points relative to origin
+			let worldOriginX = x + pivotX;
+			let worldOriginY = y + pivotY;
+			let fx = -pivotX;
+			let fy = -pivotY;
+			let fx2 = width - pivotX;
+			let fy2 = height - pivotY;
+
+			// construct corner points, start from top left and go counter clockwise
+			let p1x = fx;
+			let p1y = fy;
+			let p2x = fx;
+			let p2y = fy2;
+			let p3x = fx2;
+			let p3y = fy2;
+			let p4x = fx2;
+			let p4y = fy;
+
+			let x1 = 0;
+			let y1 = 0;
+			let x2 = 0;
+			let y2 = 0;
+			let x3 = 0;
+			let y3 = 0;
+			let x4 = 0;
+			let y4 = 0;
+
+			// rotate
+			if (angle != 0) {
+				let cos = MathUtils.cosDeg(angle);
+				let sin = MathUtils.sinDeg(angle);
+
+				x1 = cos * p1x - sin * p1y;
+				y1 = sin * p1x + cos * p1y;
+
+				x4 = cos * p2x - sin * p2y;
+				y4 = sin * p2x + cos * p2y;
+
+				x3 = cos * p3x - sin * p3y;
+				y3 = sin * p3x + cos * p3y;
+
+				x2 = x3 + (x1 - x4);
+				y2 = y3 + (y1 - y4);
+			} else {
+				x1 = p1x;
+				y1 = p1y;
+
+				x4 = p2x;
+				y4 = p2y;
+
+				x3 = p3x;
+				y3 = p3y;
+
+				x2 = p4x;
+				y2 = p4y;
+			}
+
+			x1 += worldOriginX;
+			y1 += worldOriginY;
+			x2 += worldOriginX;
+			y2 += worldOriginY;
+			x3 += worldOriginX;
+			y3 += worldOriginY;
+			x4 += worldOriginX;
+			y4 += worldOriginY;
+
+			var i = 0;
+			quad[i++] = x1;
+			quad[i++] = y1;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = 0;
+			quad[i++] = 1;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x2;
+			quad[i++] = y2;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = 1;
+			quad[i++] = 1;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x3;
+			quad[i++] = y3;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = 1;
+			quad[i++] = 0;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x4;
+			quad[i++] = y4;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = 0;
+			quad[i++] = 0;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i] = 0;
+			}
+			this.batcher.draw(texture, quad, QUAD_TRIANGLES);
+		}
+
+		drawRegion (region: TextureAtlasRegion, x: number, y: number, width: number, height: number, color: Color = null) {
+			this.enableRenderer(this.batcher);
+			if (color === null) color = WHITE;
+			var i = 0;
+			quad[i++] = x;
+			quad[i++] = y;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = region.u;
+			quad[i++] = region.v2;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x + width;
+			quad[i++] = y;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = region.u2;
+			quad[i++] = region.v2;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x + width;
+			quad[i++] = y + height;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = region.u2;
+			quad[i++] = region.v;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+			}
+			quad[i++] = x;
+			quad[i++] = y + height;
+			quad[i++] = color.r;
+			quad[i++] = color.g;
+			quad[i++] = color.b;
+			quad[i++] = color.a;
+			quad[i++] = region.u;
+			quad[i++] = region.v;
+			if (this.twoColorTint) {
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i++] = 0;
+				quad[i] = 0;
+			}
+			this.batcher.draw(<GLTexture>region.page.texture, quad, QUAD_TRIANGLES);
+		}
+
+		line (x: number, y: number, x2: number, y2: number, color: Color = null, color2: Color = null) {
+			this.enableRenderer(this.shapes);
+			this.shapes.line(x, y, x2, y2, color);
+		}
+
+		triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color: Color = null, color2: Color = null, color3: Color = null) {
+			this.enableRenderer(this.shapes);
+			this.shapes.triangle(filled, x, y, x2, y2, x3, y3, color, color2, color3);
+		}
+
+		quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: Color = null, color2: Color = null, color3: Color = null, color4: Color = null) {
+			this.enableRenderer(this.shapes);
+			this.shapes.quad(filled, x, y, x2, y2, x3, y3, x4, y4, color, color2, color3, color4);
+		}
+
+		rect (filled: boolean, x: number, y: number, width: number, height: number, color: Color = null) {
+			this.enableRenderer(this.shapes);
+			this.shapes.rect(filled, x, y, width, height, color);
+		}
+
+		rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color: Color = null) {
+			this.enableRenderer(this.shapes);
+			this.shapes.rectLine(filled, x1, y1, x2, y2, width, color);
+		}
+
+		polygon (polygonVertices: ArrayLike<number>, offset: number, count: number, color: Color = null) {
+			this.enableRenderer(this.shapes);
+			this.shapes.polygon(polygonVertices, offset, count, color);
+		}
+
+		circle (filled: boolean, x: number, y: number, radius: number, color: Color = null, segments: number = 0) {
+			this.enableRenderer(this.shapes);
+			this.shapes.circle(filled, x, y, radius, color, segments);
+		}
+
+		curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color: Color = null) {
+			this.enableRenderer(this.shapes);
+			this.shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments, color);
+		}
+
+		end () {
+			if (this.activeRenderer === this.batcher) this.batcher.end();
+			else if (this.activeRenderer === this.shapes) this.shapes.end();
+			this.activeRenderer = null;
+		}
+
+		resize (resizeMode: ResizeMode) {
+			let canvas = this.canvas;
+			var dpr = window.devicePixelRatio || 1;
+			var w = Math.round(canvas.clientWidth * dpr);
+			var h = Math.round(canvas.clientHeight * dpr);
+
+			if (canvas.width != w || canvas.height != h) {
+				canvas.width = w;
+				canvas.height = h;
+			}
+			this.context.gl.viewport(0, 0, canvas.width, canvas.height);
+
+			// Nothing to do for stretch, we simply apply the viewport size of the camera.
+			if (resizeMode === ResizeMode.Expand)
+				this.camera.setViewport(w, h);
+			else if (resizeMode === ResizeMode.Fit) {
+				let sourceWidth = canvas.width, sourceHeight = canvas.height;
+				let targetWidth = this.camera.viewportWidth, targetHeight = this.camera.viewportHeight;
+				let targetRatio = targetHeight / targetWidth;
+				let sourceRatio = sourceHeight / sourceWidth;
+				let scale = targetRatio < sourceRatio ? targetWidth / sourceWidth : targetHeight / sourceHeight;
+				this.camera.setViewport(sourceWidth * scale, sourceHeight * scale);
+			}
+			this.camera.update();
+		}
+
+		private enableRenderer (renderer: PolygonBatcher | ShapeRenderer | SkeletonDebugRenderer) {
+			if (this.activeRenderer === renderer) return;
+			this.end();
+			if (renderer instanceof PolygonBatcher) {
+				this.batcherShader.bind();
+				this.batcherShader.setUniform4x4f(Shader.MVP_MATRIX, this.camera.projectionView.values);
+				this.batcherShader.setUniformi("u_texture", 0);
+				this.batcher.begin(this.batcherShader);
+				this.activeRenderer = this.batcher;
+			} else if (renderer instanceof ShapeRenderer) {
+				this.shapesShader.bind();
+				this.shapesShader.setUniform4x4f(Shader.MVP_MATRIX, this.camera.projectionView.values);
+				this.shapes.begin(this.shapesShader);
+				this.activeRenderer = this.shapes;
+			} else
+				this.activeRenderer = this.skeletonDebugRenderer;
+		}
+
+		dispose () {
+			this.batcher.dispose();
+			this.batcherShader.dispose();
+			this.shapes.dispose();
+			this.shapesShader.dispose();
+			this.skeletonDebugRenderer.dispose();
+		}
+	}
+
+	export enum ResizeMode {
+		Stretch,
+		Expand,
+		Fit
+	}
+}

+ 293 - 293
spine-ts/webgl/src/Shader.ts

@@ -1,293 +1,293 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class Shader implements Disposable, Restorable {
-		public static MVP_MATRIX = "u_projTrans";
-		public static POSITION = "a_position";
-		public static COLOR = "a_color";
-		public static COLOR2 = "a_color2";
-		public static TEXCOORDS = "a_texCoords";
-		public static SAMPLER = "u_texture";
-
-		private context: ManagedWebGLRenderingContext;
-		private vs: WebGLShader = null;
-		private vsSource: string;
-		private fs: WebGLShader = null;
-		private fsSource: string;
-		private program: WebGLProgram = null;
-		private tmp2x2: Float32Array = new Float32Array(2 * 2);
-		private tmp3x3: Float32Array = new Float32Array(3 * 3);
-		private tmp4x4: Float32Array = new Float32Array(4 * 4);
-
-		public getProgram () { return this.program; }
-		public getVertexShader () { return this.vertexShader; }
-		public getFragmentShader () { return this.fragmentShader; }
-		public getVertexShaderSource () { return this.vsSource; }
-		public getFragmentSource () { return this.fsSource; }
-
-		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, private vertexShader: string, private fragmentShader: string) {
-			this.vsSource = vertexShader;
-			this.fsSource = fragmentShader;
-			this.context = context instanceof ManagedWebGLRenderingContext? context : new ManagedWebGLRenderingContext(context);
-			this.context.addRestorable(this);
-			this.compile();
-		}
-
-		private compile () {
-			let gl = this.context.gl;
-			try {
-				this.vs = this.compileShader(gl.VERTEX_SHADER, this.vertexShader);
-				this.fs = this.compileShader(gl.FRAGMENT_SHADER, this.fragmentShader);
-				this.program = this.compileProgram(this.vs, this.fs);
-			} catch (e) {
-				this.dispose();
-				throw e;
-			}
-		}
-
-		private compileShader (type: number, source: string) {
-			let gl = this.context.gl;
-			let shader = gl.createShader(type);
-			gl.shaderSource(shader, source);
-			gl.compileShader(shader);
-			if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
-				let error = "Couldn't compile shader: " + gl.getShaderInfoLog(shader);
-				gl.deleteShader(shader);
-				if (!gl.isContextLost()) throw new Error(error);
-			}
-			return shader;
-		}
-
-		private compileProgram (vs: WebGLShader, fs: WebGLShader) {
-			let gl = this.context.gl;
-			let program = gl.createProgram();
-			gl.attachShader(program, vs);
-			gl.attachShader(program, fs);
-			gl.linkProgram(program);
-
-			if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
-				let error = "Couldn't compile shader program: " + gl.getProgramInfoLog(program);
-				gl.deleteProgram(program);
-				if (!gl.isContextLost()) throw new Error(error);
-			}
-			return program;
-		}
-
-		restore () {
-			this.compile();
-		}
-
-		public bind () {
-			this.context.gl.useProgram(this.program);
-		}
-
-		public unbind () {
-			this.context.gl.useProgram(null);
-		}
-
-		public setUniformi (uniform: string, value: number) {
-			this.context.gl.uniform1i(this.getUniformLocation(uniform), value);
-		}
-
-		public setUniformf (uniform: string, value: number) {
-			this.context.gl.uniform1f(this.getUniformLocation(uniform), value);
-		}
-
-		public setUniform2f (uniform: string, value: number, value2: number) {
-			this.context.gl.uniform2f(this.getUniformLocation(uniform), value, value2);
-		}
-
-		public setUniform3f (uniform: string, value: number, value2: number, value3: number) {
-			this.context.gl.uniform3f(this.getUniformLocation(uniform), value, value2, value3);
-		}
-
-		public setUniform4f (uniform: string, value: number, value2: number, value3: number, value4: number) {
-			this.context.gl.uniform4f(this.getUniformLocation(uniform), value, value2, value3, value4);
-		}
-
-		public setUniform2x2f (uniform: string, value: ArrayLike<number>) {
-			let gl = this.context.gl;
-			this.tmp2x2.set(value);
-			gl.uniformMatrix2fv(this.getUniformLocation(uniform), false, this.tmp2x2);
-		}
-
-		public setUniform3x3f (uniform: string, value: ArrayLike<number>) {
-			let gl = this.context.gl;
-			this.tmp3x3.set(value);
-			gl.uniformMatrix3fv(this.getUniformLocation(uniform), false, this.tmp3x3);
-		}
-
-		public setUniform4x4f (uniform: string, value: ArrayLike<number>) {
-			let gl = this.context.gl;
-			this.tmp4x4.set(value);
-			gl.uniformMatrix4fv(this.getUniformLocation(uniform), false, this.tmp4x4);
-		}
-
-		public getUniformLocation (uniform: string): WebGLUniformLocation {
-			let gl = this.context.gl;
-			let location = gl.getUniformLocation(this.program, uniform);
-			if (!location && !gl.isContextLost()) throw new Error(`Couldn't find location for uniform ${uniform}`);
-			return location;
-		}
-
-		public getAttributeLocation (attribute: string): number {
-			let gl = this.context.gl;
-			let location = gl.getAttribLocation(this.program, attribute);
-			if (location == -1 && !gl.isContextLost()) throw new Error(`Couldn't find location for attribute ${attribute}`);
-			return location;
-		}
-
-		public dispose () {
-			this.context.removeRestorable(this);
-
-			let gl = this.context.gl;
-			if (this.vs) {
-				gl.deleteShader(this.vs);
-				this.vs = null;
-			}
-
-			if (this.fs) {
-				gl.deleteShader(this.fs);
-				this.fs = null;
-			}
-
-			if (this.program) {
-				gl.deleteProgram(this.program);
-				this.program = null;
-			}
-		}
-
-		public static newColoredTextured (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader {
-			let vs = `
-				attribute vec4 ${Shader.POSITION};
-				attribute vec4 ${Shader.COLOR};
-				attribute vec2 ${Shader.TEXCOORDS};
-				uniform mat4 ${Shader.MVP_MATRIX};
-				varying vec4 v_color;
-				varying vec2 v_texCoords;
-
-				void main () {
-					v_color = ${Shader.COLOR};
-					v_texCoords = ${Shader.TEXCOORDS};
-					gl_Position = ${Shader.MVP_MATRIX} * ${Shader.POSITION};
-				}
-			`;
-
-			let fs = `
-				#ifdef GL_ES
-					#define LOWP lowp
-					precision mediump float;
-				#else
-					#define LOWP
-				#endif
-				varying LOWP vec4 v_color;
-				varying vec2 v_texCoords;
-				uniform sampler2D u_texture;
-
-				void main () {
-					gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
-				}
-			`;
-
-			return new Shader(context, vs, fs);
-		}
-
-		public static newTwoColoredTextured (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader {
-			let vs = `
-				attribute vec4 ${Shader.POSITION};
-				attribute vec4 ${Shader.COLOR};
-				attribute vec4 ${Shader.COLOR2};
-				attribute vec2 ${Shader.TEXCOORDS};
-				uniform mat4 ${Shader.MVP_MATRIX};
-				varying vec4 v_light;
-				varying vec4 v_dark;
-				varying vec2 v_texCoords;
-
-				void main () {
-					v_light = ${Shader.COLOR};
-					v_dark = ${Shader.COLOR2};
-					v_texCoords = ${Shader.TEXCOORDS};
-					gl_Position = ${Shader.MVP_MATRIX} * ${Shader.POSITION};
-				}
-			`;
-
-			let fs = `
-				#ifdef GL_ES
-					#define LOWP lowp
-					precision mediump float;
-				#else
-					#define LOWP
-				#endif
-				varying LOWP vec4 v_light;
-				varying LOWP vec4 v_dark;
-				varying vec2 v_texCoords;
-				uniform sampler2D u_texture;
-
-				void main () {
-					vec4 texColor = texture2D(u_texture, v_texCoords);
-					gl_FragColor.a = texColor.a * v_light.a;
-					gl_FragColor.rgb = ((texColor.a - 1.0) * v_dark.a + 1.0 - texColor.rgb) * v_dark.rgb + texColor.rgb * v_light.rgb;
-				}
-			`;
-
-			return new Shader(context, vs, fs);
-		}
-
-		public static newColored (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader {
-			let vs = `
-				attribute vec4 ${Shader.POSITION};
-				attribute vec4 ${Shader.COLOR};
-				uniform mat4 ${Shader.MVP_MATRIX};
-				varying vec4 v_color;
-
-				void main () {
-					v_color = ${Shader.COLOR};
-					gl_Position = ${Shader.MVP_MATRIX} * ${Shader.POSITION};
-				}
-			`;
-
-			let fs = `
-				#ifdef GL_ES
-					#define LOWP lowp
-					precision mediump float;
-				#else
-					#define LOWP
-				#endif
-				varying LOWP vec4 v_color;
-
-				void main () {
-					gl_FragColor = v_color;
-				}
-			`;
-
-			return new Shader(context, vs, fs);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class Shader implements Disposable, Restorable {
+		public static MVP_MATRIX = "u_projTrans";
+		public static POSITION = "a_position";
+		public static COLOR = "a_color";
+		public static COLOR2 = "a_color2";
+		public static TEXCOORDS = "a_texCoords";
+		public static SAMPLER = "u_texture";
+
+		private context: ManagedWebGLRenderingContext;
+		private vs: WebGLShader = null;
+		private vsSource: string;
+		private fs: WebGLShader = null;
+		private fsSource: string;
+		private program: WebGLProgram = null;
+		private tmp2x2: Float32Array = new Float32Array(2 * 2);
+		private tmp3x3: Float32Array = new Float32Array(3 * 3);
+		private tmp4x4: Float32Array = new Float32Array(4 * 4);
+
+		public getProgram () { return this.program; }
+		public getVertexShader () { return this.vertexShader; }
+		public getFragmentShader () { return this.fragmentShader; }
+		public getVertexShaderSource () { return this.vsSource; }
+		public getFragmentSource () { return this.fsSource; }
+
+		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, private vertexShader: string, private fragmentShader: string) {
+			this.vsSource = vertexShader;
+			this.fsSource = fragmentShader;
+			this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
+			this.context.addRestorable(this);
+			this.compile();
+		}
+
+		private compile () {
+			let gl = this.context.gl;
+			try {
+				this.vs = this.compileShader(gl.VERTEX_SHADER, this.vertexShader);
+				this.fs = this.compileShader(gl.FRAGMENT_SHADER, this.fragmentShader);
+				this.program = this.compileProgram(this.vs, this.fs);
+			} catch (e) {
+				this.dispose();
+				throw e;
+			}
+		}
+
+		private compileShader (type: number, source: string) {
+			let gl = this.context.gl;
+			let shader = gl.createShader(type);
+			gl.shaderSource(shader, source);
+			gl.compileShader(shader);
+			if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+				let error = "Couldn't compile shader: " + gl.getShaderInfoLog(shader);
+				gl.deleteShader(shader);
+				if (!gl.isContextLost()) throw new Error(error);
+			}
+			return shader;
+		}
+
+		private compileProgram (vs: WebGLShader, fs: WebGLShader) {
+			let gl = this.context.gl;
+			let program = gl.createProgram();
+			gl.attachShader(program, vs);
+			gl.attachShader(program, fs);
+			gl.linkProgram(program);
+
+			if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
+				let error = "Couldn't compile shader program: " + gl.getProgramInfoLog(program);
+				gl.deleteProgram(program);
+				if (!gl.isContextLost()) throw new Error(error);
+			}
+			return program;
+		}
+
+		restore () {
+			this.compile();
+		}
+
+		public bind () {
+			this.context.gl.useProgram(this.program);
+		}
+
+		public unbind () {
+			this.context.gl.useProgram(null);
+		}
+
+		public setUniformi (uniform: string, value: number) {
+			this.context.gl.uniform1i(this.getUniformLocation(uniform), value);
+		}
+
+		public setUniformf (uniform: string, value: number) {
+			this.context.gl.uniform1f(this.getUniformLocation(uniform), value);
+		}
+
+		public setUniform2f (uniform: string, value: number, value2: number) {
+			this.context.gl.uniform2f(this.getUniformLocation(uniform), value, value2);
+		}
+
+		public setUniform3f (uniform: string, value: number, value2: number, value3: number) {
+			this.context.gl.uniform3f(this.getUniformLocation(uniform), value, value2, value3);
+		}
+
+		public setUniform4f (uniform: string, value: number, value2: number, value3: number, value4: number) {
+			this.context.gl.uniform4f(this.getUniformLocation(uniform), value, value2, value3, value4);
+		}
+
+		public setUniform2x2f (uniform: string, value: ArrayLike<number>) {
+			let gl = this.context.gl;
+			this.tmp2x2.set(value);
+			gl.uniformMatrix2fv(this.getUniformLocation(uniform), false, this.tmp2x2);
+		}
+
+		public setUniform3x3f (uniform: string, value: ArrayLike<number>) {
+			let gl = this.context.gl;
+			this.tmp3x3.set(value);
+			gl.uniformMatrix3fv(this.getUniformLocation(uniform), false, this.tmp3x3);
+		}
+
+		public setUniform4x4f (uniform: string, value: ArrayLike<number>) {
+			let gl = this.context.gl;
+			this.tmp4x4.set(value);
+			gl.uniformMatrix4fv(this.getUniformLocation(uniform), false, this.tmp4x4);
+		}
+
+		public getUniformLocation (uniform: string): WebGLUniformLocation {
+			let gl = this.context.gl;
+			let location = gl.getUniformLocation(this.program, uniform);
+			if (!location && !gl.isContextLost()) throw new Error(`Couldn't find location for uniform ${uniform}`);
+			return location;
+		}
+
+		public getAttributeLocation (attribute: string): number {
+			let gl = this.context.gl;
+			let location = gl.getAttribLocation(this.program, attribute);
+			if (location == -1 && !gl.isContextLost()) throw new Error(`Couldn't find location for attribute ${attribute}`);
+			return location;
+		}
+
+		public dispose () {
+			this.context.removeRestorable(this);
+
+			let gl = this.context.gl;
+			if (this.vs) {
+				gl.deleteShader(this.vs);
+				this.vs = null;
+			}
+
+			if (this.fs) {
+				gl.deleteShader(this.fs);
+				this.fs = null;
+			}
+
+			if (this.program) {
+				gl.deleteProgram(this.program);
+				this.program = null;
+			}
+		}
+
+		public static newColoredTextured (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader {
+			let vs = `
+				attribute vec4 ${Shader.POSITION};
+				attribute vec4 ${Shader.COLOR};
+				attribute vec2 ${Shader.TEXCOORDS};
+				uniform mat4 ${Shader.MVP_MATRIX};
+				varying vec4 v_color;
+				varying vec2 v_texCoords;
+
+				void main () {
+					v_color = ${Shader.COLOR};
+					v_texCoords = ${Shader.TEXCOORDS};
+					gl_Position = ${Shader.MVP_MATRIX} * ${Shader.POSITION};
+				}
+			`;
+
+			let fs = `
+				#ifdef GL_ES
+					#define LOWP lowp
+					precision mediump float;
+				#else
+					#define LOWP
+				#endif
+				varying LOWP vec4 v_color;
+				varying vec2 v_texCoords;
+				uniform sampler2D u_texture;
+
+				void main () {
+					gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
+				}
+			`;
+
+			return new Shader(context, vs, fs);
+		}
+
+		public static newTwoColoredTextured (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader {
+			let vs = `
+				attribute vec4 ${Shader.POSITION};
+				attribute vec4 ${Shader.COLOR};
+				attribute vec4 ${Shader.COLOR2};
+				attribute vec2 ${Shader.TEXCOORDS};
+				uniform mat4 ${Shader.MVP_MATRIX};
+				varying vec4 v_light;
+				varying vec4 v_dark;
+				varying vec2 v_texCoords;
+
+				void main () {
+					v_light = ${Shader.COLOR};
+					v_dark = ${Shader.COLOR2};
+					v_texCoords = ${Shader.TEXCOORDS};
+					gl_Position = ${Shader.MVP_MATRIX} * ${Shader.POSITION};
+				}
+			`;
+
+			let fs = `
+				#ifdef GL_ES
+					#define LOWP lowp
+					precision mediump float;
+				#else
+					#define LOWP
+				#endif
+				varying LOWP vec4 v_light;
+				varying LOWP vec4 v_dark;
+				varying vec2 v_texCoords;
+				uniform sampler2D u_texture;
+
+				void main () {
+					vec4 texColor = texture2D(u_texture, v_texCoords);
+					gl_FragColor.a = texColor.a * v_light.a;
+					gl_FragColor.rgb = ((texColor.a - 1.0) * v_dark.a + 1.0 - texColor.rgb) * v_dark.rgb + texColor.rgb * v_light.rgb;
+				}
+			`;
+
+			return new Shader(context, vs, fs);
+		}
+
+		public static newColored (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader {
+			let vs = `
+				attribute vec4 ${Shader.POSITION};
+				attribute vec4 ${Shader.COLOR};
+				uniform mat4 ${Shader.MVP_MATRIX};
+				varying vec4 v_color;
+
+				void main () {
+					v_color = ${Shader.COLOR};
+					gl_Position = ${Shader.MVP_MATRIX} * ${Shader.POSITION};
+				}
+			`;
+
+			let fs = `
+				#ifdef GL_ES
+					#define LOWP lowp
+					precision mediump float;
+				#else
+					#define LOWP
+				#endif
+				varying LOWP vec4 v_color;
+
+				void main () {
+					gl_FragColor = v_color;
+				}
+			`;
+
+			return new Shader(context, vs, fs);
+		}
+	}
+}

+ 349 - 349
spine-ts/webgl/src/ShapeRenderer.ts

@@ -1,349 +1,349 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class ShapeRenderer implements Disposable {
-		private context: ManagedWebGLRenderingContext;
-		private isDrawing = false;
-		private mesh: Mesh;
-		private shapeType = ShapeType.Filled;
-		private color = new Color(1, 1, 1, 1);
-		private shader: Shader;
-		private vertexIndex = 0;
-		private tmp = new Vector2();
-		private srcColorBlend: number;
-		private srcAlphaBlend: number;
-		private dstBlend: number;
-
-		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, maxVertices: number = 10920) {
-			if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
-			this.context = context instanceof ManagedWebGLRenderingContext? context : new ManagedWebGLRenderingContext(context);
-			this.mesh = new Mesh(context, [new Position2Attribute(), new ColorAttribute()], maxVertices, 0);
-			let gl = this.context.gl;
-			this.srcColorBlend = gl.SRC_ALPHA;
-			this.srcAlphaBlend = gl.ONE;
-			this.dstBlend = gl.ONE_MINUS_SRC_ALPHA;
-		}
-
-		begin (shader: Shader) {
-			if (this.isDrawing) throw new Error("ShapeRenderer.begin() has already been called");
-			this.shader = shader;
-			this.vertexIndex = 0;
-			this.isDrawing = true;
-
-			let gl = this.context.gl;
-			gl.enable(gl.BLEND);
-			gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend);
-		}
-
-		setBlendMode (srcColorBlend: number, srcAlphaBlend: number, dstBlend: number) {
-			this.srcColorBlend = srcColorBlend;
-			this.srcAlphaBlend = srcAlphaBlend;
-			this.dstBlend = dstBlend;
-			if (this.isDrawing) {
-				this.flush();
-				let gl = this.context.gl;
-				gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend);
-			}
-		}
-
-		setColor (color: Color) {
-			this.color.setFromColor(color);
-		}
-
-		setColorWith (r: number, g: number, b: number, a: number) {
-			this.color.set(r, g, b, a);
-		}
-
-		point (x: number, y: number, color: Color = null) {
-			this.check(ShapeType.Point, 1);
-			if (color === null) color = this.color;
-			this.vertex(x, y, color);
-		}
-
-		line (x: number, y: number, x2: number, y2: number, color: Color = null) {
-			this.check(ShapeType.Line, 2);
-			let vertices = this.mesh.getVertices();
-			let idx = this.vertexIndex;
-			if (color === null) color = this.color;
-			this.vertex(x, y, color);
-			this.vertex(x2, y2, color);
-		}
-
-		triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color: Color = null, color2: Color = null, color3: Color = null) {
-			this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
-			let vertices = this.mesh.getVertices();
-			let idx = this.vertexIndex;
-			if (color === null) color = this.color;
-			if (color2 === null) color2 = this.color;
-			if (color3 === null) color3 = this.color;
-			if (filled) {
-				this.vertex(x, y, color);
-				this.vertex(x2, y2, color2);
-				this.vertex(x3, y3, color3);
-			} else {
-				this.vertex(x, y, color);
-				this.vertex(x2, y2, color2);
-
-				this.vertex(x2, y2, color);
-				this.vertex(x3, y3, color2);
-
-				this.vertex(x3, y3, color);
-				this.vertex(x, y, color2);
-			}
-		}
-
-		quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: Color = null, color2: Color = null, color3: Color = null, color4: Color = null) {
-			this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
-			let vertices = this.mesh.getVertices();
-			let idx = this.vertexIndex;
-			if (color === null) color = this.color;
-			if (color2 === null) color2 = this.color;
-			if (color3 === null) color3 = this.color;
-			if (color4 === null) color4 = this.color;
-			if (filled) {
-				this.vertex(x, y, color); this.vertex(x2, y2, color2); this.vertex(x3, y3, color3);
-				this.vertex(x3, y3, color3); this.vertex(x4, y4, color4); this.vertex(x, y, color);
-			} else {
-				this.vertex(x, y, color); this.vertex(x2, y2, color2);
-				this.vertex(x2, y2, color2); this.vertex(x3, y3, color3);
-				this.vertex(x3, y3, color3); this.vertex(x4, y4, color4);
-				this.vertex(x4, y4, color4); this.vertex(x, y, color);
-			}
-		}
-
-		rect (filled: boolean, x: number, y: number, width: number, height: number, color: Color = null) {
-			this.quad(filled, x, y, x + width, y, x + width, y + height, x, y + height, color, color, color, color);
-		}
-
-		rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color: Color = null) {
-			this.check(filled ? ShapeType.Filled : ShapeType.Line, 8);
-			if (color === null) color = this.color;
-			let t = this.tmp.set(y2 - y1, x1 - x2);
-			t.normalize();
-			width *= 0.5;
-			let tx = t.x * width;
-			let ty = t.y * width;
-			if (!filled) {
-				this.vertex(x1 + tx, y1 + ty, color);
-				this.vertex(x1 - tx, y1 - ty, color);
-				this.vertex(x2 + tx, y2 + ty, color);
-				this.vertex(x2 - tx, y2 - ty, color);
-
-				this.vertex(x2 + tx, y2 + ty, color);
-				this.vertex(x1 + tx, y1 + ty, color);
-
-				this.vertex(x2 - tx, y2 - ty, color);
-				this.vertex(x1 - tx, y1 - ty, color);
-			} else {
-				this.vertex(x1 + tx, y1 + ty, color);
-				this.vertex(x1 - tx, y1 - ty, color);
-				this.vertex(x2 + tx, y2 + ty, color);
-
-				this.vertex(x2 - tx, y2 - ty, color);
-				this.vertex(x2 + tx, y2 + ty, color);
-				this.vertex(x1 - tx, y1 - ty, color);
-			}
-		}
-
-		x (x: number, y: number, size: number) {
-			this.line(x - size, y - size, x + size, y + size);
-			this.line(x - size, y + size, x + size, y - size);
-		}
-
-		polygon (polygonVertices: ArrayLike<number>, offset: number, count: number, color: Color = null) {
-			if (count < 3) throw new Error("Polygon must contain at least 3 vertices");
-			this.check(ShapeType.Line, count * 2);
-			if (color === null) color = this.color;
-			let vertices = this.mesh.getVertices();
-			let idx = this.vertexIndex;
-
-			offset <<= 1;
-			count <<= 1;
-
-			let firstX = polygonVertices[offset];
-			let firstY = polygonVertices[offset + 1];
-			let last = offset + count;
-
-			for (let i = offset, n = offset + count - 2; i < n; i += 2) {
-				let x1 = polygonVertices[i];
-				let y1 = polygonVertices[i+1];
-
-				let x2 = 0;
-				let y2 = 0;
-
-				if (i + 2 >= last) {
-					x2 = firstX;
-					y2 = firstY;
-				} else {
-					x2 = polygonVertices[i + 2];
-					y2 = polygonVertices[i + 3];
-				}
-
-				this.vertex(x1, y1, color);
-				this.vertex(x2, y2, color);
-			}
-		}
-
-		circle (filled: boolean, x: number, y: number, radius: number, color: Color = null, segments: number = 0) {
-			if (segments === 0) segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0);
-			if (segments <= 0) throw new Error("segments must be > 0.");
-			if (color === null) color = this.color;
-			let angle = 2 * MathUtils.PI / segments;
-			let cos = Math.cos(angle);
-			let sin = Math.sin(angle);
-			let cx = radius, cy = 0;
-			if (!filled) {
-				this.check(ShapeType.Line, segments * 2 + 2);
-				for (let i = 0; i < segments; i++) {
-					this.vertex(x + cx, y + cy, color);
-					let temp = cx;
-					cx = cos * cx - sin * cy;
-					cy = sin * temp + cos * cy;
-					this.vertex(x + cx, y + cy, color);
-				}
-				// Ensure the last segment is identical to the first.
-				this.vertex(x + cx, y + cy, color);
-			} else {
-				this.check(ShapeType.Filled, segments * 3 + 3);
-				segments--;
-				for (let i = 0; i < segments; i++) {
-					this.vertex(x, y, color);
-					this.vertex(x + cx, y + cy, color);
-					let temp = cx;
-					cx = cos * cx - sin * cy;
-					cy = sin * temp + cos * cy;
-					this.vertex(x + cx, y + cy, color);
-				}
-				// Ensure the last segment is identical to the first.
-				this.vertex(x, y, color);
-				this.vertex(x + cx, y + cy, color);
-			}
-
-			let temp = cx;
-			cx = radius;
-			cy = 0;
-			this.vertex(x + cx, y + cy, color);
-		}
-
-		curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color: Color = null) {
-			this.check(ShapeType.Line, segments * 2 + 2);
-			if (color === null) color = this.color;
-
-			// Algorithm from: http://www.antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION
-			let subdiv_step = 1 / segments;
-			let subdiv_step2 = subdiv_step * subdiv_step;
-			let subdiv_step3 = subdiv_step * subdiv_step * subdiv_step;
-
-			let pre1 = 3 * subdiv_step;
-			let pre2 = 3 * subdiv_step2;
-			let pre4 = 6 * subdiv_step2;
-			let pre5 = 6 * subdiv_step3;
-
-			let tmp1x = x1 - cx1 * 2 + cx2;
-			let tmp1y = y1 - cy1 * 2 + cy2;
-
-			let tmp2x = (cx1 - cx2) * 3 - x1 + x2;
-			let tmp2y = (cy1 - cy2) * 3 - y1 + y2;
-
-			let fx = x1;
-			let fy = y1;
-
-			let dfx = (cx1 - x1) * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
-			let dfy = (cy1 - y1) * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
-
-			let ddfx = tmp1x * pre4 + tmp2x * pre5;
-			let ddfy = tmp1y * pre4 + tmp2y * pre5;
-
-			let dddfx = tmp2x * pre5;
-			let dddfy = tmp2y * pre5;
-
-			while (segments-- > 0) {
-				this.vertex(fx, fy, color);
-				fx += dfx;
-				fy += dfy;
-				dfx += ddfx;
-				dfy += ddfy;
-				ddfx += dddfx;
-				ddfy += dddfy;
-				this.vertex(fx, fy, color);
-			}
-			this.vertex(fx, fy, color);
-			this.vertex(x2, y2, color);
-		}
-
-		private vertex (x: number, y: number, color: Color) {
-			let idx = this.vertexIndex;
-			let vertices = this.mesh.getVertices();
-			vertices[idx++] = x;
-			vertices[idx++] = y;
-			vertices[idx++] = color.r;
-			vertices[idx++] = color.g;
-			vertices[idx++] = color.b;
-			vertices[idx++] = color.a;
-			this.vertexIndex = idx;
-		}
-
-		end () {
-			if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called");
-			this.flush();
-			let gl = this.context.gl;
-			gl.disable(gl.BLEND);
-			this.isDrawing = false;
-		}
-
-		private flush () {
-			if (this.vertexIndex == 0) return;
-			this.mesh.setVerticesLength(this.vertexIndex);
-			this.mesh.draw(this.shader, this.shapeType);
-			this.vertexIndex = 0;
-		}
-
-		private check(shapeType: ShapeType, numVertices: number) {
-			if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called");
-			if (this.shapeType == shapeType) {
-				if (this.mesh.maxVertices() - this.mesh.numVertices() < numVertices) this.flush();
-				else return;
-			} else {
-				this.flush();
-				this.shapeType = shapeType;
-			}
-		}
-
-		dispose () {
-			this.mesh.dispose();
-		}
-	}
-
-	export enum ShapeType {
-		Point = 0x0000,
-		Line = 0x0001,
-		Filled = 0x0004
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class ShapeRenderer implements Disposable {
+		private context: ManagedWebGLRenderingContext;
+		private isDrawing = false;
+		private mesh: Mesh;
+		private shapeType = ShapeType.Filled;
+		private color = new Color(1, 1, 1, 1);
+		private shader: Shader;
+		private vertexIndex = 0;
+		private tmp = new Vector2();
+		private srcColorBlend: number;
+		private srcAlphaBlend: number;
+		private dstBlend: number;
+
+		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, maxVertices: number = 10920) {
+			if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
+			this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
+			this.mesh = new Mesh(context, [new Position2Attribute(), new ColorAttribute()], maxVertices, 0);
+			let gl = this.context.gl;
+			this.srcColorBlend = gl.SRC_ALPHA;
+			this.srcAlphaBlend = gl.ONE;
+			this.dstBlend = gl.ONE_MINUS_SRC_ALPHA;
+		}
+
+		begin (shader: Shader) {
+			if (this.isDrawing) throw new Error("ShapeRenderer.begin() has already been called");
+			this.shader = shader;
+			this.vertexIndex = 0;
+			this.isDrawing = true;
+
+			let gl = this.context.gl;
+			gl.enable(gl.BLEND);
+			gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend);
+		}
+
+		setBlendMode (srcColorBlend: number, srcAlphaBlend: number, dstBlend: number) {
+			this.srcColorBlend = srcColorBlend;
+			this.srcAlphaBlend = srcAlphaBlend;
+			this.dstBlend = dstBlend;
+			if (this.isDrawing) {
+				this.flush();
+				let gl = this.context.gl;
+				gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend);
+			}
+		}
+
+		setColor (color: Color) {
+			this.color.setFromColor(color);
+		}
+
+		setColorWith (r: number, g: number, b: number, a: number) {
+			this.color.set(r, g, b, a);
+		}
+
+		point (x: number, y: number, color: Color = null) {
+			this.check(ShapeType.Point, 1);
+			if (color === null) color = this.color;
+			this.vertex(x, y, color);
+		}
+
+		line (x: number, y: number, x2: number, y2: number, color: Color = null) {
+			this.check(ShapeType.Line, 2);
+			let vertices = this.mesh.getVertices();
+			let idx = this.vertexIndex;
+			if (color === null) color = this.color;
+			this.vertex(x, y, color);
+			this.vertex(x2, y2, color);
+		}
+
+		triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color: Color = null, color2: Color = null, color3: Color = null) {
+			this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
+			let vertices = this.mesh.getVertices();
+			let idx = this.vertexIndex;
+			if (color === null) color = this.color;
+			if (color2 === null) color2 = this.color;
+			if (color3 === null) color3 = this.color;
+			if (filled) {
+				this.vertex(x, y, color);
+				this.vertex(x2, y2, color2);
+				this.vertex(x3, y3, color3);
+			} else {
+				this.vertex(x, y, color);
+				this.vertex(x2, y2, color2);
+
+				this.vertex(x2, y2, color);
+				this.vertex(x3, y3, color2);
+
+				this.vertex(x3, y3, color);
+				this.vertex(x, y, color2);
+			}
+		}
+
+		quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: Color = null, color2: Color = null, color3: Color = null, color4: Color = null) {
+			this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
+			let vertices = this.mesh.getVertices();
+			let idx = this.vertexIndex;
+			if (color === null) color = this.color;
+			if (color2 === null) color2 = this.color;
+			if (color3 === null) color3 = this.color;
+			if (color4 === null) color4 = this.color;
+			if (filled) {
+				this.vertex(x, y, color); this.vertex(x2, y2, color2); this.vertex(x3, y3, color3);
+				this.vertex(x3, y3, color3); this.vertex(x4, y4, color4); this.vertex(x, y, color);
+			} else {
+				this.vertex(x, y, color); this.vertex(x2, y2, color2);
+				this.vertex(x2, y2, color2); this.vertex(x3, y3, color3);
+				this.vertex(x3, y3, color3); this.vertex(x4, y4, color4);
+				this.vertex(x4, y4, color4); this.vertex(x, y, color);
+			}
+		}
+
+		rect (filled: boolean, x: number, y: number, width: number, height: number, color: Color = null) {
+			this.quad(filled, x, y, x + width, y, x + width, y + height, x, y + height, color, color, color, color);
+		}
+
+		rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color: Color = null) {
+			this.check(filled ? ShapeType.Filled : ShapeType.Line, 8);
+			if (color === null) color = this.color;
+			let t = this.tmp.set(y2 - y1, x1 - x2);
+			t.normalize();
+			width *= 0.5;
+			let tx = t.x * width;
+			let ty = t.y * width;
+			if (!filled) {
+				this.vertex(x1 + tx, y1 + ty, color);
+				this.vertex(x1 - tx, y1 - ty, color);
+				this.vertex(x2 + tx, y2 + ty, color);
+				this.vertex(x2 - tx, y2 - ty, color);
+
+				this.vertex(x2 + tx, y2 + ty, color);
+				this.vertex(x1 + tx, y1 + ty, color);
+
+				this.vertex(x2 - tx, y2 - ty, color);
+				this.vertex(x1 - tx, y1 - ty, color);
+			} else {
+				this.vertex(x1 + tx, y1 + ty, color);
+				this.vertex(x1 - tx, y1 - ty, color);
+				this.vertex(x2 + tx, y2 + ty, color);
+
+				this.vertex(x2 - tx, y2 - ty, color);
+				this.vertex(x2 + tx, y2 + ty, color);
+				this.vertex(x1 - tx, y1 - ty, color);
+			}
+		}
+
+		x (x: number, y: number, size: number) {
+			this.line(x - size, y - size, x + size, y + size);
+			this.line(x - size, y + size, x + size, y - size);
+		}
+
+		polygon (polygonVertices: ArrayLike<number>, offset: number, count: number, color: Color = null) {
+			if (count < 3) throw new Error("Polygon must contain at least 3 vertices");
+			this.check(ShapeType.Line, count * 2);
+			if (color === null) color = this.color;
+			let vertices = this.mesh.getVertices();
+			let idx = this.vertexIndex;
+
+			offset <<= 1;
+			count <<= 1;
+
+			let firstX = polygonVertices[offset];
+			let firstY = polygonVertices[offset + 1];
+			let last = offset + count;
+
+			for (let i = offset, n = offset + count - 2; i < n; i += 2) {
+				let x1 = polygonVertices[i];
+				let y1 = polygonVertices[i + 1];
+
+				let x2 = 0;
+				let y2 = 0;
+
+				if (i + 2 >= last) {
+					x2 = firstX;
+					y2 = firstY;
+				} else {
+					x2 = polygonVertices[i + 2];
+					y2 = polygonVertices[i + 3];
+				}
+
+				this.vertex(x1, y1, color);
+				this.vertex(x2, y2, color);
+			}
+		}
+
+		circle (filled: boolean, x: number, y: number, radius: number, color: Color = null, segments: number = 0) {
+			if (segments === 0) segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0);
+			if (segments <= 0) throw new Error("segments must be > 0.");
+			if (color === null) color = this.color;
+			let angle = 2 * MathUtils.PI / segments;
+			let cos = Math.cos(angle);
+			let sin = Math.sin(angle);
+			let cx = radius, cy = 0;
+			if (!filled) {
+				this.check(ShapeType.Line, segments * 2 + 2);
+				for (let i = 0; i < segments; i++) {
+					this.vertex(x + cx, y + cy, color);
+					let temp = cx;
+					cx = cos * cx - sin * cy;
+					cy = sin * temp + cos * cy;
+					this.vertex(x + cx, y + cy, color);
+				}
+				// Ensure the last segment is identical to the first.
+				this.vertex(x + cx, y + cy, color);
+			} else {
+				this.check(ShapeType.Filled, segments * 3 + 3);
+				segments--;
+				for (let i = 0; i < segments; i++) {
+					this.vertex(x, y, color);
+					this.vertex(x + cx, y + cy, color);
+					let temp = cx;
+					cx = cos * cx - sin * cy;
+					cy = sin * temp + cos * cy;
+					this.vertex(x + cx, y + cy, color);
+				}
+				// Ensure the last segment is identical to the first.
+				this.vertex(x, y, color);
+				this.vertex(x + cx, y + cy, color);
+			}
+
+			let temp = cx;
+			cx = radius;
+			cy = 0;
+			this.vertex(x + cx, y + cy, color);
+		}
+
+		curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color: Color = null) {
+			this.check(ShapeType.Line, segments * 2 + 2);
+			if (color === null) color = this.color;
+
+			// Algorithm from: http://www.antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION
+			let subdiv_step = 1 / segments;
+			let subdiv_step2 = subdiv_step * subdiv_step;
+			let subdiv_step3 = subdiv_step * subdiv_step * subdiv_step;
+
+			let pre1 = 3 * subdiv_step;
+			let pre2 = 3 * subdiv_step2;
+			let pre4 = 6 * subdiv_step2;
+			let pre5 = 6 * subdiv_step3;
+
+			let tmp1x = x1 - cx1 * 2 + cx2;
+			let tmp1y = y1 - cy1 * 2 + cy2;
+
+			let tmp2x = (cx1 - cx2) * 3 - x1 + x2;
+			let tmp2y = (cy1 - cy2) * 3 - y1 + y2;
+
+			let fx = x1;
+			let fy = y1;
+
+			let dfx = (cx1 - x1) * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
+			let dfy = (cy1 - y1) * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
+
+			let ddfx = tmp1x * pre4 + tmp2x * pre5;
+			let ddfy = tmp1y * pre4 + tmp2y * pre5;
+
+			let dddfx = tmp2x * pre5;
+			let dddfy = tmp2y * pre5;
+
+			while (segments-- > 0) {
+				this.vertex(fx, fy, color);
+				fx += dfx;
+				fy += dfy;
+				dfx += ddfx;
+				dfy += ddfy;
+				ddfx += dddfx;
+				ddfy += dddfy;
+				this.vertex(fx, fy, color);
+			}
+			this.vertex(fx, fy, color);
+			this.vertex(x2, y2, color);
+		}
+
+		private vertex (x: number, y: number, color: Color) {
+			let idx = this.vertexIndex;
+			let vertices = this.mesh.getVertices();
+			vertices[idx++] = x;
+			vertices[idx++] = y;
+			vertices[idx++] = color.r;
+			vertices[idx++] = color.g;
+			vertices[idx++] = color.b;
+			vertices[idx++] = color.a;
+			this.vertexIndex = idx;
+		}
+
+		end () {
+			if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called");
+			this.flush();
+			let gl = this.context.gl;
+			gl.disable(gl.BLEND);
+			this.isDrawing = false;
+		}
+
+		private flush () {
+			if (this.vertexIndex == 0) return;
+			this.mesh.setVerticesLength(this.vertexIndex);
+			this.mesh.draw(this.shader, this.shapeType);
+			this.vertexIndex = 0;
+		}
+
+		private check (shapeType: ShapeType, numVertices: number) {
+			if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called");
+			if (this.shapeType == shapeType) {
+				if (this.mesh.maxVertices() - this.mesh.numVertices() < numVertices) this.flush();
+				else return;
+			} else {
+				this.flush();
+				this.shapeType = shapeType;
+			}
+		}
+
+		dispose () {
+			this.mesh.dispose();
+		}
+	}
+
+	export enum ShapeType {
+		Point = 0x0000,
+		Line = 0x0001,
+		Filled = 0x0004
+	}
+}

+ 225 - 225
spine-ts/webgl/src/SkeletonDebugRenderer.ts

@@ -1,225 +1,225 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class SkeletonDebugRenderer implements Disposable {
-		boneLineColor = new Color(1, 0, 0, 1);
-		boneOriginColor = new Color(0, 1, 0, 1);
-		attachmentLineColor = new Color(0, 0, 1, 0.5);
-		triangleLineColor = new Color(1, 0.64, 0, 0.5);
-		pathColor = new Color().setFromString("FF7F00");
-		clipColor = new Color(0.8, 0, 0, 2);
-		aabbColor = new Color(0, 1, 0, 0.5);
-		drawBones = true;
-		drawRegionAttachments = true;
-		drawBoundingBoxes = true;
-		drawMeshHull = true;
-		drawMeshTriangles = true;
-		drawPaths = true;
-		drawSkeletonXY = false;
-		drawClipping = true;
-		premultipliedAlpha = false;
-		scale = 1;
-		boneWidth = 2;
-
-		private context: ManagedWebGLRenderingContext;
-		private bounds = new SkeletonBounds();
-		private temp = new Array<number>();
-		private vertices = Utils.newFloatArray(2 * 1024);
-		private static LIGHT_GRAY = new Color(192 / 255, 192 / 255, 192 / 255, 1);
-		private static GREEN = new Color(0, 1, 0, 1);
-
-		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext) {
-			this.context = context instanceof ManagedWebGLRenderingContext? context : new ManagedWebGLRenderingContext(context);
-		}
-
-		draw (shapes: ShapeRenderer, skeleton: Skeleton, ignoredBones: Array<string> = null) {
-			let skeletonX = skeleton.x;
-			let skeletonY = skeleton.y;
-			let gl = this.context.gl;
-			let srcFunc = this.premultipliedAlpha ? gl.ONE : gl.SRC_ALPHA;
-			shapes.setBlendMode(srcFunc, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
-
-			let bones = skeleton.bones;
-			if (this.drawBones) {
-				shapes.setColor(this.boneLineColor);
-				for (let i = 0, n = bones.length; i < n; i++) {
-					let bone = bones[i];
-					if (ignoredBones && ignoredBones.indexOf(bone.data.name) > -1) continue;
-					if (!bone.parent) continue;
-					let x = skeletonX + bone.data.length * bone.a + bone.worldX;
-					let y = skeletonY + bone.data.length * bone.c + bone.worldY;
-					shapes.rectLine(true, skeletonX + bone.worldX, skeletonY + bone.worldY, x, y, this.boneWidth * this.scale);
-				}
-				if (this.drawSkeletonXY) shapes.x(skeletonX, skeletonY, 4 * this.scale);
-			}
-
-			if (this.drawRegionAttachments) {
-				shapes.setColor(this.attachmentLineColor);
-				let slots = skeleton.slots;
-				for (let i = 0, n = slots.length; i < n; i++) {
-					let slot = slots[i];
-					let attachment = slot.getAttachment();
-					if (attachment instanceof RegionAttachment) {
-						let regionAttachment = <RegionAttachment>attachment;
-						let vertices = this.vertices;
-						regionAttachment.computeWorldVertices(slot.bone, vertices, 0, 2);
-						shapes.line(vertices[0], vertices[1], vertices[2], vertices[3]);
-						shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
-						shapes.line(vertices[4], vertices[5], vertices[6], vertices[7]);
-						shapes.line(vertices[6], vertices[7], vertices[0], vertices[1]);
-					}
-				}
-			}
-
-			if (this.drawMeshHull || this.drawMeshTriangles) {
-				let slots = skeleton.slots;
-				for (let i = 0, n = slots.length; i < n; i++) {
-					let slot = slots[i];
-					if (!slot.bone.active) continue;
-					let attachment = slot.getAttachment();
-					if (!(attachment instanceof MeshAttachment)) continue;
-					let mesh = <MeshAttachment>attachment;
-					let vertices = this.vertices;
-					mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, 2);
-					let triangles = mesh.triangles;
-					let hullLength = mesh.hullLength;
-					if (this.drawMeshTriangles) {
-						shapes.setColor(this.triangleLineColor);
-						for (let ii = 0, nn = triangles.length; ii < nn; ii += 3) {
-							let v1 = triangles[ii] * 2, v2 = triangles[ii + 1] * 2, v3 = triangles[ii + 2] * 2;
-							shapes.triangle(false, vertices[v1], vertices[v1 + 1], //
-								vertices[v2], vertices[v2 + 1], //
-								vertices[v3], vertices[v3 + 1] //
-							);
-						}
-					}
-					if (this.drawMeshHull && hullLength > 0) {
-						shapes.setColor(this.attachmentLineColor);
-						hullLength = (hullLength >> 1) * 2;
-						let lastX = vertices[hullLength - 2], lastY = vertices[hullLength - 1];
-						for (let ii = 0, nn = hullLength; ii < nn; ii += 2) {
-							let x = vertices[ii], y = vertices[ii + 1];
-							shapes.line(x, y, lastX, lastY);
-							lastX = x;
-							lastY = y;
-						}
-					}
-				}
-			}
-
-			if (this.drawBoundingBoxes) {
-				let bounds = this.bounds;
-				bounds.update(skeleton, true);
-				shapes.setColor(this.aabbColor);
-				shapes.rect(false, bounds.minX, bounds.minY, bounds.getWidth(), bounds.getHeight());
-				let polygons = bounds.polygons;
-				let boxes = bounds.boundingBoxes;
-				for (let i = 0, n = polygons.length; i < n; i++) {
-					let polygon = polygons[i];
-					shapes.setColor(boxes[i].color);
-					shapes.polygon(polygon, 0, polygon.length);
-				}
-			}
-
-			if (this.drawPaths) {
-				let slots = skeleton.slots;
-				for (let i = 0, n = slots.length; i < n; i++) {
-					let slot = slots[i];
-					if (!slot.bone.active) continue;
-					let attachment = slot.getAttachment();
-					if (!(attachment instanceof PathAttachment)) continue;
-					let path = <PathAttachment>attachment;
-					let nn = path.worldVerticesLength;
-					let world = this.temp = Utils.setArraySize(this.temp, nn, 0);
-					path.computeWorldVertices(slot, 0, nn, world, 0, 2);
-					let color = this.pathColor;
-					let x1 = world[2], y1 = world[3], x2 = 0, y2 = 0;
-					if (path.closed) {
-						shapes.setColor(color);
-						let cx1 = world[0], cy1 = world[1], cx2 = world[nn - 2], cy2 = world[nn - 1];
-						x2 = world[nn - 4];
-						y2 = world[nn - 3];
-						shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 32);
-						shapes.setColor(SkeletonDebugRenderer.LIGHT_GRAY);
-						shapes.line(x1, y1, cx1, cy1);
-						shapes.line(x2, y2, cx2, cy2);
-					}
-					nn -= 4;
-					for (let ii = 4; ii < nn; ii += 6) {
-						let cx1 = world[ii], cy1 = world[ii + 1], cx2 = world[ii + 2], cy2 = world[ii + 3];
-						x2 = world[ii + 4];
-						y2 = world[ii + 5];
-						shapes.setColor(color);
-						shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 32);
-						shapes.setColor(SkeletonDebugRenderer.LIGHT_GRAY);
-						shapes.line(x1, y1, cx1, cy1);
-						shapes.line(x2, y2, cx2, cy2);
-						x1 = x2;
-						y1 = y2;
-					}
-				}
-			}
-
-			if (this.drawBones) {
-				shapes.setColor(this.boneOriginColor);
-				for (let i = 0, n = bones.length; i < n; i++) {
-					let bone = bones[i];
-					if (ignoredBones && ignoredBones.indexOf(bone.data.name) > -1) continue;
-					shapes.circle(true, skeletonX + bone.worldX, skeletonY + bone.worldY, 3 * this.scale, SkeletonDebugRenderer.GREEN, 8);
-				}
-			}
-
-			if (this.drawClipping) {
-				let slots = skeleton.slots;
-				shapes.setColor(this.clipColor)
-				for (let i = 0, n = slots.length; i < n; i++) {
-					let slot = slots[i];
-					if (!slot.bone.active) continue;
-					let attachment = slot.getAttachment();
-					if (!(attachment instanceof ClippingAttachment)) continue;
-					let clip = <ClippingAttachment>attachment;
-					let nn = clip.worldVerticesLength;
-					let world = this.temp = Utils.setArraySize(this.temp, nn, 0);
-					clip.computeWorldVertices(slot, 0, nn, world, 0, 2);
-					for (let i = 0, n = world.length; i < n; i+=2) {
-						let x = world[i];
-						let y = world[i + 1];
-						let x2 = world[(i + 2) % world.length];
-						let y2 = world[(i + 3) % world.length];
-						shapes.line(x, y, x2, y2);
-					}
-				}
-			}
-		}
-
-		dispose () {
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class SkeletonDebugRenderer implements Disposable {
+		boneLineColor = new Color(1, 0, 0, 1);
+		boneOriginColor = new Color(0, 1, 0, 1);
+		attachmentLineColor = new Color(0, 0, 1, 0.5);
+		triangleLineColor = new Color(1, 0.64, 0, 0.5);
+		pathColor = new Color().setFromString("FF7F00");
+		clipColor = new Color(0.8, 0, 0, 2);
+		aabbColor = new Color(0, 1, 0, 0.5);
+		drawBones = true;
+		drawRegionAttachments = true;
+		drawBoundingBoxes = true;
+		drawMeshHull = true;
+		drawMeshTriangles = true;
+		drawPaths = true;
+		drawSkeletonXY = false;
+		drawClipping = true;
+		premultipliedAlpha = false;
+		scale = 1;
+		boneWidth = 2;
+
+		private context: ManagedWebGLRenderingContext;
+		private bounds = new SkeletonBounds();
+		private temp = new Array<number>();
+		private vertices = Utils.newFloatArray(2 * 1024);
+		private static LIGHT_GRAY = new Color(192 / 255, 192 / 255, 192 / 255, 1);
+		private static GREEN = new Color(0, 1, 0, 1);
+
+		constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext) {
+			this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
+		}
+
+		draw (shapes: ShapeRenderer, skeleton: Skeleton, ignoredBones: Array<string> = null) {
+			let skeletonX = skeleton.x;
+			let skeletonY = skeleton.y;
+			let gl = this.context.gl;
+			let srcFunc = this.premultipliedAlpha ? gl.ONE : gl.SRC_ALPHA;
+			shapes.setBlendMode(srcFunc, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+
+			let bones = skeleton.bones;
+			if (this.drawBones) {
+				shapes.setColor(this.boneLineColor);
+				for (let i = 0, n = bones.length; i < n; i++) {
+					let bone = bones[i];
+					if (ignoredBones && ignoredBones.indexOf(bone.data.name) > -1) continue;
+					if (!bone.parent) continue;
+					let x = skeletonX + bone.data.length * bone.a + bone.worldX;
+					let y = skeletonY + bone.data.length * bone.c + bone.worldY;
+					shapes.rectLine(true, skeletonX + bone.worldX, skeletonY + bone.worldY, x, y, this.boneWidth * this.scale);
+				}
+				if (this.drawSkeletonXY) shapes.x(skeletonX, skeletonY, 4 * this.scale);
+			}
+
+			if (this.drawRegionAttachments) {
+				shapes.setColor(this.attachmentLineColor);
+				let slots = skeleton.slots;
+				for (let i = 0, n = slots.length; i < n; i++) {
+					let slot = slots[i];
+					let attachment = slot.getAttachment();
+					if (attachment instanceof RegionAttachment) {
+						let regionAttachment = <RegionAttachment>attachment;
+						let vertices = this.vertices;
+						regionAttachment.computeWorldVertices(slot.bone, vertices, 0, 2);
+						shapes.line(vertices[0], vertices[1], vertices[2], vertices[3]);
+						shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
+						shapes.line(vertices[4], vertices[5], vertices[6], vertices[7]);
+						shapes.line(vertices[6], vertices[7], vertices[0], vertices[1]);
+					}
+				}
+			}
+
+			if (this.drawMeshHull || this.drawMeshTriangles) {
+				let slots = skeleton.slots;
+				for (let i = 0, n = slots.length; i < n; i++) {
+					let slot = slots[i];
+					if (!slot.bone.active) continue;
+					let attachment = slot.getAttachment();
+					if (!(attachment instanceof MeshAttachment)) continue;
+					let mesh = <MeshAttachment>attachment;
+					let vertices = this.vertices;
+					mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, 2);
+					let triangles = mesh.triangles;
+					let hullLength = mesh.hullLength;
+					if (this.drawMeshTriangles) {
+						shapes.setColor(this.triangleLineColor);
+						for (let ii = 0, nn = triangles.length; ii < nn; ii += 3) {
+							let v1 = triangles[ii] * 2, v2 = triangles[ii + 1] * 2, v3 = triangles[ii + 2] * 2;
+							shapes.triangle(false, vertices[v1], vertices[v1 + 1], //
+								vertices[v2], vertices[v2 + 1], //
+								vertices[v3], vertices[v3 + 1] //
+							);
+						}
+					}
+					if (this.drawMeshHull && hullLength > 0) {
+						shapes.setColor(this.attachmentLineColor);
+						hullLength = (hullLength >> 1) * 2;
+						let lastX = vertices[hullLength - 2], lastY = vertices[hullLength - 1];
+						for (let ii = 0, nn = hullLength; ii < nn; ii += 2) {
+							let x = vertices[ii], y = vertices[ii + 1];
+							shapes.line(x, y, lastX, lastY);
+							lastX = x;
+							lastY = y;
+						}
+					}
+				}
+			}
+
+			if (this.drawBoundingBoxes) {
+				let bounds = this.bounds;
+				bounds.update(skeleton, true);
+				shapes.setColor(this.aabbColor);
+				shapes.rect(false, bounds.minX, bounds.minY, bounds.getWidth(), bounds.getHeight());
+				let polygons = bounds.polygons;
+				let boxes = bounds.boundingBoxes;
+				for (let i = 0, n = polygons.length; i < n; i++) {
+					let polygon = polygons[i];
+					shapes.setColor(boxes[i].color);
+					shapes.polygon(polygon, 0, polygon.length);
+				}
+			}
+
+			if (this.drawPaths) {
+				let slots = skeleton.slots;
+				for (let i = 0, n = slots.length; i < n; i++) {
+					let slot = slots[i];
+					if (!slot.bone.active) continue;
+					let attachment = slot.getAttachment();
+					if (!(attachment instanceof PathAttachment)) continue;
+					let path = <PathAttachment>attachment;
+					let nn = path.worldVerticesLength;
+					let world = this.temp = Utils.setArraySize(this.temp, nn, 0);
+					path.computeWorldVertices(slot, 0, nn, world, 0, 2);
+					let color = this.pathColor;
+					let x1 = world[2], y1 = world[3], x2 = 0, y2 = 0;
+					if (path.closed) {
+						shapes.setColor(color);
+						let cx1 = world[0], cy1 = world[1], cx2 = world[nn - 2], cy2 = world[nn - 1];
+						x2 = world[nn - 4];
+						y2 = world[nn - 3];
+						shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 32);
+						shapes.setColor(SkeletonDebugRenderer.LIGHT_GRAY);
+						shapes.line(x1, y1, cx1, cy1);
+						shapes.line(x2, y2, cx2, cy2);
+					}
+					nn -= 4;
+					for (let ii = 4; ii < nn; ii += 6) {
+						let cx1 = world[ii], cy1 = world[ii + 1], cx2 = world[ii + 2], cy2 = world[ii + 3];
+						x2 = world[ii + 4];
+						y2 = world[ii + 5];
+						shapes.setColor(color);
+						shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 32);
+						shapes.setColor(SkeletonDebugRenderer.LIGHT_GRAY);
+						shapes.line(x1, y1, cx1, cy1);
+						shapes.line(x2, y2, cx2, cy2);
+						x1 = x2;
+						y1 = y2;
+					}
+				}
+			}
+
+			if (this.drawBones) {
+				shapes.setColor(this.boneOriginColor);
+				for (let i = 0, n = bones.length; i < n; i++) {
+					let bone = bones[i];
+					if (ignoredBones && ignoredBones.indexOf(bone.data.name) > -1) continue;
+					shapes.circle(true, skeletonX + bone.worldX, skeletonY + bone.worldY, 3 * this.scale, SkeletonDebugRenderer.GREEN, 8);
+				}
+			}
+
+			if (this.drawClipping) {
+				let slots = skeleton.slots;
+				shapes.setColor(this.clipColor)
+				for (let i = 0, n = slots.length; i < n; i++) {
+					let slot = slots[i];
+					if (!slot.bone.active) continue;
+					let attachment = slot.getAttachment();
+					if (!(attachment instanceof ClippingAttachment)) continue;
+					let clip = <ClippingAttachment>attachment;
+					let nn = clip.worldVerticesLength;
+					let world = this.temp = Utils.setArraySize(this.temp, nn, 0);
+					clip.computeWorldVertices(slot, 0, nn, world, 0, 2);
+					for (let i = 0, n = world.length; i < n; i += 2) {
+						let x = world[i];
+						let y = world[i + 1];
+						let x2 = world[(i + 2) % world.length];
+						let y2 = world[(i + 3) % world.length];
+						shapes.line(x, y, x2, y2);
+					}
+				}
+			}
+		}
+
+		dispose () {
+		}
+	}
+}

+ 299 - 299
spine-ts/webgl/src/SkeletonRenderer.ts

@@ -1,299 +1,299 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	class Renderable {
-		constructor(public vertices: ArrayLike<number>, public numVertices: number, public numFloats: number) {}
-	};
-
-	export class SkeletonRenderer {
-		static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
-
-		premultipliedAlpha = false;
-		vertexEffect: VertexEffect = null;
-		private tempColor = new Color();
-		private tempColor2 = new Color();
-		private vertices:ArrayLike<number>;
-		private vertexSize = 2 + 2 + 4;
-		private twoColorTint = false;
-		private renderable: Renderable = new Renderable(null, 0, 0);
-		private clipper: SkeletonClipping = new SkeletonClipping();
-		private temp = new Vector2();
-		private temp2 = new Vector2();
-		private temp3 = new Color();
-		private temp4 = new Color();
-
-		constructor (context: ManagedWebGLRenderingContext, twoColorTint: boolean = true) {
-			this.twoColorTint = twoColorTint;
-			if (twoColorTint)
-				this.vertexSize += 4;
-			this.vertices = Utils.newFloatArray(this.vertexSize * 1024);
-		}
-
-		draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1) {
-			let clipper = this.clipper;
-			let premultipliedAlpha = this.premultipliedAlpha;
-			let twoColorTint = this.twoColorTint;
-			let blendMode: BlendMode = null;
-
-			let tempPos = this.temp;
-			let tempUv = this.temp2;
-			let tempLight = this.temp3;
-			let tempDark = this.temp4;
-
-			let renderable: Renderable = this.renderable;
-			let uvs: ArrayLike<number> = null;
-			let triangles: Array<number> = null;
-			let drawOrder = skeleton.drawOrder;
-			let attachmentColor: Color = null;
-			let skeletonColor = skeleton.color;
-			let vertexSize = twoColorTint ? 12 : 8;
-			let inRange = false;
-			if (slotRangeStart == -1) inRange = true;
-			for (let i = 0, n = drawOrder.length; i < n; i++) {
-				let clippedVertexSize = clipper.isClipping() ? 2 : vertexSize;
-				let slot = drawOrder[i];
-				if (!slot.bone.active) {
-					clipper.clipEndWithSlot(slot);
-					continue;
-				}
-
-				if (slotRangeStart >= 0 && slotRangeStart == slot.data.index) {
-					inRange = true;
-				}
-
-				if (!inRange) {
-					clipper.clipEndWithSlot(slot);
-					continue;
-				}
-
-				if (slotRangeEnd >= 0 && slotRangeEnd == slot.data.index) {
-					inRange = false;
-				}
-
-				let attachment = slot.getAttachment();
-				let texture: GLTexture = null;
-				if (attachment instanceof RegionAttachment) {
-					let region = <RegionAttachment>attachment;
-					renderable.vertices = this.vertices;
-					renderable.numVertices = 4;
-					renderable.numFloats = clippedVertexSize << 2;
-					region.computeWorldVertices(slot.bone, renderable.vertices, 0, clippedVertexSize);
-					triangles = SkeletonRenderer.QUAD_TRIANGLES;
-					uvs = region.uvs;
-					texture = <GLTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
-					attachmentColor = region.color;
-				} else if (attachment instanceof MeshAttachment) {
-					let mesh = <MeshAttachment>attachment;
-					renderable.vertices = this.vertices;
-					renderable.numVertices = (mesh.worldVerticesLength >> 1);
-					renderable.numFloats = renderable.numVertices * clippedVertexSize;
-					if (renderable.numFloats > renderable.vertices.length) {
-						renderable.vertices = this.vertices = spine.Utils.newFloatArray(renderable.numFloats);
-					}
-					mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, renderable.vertices, 0, clippedVertexSize);
-					triangles = mesh.triangles;
-					texture = <GLTexture>(<TextureAtlasRegion>mesh.region.renderObject).page.texture;
-					uvs = mesh.uvs;
-					attachmentColor = mesh.color;
-				} else if (attachment instanceof ClippingAttachment) {
-					let clip = <ClippingAttachment>(attachment);
-					clipper.clipStart(slot, clip);
-					continue;
-				} else {
-					clipper.clipEndWithSlot(slot);
-					continue;
-				}
-
-				if (texture) {
-					let slotColor = slot.color;
-					let finalColor = this.tempColor;
-					finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r;
-					finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g;
-					finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b;
-					finalColor.a = skeletonColor.a * slotColor.a * attachmentColor.a;
-					if (premultipliedAlpha) {
-						finalColor.r *= finalColor.a;
-						finalColor.g *= finalColor.a;
-						finalColor.b *= finalColor.a;
-					}
-					let darkColor = this.tempColor2;
-					if (!slot.darkColor)
-						darkColor.set(0, 0, 0, 1.0);
-					else {
-						if (premultipliedAlpha) {
-							darkColor.r = slot.darkColor.r * finalColor.a;
-							darkColor.g = slot.darkColor.g * finalColor.a;
-							darkColor.b = slot.darkColor.b * finalColor.a;
-						} else {
-							darkColor.setFromColor(slot.darkColor);
-						}
-						darkColor.a = premultipliedAlpha ? 1.0 : 0.0;
-					}
-
-					let slotBlendMode = slot.data.blendMode;
-					if (slotBlendMode != blendMode) {
-						blendMode = slotBlendMode;
-						batcher.setBlendMode(
-							WebGLBlendModeConverter.getSourceColorGLBlendMode(blendMode, premultipliedAlpha),
-							WebGLBlendModeConverter.getSourceAlphaGLBlendMode(blendMode),
-							WebGLBlendModeConverter.getDestGLBlendMode(blendMode));
-					}
-
-					if (clipper.isClipping()) {
-						clipper.clipTriangles(renderable.vertices, renderable.numFloats, triangles, triangles.length, uvs, finalColor, darkColor, twoColorTint);
-						let clippedVertices = new Float32Array(clipper.clippedVertices);
-						let clippedTriangles = clipper.clippedTriangles;
-						if (this.vertexEffect) {
-							let vertexEffect = this.vertexEffect;
-							let verts = clippedVertices;
-							if (!twoColorTint) {
-								for (let v = 0, n = clippedVertices.length; v < n; v += vertexSize) {
-									tempPos.x = verts[v];
-									tempPos.y = verts[v + 1];
-									tempLight.set(verts[v + 2], verts[v + 3], verts[v + 4], verts[v + 5]);
-									tempUv.x = verts[v + 6];
-									tempUv.y = verts[v + 7];
-									tempDark.set(0, 0, 0, 0);
-									vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
-									verts[v] = tempPos.x;
-									verts[v + 1] = tempPos.y;
-									verts[v + 2] = tempLight.r;
-									verts[v + 3] = tempLight.g;
-									verts[v + 4] = tempLight.b;
-									verts[v + 5] = tempLight.a;
-									verts[v + 6] = tempUv.x;
-									verts[v + 7] = tempUv.y
-								}
-							} else {
-								for (let v = 0, n = clippedVertices.length; v < n; v += vertexSize) {
-									tempPos.x = verts[v];
-									tempPos.y = verts[v + 1];
-									tempLight.set(verts[v + 2], verts[v + 3], verts[v + 4], verts[v + 5]);
-									tempUv.x = verts[v + 6];
-									tempUv.y = verts[v + 7];
-									tempDark.set(verts[v + 8], verts[v + 9], verts[v + 10], verts[v + 11]);
-									vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
-									verts[v] = tempPos.x;
-									verts[v + 1] = tempPos.y;
-									verts[v + 2] = tempLight.r;
-									verts[v + 3] = tempLight.g;
-									verts[v + 4] = tempLight.b;
-									verts[v + 5] = tempLight.a;
-									verts[v + 6] = tempUv.x;
-									verts[v + 7] = tempUv.y
-									verts[v + 8] = tempDark.r;
-									verts[v + 9] = tempDark.g;
-									verts[v + 10] = tempDark.b;
-									verts[v + 11] = tempDark.a;
-								}
-							}
-						}
-						batcher.draw(texture, clippedVertices, clippedTriangles);
-					} else {
-						let verts = renderable.vertices;
-						if (this.vertexEffect) {
-							let vertexEffect = this.vertexEffect;
-							if (!twoColorTint) {
-								for (let v = 0, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) {
-									tempPos.x = verts[v];
-									tempPos.y = verts[v + 1];
-									tempUv.x = uvs[u];
-									tempUv.y = uvs[u + 1]
-									tempLight.setFromColor(finalColor);
-									tempDark.set(0, 0, 0, 0);
-									vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
-									verts[v] = tempPos.x;
-									verts[v + 1] = tempPos.y;
-									verts[v + 2] = tempLight.r;
-									verts[v + 3] = tempLight.g;
-									verts[v + 4] = tempLight.b;
-									verts[v + 5] = tempLight.a;
-									verts[v + 6] = tempUv.x;
-									verts[v + 7] = tempUv.y
-								}
-							} else {
-								for (let v = 0, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) {
-									tempPos.x = verts[v];
-									tempPos.y = verts[v + 1];
-									tempUv.x = uvs[u];
-									tempUv.y = uvs[u + 1]
-									tempLight.setFromColor(finalColor);
-									tempDark.setFromColor(darkColor);
-									vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
-									verts[v] = tempPos.x;
-									verts[v + 1] = tempPos.y;
-									verts[v + 2] = tempLight.r;
-									verts[v + 3] = tempLight.g;
-									verts[v + 4] = tempLight.b;
-									verts[v + 5] = tempLight.a;
-									verts[v + 6] = tempUv.x;
-									verts[v + 7] = tempUv.y
-									verts[v + 8] = tempDark.r;
-									verts[v + 9] = tempDark.g;
-									verts[v + 10] = tempDark.b;
-									verts[v + 11] = tempDark.a;
-								}
-							}
-						} else {
-							if (!twoColorTint) {
-								for (let v = 2, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) {
-									verts[v] = finalColor.r;
-									verts[v + 1] = finalColor.g;
-									verts[v + 2] = finalColor.b;
-									verts[v + 3] = finalColor.a;
-									verts[v + 4] = uvs[u];
-									verts[v + 5] = uvs[u + 1];
-								}
-							} else {
-								for (let v = 2, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) {
-									verts[v] = finalColor.r;
-									verts[v + 1] = finalColor.g;
-									verts[v + 2] = finalColor.b;
-									verts[v + 3] = finalColor.a;
-									verts[v + 4] = uvs[u];
-									verts[v + 5] = uvs[u + 1];
-									verts[v + 6] = darkColor.r;
-									verts[v + 7] = darkColor.g;
-									verts[v + 8] = darkColor.b;
-									verts[v + 9] = darkColor.a;
-								}
-							}
-						}
-						let view = (renderable.vertices as Float32Array).subarray(0, renderable.numFloats);
-						batcher.draw(texture, view, triangles);
-					}
-				}
-
-				clipper.clipEndWithSlot(slot);
-			}
-			clipper.clipEnd();
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	class Renderable {
+		constructor (public vertices: ArrayLike<number>, public numVertices: number, public numFloats: number) { }
+	};
+
+	export class SkeletonRenderer {
+		static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
+
+		premultipliedAlpha = false;
+		vertexEffect: VertexEffect = null;
+		private tempColor = new Color();
+		private tempColor2 = new Color();
+		private vertices: ArrayLike<number>;
+		private vertexSize = 2 + 2 + 4;
+		private twoColorTint = false;
+		private renderable: Renderable = new Renderable(null, 0, 0);
+		private clipper: SkeletonClipping = new SkeletonClipping();
+		private temp = new Vector2();
+		private temp2 = new Vector2();
+		private temp3 = new Color();
+		private temp4 = new Color();
+
+		constructor (context: ManagedWebGLRenderingContext, twoColorTint: boolean = true) {
+			this.twoColorTint = twoColorTint;
+			if (twoColorTint)
+				this.vertexSize += 4;
+			this.vertices = Utils.newFloatArray(this.vertexSize * 1024);
+		}
+
+		draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1) {
+			let clipper = this.clipper;
+			let premultipliedAlpha = this.premultipliedAlpha;
+			let twoColorTint = this.twoColorTint;
+			let blendMode: BlendMode = null;
+
+			let tempPos = this.temp;
+			let tempUv = this.temp2;
+			let tempLight = this.temp3;
+			let tempDark = this.temp4;
+
+			let renderable: Renderable = this.renderable;
+			let uvs: ArrayLike<number> = null;
+			let triangles: Array<number> = null;
+			let drawOrder = skeleton.drawOrder;
+			let attachmentColor: Color = null;
+			let skeletonColor = skeleton.color;
+			let vertexSize = twoColorTint ? 12 : 8;
+			let inRange = false;
+			if (slotRangeStart == -1) inRange = true;
+			for (let i = 0, n = drawOrder.length; i < n; i++) {
+				let clippedVertexSize = clipper.isClipping() ? 2 : vertexSize;
+				let slot = drawOrder[i];
+				if (!slot.bone.active) {
+					clipper.clipEndWithSlot(slot);
+					continue;
+				}
+
+				if (slotRangeStart >= 0 && slotRangeStart == slot.data.index) {
+					inRange = true;
+				}
+
+				if (!inRange) {
+					clipper.clipEndWithSlot(slot);
+					continue;
+				}
+
+				if (slotRangeEnd >= 0 && slotRangeEnd == slot.data.index) {
+					inRange = false;
+				}
+
+				let attachment = slot.getAttachment();
+				let texture: GLTexture = null;
+				if (attachment instanceof RegionAttachment) {
+					let region = <RegionAttachment>attachment;
+					renderable.vertices = this.vertices;
+					renderable.numVertices = 4;
+					renderable.numFloats = clippedVertexSize << 2;
+					region.computeWorldVertices(slot.bone, renderable.vertices, 0, clippedVertexSize);
+					triangles = SkeletonRenderer.QUAD_TRIANGLES;
+					uvs = region.uvs;
+					texture = <GLTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
+					attachmentColor = region.color;
+				} else if (attachment instanceof MeshAttachment) {
+					let mesh = <MeshAttachment>attachment;
+					renderable.vertices = this.vertices;
+					renderable.numVertices = (mesh.worldVerticesLength >> 1);
+					renderable.numFloats = renderable.numVertices * clippedVertexSize;
+					if (renderable.numFloats > renderable.vertices.length) {
+						renderable.vertices = this.vertices = spine.Utils.newFloatArray(renderable.numFloats);
+					}
+					mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, renderable.vertices, 0, clippedVertexSize);
+					triangles = mesh.triangles;
+					texture = <GLTexture>(<TextureAtlasRegion>mesh.region.renderObject).page.texture;
+					uvs = mesh.uvs;
+					attachmentColor = mesh.color;
+				} else if (attachment instanceof ClippingAttachment) {
+					let clip = <ClippingAttachment>(attachment);
+					clipper.clipStart(slot, clip);
+					continue;
+				} else {
+					clipper.clipEndWithSlot(slot);
+					continue;
+				}
+
+				if (texture) {
+					let slotColor = slot.color;
+					let finalColor = this.tempColor;
+					finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r;
+					finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g;
+					finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b;
+					finalColor.a = skeletonColor.a * slotColor.a * attachmentColor.a;
+					if (premultipliedAlpha) {
+						finalColor.r *= finalColor.a;
+						finalColor.g *= finalColor.a;
+						finalColor.b *= finalColor.a;
+					}
+					let darkColor = this.tempColor2;
+					if (!slot.darkColor)
+						darkColor.set(0, 0, 0, 1.0);
+					else {
+						if (premultipliedAlpha) {
+							darkColor.r = slot.darkColor.r * finalColor.a;
+							darkColor.g = slot.darkColor.g * finalColor.a;
+							darkColor.b = slot.darkColor.b * finalColor.a;
+						} else {
+							darkColor.setFromColor(slot.darkColor);
+						}
+						darkColor.a = premultipliedAlpha ? 1.0 : 0.0;
+					}
+
+					let slotBlendMode = slot.data.blendMode;
+					if (slotBlendMode != blendMode) {
+						blendMode = slotBlendMode;
+						batcher.setBlendMode(
+							WebGLBlendModeConverter.getSourceColorGLBlendMode(blendMode, premultipliedAlpha),
+							WebGLBlendModeConverter.getSourceAlphaGLBlendMode(blendMode),
+							WebGLBlendModeConverter.getDestGLBlendMode(blendMode));
+					}
+
+					if (clipper.isClipping()) {
+						clipper.clipTriangles(renderable.vertices, renderable.numFloats, triangles, triangles.length, uvs, finalColor, darkColor, twoColorTint);
+						let clippedVertices = new Float32Array(clipper.clippedVertices);
+						let clippedTriangles = clipper.clippedTriangles;
+						if (this.vertexEffect) {
+							let vertexEffect = this.vertexEffect;
+							let verts = clippedVertices;
+							if (!twoColorTint) {
+								for (let v = 0, n = clippedVertices.length; v < n; v += vertexSize) {
+									tempPos.x = verts[v];
+									tempPos.y = verts[v + 1];
+									tempLight.set(verts[v + 2], verts[v + 3], verts[v + 4], verts[v + 5]);
+									tempUv.x = verts[v + 6];
+									tempUv.y = verts[v + 7];
+									tempDark.set(0, 0, 0, 0);
+									vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
+									verts[v] = tempPos.x;
+									verts[v + 1] = tempPos.y;
+									verts[v + 2] = tempLight.r;
+									verts[v + 3] = tempLight.g;
+									verts[v + 4] = tempLight.b;
+									verts[v + 5] = tempLight.a;
+									verts[v + 6] = tempUv.x;
+									verts[v + 7] = tempUv.y
+								}
+							} else {
+								for (let v = 0, n = clippedVertices.length; v < n; v += vertexSize) {
+									tempPos.x = verts[v];
+									tempPos.y = verts[v + 1];
+									tempLight.set(verts[v + 2], verts[v + 3], verts[v + 4], verts[v + 5]);
+									tempUv.x = verts[v + 6];
+									tempUv.y = verts[v + 7];
+									tempDark.set(verts[v + 8], verts[v + 9], verts[v + 10], verts[v + 11]);
+									vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
+									verts[v] = tempPos.x;
+									verts[v + 1] = tempPos.y;
+									verts[v + 2] = tempLight.r;
+									verts[v + 3] = tempLight.g;
+									verts[v + 4] = tempLight.b;
+									verts[v + 5] = tempLight.a;
+									verts[v + 6] = tempUv.x;
+									verts[v + 7] = tempUv.y
+									verts[v + 8] = tempDark.r;
+									verts[v + 9] = tempDark.g;
+									verts[v + 10] = tempDark.b;
+									verts[v + 11] = tempDark.a;
+								}
+							}
+						}
+						batcher.draw(texture, clippedVertices, clippedTriangles);
+					} else {
+						let verts = renderable.vertices;
+						if (this.vertexEffect) {
+							let vertexEffect = this.vertexEffect;
+							if (!twoColorTint) {
+								for (let v = 0, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) {
+									tempPos.x = verts[v];
+									tempPos.y = verts[v + 1];
+									tempUv.x = uvs[u];
+									tempUv.y = uvs[u + 1]
+									tempLight.setFromColor(finalColor);
+									tempDark.set(0, 0, 0, 0);
+									vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
+									verts[v] = tempPos.x;
+									verts[v + 1] = tempPos.y;
+									verts[v + 2] = tempLight.r;
+									verts[v + 3] = tempLight.g;
+									verts[v + 4] = tempLight.b;
+									verts[v + 5] = tempLight.a;
+									verts[v + 6] = tempUv.x;
+									verts[v + 7] = tempUv.y
+								}
+							} else {
+								for (let v = 0, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) {
+									tempPos.x = verts[v];
+									tempPos.y = verts[v + 1];
+									tempUv.x = uvs[u];
+									tempUv.y = uvs[u + 1]
+									tempLight.setFromColor(finalColor);
+									tempDark.setFromColor(darkColor);
+									vertexEffect.transform(tempPos, tempUv, tempLight, tempDark);
+									verts[v] = tempPos.x;
+									verts[v + 1] = tempPos.y;
+									verts[v + 2] = tempLight.r;
+									verts[v + 3] = tempLight.g;
+									verts[v + 4] = tempLight.b;
+									verts[v + 5] = tempLight.a;
+									verts[v + 6] = tempUv.x;
+									verts[v + 7] = tempUv.y
+									verts[v + 8] = tempDark.r;
+									verts[v + 9] = tempDark.g;
+									verts[v + 10] = tempDark.b;
+									verts[v + 11] = tempDark.a;
+								}
+							}
+						} else {
+							if (!twoColorTint) {
+								for (let v = 2, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) {
+									verts[v] = finalColor.r;
+									verts[v + 1] = finalColor.g;
+									verts[v + 2] = finalColor.b;
+									verts[v + 3] = finalColor.a;
+									verts[v + 4] = uvs[u];
+									verts[v + 5] = uvs[u + 1];
+								}
+							} else {
+								for (let v = 2, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) {
+									verts[v] = finalColor.r;
+									verts[v + 1] = finalColor.g;
+									verts[v + 2] = finalColor.b;
+									verts[v + 3] = finalColor.a;
+									verts[v + 4] = uvs[u];
+									verts[v + 5] = uvs[u + 1];
+									verts[v + 6] = darkColor.r;
+									verts[v + 7] = darkColor.g;
+									verts[v + 8] = darkColor.b;
+									verts[v + 9] = darkColor.a;
+								}
+							}
+						}
+						let view = (renderable.vertices as Float32Array).subarray(0, renderable.numFloats);
+						batcher.draw(texture, view, triangles);
+					}
+				}
+
+				clipper.clipEndWithSlot(slot);
+			}
+			clipper.clipEnd();
+		}
+	}
+}

+ 121 - 121
spine-ts/webgl/src/Vector3.ts

@@ -1,121 +1,121 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class Vector3 {
-		x = 0;
-		y = 0;
-		z = 0;
-
-		constructor (x: number = 0, y: number = 0, z: number = 0) {
-			this.x = x;
-			this.y = y;
-			this.z = z;
-		}
-
-		setFrom(v: Vector3): Vector3 {
-			this.x = v.x;
-			this.y = v.y;
-			this.z = v.z;
-			return this;
-		}
-
-		set (x: number, y: number, z: number): Vector3 {
-			this.x = x;
-			this.y = y;
-			this.z = z;
-			return this;
-		}
-
-		add (v: Vector3): Vector3 {
-			this.x += v.x;
-			this.y += v.y;
-			this.z += v.z;
-			return this;
-		}
-
-		sub (v: Vector3): Vector3 {
-			this.x -= v.x;
-			this.y -= v.y;
-			this.z -= v.z;
-			return this;
-		}
-
-		scale (s: number): Vector3 {
-			this.x *= s;
-			this.y *= s;
-			this.z *= s;
-			return this;
-		}
-
-		normalize (): Vector3 {
-			let len = this.length();
-			if (len == 0) return this;
-			len = 1 / len;
-			this.x *= len;
-			this.y *= len;
-			this.z *= len;
-			return this;
-		}
-
-		cross (v: Vector3): Vector3 {
-			return this.set(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x)
-		}
-
-		multiply (matrix: Matrix4): Vector3 {
-			let l_mat = matrix.values;
-			return this.set(this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03],
-				this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13],
-				this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]);
-		}
-
-		project (matrix: Matrix4): Vector3 {
-			let l_mat = matrix.values;
-			let l_w = 1 / (this.x * l_mat[M30] + this.y * l_mat[M31] + this.z * l_mat[M32] + l_mat[M33]);
-			return this.set((this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03]) * l_w,
-				(this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13]) * l_w,
-				(this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]) * l_w);
-		}
-
-		dot (v: Vector3): number {
-			return this.x * v.x + this.y * v.y + this.z * v.z;
-		}
-
-		length (): number {
-			return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
-		}
-
-		distance (v: Vector3): number {
-			let a = v.x - this.x;
-			let b = v.y - this.y;
-			let c = v.z - this.z;
-			return Math.sqrt(a * a + b * b + c * c);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class Vector3 {
+		x = 0;
+		y = 0;
+		z = 0;
+
+		constructor (x: number = 0, y: number = 0, z: number = 0) {
+			this.x = x;
+			this.y = y;
+			this.z = z;
+		}
+
+		setFrom (v: Vector3): Vector3 {
+			this.x = v.x;
+			this.y = v.y;
+			this.z = v.z;
+			return this;
+		}
+
+		set (x: number, y: number, z: number): Vector3 {
+			this.x = x;
+			this.y = y;
+			this.z = z;
+			return this;
+		}
+
+		add (v: Vector3): Vector3 {
+			this.x += v.x;
+			this.y += v.y;
+			this.z += v.z;
+			return this;
+		}
+
+		sub (v: Vector3): Vector3 {
+			this.x -= v.x;
+			this.y -= v.y;
+			this.z -= v.z;
+			return this;
+		}
+
+		scale (s: number): Vector3 {
+			this.x *= s;
+			this.y *= s;
+			this.z *= s;
+			return this;
+		}
+
+		normalize (): Vector3 {
+			let len = this.length();
+			if (len == 0) return this;
+			len = 1 / len;
+			this.x *= len;
+			this.y *= len;
+			this.z *= len;
+			return this;
+		}
+
+		cross (v: Vector3): Vector3 {
+			return this.set(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x)
+		}
+
+		multiply (matrix: Matrix4): Vector3 {
+			let l_mat = matrix.values;
+			return this.set(this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03],
+				this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13],
+				this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]);
+		}
+
+		project (matrix: Matrix4): Vector3 {
+			let l_mat = matrix.values;
+			let l_w = 1 / (this.x * l_mat[M30] + this.y * l_mat[M31] + this.z * l_mat[M32] + l_mat[M33]);
+			return this.set((this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03]) * l_w,
+				(this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13]) * l_w,
+				(this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]) * l_w);
+		}
+
+		dot (v: Vector3): number {
+			return this.x * v.x + this.y * v.y + this.z * v.z;
+		}
+
+		length (): number {
+			return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+		}
+
+		distance (v: Vector3): number {
+			let a = v.x - this.x;
+			let b = v.y - this.y;
+			let c = v.z - this.z;
+			return Math.sqrt(a * a + b * b + c * c);
+		}
+	}
+}

+ 107 - 107
spine-ts/webgl/src/WebGL.ts

@@ -1,107 +1,107 @@
-/******************************************************************************
- * Spine Runtimes License Agreement
- * Last updated January 1, 2020. Replaces all prior versions.
- *
- * Copyright (c) 2013-2020, Esoteric Software LLC
- *
- * Integration of the Spine Runtimes into software or otherwise creating
- * derivative works of the Spine Runtimes is permitted under the terms and
- * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
- *
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
- * "Products"), provided that each user of the Products must obtain their own
- * Spine Editor license and redistribution of the Products in any form must
- * include this license and copyright notice.
- *
- * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
- * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-module spine.webgl {
-	export class ManagedWebGLRenderingContext {
-		public canvas: HTMLCanvasElement | OffscreenCanvas;
-		public gl: WebGLRenderingContext;
-		private restorables = new Array<Restorable>();
-
-		constructor(canvasOrContext: HTMLCanvasElement | WebGLRenderingContext | EventTarget, contextConfig: any = { alpha: "true" }) {
-			if (!((canvasOrContext instanceof WebGLRenderingContext) || (typeof WebGL2RenderingContext !== 'undefined' && canvasOrContext instanceof WebGL2RenderingContext)))
-				this.setupCanvas(canvasOrContext, contextConfig);
-			else {
-				this.gl = canvasOrContext;
-				this.canvas = this.gl.canvas;
-			}
-		}
-
-		private setupCanvas(canvas: any, contextConfig: any) {
-			this.gl = <WebGLRenderingContext> (canvas.getContext("webgl2", contextConfig) || canvas.getContext("webgl", contextConfig));
-			this.canvas = canvas;
-			canvas.addEventListener("webglcontextlost", (e: any) => {
-				let event = <WebGLContextEvent>e;
-				if (e) e.preventDefault();
-			});
-
-			canvas.addEventListener("webglcontextrestored", (e: any) => {
-				for (let i = 0, n = this.restorables.length; i < n; i++)
-					this.restorables[i].restore();
-			});
-		}
-
-		addRestorable(restorable: Restorable) {
-			this.restorables.push(restorable);
-		}
-
-		removeRestorable(restorable: Restorable) {
-			let index = this.restorables.indexOf(restorable);
-			if (index > -1) this.restorables.splice(index, 1);
-		}
-	}
-
-	const ONE = 1;
-	const ONE_MINUS_SRC_COLOR = 0x0301;
-	const SRC_ALPHA = 0x0302;
-	const ONE_MINUS_SRC_ALPHA = 0x0303;
-	const ONE_MINUS_DST_ALPHA = 0x0305;
-	const DST_COLOR = 0x0306;
-
-	export class WebGLBlendModeConverter {
-		static getDestGLBlendMode (blendMode: BlendMode) {
-			switch (blendMode) {
-			case BlendMode.Normal: return ONE_MINUS_SRC_ALPHA;
-			case BlendMode.Additive: return ONE;
-			case BlendMode.Multiply: return ONE_MINUS_SRC_ALPHA;
-			case BlendMode.Screen: return ONE_MINUS_SRC_ALPHA;
-			default: throw new Error("Unknown blend mode: " + blendMode);
-			}
-		}
-
-		static getSourceColorGLBlendMode (blendMode: BlendMode, premultipliedAlpha: boolean = false) {
-			switch (blendMode) {
-			case BlendMode.Normal: return premultipliedAlpha ? ONE : SRC_ALPHA;
-			case BlendMode.Additive: return premultipliedAlpha ? ONE : SRC_ALPHA;
-			case BlendMode.Multiply: return DST_COLOR;
-			case BlendMode.Screen: return ONE;
-			default: throw new Error("Unknown blend mode: " + blendMode);
-			}
-		}
-
-		static getSourceAlphaGLBlendMode (blendMode: BlendMode) {
-			switch (blendMode) {
-			case BlendMode.Normal: return ONE;
-			case BlendMode.Additive: return ONE;
-			case BlendMode.Multiply: return ONE_MINUS_SRC_ALPHA;
-			case BlendMode.Screen: return ONE_MINUS_SRC_COLOR;
-			default: throw new Error("Unknown blend mode: " + blendMode);
-			}
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+module spine.webgl {
+	export class ManagedWebGLRenderingContext {
+		public canvas: HTMLCanvasElement | OffscreenCanvas;
+		public gl: WebGLRenderingContext;
+		private restorables = new Array<Restorable>();
+
+		constructor (canvasOrContext: HTMLCanvasElement | WebGLRenderingContext | EventTarget, contextConfig: any = { alpha: "true" }) {
+			if (!((canvasOrContext instanceof WebGLRenderingContext) || (typeof WebGL2RenderingContext !== 'undefined' && canvasOrContext instanceof WebGL2RenderingContext)))
+				this.setupCanvas(canvasOrContext, contextConfig);
+			else {
+				this.gl = canvasOrContext;
+				this.canvas = this.gl.canvas;
+			}
+		}
+
+		private setupCanvas (canvas: any, contextConfig: any) {
+			this.gl = <WebGLRenderingContext>(canvas.getContext("webgl2", contextConfig) || canvas.getContext("webgl", contextConfig));
+			this.canvas = canvas;
+			canvas.addEventListener("webglcontextlost", (e: any) => {
+				let event = <WebGLContextEvent>e;
+				if (e) e.preventDefault();
+			});
+
+			canvas.addEventListener("webglcontextrestored", (e: any) => {
+				for (let i = 0, n = this.restorables.length; i < n; i++)
+					this.restorables[i].restore();
+			});
+		}
+
+		addRestorable (restorable: Restorable) {
+			this.restorables.push(restorable);
+		}
+
+		removeRestorable (restorable: Restorable) {
+			let index = this.restorables.indexOf(restorable);
+			if (index > -1) this.restorables.splice(index, 1);
+		}
+	}
+
+	const ONE = 1;
+	const ONE_MINUS_SRC_COLOR = 0x0301;
+	const SRC_ALPHA = 0x0302;
+	const ONE_MINUS_SRC_ALPHA = 0x0303;
+	const ONE_MINUS_DST_ALPHA = 0x0305;
+	const DST_COLOR = 0x0306;
+
+	export class WebGLBlendModeConverter {
+		static getDestGLBlendMode (blendMode: BlendMode) {
+			switch (blendMode) {
+				case BlendMode.Normal: return ONE_MINUS_SRC_ALPHA;
+				case BlendMode.Additive: return ONE;
+				case BlendMode.Multiply: return ONE_MINUS_SRC_ALPHA;
+				case BlendMode.Screen: return ONE_MINUS_SRC_ALPHA;
+				default: throw new Error("Unknown blend mode: " + blendMode);
+			}
+		}
+
+		static getSourceColorGLBlendMode (blendMode: BlendMode, premultipliedAlpha: boolean = false) {
+			switch (blendMode) {
+				case BlendMode.Normal: return premultipliedAlpha ? ONE : SRC_ALPHA;
+				case BlendMode.Additive: return premultipliedAlpha ? ONE : SRC_ALPHA;
+				case BlendMode.Multiply: return DST_COLOR;
+				case BlendMode.Screen: return ONE;
+				default: throw new Error("Unknown blend mode: " + blendMode);
+			}
+		}
+
+		static getSourceAlphaGLBlendMode (blendMode: BlendMode) {
+			switch (blendMode) {
+				case BlendMode.Normal: return ONE;
+				case BlendMode.Additive: return ONE;
+				case BlendMode.Multiply: return ONE_MINUS_SRC_ALPHA;
+				case BlendMode.Screen: return ONE_MINUS_SRC_COLOR;
+				default: throw new Error("Unknown blend mode: " + blendMode);
+			}
+		}
+	}
+}

部分文件因为文件数量过多而无法显示