├── README.md ├── bin ├── spine-egret-runtimes.d.ts ├── spine-egret-runtimes.js ├── spine-egret-runtimes.js.map ├── spine-egret-runtimes.min.js └── spine-egret-runtimes.min.js.map ├── package.json ├── preview.png ├── src ├── core │ ├── Animation.ts │ ├── AnimationState.ts │ ├── AnimationStateData.ts │ ├── AssetManager.ts │ ├── AtlasAttachmentLoader.ts │ ├── BlendMode.ts │ ├── Bone.ts │ ├── BoneData.ts │ ├── Constraint.ts │ ├── Event.ts │ ├── EventData.ts │ ├── IkConstraint.ts │ ├── IkConstraintData.ts │ ├── PathConstraint.ts │ ├── PathConstraintData.ts │ ├── SharedAssetManager.ts │ ├── Skeleton.ts │ ├── SkeletonBounds.ts │ ├── SkeletonClipping.ts │ ├── SkeletonData.ts │ ├── SkeletonJson.ts │ ├── Skin.ts │ ├── Slot.ts │ ├── SlotData.ts │ ├── Texture.ts │ ├── TextureAtlas.ts │ ├── TransformConstraint.ts │ ├── TransformConstraintData.ts │ ├── Triangulator.ts │ ├── Updatable.ts │ ├── Utils.ts │ ├── VertexEffect.ts │ ├── attachments │ │ ├── Attachment.ts │ │ ├── AttachmentLoader.ts │ │ ├── AttachmentType.ts │ │ ├── BoundingBoxAttachment.ts │ │ ├── ClippingAttachment.ts │ │ ├── MeshAttachment.ts │ │ ├── PathAttachment.ts │ │ ├── PointAttachment.ts │ │ └── RegionAttachment.ts │ ├── polyfills.ts │ └── vertexeffects │ │ ├── JitterEffect.ts │ │ └── SwirlEffect.ts ├── egret │ └── egret.d.ts └── impl │ ├── EventEmitter.ts │ ├── SkeletonAnimation.ts │ ├── SkeletonRenderer.ts │ └── Track.ts └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | # spine-egret-runtimes 2 | 3 | ## About 4 | 5 | > 本项目为 白鹭 的 spine 运行库 6 | > A spine runtime for Egret 7 | > Demo:https://www.bobsong.net/spine-egret-runtimes-example/. 8 | 9 | ## Preview 10 | ![img](https://github.com/BobSongCN/spine-egret-runtimes/blob/master/preview.png) 11 | 12 | ## Installing 13 | > See [Egret document](http://developer.egret.com/cn/github/egret-docs/Engine2D/projectConfig/libraryProject/index.html) for integrating to Egret project. 14 | 15 | ## Getting started 16 | 17 | ``` 18 | private loadSpineAnimation(skeletonName: string) { 19 | 20 | let json = RES.getRes(skeletonName + "_json") 21 | let atlas = RES.getRes(skeletonName + "_atlas") 22 | let imgs = { 23 | [skeletonName + '.png']: RES.getRes(skeletonName + "_png") 24 | } 25 | for (var i = 2; i < 5; i++) { 26 | let img = RES.getRes(skeletonName+i + "_png") 27 | if(img != null) 28 | { 29 | imgs[skeletonName+i + '.png'] = img 30 | } 31 | else 32 | { 33 | break; 34 | } 35 | } 36 | 37 | let texAtlas = spine.createTextureAtlas(atlas, imgs); 38 | let skelData = spine.createSkeletonData(json, texAtlas); 39 | 40 | return new spine.SkeletonAnimation(skelData); 41 | } 42 | ``` 43 | 44 | ## 参考 45 | > https://github.com/fightingcat/egret-spine 46 | 本库再上诉库基础上修改的 47 | 48 | ## Blog 49 | > https://www.bobsong.net 50 | 51 | ## Issues 52 | 53 | - 未叠加颜色 54 | - 裁剪未完成 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spine-egret-runtimes", 3 | "compilerVersion": "5.2.30" 4 | } -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobSongCN/spine-egret-runtimes/97e6c95c70c92125cdd4563d3107ac83dd95931e/preview.png -------------------------------------------------------------------------------- /src/core/AnimationStateData.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class AnimationStateData { 32 | skeletonData: SkeletonData; 33 | animationToMixTime: Map = { }; 34 | defaultMix = 0; 35 | 36 | constructor (skeletonData: SkeletonData) { 37 | if (skeletonData == null) throw new Error("skeletonData cannot be null."); 38 | this.skeletonData = skeletonData; 39 | } 40 | 41 | setMix (fromName: string, toName: string, duration: number) { 42 | let from = this.skeletonData.findAnimation(fromName); 43 | if (from == null) throw new Error("Animation not found: " + fromName); 44 | let to = this.skeletonData.findAnimation(toName); 45 | if (to == null) throw new Error("Animation not found: " + toName); 46 | this.setMixWith(from, to, duration); 47 | } 48 | 49 | setMixWith (from: Animation, to: Animation, duration: number) { 50 | if (from == null) throw new Error("from cannot be null."); 51 | if (to == null) throw new Error("to cannot be null."); 52 | let key = from.name + "." + to.name; 53 | this.animationToMixTime[key] = duration; 54 | } 55 | 56 | getMix (from: Animation, to: Animation) { 57 | let key = from.name + "." + to.name; 58 | let value = this.animationToMixTime[key]; 59 | return value === undefined ? this.defaultMix : value; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/core/AssetManager.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class AssetManager implements Disposable { 32 | private pathPrefix: string; 33 | private textureLoader: (image: HTMLImageElement) => any; 34 | private assets: Map = {}; 35 | private errors: Map = {}; 36 | private toLoad = 0; 37 | private loaded = 0; 38 | 39 | constructor (textureLoader: (image: HTMLImageElement) => any, pathPrefix: string = "") { 40 | this.textureLoader = textureLoader; 41 | this.pathPrefix = pathPrefix; 42 | } 43 | 44 | private static downloadText (url: string, success: (data: string) => void, error: (status: number, responseText: string) => void) { 45 | let request = new XMLHttpRequest(); 46 | request.open("GET", url, true); 47 | request.onload = () => { 48 | if (request.status == 200) { 49 | success(request.responseText); 50 | } else { 51 | error(request.status, request.responseText); 52 | } 53 | } 54 | request.onerror = () => { 55 | error(request.status, request.responseText); 56 | } 57 | request.send(); 58 | } 59 | 60 | private static downloadBinary (url: string, success: (data: Uint8Array) => void, error: (status: number, responseText: string) => void) { 61 | let request = new XMLHttpRequest(); 62 | request.open("GET", url, true); 63 | request.responseType = "arraybuffer"; 64 | request.onload = () => { 65 | if (request.status == 200) { 66 | success(new Uint8Array(request.response as ArrayBuffer)); 67 | } else { 68 | error(request.status, request.responseText); 69 | } 70 | } 71 | request.onerror = () => { 72 | error(request.status, request.responseText); 73 | } 74 | request.send(); 75 | } 76 | 77 | loadText(path: string, 78 | success: (path: string, text: string) => void = null, 79 | error: (path: string, error: string) => void = null) { 80 | path = this.pathPrefix + path; 81 | this.toLoad++; 82 | 83 | AssetManager.downloadText(path, (data: string): void => { 84 | this.assets[path] = data; 85 | if (success) success(path, data); 86 | this.toLoad--; 87 | this.loaded++; 88 | }, (state: number, responseText: string): void => { 89 | this.errors[path] = `Couldn't load text ${path}: status ${status}, ${responseText}`; 90 | if (error) error(path, `Couldn't load text ${path}: status ${status}, ${responseText}`); 91 | this.toLoad--; 92 | this.loaded++; 93 | }); 94 | } 95 | 96 | loadTexture (path: string, 97 | success: (path: string, image: HTMLImageElement) => void = null, 98 | error: (path: string, error: string) => void = null) { 99 | path = this.pathPrefix + path; 100 | this.toLoad++; 101 | let img = new Image(); 102 | img.crossOrigin = "anonymous"; 103 | img.onload = (ev) => { 104 | let texture = this.textureLoader(img); 105 | this.assets[path] = texture; 106 | this.toLoad--; 107 | this.loaded++; 108 | if (success) success(path, img); 109 | } 110 | img.onerror = (ev) => { 111 | this.errors[path] = `Couldn't load image ${path}`; 112 | this.toLoad--; 113 | this.loaded++; 114 | if (error) error(path, `Couldn't load image ${path}`); 115 | } 116 | img.src = path; 117 | } 118 | 119 | loadTextureData(path: string, data: string, 120 | success: (path: string, image: HTMLImageElement) => void = null, 121 | error: (path: string, error: string) => void = null) { 122 | path = this.pathPrefix + path; 123 | this.toLoad++; 124 | let img = new Image(); 125 | img.onload = (ev) => { 126 | let texture = this.textureLoader(img); 127 | this.assets[path] = texture; 128 | this.toLoad--; 129 | this.loaded++; 130 | if (success) success(path, img); 131 | } 132 | img.onerror = (ev) => { 133 | this.errors[path] = `Couldn't load image ${path}`; 134 | this.toLoad--; 135 | this.loaded++; 136 | if (error) error(path, `Couldn't load image ${path}`); 137 | } 138 | img.src = data; 139 | } 140 | 141 | loadTextureAtlas (path: string, 142 | success: (path: string, atlas: TextureAtlas) => void = null, 143 | error: (path: string, error: string) => void = null) { 144 | let parent = path.lastIndexOf("/") >= 0 ? path.substring(0, path.lastIndexOf("/")) : ""; 145 | path = this.pathPrefix + path; 146 | this.toLoad++; 147 | 148 | AssetManager.downloadText(path, (atlasData: string): void => { 149 | let pagesLoaded: any = { count: 0 }; 150 | let atlasPages = new Array(); 151 | try { 152 | let atlas = new TextureAtlas(atlasData, (path: string) => { 153 | atlasPages.push(parent + "/" + path); 154 | let image = document.createElement("img") as HTMLImageElement; 155 | image.width = 16; 156 | image.height = 16; 157 | return new FakeTexture(image); 158 | }); 159 | } catch (e) { 160 | let ex = e as Error; 161 | this.errors[path] = `Couldn't load texture atlas ${path}: ${ex.message}`; 162 | if (error) error(path, `Couldn't load texture atlas ${path}: ${ex.message}`); 163 | this.toLoad--; 164 | this.loaded++; 165 | return; 166 | } 167 | 168 | for (let atlasPage of atlasPages) { 169 | let pageLoadError = false; 170 | this.loadTexture(atlasPage, (imagePath: string, image: HTMLImageElement) => { 171 | pagesLoaded.count++; 172 | 173 | if (pagesLoaded.count == atlasPages.length) { 174 | if (!pageLoadError) { 175 | try { 176 | let atlas = new TextureAtlas(atlasData, (path: string) => { 177 | return this.get(parent + "/" + path); 178 | }); 179 | this.assets[path] = atlas; 180 | if (success) success(path, atlas); 181 | this.toLoad--; 182 | this.loaded++; 183 | } catch (e) { 184 | let ex = e as Error; 185 | this.errors[path] = `Couldn't load texture atlas ${path}: ${ex.message}`; 186 | if (error) error(path, `Couldn't load texture atlas ${path}: ${ex.message}`); 187 | this.toLoad--; 188 | this.loaded++; 189 | } 190 | } else { 191 | this.errors[path] = `Couldn't load texture atlas page ${imagePath}} of atlas ${path}`; 192 | if (error) error(path, `Couldn't load texture atlas page ${imagePath} of atlas ${path}`); 193 | this.toLoad--; 194 | this.loaded++; 195 | } 196 | } 197 | }, (imagePath: string, errorMessage: string) => { 198 | pageLoadError = true; 199 | pagesLoaded.count++; 200 | 201 | if (pagesLoaded.count == atlasPages.length) { 202 | this.errors[path] = `Couldn't load texture atlas page ${imagePath}} of atlas ${path}`; 203 | if (error) error(path, `Couldn't load texture atlas page ${imagePath} of atlas ${path}`); 204 | this.toLoad--; 205 | this.loaded++; 206 | } 207 | }); 208 | } 209 | }, (state: number, responseText: string): void => { 210 | this.errors[path] = `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`; 211 | if (error) error(path, `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`); 212 | this.toLoad--; 213 | this.loaded++; 214 | }); 215 | } 216 | 217 | get (path: string) { 218 | path = this.pathPrefix + path; 219 | return this.assets[path]; 220 | } 221 | 222 | remove (path: string) { 223 | path = this.pathPrefix + path; 224 | let asset = this.assets[path]; 225 | if ((asset).dispose) (asset).dispose(); 226 | this.assets[path] = null; 227 | } 228 | 229 | removeAll () { 230 | for (let key in this.assets) { 231 | let asset = this.assets[key]; 232 | if ((asset).dispose) (asset).dispose(); 233 | } 234 | this.assets = {}; 235 | } 236 | 237 | isLoadingComplete (): boolean { 238 | return this.toLoad == 0; 239 | } 240 | 241 | getToLoad (): number { 242 | return this.toLoad; 243 | } 244 | 245 | getLoaded (): number { 246 | return this.loaded; 247 | } 248 | 249 | dispose () { 250 | this.removeAll(); 251 | } 252 | 253 | hasErrors() { 254 | return Object.keys(this.errors).length > 0; 255 | } 256 | 257 | getErrors() { 258 | return this.errors; 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/core/AtlasAttachmentLoader.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class AtlasAttachmentLoader implements AttachmentLoader { 32 | atlas: TextureAtlas; 33 | 34 | constructor (atlas: TextureAtlas) { 35 | this.atlas = atlas; 36 | } 37 | 38 | /** @return May be null to not load an attachment. */ 39 | newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment { 40 | let region = this.atlas.findRegion(path); 41 | if (region == null) throw new Error("Region not found in atlas: " + path + " (region attachment: " + name + ")"); 42 | region.renderObject = region; 43 | let attachment = new RegionAttachment(name); 44 | attachment.setRegion(region); 45 | return attachment; 46 | } 47 | 48 | /** @return May be null to not load an attachment. */ 49 | newMeshAttachment (skin: Skin, name: string, path: string) : MeshAttachment { 50 | let region = this.atlas.findRegion(path); 51 | if (region == null) throw new Error("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); 52 | region.renderObject = region; 53 | let attachment = new MeshAttachment(name); 54 | attachment.region = region; 55 | return attachment; 56 | } 57 | 58 | /** @return May be null to not load an attachment. */ 59 | newBoundingBoxAttachment (skin: Skin, name: string) : BoundingBoxAttachment { 60 | return new BoundingBoxAttachment(name); 61 | } 62 | 63 | /** @return May be null to not load an attachment */ 64 | newPathAttachment (skin: Skin, name: string): PathAttachment { 65 | return new PathAttachment(name); 66 | } 67 | 68 | newPointAttachment(skin: Skin, name: string): PointAttachment { 69 | return new PointAttachment(name); 70 | } 71 | 72 | newClippingAttachment(skin: Skin, name: string): ClippingAttachment { 73 | return new ClippingAttachment(name); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/core/BlendMode.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export enum BlendMode { 32 | Normal, 33 | Additive, 34 | Multiply, 35 | Screen 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/core/Bone.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class Bone implements Updatable { 32 | data: BoneData; 33 | skeleton: Skeleton; 34 | parent: Bone; 35 | children = new Array(); 36 | x = 0; y = 0; rotation = 0; scaleX = 0; scaleY = 0; shearX = 0; shearY = 0; 37 | ax = 0; ay = 0; arotation = 0; ascaleX = 0; ascaleY = 0; ashearX = 0; ashearY = 0; 38 | appliedValid = false; 39 | 40 | a = 0; b = 0; worldX = 0; 41 | c = 0; d = 0; worldY = 0; 42 | 43 | sorted = false; 44 | 45 | /** @param parent May be null. */ 46 | constructor (data: BoneData, skeleton: Skeleton, parent: Bone) { 47 | if (data == null) throw new Error("data cannot be null."); 48 | if (skeleton == null) throw new Error("skeleton cannot be null."); 49 | this.data = data; 50 | this.skeleton = skeleton; 51 | this.parent = parent; 52 | this.setToSetupPose(); 53 | } 54 | 55 | /** Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. */ 56 | update () { 57 | this.updateWorldTransformWith(this.x, this.y, this.rotation, this.scaleX, this.scaleY, this.shearX, this.shearY); 58 | } 59 | 60 | /** Computes the world transform using the parent bone and this bone's local transform. */ 61 | updateWorldTransform () { 62 | this.updateWorldTransformWith(this.x, this.y, this.rotation, this.scaleX, this.scaleY, this.shearX, this.shearY); 63 | } 64 | 65 | /** Computes the world transform using the parent bone and the specified local transform. */ 66 | updateWorldTransformWith (x: number, y: number, rotation: number, scaleX: number, scaleY: number, shearX: number, shearY: number) { 67 | this.ax = x; 68 | this.ay = y; 69 | this.arotation = rotation; 70 | this.ascaleX = scaleX; 71 | this.ascaleY = scaleY; 72 | this.ashearX = shearX; 73 | this.ashearY = shearY; 74 | this.appliedValid = true; 75 | 76 | let parent = this.parent; 77 | if (parent == null) { // Root bone. 78 | let skeleton = this.skeleton; 79 | let rotationY = rotation + 90 + shearY; 80 | let sx = skeleton.scaleX; 81 | let sy = skeleton.scaleY; 82 | this.a = MathUtils.cosDeg(rotation + shearX) * scaleX * sx; 83 | this.b = MathUtils.cosDeg(rotationY) * scaleY * sy; 84 | this.c = MathUtils.sinDeg(rotation + shearX) * scaleX * sx; 85 | this.d = MathUtils.sinDeg(rotationY) * scaleY * sy; 86 | this.worldX = x * sx + skeleton.x; 87 | this.worldY = y * sy + skeleton.y; 88 | return; 89 | } 90 | 91 | let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; 92 | this.worldX = pa * x + pb * y + parent.worldX; 93 | this.worldY = pc * x + pd * y + parent.worldY; 94 | 95 | switch (this.data.transformMode) { 96 | case TransformMode.Normal: { 97 | let rotationY = rotation + 90 + shearY; 98 | let la = MathUtils.cosDeg(rotation + shearX) * scaleX; 99 | let lb = MathUtils.cosDeg(rotationY) * scaleY; 100 | let lc = MathUtils.sinDeg(rotation + shearX) * scaleX; 101 | let ld = MathUtils.sinDeg(rotationY) * scaleY; 102 | this.a = pa * la + pb * lc; 103 | this.b = pa * lb + pb * ld; 104 | this.c = pc * la + pd * lc; 105 | this.d = pc * lb + pd * ld; 106 | return; 107 | } 108 | case TransformMode.OnlyTranslation: { 109 | let rotationY = rotation + 90 + shearY; 110 | this.a = MathUtils.cosDeg(rotation + shearX) * scaleX; 111 | this.b = MathUtils.cosDeg(rotationY) * scaleY; 112 | this.c = MathUtils.sinDeg(rotation + shearX) * scaleX; 113 | this.d = MathUtils.sinDeg(rotationY) * scaleY; 114 | break; 115 | } 116 | case TransformMode.NoRotationOrReflection: { 117 | let s = pa * pa + pc * pc; 118 | let prx = 0; 119 | if (s > 0.0001) { 120 | s = Math.abs(pa * pd - pb * pc) / s; 121 | pb = pc * s; 122 | pd = pa * s; 123 | prx = Math.atan2(pc, pa) * MathUtils.radDeg; 124 | } else { 125 | pa = 0; 126 | pc = 0; 127 | prx = 90 - Math.atan2(pd, pb) * MathUtils.radDeg; 128 | } 129 | let rx = rotation + shearX - prx; 130 | let ry = rotation + shearY - prx + 90; 131 | let la = MathUtils.cosDeg(rx) * scaleX; 132 | let lb = MathUtils.cosDeg(ry) * scaleY; 133 | let lc = MathUtils.sinDeg(rx) * scaleX; 134 | let ld = MathUtils.sinDeg(ry) * scaleY; 135 | this.a = pa * la - pb * lc; 136 | this.b = pa * lb - pb * ld; 137 | this.c = pc * la + pd * lc; 138 | this.d = pc * lb + pd * ld; 139 | break; 140 | } 141 | case TransformMode.NoScale: 142 | case TransformMode.NoScaleOrReflection: { 143 | let cos = MathUtils.cosDeg(rotation); 144 | let sin = MathUtils.sinDeg(rotation); 145 | let za = (pa * cos + pb * sin) / this.skeleton.scaleX; 146 | let zc = (pc * cos + pd * sin) / this.skeleton.scaleY; 147 | let s = Math.sqrt(za * za + zc * zc); 148 | if (s > 0.00001) s = 1 / s; 149 | za *= s; 150 | zc *= s; 151 | s = Math.sqrt(za * za + zc * zc); 152 | if (this.data.transformMode == TransformMode.NoScale 153 | && (pa * pd - pb * pc < 0) != (this.skeleton.scaleX < 0 != this.skeleton.scaleY < 0)) s = -s; 154 | let r = Math.PI / 2 + Math.atan2(zc, za); 155 | let zb = Math.cos(r) * s; 156 | let zd = Math.sin(r) * s; 157 | let la = MathUtils.cosDeg(shearX) * scaleX; 158 | let lb = MathUtils.cosDeg(90 + shearY) * scaleY; 159 | let lc = MathUtils.sinDeg(shearX) * scaleX; 160 | let ld = MathUtils.sinDeg(90 + shearY) * scaleY; 161 | this.a = za * la + zb * lc; 162 | this.b = za * lb + zb * ld; 163 | this.c = zc * la + zd * lc; 164 | this.d = zc * lb + zd * ld; 165 | break; 166 | } 167 | } 168 | this.a *= this.skeleton.scaleX; 169 | this.b *= this.skeleton.scaleX; 170 | this.c *= this.skeleton.scaleY; 171 | this.d *= this.skeleton.scaleY; 172 | } 173 | 174 | setToSetupPose () { 175 | let data = this.data; 176 | this.x = data.x; 177 | this.y = data.y; 178 | this.rotation = data.rotation; 179 | this.scaleX = data.scaleX; 180 | this.scaleY = data.scaleY; 181 | this.shearX = data.shearX; 182 | this.shearY = data.shearY; 183 | } 184 | 185 | getWorldRotationX () { 186 | return Math.atan2(this.c, this.a) * MathUtils.radDeg; 187 | } 188 | 189 | getWorldRotationY () { 190 | return Math.atan2(this.d, this.b) * MathUtils.radDeg; 191 | } 192 | 193 | getWorldScaleX () { 194 | return Math.sqrt(this.a * this.a + this.c * this.c); 195 | } 196 | 197 | getWorldScaleY () { 198 | return Math.sqrt(this.b * this.b + this.d * this.d); 199 | } 200 | 201 | /** Computes the individual applied transform values from the world transform. This can be useful to perform processing using 202 | * the applied transform after the world transform has been modified directly (eg, by a constraint). 203 | *

204 | * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. */ 205 | updateAppliedTransform () { 206 | this.appliedValid = true; 207 | let parent = this.parent; 208 | if (parent == null) { 209 | this.ax = this.worldX; 210 | this.ay = this.worldY; 211 | this.arotation = Math.atan2(this.c, this.a) * MathUtils.radDeg; 212 | this.ascaleX = Math.sqrt(this.a * this.a + this.c * this.c); 213 | this.ascaleY = Math.sqrt(this.b * this.b + this.d * this.d); 214 | this.ashearX = 0; 215 | this.ashearY = Math.atan2(this.a * this.b + this.c * this.d, this.a * this.d - this.b * this.c) * MathUtils.radDeg; 216 | return; 217 | } 218 | let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; 219 | let pid = 1 / (pa * pd - pb * pc); 220 | let dx = this.worldX - parent.worldX, dy = this.worldY - parent.worldY; 221 | this.ax = (dx * pd * pid - dy * pb * pid); 222 | this.ay = (dy * pa * pid - dx * pc * pid); 223 | let ia = pid * pd; 224 | let id = pid * pa; 225 | let ib = pid * pb; 226 | let ic = pid * pc; 227 | let ra = ia * this.a - ib * this.c; 228 | let rb = ia * this.b - ib * this.d; 229 | let rc = id * this.c - ic * this.a; 230 | let rd = id * this.d - ic * this.b; 231 | this.ashearX = 0; 232 | this.ascaleX = Math.sqrt(ra * ra + rc * rc); 233 | if (this.ascaleX > 0.0001) { 234 | let det = ra * rd - rb * rc; 235 | this.ascaleY = det / this.ascaleX; 236 | this.ashearY = Math.atan2(ra * rb + rc * rd, det) * MathUtils.radDeg; 237 | this.arotation = Math.atan2(rc, ra) * MathUtils.radDeg; 238 | } else { 239 | this.ascaleX = 0; 240 | this.ascaleY = Math.sqrt(rb * rb + rd * rd); 241 | this.ashearY = 0; 242 | this.arotation = 90 - Math.atan2(rd, rb) * MathUtils.radDeg; 243 | } 244 | } 245 | 246 | worldToLocal (world: Vector2) { 247 | let a = this.a, b = this.b, c = this.c, d = this.d; 248 | let invDet = 1 / (a * d - b * c); 249 | let x = world.x - this.worldX, y = world.y - this.worldY; 250 | world.x = (x * d * invDet - y * b * invDet); 251 | world.y = (y * a * invDet - x * c * invDet); 252 | return world; 253 | } 254 | 255 | localToWorld (local: Vector2) { 256 | let x = local.x, y = local.y; 257 | local.x = x * this.a + y * this.b + this.worldX; 258 | local.y = x * this.c + y * this.d + this.worldY; 259 | return local; 260 | } 261 | 262 | worldToLocalRotation (worldRotation: number) { 263 | let sin = MathUtils.sinDeg(worldRotation), cos = MathUtils.cosDeg(worldRotation); 264 | return Math.atan2(this.a * sin - this.c * cos, this.d * cos - this.b * sin) * MathUtils.radDeg + this.rotation - this.shearX; 265 | } 266 | 267 | localToWorldRotation (localRotation: number) { 268 | localRotation -= this.rotation - this.shearX; 269 | let sin = MathUtils.sinDeg(localRotation), cos = MathUtils.cosDeg(localRotation); 270 | return Math.atan2(cos * this.c + sin * this.d, cos * this.a + sin * this.b) * MathUtils.radDeg; 271 | } 272 | 273 | rotateWorld (degrees: number) { 274 | let a = this.a, b = this.b, c = this.c, d = this.d; 275 | let cos = MathUtils.cosDeg(degrees), sin = MathUtils.sinDeg(degrees); 276 | this.a = cos * a - sin * c; 277 | this.b = cos * b - sin * d; 278 | this.c = sin * a + cos * c; 279 | this.d = sin * b + cos * d; 280 | this.appliedValid = false; 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/core/BoneData.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class BoneData { 32 | index: number; 33 | name: string; 34 | parent: BoneData; 35 | length: number; 36 | x = 0; y = 0; rotation = 0; scaleX = 1; scaleY = 1; shearX = 0; shearY = 0; 37 | transformMode = TransformMode.Normal; 38 | 39 | constructor (index: number, name: string, parent: BoneData) { 40 | if (index < 0) throw new Error("index must be >= 0."); 41 | if (name == null) throw new Error("name cannot be null."); 42 | this.index = index; 43 | this.name = name; 44 | this.parent = parent; 45 | } 46 | } 47 | 48 | export enum TransformMode { 49 | Normal, OnlyTranslation, NoRotationOrReflection, NoScale, NoScaleOrReflection 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/core/Constraint.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export interface Constraint extends Updatable { 32 | getOrder(): number; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core/Event.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class Event { 32 | data: EventData; 33 | intValue: number; 34 | floatValue: number; 35 | stringValue: string; 36 | time: number; 37 | volume: number; 38 | balance: number; 39 | 40 | constructor (time: number, data: EventData) { 41 | if (data == null) throw new Error("data cannot be null."); 42 | this.time = time; 43 | this.data = data; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/core/EventData.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class EventData { 32 | name: string; 33 | intValue: number; 34 | floatValue: number; 35 | stringValue: string; 36 | audioPath: string; 37 | volume: number; 38 | balance: number; 39 | 40 | constructor (name: string) { 41 | this.name = name; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/core/IkConstraint.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class IkConstraint implements Constraint { 32 | data: IkConstraintData; 33 | bones: Array; 34 | target: Bone; 35 | bendDirection = 0; 36 | compress = false; 37 | stretch = false; 38 | mix = 1; 39 | 40 | constructor (data: IkConstraintData, skeleton: Skeleton) { 41 | if (data == null) throw new Error("data cannot be null."); 42 | if (skeleton == null) throw new Error("skeleton cannot be null."); 43 | this.data = data; 44 | this.mix = data.mix; 45 | this.bendDirection = data.bendDirection; 46 | this.compress = data.compress; 47 | this.stretch = data.stretch; 48 | 49 | this.bones = new Array(); 50 | for (let i = 0; i < data.bones.length; i++) 51 | this.bones.push(skeleton.findBone(data.bones[i].name)); 52 | this.target = skeleton.findBone(data.target.name); 53 | } 54 | 55 | getOrder () { 56 | return this.data.order; 57 | } 58 | 59 | apply () { 60 | this.update(); 61 | } 62 | 63 | update () { 64 | let target = this.target; 65 | let bones = this.bones; 66 | switch (bones.length) { 67 | case 1: 68 | this.apply1(bones[0], target.worldX, target.worldY, this.compress, this.stretch, this.data.uniform, this.mix); 69 | break; 70 | case 2: 71 | this.apply2(bones[0], bones[1], target.worldX, target.worldY, this.bendDirection, this.stretch, this.mix); 72 | break; 73 | } 74 | } 75 | 76 | /** Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified in the world 77 | * coordinate system. */ 78 | apply1 (bone: Bone, targetX: number, targetY: number, compress: boolean, stretch: boolean, uniform: boolean, alpha: number) { 79 | if (!bone.appliedValid) bone.updateAppliedTransform(); 80 | let p = bone.parent; 81 | let id = 1 / (p.a * p.d - p.b * p.c); 82 | let x = targetX - p.worldX, y = targetY - p.worldY; 83 | let tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay; 84 | let rotationIK = Math.atan2(ty, tx) * MathUtils.radDeg - bone.ashearX - bone.arotation; 85 | if (bone.ascaleX < 0) rotationIK += 180; 86 | if (rotationIK > 180) 87 | rotationIK -= 360; 88 | else if (rotationIK < -180) rotationIK += 360; 89 | let sx = bone.ascaleX, sy = bone.ascaleY; 90 | if (compress || stretch) { 91 | let b = bone.data.length * sx, dd = Math.sqrt(tx * tx + ty * ty); 92 | if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001) { 93 | let s = (dd / b - 1) * alpha + 1; 94 | sx *= s; 95 | if (uniform) sy *= s; 96 | } 97 | } 98 | bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, 99 | bone.ashearY); 100 | } 101 | 102 | /** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The 103 | * target is specified in the world coordinate system. 104 | * @param child A direct descendant of the parent bone. */ 105 | apply2 (parent: Bone, child: Bone, targetX: number, targetY: number, bendDir: number, stretch: boolean, alpha: number) { 106 | if (alpha == 0) { 107 | child.updateWorldTransform(); 108 | return; 109 | } 110 | if (!parent.appliedValid) parent.updateAppliedTransform(); 111 | if (!child.appliedValid) child.updateAppliedTransform(); 112 | let px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX; 113 | let os1 = 0, os2 = 0, s2 = 0; 114 | if (psx < 0) { 115 | psx = -psx; 116 | os1 = 180; 117 | s2 = -1; 118 | } else { 119 | os1 = 0; 120 | s2 = 1; 121 | } 122 | if (psy < 0) { 123 | psy = -psy; 124 | s2 = -s2; 125 | } 126 | if (csx < 0) { 127 | csx = -csx; 128 | os2 = 180; 129 | } else 130 | os2 = 0; 131 | let cx = child.ax, cy = 0, cwx = 0, cwy = 0, a = parent.a, b = parent.b, c = parent.c, d = parent.d; 132 | let u = Math.abs(psx - psy) <= 0.0001; 133 | if (!u) { 134 | cy = 0; 135 | cwx = a * cx + parent.worldX; 136 | cwy = c * cx + parent.worldY; 137 | } else { 138 | cy = child.ay; 139 | cwx = a * cx + b * cy + parent.worldX; 140 | cwy = c * cx + d * cy + parent.worldY; 141 | } 142 | let pp = parent.parent; 143 | a = pp.a; 144 | b = pp.b; 145 | c = pp.c; 146 | d = pp.d; 147 | let id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; 148 | let tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py, dd = tx * tx + ty * ty; 149 | x = cwx - pp.worldX; 150 | y = cwy - pp.worldY; 151 | let dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; 152 | let l1 = Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1 = 0, a2 = 0; 153 | outer: 154 | if (u) { 155 | l2 *= psx; 156 | let cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); 157 | if (cos < -1) 158 | cos = -1; 159 | else if (cos > 1) { 160 | cos = 1; 161 | if (stretch && l1 + l2 > 0.0001) sx *= (Math.sqrt(dd) / (l1 + l2) - 1) * alpha + 1; 162 | } 163 | a2 = Math.acos(cos) * bendDir; 164 | a = l1 + l2 * cos; 165 | b = l2 * Math.sin(a2); 166 | a1 = Math.atan2(ty * a - tx * b, tx * a + ty * b); 167 | } else { 168 | a = psx * l2; 169 | b = psy * l2; 170 | let aa = a * a, bb = b * b, ta = Math.atan2(ty, tx); 171 | c = bb * l1 * l1 + aa * dd - aa * bb; 172 | let c1 = -2 * bb * l1, c2 = bb - aa; 173 | d = c1 * c1 - 4 * c2 * c; 174 | if (d >= 0) { 175 | let q = Math.sqrt(d); 176 | if (c1 < 0) q = -q; 177 | q = -(c1 + q) / 2; 178 | let r0 = q / c2, r1 = c / q; 179 | let r = Math.abs(r0) < Math.abs(r1) ? r0 : r1; 180 | if (r * r <= dd) { 181 | y = Math.sqrt(dd - r * r) * bendDir; 182 | a1 = ta - Math.atan2(y, r); 183 | a2 = Math.atan2(y / psy, (r - l1) / psx); 184 | break outer; 185 | } 186 | } 187 | let minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; 188 | let maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; 189 | c = -a * l1 / (aa - bb); 190 | if (c >= -1 && c <= 1) { 191 | c = Math.acos(c); 192 | x = a * Math.cos(c) + l1; 193 | y = b * Math.sin(c); 194 | d = x * x + y * y; 195 | if (d < minDist) { 196 | minAngle = c; 197 | minDist = d; 198 | minX = x; 199 | minY = y; 200 | } 201 | if (d > maxDist) { 202 | maxAngle = c; 203 | maxDist = d; 204 | maxX = x; 205 | maxY = y; 206 | } 207 | } 208 | if (dd <= (minDist + maxDist) / 2) { 209 | a1 = ta - Math.atan2(minY * bendDir, minX); 210 | a2 = minAngle * bendDir; 211 | } else { 212 | a1 = ta - Math.atan2(maxY * bendDir, maxX); 213 | a2 = maxAngle * bendDir; 214 | } 215 | } 216 | let os = Math.atan2(cy, cx) * s2; 217 | let rotation = parent.arotation; 218 | a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation; 219 | if (a1 > 180) 220 | a1 -= 360; 221 | else if (a1 < -180) a1 += 360; 222 | parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0); 223 | rotation = child.arotation; 224 | a2 = ((a2 + os) * MathUtils.radDeg - child.ashearX) * s2 + os2 - rotation; 225 | if (a2 > 180) 226 | a2 -= 360; 227 | else if (a2 < -180) a2 += 360; 228 | child.updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/core/IkConstraintData.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class IkConstraintData { 32 | name: string; 33 | order = 0; 34 | bones = new Array(); 35 | target: BoneData; 36 | bendDirection = 1; 37 | compress = false; 38 | stretch = false; 39 | uniform = false; 40 | mix = 1; 41 | 42 | constructor (name: string) { 43 | this.name = name; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/core/PathConstraint.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class PathConstraint implements Constraint { 32 | static NONE = -1; static BEFORE = -2; static AFTER = -3; 33 | static epsilon = 0.00001; 34 | 35 | data: PathConstraintData; 36 | bones: Array; 37 | target: Slot; 38 | position = 0; spacing = 0; rotateMix = 0; translateMix = 0; 39 | 40 | spaces = new Array(); positions = new Array(); 41 | world = new Array(); curves = new Array(); lengths = new Array(); 42 | segments = new Array(); 43 | 44 | constructor (data: PathConstraintData, skeleton: Skeleton) { 45 | if (data == null) throw new Error("data cannot be null."); 46 | if (skeleton == null) throw new Error("skeleton cannot be null."); 47 | this.data = data; 48 | this.bones = new Array(); 49 | for (let i = 0, n = data.bones.length; i < n; i++) 50 | this.bones.push(skeleton.findBone(data.bones[i].name)); 51 | this.target = skeleton.findSlot(data.target.name); 52 | this.position = data.position; 53 | this.spacing = data.spacing; 54 | this.rotateMix = data.rotateMix; 55 | this.translateMix = data.translateMix; 56 | } 57 | 58 | apply () { 59 | this.update(); 60 | } 61 | 62 | update () { 63 | let attachment = this.target.getAttachment(); 64 | if (!(attachment instanceof PathAttachment)) return; 65 | 66 | let rotateMix = this.rotateMix, translateMix = this.translateMix; 67 | let translate = translateMix > 0, rotate = rotateMix > 0; 68 | if (!translate && !rotate) return; 69 | 70 | let data = this.data; 71 | let percentSpacing = data.spacingMode == SpacingMode.Percent; 72 | let rotateMode = data.rotateMode; 73 | let tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; 74 | let boneCount = this.bones.length, spacesCount = tangents ? boneCount : boneCount + 1; 75 | let bones = this.bones; 76 | let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array = null; 77 | let spacing = this.spacing; 78 | if (scale || !percentSpacing) { 79 | if (scale) lengths = Utils.setArraySize(this.lengths, boneCount); 80 | let lengthSpacing = data.spacingMode == SpacingMode.Length; 81 | for (let i = 0, n = spacesCount - 1; i < n;) { 82 | let bone = bones[i]; 83 | let setupLength = bone.data.length; 84 | if (setupLength < PathConstraint.epsilon) { 85 | if (scale) lengths[i] = 0; 86 | spaces[++i] = 0; 87 | } else if (percentSpacing) { 88 | if (scale) { 89 | let x = setupLength * bone.a, y = setupLength * bone.c; 90 | let length = Math.sqrt(x * x + y * y); 91 | lengths[i] = length; 92 | } 93 | spaces[++i] = spacing; 94 | } else { 95 | let x = setupLength * bone.a, y = setupLength * bone.c; 96 | let length = Math.sqrt(x * x + y * y); 97 | if (scale) lengths[i] = length; 98 | spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; 99 | } 100 | } 101 | } else { 102 | for (let i = 1; i < spacesCount; i++) 103 | spaces[i] = spacing; 104 | } 105 | 106 | let positions = this.computeWorldPositions(attachment, spacesCount, tangents, 107 | data.positionMode == PositionMode.Percent, percentSpacing); 108 | let boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; 109 | let tip = false; 110 | if (offsetRotation == 0) 111 | tip = rotateMode == RotateMode.Chain; 112 | else { 113 | tip = false; 114 | let p = this.target.bone; 115 | offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.degRad : -MathUtils.degRad; 116 | } 117 | for (let i = 0, p = 3; i < boneCount; i++, p += 3) { 118 | let bone = bones[i]; 119 | bone.worldX += (boneX - bone.worldX) * translateMix; 120 | bone.worldY += (boneY - bone.worldY) * translateMix; 121 | let x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; 122 | if (scale) { 123 | let length = lengths[i]; 124 | if (length != 0) { 125 | let s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; 126 | bone.a *= s; 127 | bone.c *= s; 128 | } 129 | } 130 | boneX = x; 131 | boneY = y; 132 | if (rotate) { 133 | let a = bone.a, b = bone.b, c = bone.c, d = bone.d, r = 0, cos = 0, sin = 0; 134 | if (tangents) 135 | r = positions[p - 1]; 136 | else if (spaces[i + 1] == 0) 137 | r = positions[p + 2]; 138 | else 139 | r = Math.atan2(dy, dx); 140 | r -= Math.atan2(c, a); 141 | if (tip) { 142 | cos = Math.cos(r); 143 | sin = Math.sin(r); 144 | let length = bone.data.length; 145 | boneX += (length * (cos * a - sin * c) - dx) * rotateMix; 146 | boneY += (length * (sin * a + cos * c) - dy) * rotateMix; 147 | } else { 148 | r += offsetRotation; 149 | } 150 | if (r > MathUtils.PI) 151 | r -= MathUtils.PI2; 152 | else if (r < -MathUtils.PI) // 153 | r += MathUtils.PI2; 154 | r *= rotateMix; 155 | cos = Math.cos(r); 156 | sin = Math.sin(r); 157 | bone.a = cos * a - sin * c; 158 | bone.b = cos * b - sin * d; 159 | bone.c = sin * a + cos * c; 160 | bone.d = sin * b + cos * d; 161 | } 162 | bone.appliedValid = false; 163 | } 164 | } 165 | 166 | computeWorldPositions (path: PathAttachment, spacesCount: number, tangents: boolean, percentPosition: boolean, 167 | percentSpacing: boolean) { 168 | let target = this.target; 169 | let position = this.position; 170 | let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array = null; 171 | let closed = path.closed; 172 | let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PathConstraint.NONE; 173 | 174 | if (!path.constantSpeed) { 175 | let lengths = path.lengths; 176 | curveCount -= closed ? 1 : 2; 177 | let pathLength = lengths[curveCount]; 178 | if (percentPosition) position *= pathLength; 179 | if (percentSpacing) { 180 | for (let i = 1; i < spacesCount; i++) 181 | spaces[i] *= pathLength; 182 | } 183 | world = Utils.setArraySize(this.world, 8); 184 | for (let i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { 185 | let space = spaces[i]; 186 | position += space; 187 | let p = position; 188 | 189 | if (closed) { 190 | p %= pathLength; 191 | if (p < 0) p += pathLength; 192 | curve = 0; 193 | } else if (p < 0) { 194 | if (prevCurve != PathConstraint.BEFORE) { 195 | prevCurve = PathConstraint.BEFORE; 196 | path.computeWorldVertices(target, 2, 4, world, 0, 2); 197 | } 198 | this.addBeforePosition(p, world, 0, out, o); 199 | continue; 200 | } else if (p > pathLength) { 201 | if (prevCurve != PathConstraint.AFTER) { 202 | prevCurve = PathConstraint.AFTER; 203 | path.computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); 204 | } 205 | this.addAfterPosition(p - pathLength, world, 0, out, o); 206 | continue; 207 | } 208 | 209 | // Determine curve containing position. 210 | for (;; curve++) { 211 | let length = lengths[curve]; 212 | if (p > length) continue; 213 | if (curve == 0) 214 | p /= length; 215 | else { 216 | let prev = lengths[curve - 1]; 217 | p = (p - prev) / (length - prev); 218 | } 219 | break; 220 | } 221 | if (curve != prevCurve) { 222 | prevCurve = curve; 223 | if (closed && curve == curveCount) { 224 | path.computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2); 225 | path.computeWorldVertices(target, 0, 4, world, 4, 2); 226 | } else 227 | path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); 228 | } 229 | this.addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o, 230 | tangents || (i > 0 && space == 0)); 231 | } 232 | return out; 233 | } 234 | 235 | // World vertices. 236 | if (closed) { 237 | verticesLength += 2; 238 | world = Utils.setArraySize(this.world, verticesLength); 239 | path.computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); 240 | path.computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); 241 | world[verticesLength - 2] = world[0]; 242 | world[verticesLength - 1] = world[1]; 243 | } else { 244 | curveCount--; 245 | verticesLength -= 4; 246 | world = Utils.setArraySize(this.world, verticesLength); 247 | path.computeWorldVertices(target, 2, verticesLength, world, 0, 2); 248 | } 249 | 250 | // Curve lengths. 251 | let curves = Utils.setArraySize(this.curves, curveCount); 252 | let pathLength = 0; 253 | let x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0; 254 | let tmpx = 0, tmpy = 0, dddfx = 0, dddfy = 0, ddfx = 0, ddfy = 0, dfx = 0, dfy = 0; 255 | for (let i = 0, w = 2; i < curveCount; i++, w += 6) { 256 | cx1 = world[w]; 257 | cy1 = world[w + 1]; 258 | cx2 = world[w + 2]; 259 | cy2 = world[w + 3]; 260 | x2 = world[w + 4]; 261 | y2 = world[w + 5]; 262 | tmpx = (x1 - cx1 * 2 + cx2) * 0.1875; 263 | tmpy = (y1 - cy1 * 2 + cy2) * 0.1875; 264 | dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375; 265 | dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375; 266 | ddfx = tmpx * 2 + dddfx; 267 | ddfy = tmpy * 2 + dddfy; 268 | dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667; 269 | dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667; 270 | pathLength += Math.sqrt(dfx * dfx + dfy * dfy); 271 | dfx += ddfx; 272 | dfy += ddfy; 273 | ddfx += dddfx; 274 | ddfy += dddfy; 275 | pathLength += Math.sqrt(dfx * dfx + dfy * dfy); 276 | dfx += ddfx; 277 | dfy += ddfy; 278 | pathLength += Math.sqrt(dfx * dfx + dfy * dfy); 279 | dfx += ddfx + dddfx; 280 | dfy += ddfy + dddfy; 281 | pathLength += Math.sqrt(dfx * dfx + dfy * dfy); 282 | curves[i] = pathLength; 283 | x1 = x2; 284 | y1 = y2; 285 | } 286 | if (percentPosition) 287 | position *= pathLength; 288 | else 289 | position *= pathLength / path.lengths[curveCount - 1]; 290 | if (percentSpacing) { 291 | for (let i = 1; i < spacesCount; i++) 292 | spaces[i] *= pathLength; 293 | } 294 | 295 | let segments = this.segments; 296 | let curveLength = 0; 297 | for (let i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { 298 | let space = spaces[i]; 299 | position += space; 300 | let p = position; 301 | 302 | if (closed) { 303 | p %= pathLength; 304 | if (p < 0) p += pathLength; 305 | curve = 0; 306 | } else if (p < 0) { 307 | this.addBeforePosition(p, world, 0, out, o); 308 | continue; 309 | } else if (p > pathLength) { 310 | this.addAfterPosition(p - pathLength, world, verticesLength - 4, out, o); 311 | continue; 312 | } 313 | 314 | // Determine curve containing position. 315 | for (;; curve++) { 316 | let length = curves[curve]; 317 | if (p > length) continue; 318 | if (curve == 0) 319 | p /= length; 320 | else { 321 | let prev = curves[curve - 1]; 322 | p = (p - prev) / (length - prev); 323 | } 324 | break; 325 | } 326 | 327 | // Curve segment lengths. 328 | if (curve != prevCurve) { 329 | prevCurve = curve; 330 | let ii = curve * 6; 331 | x1 = world[ii]; 332 | y1 = world[ii + 1]; 333 | cx1 = world[ii + 2]; 334 | cy1 = world[ii + 3]; 335 | cx2 = world[ii + 4]; 336 | cy2 = world[ii + 5]; 337 | x2 = world[ii + 6]; 338 | y2 = world[ii + 7]; 339 | tmpx = (x1 - cx1 * 2 + cx2) * 0.03; 340 | tmpy = (y1 - cy1 * 2 + cy2) * 0.03; 341 | dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006; 342 | dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006; 343 | ddfx = tmpx * 2 + dddfx; 344 | ddfy = tmpy * 2 + dddfy; 345 | dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667; 346 | dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667; 347 | curveLength = Math.sqrt(dfx * dfx + dfy * dfy); 348 | segments[0] = curveLength; 349 | for (ii = 1; ii < 8; ii++) { 350 | dfx += ddfx; 351 | dfy += ddfy; 352 | ddfx += dddfx; 353 | ddfy += dddfy; 354 | curveLength += Math.sqrt(dfx * dfx + dfy * dfy); 355 | segments[ii] = curveLength; 356 | } 357 | dfx += ddfx; 358 | dfy += ddfy; 359 | curveLength += Math.sqrt(dfx * dfx + dfy * dfy); 360 | segments[8] = curveLength; 361 | dfx += ddfx + dddfx; 362 | dfy += ddfy + dddfy; 363 | curveLength += Math.sqrt(dfx * dfx + dfy * dfy); 364 | segments[9] = curveLength; 365 | segment = 0; 366 | } 367 | 368 | // Weight by segment length. 369 | p *= curveLength; 370 | for (;; segment++) { 371 | let length = segments[segment]; 372 | if (p > length) continue; 373 | if (segment == 0) 374 | p /= length; 375 | else { 376 | let prev = segments[segment - 1]; 377 | p = segment + (p - prev) / (length - prev); 378 | } 379 | break; 380 | } 381 | this.addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0)); 382 | } 383 | return out; 384 | } 385 | 386 | addBeforePosition (p: number, temp: Array, i: number, out: Array, o: number) { 387 | let x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = Math.atan2(dy, dx); 388 | out[o] = x1 + p * Math.cos(r); 389 | out[o + 1] = y1 + p * Math.sin(r); 390 | out[o + 2] = r; 391 | } 392 | 393 | addAfterPosition (p: number, temp: Array, i: number, out: Array, o: number) { 394 | let x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = Math.atan2(dy, dx); 395 | out[o] = x1 + p * Math.cos(r); 396 | out[o + 1] = y1 + p * Math.sin(r); 397 | out[o + 2] = r; 398 | } 399 | 400 | addCurvePosition (p: number, x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, 401 | out: Array, o: number, tangents: boolean) { 402 | if (p == 0 || isNaN(p)) { 403 | out[o] = x1; 404 | out[o + 1] = y1; 405 | out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1); 406 | return; 407 | } 408 | let tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u; 409 | let ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p; 410 | let x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; 411 | out[o] = x; 412 | out[o + 1] = y; 413 | if (tangents) { 414 | if (p < 0.001) 415 | out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1); 416 | else 417 | out[o + 2] = Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); 418 | } 419 | } 420 | 421 | getOrder () { 422 | return this.data.order; 423 | } 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /src/core/PathConstraintData.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class PathConstraintData { 32 | name: string; 33 | order = 0; 34 | bones = new Array(); 35 | target: SlotData; 36 | positionMode: PositionMode; 37 | spacingMode: SpacingMode; 38 | rotateMode: RotateMode; 39 | offsetRotation: number; 40 | position: number; spacing: number; rotateMix: number; translateMix: number; 41 | 42 | constructor (name: string) { 43 | this.name = name; 44 | } 45 | } 46 | 47 | export enum PositionMode { 48 | Fixed, Percent 49 | } 50 | 51 | export enum SpacingMode { 52 | Length, Fixed, Percent 53 | } 54 | 55 | export enum RotateMode { 56 | Tangent, Chain, ChainScale 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/core/SharedAssetManager.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | class Assets { 32 | clientId: string; 33 | toLoad = new Array(); 34 | assets: Map = {}; 35 | textureLoader: (image: HTMLImageElement) => any; 36 | 37 | constructor(clientId: string) { 38 | this.clientId = clientId; 39 | } 40 | 41 | loaded() { 42 | let i = 0; 43 | for (let v in this.assets) i++; 44 | return i; 45 | } 46 | } 47 | 48 | export class SharedAssetManager implements Disposable { 49 | private pathPrefix: string; 50 | private clientAssets: Map = {}; 51 | private queuedAssets: Map = {}; 52 | private rawAssets: Map = {} 53 | private errors: Map = {}; 54 | 55 | constructor (pathPrefix: string = "") { 56 | this.pathPrefix = pathPrefix; 57 | } 58 | 59 | private queueAsset(clientId: string, textureLoader: (image: HTMLImageElement) => any, path: string): boolean { 60 | let clientAssets = this.clientAssets[clientId]; 61 | if (clientAssets === null || clientAssets === undefined) { 62 | clientAssets = new Assets(clientId); 63 | this.clientAssets[clientId] = clientAssets; 64 | } 65 | if (textureLoader !== null) clientAssets.textureLoader = textureLoader; 66 | clientAssets.toLoad.push(path); 67 | 68 | // check if already queued, in which case we can skip actual 69 | // loading 70 | if (this.queuedAssets[path] === path) { 71 | return false; 72 | } else { 73 | this.queuedAssets[path] = path; 74 | return true; 75 | } 76 | } 77 | 78 | loadText(clientId: string, path: string) { 79 | path = this.pathPrefix + path; 80 | if (!this.queueAsset(clientId, null, path)) return; 81 | let request = new XMLHttpRequest(); 82 | request.onreadystatechange = () => { 83 | if (request.readyState == XMLHttpRequest.DONE) { 84 | if (request.status >= 200 && request.status < 300) { 85 | this.rawAssets[path] = request.responseText; 86 | } else { 87 | this.errors[path] = `Couldn't load text ${path}: status ${request.status}, ${request.responseText}`; 88 | } 89 | } 90 | }; 91 | request.open("GET", path, true); 92 | request.send(); 93 | } 94 | 95 | loadJson(clientId: string, path: string) { 96 | path = this.pathPrefix + path; 97 | if (!this.queueAsset(clientId, null, path)) return; 98 | let request = new XMLHttpRequest(); 99 | request.onreadystatechange = () => { 100 | if (request.readyState == XMLHttpRequest.DONE) { 101 | if (request.status >= 200 && request.status < 300) { 102 | this.rawAssets[path] = JSON.parse(request.responseText); 103 | } else { 104 | this.errors[path] = `Couldn't load text ${path}: status ${request.status}, ${request.responseText}`; 105 | } 106 | } 107 | }; 108 | request.open("GET", path, true); 109 | request.send(); 110 | } 111 | 112 | loadTexture (clientId: string, textureLoader: (image: HTMLImageElement) => any, path: string) { 113 | path = this.pathPrefix + path; 114 | if (!this.queueAsset(clientId, textureLoader, path)) return; 115 | 116 | let img = new Image(); 117 | img.src = path; 118 | img.crossOrigin = "anonymous"; 119 | img.onload = (ev) => { 120 | this.rawAssets[path] = img; 121 | } 122 | img.onerror = (ev) => { 123 | this.errors[path] = `Couldn't load image ${path}`; 124 | } 125 | } 126 | 127 | get (clientId: string, path: string) { 128 | path = this.pathPrefix + path; 129 | let clientAssets = this.clientAssets[clientId]; 130 | if (clientAssets === null || clientAssets === undefined) return true; 131 | return clientAssets.assets[path]; 132 | } 133 | 134 | private updateClientAssets(clientAssets: Assets): void { 135 | for (let i = 0; i < clientAssets.toLoad.length; i++) { 136 | let path = clientAssets.toLoad[i]; 137 | let asset = clientAssets.assets[path]; 138 | if (asset === null || asset === undefined) { 139 | let rawAsset = this.rawAssets[path]; 140 | if (rawAsset === null || rawAsset === undefined) continue; 141 | if (rawAsset instanceof HTMLImageElement) { 142 | clientAssets.assets[path] = clientAssets.textureLoader(rawAsset); 143 | } else { 144 | clientAssets.assets[path] = rawAsset; 145 | } 146 | } 147 | } 148 | } 149 | 150 | isLoadingComplete (clientId: string): boolean { 151 | let clientAssets = this.clientAssets[clientId]; 152 | if (clientAssets === null || clientAssets === undefined) return true; 153 | this.updateClientAssets(clientAssets); 154 | return clientAssets.toLoad.length == clientAssets.loaded(); 155 | 156 | } 157 | 158 | /*remove (clientId: string, path: string) { 159 | path = this.pathPrefix + path; 160 | let asset = this.assets[path]; 161 | if ((asset).dispose) (asset).dispose(); 162 | this.assets[path] = null; 163 | } 164 | 165 | removeAll () { 166 | for (let key in this.assets) { 167 | let asset = this.assets[key]; 168 | if ((asset).dispose) (asset).dispose(); 169 | } 170 | this.assets = {}; 171 | }*/ 172 | 173 | dispose () { 174 | // this.removeAll(); 175 | } 176 | 177 | hasErrors() { 178 | return Object.keys(this.errors).length > 0; 179 | } 180 | 181 | getErrors() { 182 | return this.errors; 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/core/SkeletonBounds.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class SkeletonBounds { 32 | minX = 0; minY = 0; maxX = 0; maxY = 0; 33 | boundingBoxes = new Array(); 34 | polygons = new Array>(); 35 | private polygonPool = new Pool>(() => { 36 | return Utils.newFloatArray(16); 37 | }); 38 | 39 | update (skeleton: Skeleton, updateAabb: boolean) { 40 | if (skeleton == null) throw new Error("skeleton cannot be null."); 41 | let boundingBoxes = this.boundingBoxes; 42 | let polygons = this.polygons; 43 | let polygonPool = this.polygonPool; 44 | let slots = skeleton.slots; 45 | let slotCount = slots.length; 46 | 47 | boundingBoxes.length = 0; 48 | polygonPool.freeAll(polygons); 49 | polygons.length = 0; 50 | 51 | for (let i = 0; i < slotCount; i++) { 52 | let slot = slots[i]; 53 | let attachment = slot.getAttachment(); 54 | if (attachment instanceof BoundingBoxAttachment) { 55 | let boundingBox = attachment as BoundingBoxAttachment; 56 | boundingBoxes.push(boundingBox); 57 | 58 | let polygon = polygonPool.obtain(); 59 | if (polygon.length != boundingBox.worldVerticesLength) { 60 | polygon = Utils.newFloatArray(boundingBox.worldVerticesLength); 61 | } 62 | polygons.push(polygon); 63 | boundingBox.computeWorldVertices(slot, 0, boundingBox.worldVerticesLength, polygon, 0, 2); 64 | } 65 | } 66 | 67 | if (updateAabb) { 68 | this.aabbCompute(); 69 | } else { 70 | this.minX = Number.POSITIVE_INFINITY; 71 | this.minY = Number.POSITIVE_INFINITY; 72 | this.maxX = Number.NEGATIVE_INFINITY; 73 | this.maxY = Number.NEGATIVE_INFINITY; 74 | } 75 | } 76 | 77 | aabbCompute () { 78 | let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY; 79 | let polygons = this.polygons; 80 | for (let i = 0, n = polygons.length; i < n; i++) { 81 | let polygon = polygons[i]; 82 | let vertices = polygon; 83 | for (let ii = 0, nn = polygon.length; ii < nn; ii += 2) { 84 | let x = vertices[ii]; 85 | let y = vertices[ii + 1]; 86 | minX = Math.min(minX, x); 87 | minY = Math.min(minY, y); 88 | maxX = Math.max(maxX, x); 89 | maxY = Math.max(maxY, y); 90 | } 91 | } 92 | this.minX = minX; 93 | this.minY = minY; 94 | this.maxX = maxX; 95 | this.maxY = maxY; 96 | } 97 | 98 | /** Returns true if the axis aligned bounding box contains the point. */ 99 | aabbContainsPoint (x: number, y: number) { 100 | return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY; 101 | } 102 | 103 | /** Returns true if the axis aligned bounding box intersects the line segment. */ 104 | aabbIntersectsSegment (x1: number, y1: number, x2: number, y2: number) { 105 | let minX = this.minX; 106 | let minY = this.minY; 107 | let maxX = this.maxX; 108 | let maxY = this.maxY; 109 | if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY)) 110 | return false; 111 | let m = (y2 - y1) / (x2 - x1); 112 | let y = m * (minX - x1) + y1; 113 | if (y > minY && y < maxY) return true; 114 | y = m * (maxX - x1) + y1; 115 | if (y > minY && y < maxY) return true; 116 | let x = (minY - y1) / m + x1; 117 | if (x > minX && x < maxX) return true; 118 | x = (maxY - y1) / m + x1; 119 | if (x > minX && x < maxX) return true; 120 | return false; 121 | } 122 | 123 | /** Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. */ 124 | aabbIntersectsSkeleton (bounds: SkeletonBounds) { 125 | return this.minX < bounds.maxX && this.maxX > bounds.minX && this.minY < bounds.maxY && this.maxY > bounds.minY; 126 | } 127 | 128 | /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more 129 | * efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */ 130 | containsPoint (x: number, y: number): BoundingBoxAttachment { 131 | let polygons = this.polygons; 132 | for (let i = 0, n = polygons.length; i < n; i++) 133 | if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i]; 134 | return null; 135 | } 136 | 137 | /** Returns true if the polygon contains the point. */ 138 | containsPointPolygon (polygon: ArrayLike, x: number, y: number) { 139 | let vertices = polygon; 140 | let nn = polygon.length; 141 | 142 | let prevIndex = nn - 2; 143 | let inside = false; 144 | for (let ii = 0; ii < nn; ii += 2) { 145 | let vertexY = vertices[ii + 1]; 146 | let prevY = vertices[prevIndex + 1]; 147 | if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) { 148 | let vertexX = vertices[ii]; 149 | if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside; 150 | } 151 | prevIndex = ii; 152 | } 153 | return inside; 154 | } 155 | 156 | /** Returns the first bounding box attachment that contains any part of the line segment, or null. When doing many checks, it 157 | * is usually more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns 158 | * true. */ 159 | intersectsSegment (x1: number, y1: number, x2: number, y2: number) { 160 | let polygons = this.polygons; 161 | for (let i = 0, n = polygons.length; i < n; i++) 162 | if (this.intersectsSegmentPolygon(polygons[i], x1, y1, x2, y2)) return this.boundingBoxes[i]; 163 | return null; 164 | } 165 | 166 | /** Returns true if the polygon contains any part of the line segment. */ 167 | intersectsSegmentPolygon (polygon: ArrayLike, x1: number, y1: number, x2: number, y2: number) { 168 | let vertices = polygon; 169 | let nn = polygon.length; 170 | 171 | let width12 = x1 - x2, height12 = y1 - y2; 172 | let det1 = x1 * y2 - y1 * x2; 173 | let x3 = vertices[nn - 2], y3 = vertices[nn - 1]; 174 | for (let ii = 0; ii < nn; ii += 2) { 175 | let x4 = vertices[ii], y4 = vertices[ii + 1]; 176 | let det2 = x3 * y4 - y3 * x4; 177 | let width34 = x3 - x4, height34 = y3 - y4; 178 | let det3 = width12 * height34 - height12 * width34; 179 | let x = (det1 * width34 - width12 * det2) / det3; 180 | if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) { 181 | let y = (det1 * height34 - height12 * det2) / det3; 182 | if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true; 183 | } 184 | x3 = x4; 185 | y3 = y4; 186 | } 187 | return false; 188 | } 189 | 190 | /** Returns the polygon for the specified bounding box, or null. */ 191 | getPolygon (boundingBox: BoundingBoxAttachment) { 192 | if (boundingBox == null) throw new Error("boundingBox cannot be null."); 193 | let index = this.boundingBoxes.indexOf(boundingBox); 194 | return index == -1 ? null : this.polygons[index]; 195 | } 196 | 197 | getWidth () { 198 | return this.maxX - this.minX; 199 | } 200 | 201 | getHeight () { 202 | return this.maxY - this.minY; 203 | } 204 | } 205 | 206 | } 207 | -------------------------------------------------------------------------------- /src/core/SkeletonClipping.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class SkeletonClipping { 32 | private triangulator = new Triangulator(); 33 | private clippingPolygon = new Array(); 34 | private clipOutput = new Array(); 35 | clippedVertices = new Array(); 36 | clippedTriangles = new Array(); 37 | private scratch = new Array(); 38 | 39 | private clipAttachment: ClippingAttachment; 40 | private clippingPolygons: Array>; 41 | 42 | clipStart (slot: Slot, clip: ClippingAttachment): number { 43 | if (this.clipAttachment != null) return 0; 44 | this.clipAttachment = clip; 45 | 46 | let n = clip.worldVerticesLength; 47 | let vertices = Utils.setArraySize(this.clippingPolygon, n); 48 | clip.computeWorldVertices(slot, 0, n, vertices, 0, 2); 49 | let clippingPolygon = this.clippingPolygon; 50 | SkeletonClipping.makeClockwise(clippingPolygon); 51 | let clippingPolygons = this.clippingPolygons = this.triangulator.decompose(clippingPolygon, this.triangulator.triangulate(clippingPolygon)); 52 | for (let i = 0, n = clippingPolygons.length; i < n; i++) { 53 | let polygon = clippingPolygons[i]; 54 | SkeletonClipping.makeClockwise(polygon); 55 | polygon.push(polygon[0]); 56 | polygon.push(polygon[1]); 57 | } 58 | 59 | return clippingPolygons.length; 60 | } 61 | 62 | clipEndWithSlot (slot: Slot) { 63 | if (this.clipAttachment != null && this.clipAttachment.endSlot == slot.data) this.clipEnd(); 64 | } 65 | 66 | clipEnd () { 67 | if (this.clipAttachment == null) return; 68 | this.clipAttachment = null; 69 | this.clippingPolygons = null; 70 | this.clippedVertices.length = 0; 71 | this.clippedTriangles.length = 0; 72 | this.clippingPolygon.length = 0; 73 | } 74 | 75 | isClipping (): boolean { 76 | return this.clipAttachment != null; 77 | } 78 | 79 | clipTriangles (vertices: ArrayLike, verticesLength: number, triangles: ArrayLike, trianglesLength: number, uvs: ArrayLike, 80 | light: Color, dark: Color, twoColor: boolean) { 81 | 82 | let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; 83 | let clippedTriangles = this.clippedTriangles; 84 | let polygons = this.clippingPolygons; 85 | let polygonsCount = this.clippingPolygons.length; 86 | let vertexSize = twoColor ? 12 : 8; 87 | 88 | let index = 0; 89 | clippedVertices.length = 0; 90 | clippedTriangles.length = 0; 91 | outer: 92 | for (let i = 0; i < trianglesLength; i += 3) { 93 | let vertexOffset = triangles[i] << 1; 94 | let x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; 95 | let u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; 96 | 97 | vertexOffset = triangles[i + 1] << 1; 98 | let x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; 99 | let u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; 100 | 101 | vertexOffset = triangles[i + 2] << 1; 102 | let x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; 103 | let u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; 104 | 105 | for (let p = 0; p < polygonsCount; p++) { 106 | let s = clippedVertices.length; 107 | if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { 108 | let clipOutputLength = clipOutput.length; 109 | if (clipOutputLength == 0) continue; 110 | let d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1; 111 | let d = 1 / (d0 * d2 + d1 * (y1 - y3)); 112 | 113 | let clipOutputCount = clipOutputLength >> 1; 114 | let clipOutputItems = this.clipOutput; 115 | let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + clipOutputCount * vertexSize); 116 | for (let ii = 0; ii < clipOutputLength; ii += 2) { 117 | let x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; 118 | clippedVerticesItems[s] = x; 119 | clippedVerticesItems[s + 1] = y; 120 | clippedVerticesItems[s + 2] = light.r; 121 | clippedVerticesItems[s + 3] = light.g; 122 | clippedVerticesItems[s + 4] = light.b; 123 | clippedVerticesItems[s + 5] = light.a; 124 | let c0 = x - x3, c1 = y - y3; 125 | let a = (d0 * c0 + d1 * c1) * d; 126 | let b = (d4 * c0 + d2 * c1) * d; 127 | let c = 1 - a - b; 128 | clippedVerticesItems[s + 6] = u1 * a + u2 * b + u3 * c; 129 | clippedVerticesItems[s + 7] = v1 * a + v2 * b + v3 * c; 130 | if (twoColor) { 131 | clippedVerticesItems[s + 8] = dark.r; 132 | clippedVerticesItems[s + 9] = dark.g; 133 | clippedVerticesItems[s + 10] = dark.b; 134 | clippedVerticesItems[s + 11] = dark.a; 135 | } 136 | s += vertexSize; 137 | } 138 | 139 | s = clippedTriangles.length; 140 | let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2)); 141 | clipOutputCount--; 142 | for (let ii = 1; ii < clipOutputCount; ii++) { 143 | clippedTrianglesItems[s] = index; 144 | clippedTrianglesItems[s + 1] = (index + ii); 145 | clippedTrianglesItems[s + 2] = (index + ii + 1); 146 | s += 3; 147 | } 148 | index += clipOutputCount + 1; 149 | 150 | } else { 151 | let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * vertexSize); 152 | clippedVerticesItems[s] = x1; 153 | clippedVerticesItems[s + 1] = y1; 154 | clippedVerticesItems[s + 2] = light.r; 155 | clippedVerticesItems[s + 3] = light.g; 156 | clippedVerticesItems[s + 4] = light.b; 157 | clippedVerticesItems[s + 5] = light.a; 158 | if (!twoColor) { 159 | clippedVerticesItems[s + 6] = u1; 160 | clippedVerticesItems[s + 7] = v1; 161 | 162 | clippedVerticesItems[s + 8] = x2; 163 | clippedVerticesItems[s + 9] = y2; 164 | clippedVerticesItems[s + 10] = light.r; 165 | clippedVerticesItems[s + 11] = light.g; 166 | clippedVerticesItems[s + 12] = light.b; 167 | clippedVerticesItems[s + 13] = light.a; 168 | clippedVerticesItems[s + 14] = u2; 169 | clippedVerticesItems[s + 15] = v2; 170 | 171 | clippedVerticesItems[s + 16] = x3; 172 | clippedVerticesItems[s + 17] = y3; 173 | clippedVerticesItems[s + 18] = light.r; 174 | clippedVerticesItems[s + 19] = light.g; 175 | clippedVerticesItems[s + 20] = light.b; 176 | clippedVerticesItems[s + 21] = light.a; 177 | clippedVerticesItems[s + 22] = u3; 178 | clippedVerticesItems[s + 23] = v3; 179 | } else { 180 | clippedVerticesItems[s + 6] = u1; 181 | clippedVerticesItems[s + 7] = v1; 182 | clippedVerticesItems[s + 8] = dark.r; 183 | clippedVerticesItems[s + 9] = dark.g; 184 | clippedVerticesItems[s + 10] = dark.b; 185 | clippedVerticesItems[s + 11] = dark.a; 186 | 187 | clippedVerticesItems[s + 12] = x2; 188 | clippedVerticesItems[s + 13] = y2; 189 | clippedVerticesItems[s + 14] = light.r; 190 | clippedVerticesItems[s + 15] = light.g; 191 | clippedVerticesItems[s + 16] = light.b; 192 | clippedVerticesItems[s + 17] = light.a; 193 | clippedVerticesItems[s + 18] = u2; 194 | clippedVerticesItems[s + 19] = v2; 195 | clippedVerticesItems[s + 20] = dark.r; 196 | clippedVerticesItems[s + 21] = dark.g; 197 | clippedVerticesItems[s + 22] = dark.b; 198 | clippedVerticesItems[s + 23] = dark.a; 199 | 200 | clippedVerticesItems[s + 24] = x3; 201 | clippedVerticesItems[s + 25] = y3; 202 | clippedVerticesItems[s + 26] = light.r; 203 | clippedVerticesItems[s + 27] = light.g; 204 | clippedVerticesItems[s + 28] = light.b; 205 | clippedVerticesItems[s + 29] = light.a; 206 | clippedVerticesItems[s + 30] = u3; 207 | clippedVerticesItems[s + 31] = v3; 208 | clippedVerticesItems[s + 32] = dark.r; 209 | clippedVerticesItems[s + 33] = dark.g; 210 | clippedVerticesItems[s + 34] = dark.b; 211 | clippedVerticesItems[s + 35] = dark.a; 212 | } 213 | 214 | s = clippedTriangles.length; 215 | let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3); 216 | clippedTrianglesItems[s] = index; 217 | clippedTrianglesItems[s + 1] = (index + 1); 218 | clippedTrianglesItems[s + 2] = (index + 2); 219 | index += 3; 220 | continue outer; 221 | } 222 | } 223 | } 224 | } 225 | 226 | /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping 227 | * area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */ 228 | clip (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, clippingArea: Array, output: Array) { 229 | let originalOutput = output; 230 | let clipped = false; 231 | 232 | // Avoid copy at the end. 233 | let input: Array = null; 234 | if (clippingArea.length % 4 >= 2) { 235 | input = output; 236 | output = this.scratch; 237 | } else 238 | input = this.scratch; 239 | 240 | input.length = 0; 241 | input.push(x1); 242 | input.push(y1); 243 | input.push(x2); 244 | input.push(y2); 245 | input.push(x3); 246 | input.push(y3); 247 | input.push(x1); 248 | input.push(y1); 249 | output.length = 0; 250 | 251 | let clippingVertices = clippingArea; 252 | let clippingVerticesLast = clippingArea.length - 4; 253 | for (let i = 0;; i += 2) { 254 | let edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; 255 | let edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; 256 | let deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; 257 | 258 | let inputVertices = input; 259 | let inputVerticesLength = input.length - 2, outputStart = output.length; 260 | for (let ii = 0; ii < inputVerticesLength; ii += 2) { 261 | let inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; 262 | let inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; 263 | let side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; 264 | if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { 265 | if (side2) { // v1 inside, v2 inside 266 | output.push(inputX2); 267 | output.push(inputY2); 268 | continue; 269 | } 270 | // v1 inside, v2 outside 271 | let c0 = inputY2 - inputY, c2 = inputX2 - inputX; 272 | let s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); 273 | if (Math.abs(s) > 0.000001) { 274 | let ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; 275 | output.push(edgeX + (edgeX2 - edgeX) * ua); 276 | output.push(edgeY + (edgeY2 - edgeY) * ua); 277 | } else { 278 | output.push(edgeX); 279 | output.push(edgeY); 280 | } 281 | } else if (side2) { // v1 outside, v2 inside 282 | let c0 = inputY2 - inputY, c2 = inputX2 - inputX; 283 | let s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); 284 | if (Math.abs(s) > 0.000001) { 285 | let ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; 286 | output.push(edgeX + (edgeX2 - edgeX) * ua); 287 | output.push(edgeY + (edgeY2 - edgeY) * ua); 288 | } else { 289 | output.push(edgeX); 290 | output.push(edgeY); 291 | } 292 | output.push(inputX2); 293 | output.push(inputY2); 294 | } 295 | clipped = true; 296 | } 297 | 298 | if (outputStart == output.length) { // All edges outside. 299 | originalOutput.length = 0; 300 | return true; 301 | } 302 | 303 | output.push(output[0]); 304 | output.push(output[1]); 305 | 306 | if (i == clippingVerticesLast) break; 307 | let temp = output; 308 | output = input; 309 | output.length = 0; 310 | input = temp; 311 | } 312 | 313 | if (originalOutput != output) { 314 | originalOutput.length = 0; 315 | for (let i = 0, n = output.length - 2; i < n; i++) 316 | originalOutput[i] = output[i]; 317 | } else 318 | originalOutput.length = originalOutput.length - 2; 319 | 320 | return clipped; 321 | } 322 | 323 | public static makeClockwise (polygon: ArrayLike) { 324 | let vertices = polygon; 325 | let verticeslength = polygon.length; 326 | 327 | let area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x = 0, p1y = 0, p2x = 0, p2y = 0; 328 | for (let i = 0, n = verticeslength - 3; i < n; i += 2) { 329 | p1x = vertices[i]; 330 | p1y = vertices[i + 1]; 331 | p2x = vertices[i + 2]; 332 | p2y = vertices[i + 3]; 333 | area += p1x * p2y - p2x * p1y; 334 | } 335 | if (area < 0) return; 336 | 337 | for (let i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { 338 | let x = vertices[i], y = vertices[i + 1]; 339 | let other = lastX - i; 340 | vertices[i] = vertices[other]; 341 | vertices[i + 1] = vertices[other + 1]; 342 | vertices[other] = x; 343 | vertices[other + 1] = y; 344 | } 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/core/SkeletonData.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class SkeletonData { 32 | name: string; 33 | bones = new Array(); // Ordered parents first. 34 | slots = new Array(); // Setup pose draw order. 35 | skins = new Array(); 36 | defaultSkin: Skin; 37 | events = new Array(); 38 | animations = new Array(); 39 | ikConstraints = new Array(); 40 | transformConstraints = new Array(); 41 | pathConstraints = new Array(); 42 | width: number; height: number; 43 | version: string; hash: string; 44 | 45 | // Nonessential 46 | fps = 0; 47 | imagesPath: string; 48 | 49 | findBone (boneName: string) { 50 | if (boneName == null) throw new Error("boneName cannot be null."); 51 | let bones = this.bones; 52 | for (let i = 0, n = bones.length; i < n; i++) { 53 | let bone = bones[i]; 54 | if (bone.name == boneName) return bone; 55 | } 56 | return null; 57 | } 58 | 59 | findBoneIndex (boneName: string) { 60 | if (boneName == null) throw new Error("boneName cannot be null."); 61 | let bones = this.bones; 62 | for (let i = 0, n = bones.length; i < n; i++) 63 | if (bones[i].name == boneName) return i; 64 | return -1; 65 | } 66 | 67 | findSlot (slotName: string) { 68 | if (slotName == null) throw new Error("slotName cannot be null."); 69 | let slots = this.slots; 70 | for (let i = 0, n = slots.length; i < n; i++) { 71 | let slot = slots[i]; 72 | if (slot.name == slotName) return slot; 73 | } 74 | return null; 75 | } 76 | 77 | findSlotIndex (slotName: string) { 78 | if (slotName == null) throw new Error("slotName cannot be null."); 79 | let slots = this.slots; 80 | for (let i = 0, n = slots.length; i < n; i++) 81 | if (slots[i].name == slotName) return i; 82 | return -1; 83 | } 84 | 85 | findSkin (skinName: string) { 86 | if (skinName == null) throw new Error("skinName cannot be null."); 87 | let skins = this.skins; 88 | for (let i = 0, n = skins.length; i < n; i++) { 89 | let skin = skins[i]; 90 | if (skin.name == skinName) return skin; 91 | } 92 | return null; 93 | } 94 | 95 | findEvent (eventDataName: string) { 96 | if (eventDataName == null) throw new Error("eventDataName cannot be null."); 97 | let events = this.events; 98 | for (let i = 0, n = events.length; i < n; i++) { 99 | let event = events[i]; 100 | if (event.name == eventDataName) return event; 101 | } 102 | return null; 103 | } 104 | 105 | findAnimation (animationName: string) { 106 | if (animationName == null) throw new Error("animationName cannot be null."); 107 | let animations = this.animations; 108 | for (let i = 0, n = animations.length; i < n; i++) { 109 | let animation = animations[i]; 110 | if (animation.name == animationName) return animation; 111 | } 112 | return null; 113 | } 114 | 115 | findIkConstraint (constraintName: string) { 116 | if (constraintName == null) throw new Error("constraintName cannot be null."); 117 | let ikConstraints = this.ikConstraints; 118 | for (let i = 0, n = ikConstraints.length; i < n; i++) { 119 | let constraint = ikConstraints[i]; 120 | if (constraint.name == constraintName) return constraint; 121 | } 122 | return null; 123 | } 124 | 125 | findTransformConstraint (constraintName: string) { 126 | if (constraintName == null) throw new Error("constraintName cannot be null."); 127 | let transformConstraints = this.transformConstraints; 128 | for (let i = 0, n = transformConstraints.length; i < n; i++) { 129 | let constraint = transformConstraints[i]; 130 | if (constraint.name == constraintName) return constraint; 131 | } 132 | return null; 133 | } 134 | 135 | findPathConstraint (constraintName: string) { 136 | if (constraintName == null) throw new Error("constraintName cannot be null."); 137 | let pathConstraints = this.pathConstraints; 138 | for (let i = 0, n = pathConstraints.length; i < n; i++) { 139 | let constraint = pathConstraints[i]; 140 | if (constraint.name == constraintName) return constraint; 141 | } 142 | return null; 143 | } 144 | 145 | findPathConstraintIndex (pathConstraintName: string) { 146 | if (pathConstraintName == null) throw new Error("pathConstraintName cannot be null."); 147 | let pathConstraints = this.pathConstraints; 148 | for (let i = 0, n = pathConstraints.length; i < n; i++) 149 | if (pathConstraints[i].name == pathConstraintName) return i; 150 | return -1; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/core/Skin.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class Skin { 32 | name: string; 33 | attachments = new Array>(); 34 | 35 | constructor (name: string) { 36 | if (name == null) throw new Error("name cannot be null."); 37 | this.name = name; 38 | } 39 | 40 | addAttachment (slotIndex: number, name: string, attachment: Attachment) { 41 | if (attachment == null) throw new Error("attachment cannot be null."); 42 | let attachments = this.attachments; 43 | if (slotIndex >= attachments.length) attachments.length = slotIndex + 1; 44 | if (!attachments[slotIndex]) attachments[slotIndex] = { }; 45 | attachments[slotIndex][name] = attachment; 46 | } 47 | 48 | /** @return May be null. */ 49 | getAttachment (slotIndex: number, name: string): Attachment { 50 | let dictionary = this.attachments[slotIndex]; 51 | return dictionary ? dictionary[name] : null; 52 | } 53 | 54 | /** Attach each attachment in this skin if the corresponding attachment in the old skin is currently attached. */ 55 | attachAll (skeleton: Skeleton, oldSkin: Skin) { 56 | let slotIndex = 0; 57 | for (let i = 0; i < skeleton.slots.length; i++) { 58 | let slot = skeleton.slots[i]; 59 | let slotAttachment = slot.getAttachment(); 60 | if (slotAttachment && slotIndex < oldSkin.attachments.length) { 61 | let dictionary = oldSkin.attachments[slotIndex]; 62 | for (let key in dictionary) { 63 | let skinAttachment:Attachment = dictionary[key]; 64 | if (slotAttachment == skinAttachment) { 65 | let attachment = this.getAttachment(slotIndex, key); 66 | if (attachment != null) slot.setAttachment(attachment); 67 | break; 68 | } 69 | } 70 | } 71 | slotIndex++; 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/core/Slot.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class Slot { 32 | data: SlotData; 33 | bone: Bone; 34 | color: Color; 35 | darkColor: Color; 36 | private attachment: Attachment; 37 | private attachmentTime: number; 38 | attachmentVertices = new Array(); 39 | 40 | constructor (data: SlotData, bone: Bone) { 41 | if (data == null) throw new Error("data cannot be null."); 42 | if (bone == null) throw new Error("bone cannot be null."); 43 | this.data = data; 44 | this.bone = bone; 45 | this.color = new Color(); 46 | this.darkColor = data.darkColor == null ? null : new Color(); 47 | this.setToSetupPose(); 48 | } 49 | 50 | /** @return May be null. */ 51 | getAttachment (): Attachment { 52 | return this.attachment; 53 | } 54 | 55 | /** Sets the attachment and if it changed, resets {@link #getAttachmentTime()} and clears {@link #getAttachmentVertices()}. 56 | * @param attachment May be null. */ 57 | setAttachment (attachment: Attachment) { 58 | if (this.attachment == attachment) return; 59 | this.attachment = attachment; 60 | this.attachmentTime = this.bone.skeleton.time; 61 | this.attachmentVertices.length = 0; 62 | } 63 | 64 | setAttachmentTime (time: number) { 65 | this.attachmentTime = this.bone.skeleton.time - time; 66 | } 67 | 68 | /** Returns the time since the attachment was set. */ 69 | getAttachmentTime (): number { 70 | return this.bone.skeleton.time - this.attachmentTime; 71 | } 72 | 73 | setToSetupPose () { 74 | this.color.setFromColor(this.data.color); 75 | if (this.darkColor != null) this.darkColor.setFromColor(this.data.darkColor); 76 | if (this.data.attachmentName == null) 77 | this.attachment = null; 78 | else { 79 | this.attachment = null; 80 | this.setAttachment(this.bone.skeleton.getAttachment(this.data.index, this.data.attachmentName)); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/core/SlotData.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class SlotData { 32 | index: number; 33 | name: string; 34 | boneData: BoneData; 35 | color = new Color(1, 1, 1, 1); 36 | darkColor: Color; 37 | attachmentName: string; 38 | blendMode: BlendMode; 39 | 40 | constructor (index: number, name: string, boneData: BoneData) { 41 | if (index < 0) throw new Error("index must be >= 0."); 42 | if (name == null) throw new Error("name cannot be null."); 43 | if (boneData == null) throw new Error("boneData cannot be null."); 44 | this.index = index; 45 | this.name = name; 46 | this.boneData = boneData; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/core/Texture.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export abstract class Texture { 32 | protected _image: HTMLImageElement; 33 | 34 | constructor (image: HTMLImageElement) { 35 | this._image = image; 36 | } 37 | 38 | getImage (): HTMLImageElement { 39 | return this._image; 40 | } 41 | 42 | abstract setFilters (minFilter: TextureFilter, magFilter: TextureFilter): void; 43 | abstract setWraps (uWrap: TextureWrap, vWrap: TextureWrap): void; 44 | abstract dispose (): void; 45 | 46 | public static filterFromString (text: string): TextureFilter { 47 | switch (text.toLowerCase()) { 48 | case "nearest": return TextureFilter.Nearest; 49 | case "linear": return TextureFilter.Linear; 50 | case "mipmap": return TextureFilter.MipMap; 51 | case "mipmapnearestnearest": return TextureFilter.MipMapNearestNearest; 52 | case "mipmaplinearnearest": return TextureFilter.MipMapLinearNearest; 53 | case "mipmapnearestlinear": return TextureFilter.MipMapNearestLinear; 54 | case "mipmaplinearlinear": return TextureFilter.MipMapLinearLinear; 55 | default: throw new Error(`Unknown texture filter ${text}`); 56 | } 57 | } 58 | 59 | public static wrapFromString (text: string): TextureWrap { 60 | switch (text.toLowerCase()) { 61 | case "mirroredtepeat": return TextureWrap.MirroredRepeat; 62 | case "clamptoedge": return TextureWrap.ClampToEdge; 63 | case "repeat": return TextureWrap.Repeat; 64 | default: throw new Error(`Unknown texture wrap ${text}`); 65 | } 66 | } 67 | } 68 | 69 | export enum TextureFilter { 70 | Nearest = 9728, // WebGLRenderingContext.NEAREST 71 | Linear = 9729, // WebGLRenderingContext.LINEAR 72 | MipMap = 9987, // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR 73 | MipMapNearestNearest = 9984, // WebGLRenderingContext.NEAREST_MIPMAP_NEAREST 74 | MipMapLinearNearest = 9985, // WebGLRenderingContext.LINEAR_MIPMAP_NEAREST 75 | MipMapNearestLinear = 9986, // WebGLRenderingContext.NEAREST_MIPMAP_LINEAR 76 | MipMapLinearLinear = 9987 // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR 77 | } 78 | 79 | export enum TextureWrap { 80 | MirroredRepeat = 33648, // WebGLRenderingContext.MIRRORED_REPEAT 81 | ClampToEdge = 33071, // WebGLRenderingContext.CLAMP_TO_EDGE 82 | Repeat = 10497 // WebGLRenderingContext.REPEAT 83 | } 84 | 85 | export class TextureRegion { 86 | renderObject: any; 87 | u = 0; v = 0; 88 | u2 = 0; v2 = 0; 89 | width = 0; height = 0; 90 | rotate = false; 91 | offsetX = 0; offsetY = 0; 92 | originalWidth = 0; originalHeight = 0; 93 | } 94 | 95 | export class FakeTexture extends Texture { 96 | setFilters(minFilter: TextureFilter, magFilter: TextureFilter) { } 97 | setWraps(uWrap: TextureWrap, vWrap: TextureWrap) { } 98 | dispose() { } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/core/TextureAtlas.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class TextureAtlas implements Disposable { 32 | pages = new Array(); 33 | regions = new Array(); 34 | 35 | constructor (atlasText: string, textureLoader: (path: string) => any) { 36 | this.load(atlasText, textureLoader); 37 | } 38 | 39 | private load (atlasText: string, textureLoader: (path: string) => any) { 40 | if (textureLoader == null) 41 | throw new Error("textureLoader cannot be null."); 42 | 43 | let reader = new TextureAtlasReader(atlasText); 44 | let tuple = new Array(4); 45 | let page:TextureAtlasPage = null; 46 | while (true) { 47 | let line = reader.readLine(); 48 | if (line == null) 49 | break; 50 | line = line.trim(); 51 | if (line.length == 0) 52 | page = null; 53 | else if (!page) { 54 | page = new TextureAtlasPage(); 55 | page.name = line; 56 | 57 | if (reader.readTuple(tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. 58 | page.width = parseInt(tuple[0]); 59 | page.height = parseInt(tuple[1]); 60 | reader.readTuple(tuple); 61 | } 62 | // page.format = Format[tuple[0]]; we don't need format in WebGL 63 | 64 | reader.readTuple(tuple); 65 | page.minFilter = Texture.filterFromString(tuple[0]); 66 | page.magFilter = Texture.filterFromString(tuple[1]); 67 | 68 | let direction= reader.readValue(); 69 | page.uWrap = TextureWrap.ClampToEdge; 70 | page.vWrap = TextureWrap.ClampToEdge; 71 | if (direction == "x") 72 | page.uWrap = TextureWrap.Repeat; 73 | else if (direction == "y") 74 | page.vWrap = TextureWrap.Repeat; 75 | else if (direction == "xy") 76 | page.uWrap = page.vWrap = TextureWrap.Repeat; 77 | 78 | page.texture = textureLoader(line); 79 | page.texture.setFilters(page.minFilter, page.magFilter); 80 | page.texture.setWraps(page.uWrap, page.vWrap); 81 | page.width = page.texture.getImage().width; 82 | page.height = page.texture.getImage().height; 83 | this.pages.push(page); 84 | } else { 85 | let region:TextureAtlasRegion = new TextureAtlasRegion(); 86 | region.name = line; 87 | region.page = page; 88 | 89 | region.rotate = reader.readValue() == "true"; 90 | 91 | reader.readTuple(tuple); 92 | let x = parseInt(tuple[0]); 93 | let y = parseInt(tuple[1]); 94 | 95 | reader.readTuple(tuple); 96 | let width = parseInt(tuple[0]); 97 | let height = parseInt(tuple[1]); 98 | 99 | region.u = x / page.width; 100 | region.v = y / page.height; 101 | if (region.rotate) { 102 | region.u2 = (x + height) / page.width; 103 | region.v2 = (y + width) / page.height; 104 | } else { 105 | region.u2 = (x + width) / page.width; 106 | region.v2 = (y + height) / page.height; 107 | } 108 | region.x = x; 109 | region.y = y; 110 | region.width = Math.abs(width); 111 | region.height = Math.abs(height); 112 | 113 | if (reader.readTuple(tuple) == 4) { // split is optional 114 | // region.splits = new Vector.(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])); 115 | if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits 116 | //region.pads = Vector.(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])); 117 | reader.readTuple(tuple); 118 | } 119 | } 120 | 121 | region.originalWidth = parseInt(tuple[0]); 122 | region.originalHeight = parseInt(tuple[1]); 123 | 124 | reader.readTuple(tuple); 125 | region.offsetX = parseInt(tuple[0]); 126 | region.offsetY = parseInt(tuple[1]); 127 | 128 | region.index = parseInt(reader.readValue()); 129 | 130 | region.texture = page.texture; 131 | this.regions.push(region); 132 | } 133 | } 134 | } 135 | 136 | findRegion (name: string): TextureAtlasRegion { 137 | for (let i = 0; i < this.regions.length; i++) { 138 | if (this.regions[i].name == name) { 139 | return this.regions[i]; 140 | } 141 | } 142 | return null; 143 | } 144 | 145 | dispose () { 146 | for (let i = 0; i < this.pages.length; i++) { 147 | this.pages[i].texture.dispose(); 148 | } 149 | } 150 | } 151 | 152 | class TextureAtlasReader { 153 | lines: Array; 154 | index: number = 0; 155 | 156 | constructor (text: string) { 157 | this.lines = text.split(/\r\n|\r|\n/); 158 | } 159 | 160 | readLine (): string { 161 | if (this.index >= this.lines.length) 162 | return null; 163 | return this.lines[this.index++]; 164 | } 165 | 166 | readValue (): string { 167 | let line = this.readLine(); 168 | let colon= line.indexOf(":"); 169 | if (colon == -1) 170 | throw new Error("Invalid line: " + line); 171 | return line.substring(colon + 1).trim(); 172 | } 173 | 174 | readTuple (tuple: Array): number { 175 | let line = this.readLine(); 176 | let colon = line.indexOf(":"); 177 | if (colon == -1) 178 | throw new Error("Invalid line: " + line); 179 | let i = 0, lastMatch = colon + 1; 180 | for (; i < 3; i++) { 181 | let comma = line.indexOf(",", lastMatch); 182 | if (comma == -1) break; 183 | tuple[i] = line.substr(lastMatch, comma - lastMatch).trim(); 184 | lastMatch = comma + 1; 185 | } 186 | tuple[i] = line.substring(lastMatch).trim(); 187 | return i + 1; 188 | } 189 | } 190 | 191 | export class TextureAtlasPage { 192 | name: string; 193 | minFilter: TextureFilter; 194 | magFilter: TextureFilter; 195 | uWrap: TextureWrap; 196 | vWrap: TextureWrap; 197 | texture: Texture; 198 | width: number; 199 | height: number; 200 | } 201 | 202 | export class TextureAtlasRegion extends TextureRegion { 203 | page: TextureAtlasPage; 204 | name: string; 205 | x: number; 206 | y: number; 207 | index: number; 208 | rotate: boolean; 209 | texture: Texture; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/core/TransformConstraint.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class TransformConstraint implements Constraint { 32 | data: TransformConstraintData; 33 | bones: Array; 34 | target: Bone; 35 | rotateMix = 0; translateMix = 0; scaleMix = 0; shearMix = 0; 36 | temp = new Vector2(); 37 | 38 | constructor (data: TransformConstraintData, skeleton: Skeleton) { 39 | if (data == null) throw new Error("data cannot be null."); 40 | if (skeleton == null) throw new Error("skeleton cannot be null."); 41 | this.data = data; 42 | this.rotateMix = data.rotateMix; 43 | this.translateMix = data.translateMix; 44 | this.scaleMix = data.scaleMix; 45 | this.shearMix = data.shearMix; 46 | this.bones = new Array(); 47 | for (let i = 0; i < data.bones.length; i++) 48 | this.bones.push(skeleton.findBone(data.bones[i].name)); 49 | this.target = skeleton.findBone(data.target.name); 50 | } 51 | 52 | apply () { 53 | this.update(); 54 | } 55 | 56 | update () { 57 | if (this.data.local) { 58 | if (this.data.relative) 59 | this.applyRelativeLocal(); 60 | else 61 | this.applyAbsoluteLocal(); 62 | 63 | } else { 64 | if (this.data.relative) 65 | this.applyRelativeWorld(); 66 | else 67 | this.applyAbsoluteWorld(); 68 | } 69 | } 70 | 71 | applyAbsoluteWorld () { 72 | let rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; 73 | let target = this.target; 74 | let ta = target.a, tb = target.b, tc = target.c, td = target.d; 75 | let degRadReflect = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad; 76 | let offsetRotation = this.data.offsetRotation * degRadReflect; 77 | let offsetShearY = this.data.offsetShearY * degRadReflect; 78 | let bones = this.bones; 79 | for (let i = 0, n = bones.length; i < n; i++) { 80 | let bone = bones[i]; 81 | let modified = false; 82 | 83 | if (rotateMix != 0) { 84 | let a = bone.a, b = bone.b, c = bone.c, d = bone.d; 85 | let r = Math.atan2(tc, ta) - Math.atan2(c, a) + offsetRotation; 86 | if (r > MathUtils.PI) 87 | r -= MathUtils.PI2; 88 | else if (r < -MathUtils.PI) 89 | r += MathUtils.PI2; 90 | r *= rotateMix; 91 | let cos = Math.cos(r), sin = Math.sin(r); 92 | bone.a = cos * a - sin * c; 93 | bone.b = cos * b - sin * d; 94 | bone.c = sin * a + cos * c; 95 | bone.d = sin * b + cos * d; 96 | modified = true; 97 | } 98 | 99 | if (translateMix != 0) { 100 | let temp = this.temp; 101 | target.localToWorld(temp.set(this.data.offsetX, this.data.offsetY)); 102 | bone.worldX += (temp.x - bone.worldX) * translateMix; 103 | bone.worldY += (temp.y - bone.worldY) * translateMix; 104 | modified = true; 105 | } 106 | 107 | if (scaleMix > 0) { 108 | let s = Math.sqrt(bone.a * bone.a + bone.c * bone.c); 109 | let ts = Math.sqrt(ta * ta + tc * tc); 110 | if (s > 0.00001) s = (s + (ts - s + this.data.offsetScaleX) * scaleMix) / s; 111 | bone.a *= s; 112 | bone.c *= s; 113 | s = Math.sqrt(bone.b * bone.b + bone.d * bone.d); 114 | ts = Math.sqrt(tb * tb + td * td); 115 | if (s > 0.00001) s = (s + (ts - s + this.data.offsetScaleY) * scaleMix) / s; 116 | bone.b *= s; 117 | bone.d *= s; 118 | modified = true; 119 | } 120 | 121 | if (shearMix > 0) { 122 | let b = bone.b, d = bone.d; 123 | let by = Math.atan2(d, b); 124 | let r = Math.atan2(td, tb) - Math.atan2(tc, ta) - (by - Math.atan2(bone.c, bone.a)); 125 | if (r > MathUtils.PI) 126 | r -= MathUtils.PI2; 127 | else if (r < -MathUtils.PI) 128 | r += MathUtils.PI2; 129 | r = by + (r + offsetShearY) * shearMix; 130 | let s = Math.sqrt(b * b + d * d); 131 | bone.b = Math.cos(r) * s; 132 | bone.d = Math.sin(r) * s; 133 | modified = true; 134 | } 135 | 136 | if (modified) bone.appliedValid = false; 137 | } 138 | } 139 | 140 | applyRelativeWorld () { 141 | let rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; 142 | let target = this.target; 143 | let ta = target.a, tb = target.b, tc = target.c, td = target.d; 144 | let degRadReflect = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad; 145 | let offsetRotation = this.data.offsetRotation * degRadReflect, offsetShearY = this.data.offsetShearY * degRadReflect; 146 | let bones = this.bones; 147 | for (let i = 0, n = bones.length; i < n; i++) { 148 | let bone = bones[i]; 149 | let modified = false; 150 | 151 | if (rotateMix != 0) { 152 | let a = bone.a, b = bone.b, c = bone.c, d = bone.d; 153 | let r = Math.atan2(tc, ta) + offsetRotation; 154 | if (r > MathUtils.PI) 155 | r -= MathUtils.PI2; 156 | else if (r < -MathUtils.PI) r += MathUtils.PI2; 157 | r *= rotateMix; 158 | let cos = Math.cos(r), sin = Math.sin(r); 159 | bone.a = cos * a - sin * c; 160 | bone.b = cos * b - sin * d; 161 | bone.c = sin * a + cos * c; 162 | bone.d = sin * b + cos * d; 163 | modified = true; 164 | } 165 | 166 | if (translateMix != 0) { 167 | let temp = this.temp; 168 | target.localToWorld(temp.set(this.data.offsetX, this.data.offsetY)); 169 | bone.worldX += temp.x * translateMix; 170 | bone.worldY += temp.y * translateMix; 171 | modified = true; 172 | } 173 | 174 | if (scaleMix > 0) { 175 | let s = (Math.sqrt(ta * ta + tc * tc) - 1 + this.data.offsetScaleX) * scaleMix + 1; 176 | bone.a *= s; 177 | bone.c *= s; 178 | s = (Math.sqrt(tb * tb + td * td) - 1 + this.data.offsetScaleY) * scaleMix + 1; 179 | bone.b *= s; 180 | bone.d *= s; 181 | modified = true; 182 | } 183 | 184 | if (shearMix > 0) { 185 | let r = Math.atan2(td, tb) - Math.atan2(tc, ta); 186 | if (r > MathUtils.PI) 187 | r -= MathUtils.PI2; 188 | else if (r < -MathUtils.PI) r += MathUtils.PI2; 189 | let b = bone.b, d = bone.d; 190 | r = Math.atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; 191 | let s = Math.sqrt(b * b + d * d); 192 | bone.b = Math.cos(r) * s; 193 | bone.d = Math.sin(r) * s; 194 | modified = true; 195 | } 196 | 197 | if (modified) bone.appliedValid = false; 198 | } 199 | } 200 | 201 | applyAbsoluteLocal () { 202 | let rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; 203 | let target = this.target; 204 | if (!target.appliedValid) target.updateAppliedTransform(); 205 | let bones = this.bones; 206 | for (let i = 0, n = bones.length; i < n; i++) { 207 | let bone = bones[i]; 208 | if (!bone.appliedValid) bone.updateAppliedTransform(); 209 | 210 | let rotation = bone.arotation; 211 | if (rotateMix != 0) { 212 | let r = target.arotation - rotation + this.data.offsetRotation; 213 | r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360; 214 | rotation += r * rotateMix; 215 | } 216 | 217 | let x = bone.ax, y = bone.ay; 218 | if (translateMix != 0) { 219 | x += (target.ax - x + this.data.offsetX) * translateMix; 220 | y += (target.ay - y + this.data.offsetY) * translateMix; 221 | } 222 | 223 | let scaleX = bone.ascaleX, scaleY = bone.ascaleY; 224 | if (scaleMix != 0) { 225 | if (scaleX > 0.00001) scaleX = (scaleX + (target.ascaleX - scaleX + this.data.offsetScaleX) * scaleMix) / scaleX; 226 | if (scaleY > 0.00001) scaleY = (scaleY + (target.ascaleY - scaleY + this.data.offsetScaleY) * scaleMix) / scaleY; 227 | } 228 | 229 | let shearY = bone.ashearY; 230 | if (shearMix != 0) { 231 | let r = target.ashearY - shearY + this.data.offsetShearY; 232 | r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360; 233 | bone.shearY += r * shearMix; 234 | } 235 | 236 | bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); 237 | } 238 | } 239 | 240 | applyRelativeLocal () { 241 | let rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; 242 | let target = this.target; 243 | if (!target.appliedValid) target.updateAppliedTransform(); 244 | let bones = this.bones; 245 | for (let i = 0, n = bones.length; i < n; i++) { 246 | let bone = bones[i]; 247 | if (!bone.appliedValid) bone.updateAppliedTransform(); 248 | 249 | let rotation = bone.arotation; 250 | if (rotateMix != 0) rotation += (target.arotation + this.data.offsetRotation) * rotateMix; 251 | 252 | let x = bone.ax, y = bone.ay; 253 | if (translateMix != 0) { 254 | x += (target.ax + this.data.offsetX) * translateMix; 255 | y += (target.ay + this.data.offsetY) * translateMix; 256 | } 257 | 258 | let scaleX = bone.ascaleX, scaleY = bone.ascaleY; 259 | if (scaleMix != 0) { 260 | if (scaleX > 0.00001) scaleX *= ((target.ascaleX - 1 + this.data.offsetScaleX) * scaleMix) + 1; 261 | if (scaleY > 0.00001) scaleY *= ((target.ascaleY - 1 + this.data.offsetScaleY) * scaleMix) + 1; 262 | } 263 | 264 | let shearY = bone.ashearY; 265 | if (shearMix != 0) shearY += (target.ashearY + this.data.offsetShearY) * shearMix; 266 | 267 | bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); 268 | } 269 | } 270 | 271 | getOrder () { 272 | return this.data.order; 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/core/TransformConstraintData.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class TransformConstraintData { 32 | name: string; 33 | order = 0; 34 | bones = new Array(); 35 | target: BoneData; 36 | rotateMix = 0; translateMix = 0; scaleMix = 0; shearMix = 0; 37 | offsetRotation = 0; offsetX = 0; offsetY = 0; offsetScaleX = 0; offsetScaleY = 0; offsetShearY = 0; 38 | relative = false; 39 | local = false; 40 | 41 | constructor (name: string) { 42 | if (name == null) throw new Error("name cannot be null."); 43 | this.name = name; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/core/Triangulator.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class Triangulator { 32 | private convexPolygons = new Array>(); 33 | private convexPolygonsIndices = new Array>(); 34 | 35 | private indicesArray = new Array(); 36 | private isConcaveArray = new Array(); 37 | private triangles = new Array(); 38 | 39 | private polygonPool = new Pool>(() => { 40 | return new Array(); 41 | }); 42 | 43 | private polygonIndicesPool = new Pool>(() => { 44 | return new Array(); 45 | }); 46 | 47 | public triangulate (verticesArray: ArrayLike): Array { 48 | let vertices = verticesArray; 49 | let vertexCount = verticesArray.length >> 1; 50 | 51 | let indices = this.indicesArray; 52 | indices.length = 0; 53 | for (let i = 0; i < vertexCount; i++) 54 | indices[i] = i; 55 | 56 | let isConcave = this.isConcaveArray; 57 | isConcave.length = 0; 58 | for (let i = 0, n = vertexCount; i < n; ++i) 59 | isConcave[i] = Triangulator.isConcave(i, vertexCount, vertices, indices); 60 | 61 | let triangles = this.triangles; 62 | triangles.length = 0; 63 | 64 | while (vertexCount > 3) { 65 | // Find ear tip. 66 | let previous = vertexCount - 1, i = 0, next = 1; 67 | while (true) { 68 | outer: 69 | if (!isConcave[i]) { 70 | let p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1; 71 | let p1x = vertices[p1], p1y = vertices[p1 + 1]; 72 | let p2x = vertices[p2], p2y = vertices[p2 + 1]; 73 | let p3x = vertices[p3], p3y = vertices[p3 + 1]; 74 | for (let ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { 75 | if (!isConcave[ii]) continue; 76 | let v = indices[ii] << 1; 77 | let vx = vertices[v], vy = vertices[v + 1]; 78 | if (Triangulator.positiveArea(p3x, p3y, p1x, p1y, vx, vy)) { 79 | if (Triangulator.positiveArea(p1x, p1y, p2x, p2y, vx, vy)) { 80 | if (Triangulator.positiveArea(p2x, p2y, p3x, p3y, vx, vy)) break outer; 81 | } 82 | } 83 | } 84 | break; 85 | } 86 | 87 | if (next == 0) { 88 | do { 89 | if (!isConcave[i]) break; 90 | i--; 91 | } while (i > 0); 92 | break; 93 | } 94 | 95 | previous = i; 96 | i = next; 97 | next = (next + 1) % vertexCount; 98 | } 99 | 100 | // Cut ear tip. 101 | triangles.push(indices[(vertexCount + i - 1) % vertexCount]); 102 | triangles.push(indices[i]); 103 | triangles.push(indices[(i + 1) % vertexCount]); 104 | indices.splice(i, 1); 105 | isConcave.splice(i, 1); 106 | vertexCount--; 107 | 108 | let previousIndex = (vertexCount + i - 1) % vertexCount; 109 | let nextIndex = i == vertexCount ? 0 : i; 110 | isConcave[previousIndex] = Triangulator.isConcave(previousIndex, vertexCount, vertices, indices); 111 | isConcave[nextIndex] = Triangulator.isConcave(nextIndex, vertexCount, vertices, indices); 112 | } 113 | 114 | if (vertexCount == 3) { 115 | triangles.push(indices[2]); 116 | triangles.push(indices[0]); 117 | triangles.push(indices[1]); 118 | } 119 | 120 | return triangles; 121 | } 122 | 123 | decompose (verticesArray: Array, triangles: Array) : Array> { 124 | let vertices = verticesArray; 125 | let convexPolygons = this.convexPolygons; 126 | this.polygonPool.freeAll(convexPolygons); 127 | convexPolygons.length = 0; 128 | 129 | let convexPolygonsIndices = this.convexPolygonsIndices; 130 | this.polygonIndicesPool.freeAll(convexPolygonsIndices); 131 | convexPolygonsIndices.length = 0; 132 | 133 | let polygonIndices = this.polygonIndicesPool.obtain(); 134 | polygonIndices.length = 0; 135 | 136 | let polygon = this.polygonPool.obtain(); 137 | polygon.length = 0; 138 | 139 | // Merge subsequent triangles if they form a triangle fan. 140 | let fanBaseIndex = -1, lastWinding = 0; 141 | for (let i = 0, n = triangles.length; i < n; i += 3) { 142 | let t1 = triangles[i] << 1, t2 = triangles[i + 1] << 1, t3 = triangles[i + 2] << 1; 143 | let x1 = vertices[t1], y1 = vertices[t1 + 1]; 144 | let x2 = vertices[t2], y2 = vertices[t2 + 1]; 145 | let x3 = vertices[t3], y3 = vertices[t3 + 1]; 146 | 147 | // If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan). 148 | let merged = false; 149 | if (fanBaseIndex == t1) { 150 | let o = polygon.length - 4; 151 | let winding1 = Triangulator.winding(polygon[o], polygon[o + 1], polygon[o + 2], polygon[o + 3], x3, y3); 152 | let winding2 = Triangulator.winding(x3, y3, polygon[0], polygon[1], polygon[2], polygon[3]); 153 | if (winding1 == lastWinding && winding2 == lastWinding) { 154 | polygon.push(x3); 155 | polygon.push(y3); 156 | polygonIndices.push(t3); 157 | merged = true; 158 | } 159 | } 160 | 161 | // Otherwise make this triangle the new base. 162 | if (!merged) { 163 | if (polygon.length > 0) { 164 | convexPolygons.push(polygon); 165 | convexPolygonsIndices.push(polygonIndices); 166 | } else { 167 | this.polygonPool.free(polygon) 168 | this.polygonIndicesPool.free(polygonIndices); 169 | } 170 | polygon = this.polygonPool.obtain(); 171 | polygon.length = 0; 172 | polygon.push(x1); 173 | polygon.push(y1); 174 | polygon.push(x2); 175 | polygon.push(y2); 176 | polygon.push(x3); 177 | polygon.push(y3); 178 | polygonIndices = this.polygonIndicesPool.obtain(); 179 | polygonIndices.length = 0; 180 | polygonIndices.push(t1); 181 | polygonIndices.push(t2); 182 | polygonIndices.push(t3); 183 | lastWinding = Triangulator.winding(x1, y1, x2, y2, x3, y3); 184 | fanBaseIndex = t1; 185 | } 186 | } 187 | 188 | if (polygon.length > 0) { 189 | convexPolygons.push(polygon); 190 | convexPolygonsIndices.push(polygonIndices); 191 | } 192 | 193 | // Go through the list of polygons and try to merge the remaining triangles with the found triangle fans. 194 | for (let i = 0, n = convexPolygons.length; i < n; i++) { 195 | polygonIndices = convexPolygonsIndices[i]; 196 | if (polygonIndices.length == 0) continue; 197 | let firstIndex = polygonIndices[0]; 198 | let lastIndex = polygonIndices[polygonIndices.length - 1]; 199 | 200 | polygon = convexPolygons[i]; 201 | let o = polygon.length - 4; 202 | let prevPrevX = polygon[o], prevPrevY = polygon[o + 1]; 203 | let prevX = polygon[o + 2], prevY = polygon[o + 3]; 204 | let firstX = polygon[0], firstY = polygon[1]; 205 | let secondX = polygon[2], secondY = polygon[3]; 206 | let winding = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); 207 | 208 | for (let ii = 0; ii < n; ii++) { 209 | if (ii == i) continue; 210 | let otherIndices = convexPolygonsIndices[ii]; 211 | if (otherIndices.length != 3) continue; 212 | let otherFirstIndex = otherIndices[0]; 213 | let otherSecondIndex = otherIndices[1]; 214 | let otherLastIndex = otherIndices[2]; 215 | 216 | let otherPoly = convexPolygons[ii]; 217 | let x3 = otherPoly[otherPoly.length - 2], y3 = otherPoly[otherPoly.length - 1]; 218 | 219 | if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue; 220 | let winding1 = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3); 221 | let winding2 = Triangulator.winding(x3, y3, firstX, firstY, secondX, secondY); 222 | if (winding1 == winding && winding2 == winding) { 223 | otherPoly.length = 0; 224 | otherIndices.length = 0; 225 | polygon.push(x3); 226 | polygon.push(y3); 227 | polygonIndices.push(otherLastIndex); 228 | prevPrevX = prevX; 229 | prevPrevY = prevY; 230 | prevX = x3; 231 | prevY = y3; 232 | ii = 0; 233 | } 234 | } 235 | } 236 | 237 | // Remove empty polygons that resulted from the merge step above. 238 | for (let i = convexPolygons.length - 1; i >= 0; i--) { 239 | polygon = convexPolygons[i]; 240 | if (polygon.length == 0) { 241 | convexPolygons.splice(i, 1); 242 | this.polygonPool.free(polygon); 243 | polygonIndices = convexPolygonsIndices[i] 244 | convexPolygonsIndices.splice(i, 1) 245 | this.polygonIndicesPool.free(polygonIndices); 246 | } 247 | } 248 | 249 | return convexPolygons; 250 | } 251 | 252 | private static isConcave (index: number, vertexCount: number, vertices: ArrayLike, indices: ArrayLike): boolean { 253 | let previous = indices[(vertexCount + index - 1) % vertexCount] << 1; 254 | let current = indices[index] << 1; 255 | let next = indices[(index + 1) % vertexCount] << 1; 256 | return !this.positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[next], 257 | vertices[next + 1]); 258 | } 259 | 260 | private static positiveArea (p1x: number, p1y: number, p2x: number, p2y: number, p3x: number, p3y: number): boolean { 261 | return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; 262 | } 263 | 264 | private static winding (p1x: number, p1y: number, p2x: number, p2y: number, p3x: number, p3y: number): number { 265 | let px = p2x - p1x, py = p2y - p1y; 266 | return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1; 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/core/Updatable.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export interface Updatable { 32 | update(): void; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core/Utils.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export interface Map { 32 | [key: string]: T; 33 | } 34 | 35 | export class IntSet { 36 | array = new Array(); 37 | 38 | add (value: number): boolean { 39 | let contains = this.contains(value); 40 | this.array[value | 0] = value | 0; 41 | return !contains; 42 | } 43 | 44 | contains (value: number) { 45 | return this.array[value | 0] != undefined; 46 | } 47 | 48 | remove (value: number) { 49 | this.array[value | 0] = undefined; 50 | } 51 | 52 | clear () { 53 | this.array.length = 0; 54 | } 55 | } 56 | 57 | export interface Disposable { 58 | dispose (): void; 59 | } 60 | 61 | export interface Restorable { 62 | restore (): void; 63 | } 64 | 65 | export class Color { 66 | public static WHITE = new Color(1, 1, 1, 1); 67 | public static RED = new Color(1, 0, 0, 1); 68 | public static GREEN = new Color(0, 1, 0, 1); 69 | public static BLUE = new Color(0, 0, 1, 1); 70 | public static MAGENTA = new Color(1, 0, 1, 1); 71 | 72 | constructor (public r: number = 0, public g: number = 0, public b: number = 0, public a: number = 0) { 73 | } 74 | 75 | set (r: number, g: number, b: number, a: number) { 76 | this.r = r; 77 | this.g = g; 78 | this.b = b; 79 | this.a = a; 80 | this.clamp(); 81 | return this; 82 | } 83 | 84 | setFromColor (c: Color) { 85 | this.r = c.r; 86 | this.g = c.g; 87 | this.b = c.b; 88 | this.a = c.a; 89 | return this; 90 | } 91 | 92 | setFromString (hex: string) { 93 | hex = hex.charAt(0) == '#' ? hex.substr(1) : hex; 94 | this.r = parseInt(hex.substr(0, 2), 16) / 255.0; 95 | this.g = parseInt(hex.substr(2, 2), 16) / 255.0; 96 | this.b = parseInt(hex.substr(4, 2), 16) / 255.0; 97 | this.a = (hex.length != 8 ? 255 : parseInt(hex.substr(6, 2), 16)) / 255.0; 98 | return this; 99 | } 100 | 101 | add (r: number, g: number, b: number, a: number) { 102 | this.r += r; 103 | this.g += g; 104 | this.b += b; 105 | this.a += a; 106 | this.clamp(); 107 | return this; 108 | } 109 | 110 | clamp () { 111 | if (this.r < 0) this.r = 0; 112 | else if (this.r > 1) this.r = 1; 113 | 114 | if (this.g < 0) this.g = 0; 115 | else if (this.g > 1) this.g = 1; 116 | 117 | if (this.b < 0) this.b = 0; 118 | else if (this.b > 1) this.b = 1; 119 | 120 | if (this.a < 0) this.a = 0; 121 | else if (this.a > 1) this.a = 1; 122 | return this; 123 | } 124 | } 125 | 126 | export class MathUtils { 127 | static PI = 3.1415927; 128 | static PI2 = MathUtils.PI * 2; 129 | static radiansToDegrees = 180 / MathUtils.PI; 130 | static radDeg = MathUtils.radiansToDegrees; 131 | static degreesToRadians = MathUtils.PI / 180; 132 | static degRad = MathUtils.degreesToRadians; 133 | 134 | static clamp (value: number, min: number, max: number) { 135 | if (value < min) return min; 136 | if (value > max) return max; 137 | return value; 138 | } 139 | 140 | static cosDeg (degrees: number) { 141 | return Math.cos(degrees * MathUtils.degRad); 142 | } 143 | 144 | static sinDeg (degrees: number) { 145 | return Math.sin(degrees * MathUtils.degRad); 146 | } 147 | 148 | static signum (value: number): number { 149 | return value > 0 ? 1 : value < 0 ? -1 : 0; 150 | } 151 | 152 | static toInt (x: number) { 153 | return x > 0 ? Math.floor(x) : Math.ceil(x); 154 | } 155 | 156 | static cbrt (x: number) { 157 | let y = Math.pow(Math.abs(x), 1/3); 158 | return x < 0 ? -y : y; 159 | } 160 | 161 | static randomTriangular (min: number, max: number): number { 162 | return MathUtils.randomTriangularWith(min, max, (min + max) * 0.5); 163 | } 164 | 165 | static randomTriangularWith (min: number, max: number, mode: number): number { 166 | let u = Math.random(); 167 | let d = max - min; 168 | if (u <= (mode - min) / d) return min + Math.sqrt(u * d * (mode - min)); 169 | return max - Math.sqrt((1 - u) * d * (max - mode)); 170 | } 171 | } 172 | 173 | export abstract class Interpolation { 174 | protected abstract applyInternal (a: number): number; 175 | apply(start: number, end: number, a: number): number { 176 | return start + (end - start) * this.applyInternal(a); 177 | } 178 | } 179 | 180 | export class Pow extends Interpolation { 181 | protected power = 2; 182 | 183 | constructor (power: number) { 184 | super(); 185 | this.power = power; 186 | } 187 | 188 | applyInternal (a: number): number { 189 | if (a <= 0.5) return Math.pow(a * 2, this.power) / 2; 190 | return Math.pow((a - 1) * 2, this.power) / (this.power % 2 == 0 ? -2 : 2) + 1; 191 | } 192 | } 193 | 194 | export class PowOut extends Pow { 195 | constructor (power: number) { 196 | super(power); 197 | } 198 | 199 | applyInternal (a: number) : number { 200 | return Math.pow(a - 1, this.power) * (this.power % 2 == 0 ? -1 : 1) + 1; 201 | } 202 | } 203 | 204 | export class Utils { 205 | static SUPPORTS_TYPED_ARRAYS = typeof(Float32Array) !== "undefined"; 206 | 207 | static arrayCopy (source: ArrayLike, sourceStart: number, dest: ArrayLike, destStart: number, numElements: number) { 208 | for (let i = sourceStart, j = destStart; i < sourceStart + numElements; i++, j++) { 209 | dest[j] = source[i]; 210 | } 211 | } 212 | 213 | static setArraySize (array: Array, size: number, value: any = 0): Array { 214 | let oldSize = array.length; 215 | if (oldSize == size) return array; 216 | array.length = size; 217 | if (oldSize < size) { 218 | for (let i = oldSize; i < size; i++) array[i] = value; 219 | } 220 | return array; 221 | } 222 | 223 | static ensureArrayCapacity (array: Array, size: number, value: any = 0): Array { 224 | if (array.length >= size) return array; 225 | return Utils.setArraySize(array, size, value); 226 | } 227 | 228 | static newArray (size: number, defaultValue: T): Array { 229 | let array = new Array(size); 230 | for (let i = 0; i < size; i++) array[i] = defaultValue; 231 | return array; 232 | } 233 | 234 | static newFloatArray (size: number): ArrayLike { 235 | if (Utils.SUPPORTS_TYPED_ARRAYS) { 236 | return new Float32Array(size) 237 | } else { 238 | let array = new Array(size); 239 | for (let i = 0; i < array.length; i++) array[i] = 0; 240 | return array; 241 | } 242 | } 243 | 244 | static newShortArray (size: number): ArrayLike { 245 | if (Utils.SUPPORTS_TYPED_ARRAYS) { 246 | return new Int16Array(size) 247 | } else { 248 | let array = new Array(size); 249 | for (let i = 0; i < array.length; i++) array[i] = 0; 250 | return array; 251 | } 252 | } 253 | 254 | static toFloatArray (array: Array) { 255 | return Utils.SUPPORTS_TYPED_ARRAYS ? new Float32Array(array) : array; 256 | } 257 | 258 | static toSinglePrecision (value: number) { 259 | return Utils.SUPPORTS_TYPED_ARRAYS ? Math.fround(value) : value; 260 | } 261 | 262 | // This function is used to fix WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109 263 | static webkit602BugfixHelper (alpha: number, blend: MixBlend) { 264 | 265 | } 266 | } 267 | 268 | export class DebugUtils { 269 | static logBones(skeleton: Skeleton) { 270 | for (let i = 0; i < skeleton.bones.length; i++) { 271 | let bone = skeleton.bones[i]; 272 | console.log(bone.data.name + ", " + bone.a + ", " + bone.b + ", " + bone.c + ", " + bone.d + ", " + bone.worldX + ", " + bone.worldY); 273 | } 274 | } 275 | } 276 | 277 | export class Pool { 278 | private items = new Array(); 279 | private instantiator: () => T; 280 | 281 | constructor (instantiator: () => T) { 282 | this.instantiator = instantiator; 283 | } 284 | 285 | obtain () { 286 | return this.items.length > 0 ? this.items.pop() : this.instantiator(); 287 | } 288 | 289 | free (item: T) { 290 | if ((item as any).reset) (item as any).reset(); 291 | this.items.push(item); 292 | } 293 | 294 | freeAll (items: ArrayLike) { 295 | for (let i = 0; i < items.length; i++) { 296 | if ((items[i] as any).reset) (items[i] as any).reset(); 297 | this.items[i] = items[i]; 298 | } 299 | } 300 | 301 | clear () { 302 | this.items.length = 0; 303 | } 304 | } 305 | 306 | export class Vector2 { 307 | constructor (public x = 0, public y = 0) { 308 | } 309 | 310 | set (x: number, y: number): Vector2 { 311 | this.x = x; 312 | this.y = y; 313 | return this; 314 | } 315 | 316 | length () { 317 | let x = this.x; 318 | let y = this.y; 319 | return Math.sqrt(x * x + y * y); 320 | } 321 | 322 | normalize () { 323 | let len = this.length(); 324 | if (len != 0) { 325 | this.x /= len; 326 | this.y /= len; 327 | } 328 | return this; 329 | } 330 | } 331 | 332 | export class TimeKeeper { 333 | maxDelta = 0.064; 334 | framesPerSecond = 0; 335 | delta = 0; 336 | totalTime = 0; 337 | 338 | private lastTime = Date.now() / 1000; 339 | private frameCount = 0; 340 | private frameTime = 0; 341 | 342 | update () { 343 | let now = Date.now() / 1000; 344 | this.delta = now - this.lastTime; 345 | this.frameTime += this.delta; 346 | this.totalTime += this.delta; 347 | if (this.delta > this.maxDelta) this.delta = this.maxDelta; 348 | this.lastTime = now; 349 | 350 | this.frameCount++; 351 | if (this.frameTime > 1) { 352 | this.framesPerSecond = this.frameCount / this.frameTime; 353 | this.frameTime = 0; 354 | this.frameCount = 0; 355 | } 356 | } 357 | } 358 | 359 | export interface ArrayLike { 360 | length: number; 361 | [n: number]: T; 362 | } 363 | 364 | export class WindowedMean { 365 | values: Array; 366 | addedValues = 0; 367 | lastValue = 0; 368 | mean = 0; 369 | dirty = true; 370 | 371 | constructor (windowSize: number = 32) { 372 | this.values = new Array(windowSize); 373 | } 374 | 375 | hasEnoughData () { 376 | return this.addedValues >= this.values.length; 377 | } 378 | 379 | addValue (value: number) { 380 | if (this.addedValues < this.values.length) 381 | this.addedValues++; 382 | this.values[this.lastValue++] = value; 383 | if (this.lastValue > this.values.length - 1) this.lastValue = 0; 384 | this.dirty = true; 385 | } 386 | 387 | getMean () { 388 | if (this.hasEnoughData()) { 389 | if (this.dirty) { 390 | let mean = 0; 391 | for (let i = 0; i < this.values.length; i++) { 392 | mean += this.values[i]; 393 | } 394 | this.mean = mean / this.values.length; 395 | this.dirty = false; 396 | } 397 | return this.mean; 398 | } else { 399 | return 0; 400 | } 401 | } 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/core/VertexEffect.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export interface VertexEffect { 32 | begin(skeleton: Skeleton): void; 33 | transform(position: Vector2, uv: Vector2, light: Color, dark: Color): void; 34 | end(): void; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/core/attachments/Attachment.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export abstract class Attachment { 32 | name: string; 33 | 34 | constructor (name: string) { 35 | if (name == null) throw new Error("name cannot be null."); 36 | this.name = name; 37 | } 38 | } 39 | 40 | export abstract class VertexAttachment extends Attachment { 41 | private static nextID = 0; 42 | 43 | id = (VertexAttachment.nextID++ & 65535) << 11; 44 | bones: Array; 45 | vertices: ArrayLike; 46 | worldVerticesLength = 0; 47 | 48 | constructor (name: string) { 49 | super(name); 50 | } 51 | 52 | /** Transforms local vertices to world coordinates. 53 | * @param start The index of the first local vertex value to transform. Each vertex has 2 values, x and y. 54 | * @param count The number of world vertex values to output. Must be <= {@link #getWorldVerticesLength()} - start. 55 | * @param worldVertices The output world vertices. Must have a length >= offset + count. 56 | * @param offset The worldVertices index to begin writing values. */ 57 | computeWorldVertices (slot: Slot, start: number, count: number, worldVertices: ArrayLike, offset: number, stride: number) { 58 | count = offset + (count >> 1) * stride; 59 | let skeleton = slot.bone.skeleton; 60 | let deformArray = slot.attachmentVertices; 61 | let vertices = this.vertices; 62 | let bones = this.bones; 63 | if (bones == null) { 64 | if (deformArray.length > 0) vertices = deformArray; 65 | let bone = slot.bone; 66 | let x = bone.worldX; 67 | let y = bone.worldY; 68 | let a = bone.a, b = bone.b, c = bone.c, d = bone.d; 69 | for (let v = start, w = offset; w < count; v += 2, w += stride) { 70 | let vx = vertices[v], vy = vertices[v + 1]; 71 | worldVertices[w] = vx * a + vy * b + x; 72 | worldVertices[w + 1] = vx * c + vy * d + y; 73 | } 74 | return; 75 | } 76 | let v = 0, skip = 0; 77 | for (let i = 0; i < start; i += 2) { 78 | let n = bones[v]; 79 | v += n + 1; 80 | skip += n; 81 | } 82 | let skeletonBones = skeleton.bones; 83 | if (deformArray.length == 0) { 84 | for (let w = offset, b = skip * 3; w < count; w += stride) { 85 | let wx = 0, wy = 0; 86 | let n = bones[v++]; 87 | n += v; 88 | for (; v < n; v++, b += 3) { 89 | let bone = skeletonBones[bones[v]]; 90 | let vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2]; 91 | wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; 92 | wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; 93 | } 94 | worldVertices[w] = wx; 95 | worldVertices[w + 1] = wy; 96 | } 97 | } else { 98 | let deform = deformArray; 99 | for (let w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { 100 | let wx = 0, wy = 0; 101 | let n = bones[v++]; 102 | n += v; 103 | for (; v < n; v++, b += 3, f += 2) { 104 | let bone = skeletonBones[bones[v]]; 105 | let vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2]; 106 | wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; 107 | wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; 108 | } 109 | worldVertices[w] = wx; 110 | worldVertices[w + 1] = wy; 111 | } 112 | } 113 | } 114 | 115 | /** Returns true if a deform originally applied to the specified attachment should be applied to this attachment. */ 116 | applyDeform (sourceAttachment: VertexAttachment) { 117 | return this == sourceAttachment; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/core/attachments/AttachmentLoader.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export interface AttachmentLoader { 32 | /** @return May be null to not load an attachment. */ 33 | newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment; 34 | 35 | /** @return May be null to not load an attachment. */ 36 | newMeshAttachment (skin: Skin, name: string, path: string) : MeshAttachment; 37 | 38 | /** @return May be null to not load an attachment. */ 39 | newBoundingBoxAttachment (skin: Skin, name: string) : BoundingBoxAttachment; 40 | 41 | /** @return May be null to not load an attachment */ 42 | newPathAttachment(skin: Skin, name: string): PathAttachment; 43 | 44 | /** @return May be null to not load an attachment */ 45 | newPointAttachment(skin: Skin, name: string): PointAttachment; 46 | 47 | /** @return May be null to not load an attachment */ 48 | newClippingAttachment(skin: Skin, name: string): ClippingAttachment; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/core/attachments/AttachmentType.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export enum AttachmentType { 32 | Region, BoundingBox, Mesh, LinkedMesh, Path, Point 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core/attachments/BoundingBoxAttachment.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class BoundingBoxAttachment extends VertexAttachment { 32 | color = new Color(1, 1, 1, 1); 33 | 34 | constructor (name: string) { 35 | super(name); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/core/attachments/ClippingAttachment.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class ClippingAttachment extends VertexAttachment { 32 | endSlot: SlotData; 33 | 34 | // Nonessential. 35 | color = new Color(0.2275, 0.2275, 0.8078, 1); // ce3a3aff 36 | 37 | constructor (name: string) { 38 | super(name); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/core/attachments/MeshAttachment.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class MeshAttachment extends VertexAttachment { 32 | region: TextureRegion; 33 | path: string; 34 | regionUVs: ArrayLike; uvs: ArrayLike; 35 | triangles: Array; 36 | color = new Color(1, 1, 1, 1); 37 | hullLength: number; 38 | private parentMesh: MeshAttachment; 39 | inheritDeform = false; 40 | tempColor = new Color(0, 0, 0, 0); 41 | 42 | constructor (name: string) { 43 | super(name); 44 | } 45 | 46 | updateUVs () { 47 | let regionUVs = this.regionUVs; 48 | if (this.uvs == null || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length); 49 | let uvs = this.uvs; 50 | let u = 0, v = 0, width = 0, height = 0; 51 | if (this.region instanceof TextureAtlasRegion) { 52 | let region = this.region; 53 | let textureWidth = region.texture.getImage().width, textureHeight = region.texture.getImage().height; 54 | if (region.rotate) { 55 | u = region.u - (region.originalHeight - region.offsetY - region.height) / textureWidth; 56 | v = region.v - (region.originalWidth - region.offsetX - region.width) / textureHeight; 57 | width = region.originalHeight / textureWidth; 58 | height = region.originalWidth / textureHeight; 59 | for (let i = 0, n = uvs.length; i < n; i += 2) { 60 | uvs[i] = u + regionUVs[i + 1] * width; 61 | uvs[i + 1] = v + height - regionUVs[i] * height; 62 | } 63 | return; 64 | } 65 | u = region.u - region.offsetX / textureWidth; 66 | v = region.v - (region.originalHeight - region.offsetY - region.height) / textureHeight; 67 | width = region.originalWidth / textureWidth; 68 | height = region.originalHeight / textureHeight; 69 | } else if (this.region == null) { 70 | u = v = 0; 71 | width = height = 1; 72 | } else { 73 | u = this.region.u; 74 | v = this.region.v; 75 | width = this.region.u2 - u; 76 | height = this.region.v2 - v; 77 | } 78 | 79 | for (let i = 0, n = uvs.length; i < n; i += 2) { 80 | uvs[i] = u + regionUVs[i] * width; 81 | uvs[i + 1] = v + regionUVs[i + 1] * height; 82 | } 83 | } 84 | 85 | /*updateUVs () { 86 | let u = 0, v = 0, width = 0, height = 0; 87 | if (this.region == null) { 88 | u = v = 0; 89 | width = height = 1; 90 | } else { 91 | u = this.region.u; 92 | v = this.region.v; 93 | width = this.region.u2 - u; 94 | height = this.region.v2 - v; 95 | } 96 | let regionUVs = this.regionUVs; 97 | if (this.uvs == null || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length); 98 | let uvs = this.uvs; 99 | if (this.region.rotate) { 100 | for (let i = 0, n = uvs.length; i < n; i += 2) { 101 | uvs[i] = u + regionUVs[i + 1] * width; 102 | uvs[i + 1] = v + height - regionUVs[i] * height; 103 | } 104 | } else { 105 | for (let i = 0, n = uvs.length; i < n; i += 2) { 106 | uvs[i] = u + regionUVs[i] * width; 107 | uvs[i + 1] = v + regionUVs[i + 1] * height; 108 | } 109 | } 110 | }*/ 111 | 112 | applyDeform (sourceAttachment: VertexAttachment): boolean { 113 | return this == sourceAttachment || (this.inheritDeform && this.parentMesh == sourceAttachment); 114 | } 115 | 116 | getParentMesh () { 117 | return this.parentMesh; 118 | } 119 | 120 | /** @param parentMesh May be null. */ 121 | setParentMesh (parentMesh: MeshAttachment) { 122 | this.parentMesh = parentMesh; 123 | if (parentMesh != null) { 124 | this.bones = parentMesh.bones; 125 | this.vertices = parentMesh.vertices; 126 | this.worldVerticesLength = parentMesh.worldVerticesLength; 127 | this.regionUVs = parentMesh.regionUVs; 128 | this.triangles = parentMesh.triangles; 129 | this.hullLength = parentMesh.hullLength; 130 | this.worldVerticesLength = parentMesh.worldVerticesLength 131 | } 132 | } 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/core/attachments/PathAttachment.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class PathAttachment extends VertexAttachment { 32 | lengths: Array; 33 | closed = false; constantSpeed = false; 34 | color = new Color(1, 1, 1, 1); 35 | 36 | constructor (name: string) { 37 | super(name); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/core/attachments/PointAttachment.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class PointAttachment extends VertexAttachment { 32 | x: number; y: number; rotation: number; 33 | color = new Color(0.38, 0.94, 0, 1); 34 | 35 | constructor (name: string) { 36 | super(name); 37 | } 38 | 39 | computeWorldPosition (bone: Bone, point: Vector2) { 40 | point.x = this.x * bone.a + this.y * bone.b + bone.worldX; 41 | point.y = this.x * bone.c + this.y * bone.d + bone.worldY; 42 | return point; 43 | } 44 | 45 | computeWorldRotation (bone: Bone) { 46 | let cos = MathUtils.cosDeg(this.rotation), sin = MathUtils.sinDeg(this.rotation); 47 | let x = cos * bone.a + sin * bone.b; 48 | let y = cos * bone.c + sin * bone.d; 49 | return Math.atan2(y, x) * MathUtils.radDeg; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/core/attachments/RegionAttachment.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class RegionAttachment extends Attachment { 32 | static OX1 = 0; 33 | static OY1 = 1; 34 | static OX2 = 2; 35 | static OY2 = 3; 36 | static OX3 = 4; 37 | static OY3 = 5; 38 | static OX4 = 6; 39 | static OY4 = 7; 40 | 41 | static X1 = 0; 42 | static Y1 = 1; 43 | static C1R = 2; 44 | static C1G = 3; 45 | static C1B = 4; 46 | static C1A = 5; 47 | static U1 = 6; 48 | static V1 = 7; 49 | 50 | static X2 = 8; 51 | static Y2 = 9; 52 | static C2R = 10; 53 | static C2G = 11; 54 | static C2B = 12; 55 | static C2A = 13; 56 | static U2 = 14; 57 | static V2 = 15; 58 | 59 | static X3 = 16; 60 | static Y3 = 17; 61 | static C3R = 18; 62 | static C3G = 19; 63 | static C3B = 20; 64 | static C3A = 21; 65 | static U3 = 22; 66 | static V3 = 23; 67 | 68 | static X4 = 24; 69 | static Y4 = 25; 70 | static C4R = 26; 71 | static C4G = 27; 72 | static C4B = 28; 73 | static C4A = 29; 74 | static U4 = 30; 75 | static V4 = 31; 76 | 77 | x = 0; y = 0; scaleX = 1; scaleY = 1; rotation = 0; width = 0; height = 0; 78 | color = new Color(1, 1, 1, 1); 79 | 80 | path: string; 81 | rendererObject: any; 82 | region: TextureRegion; 83 | 84 | offset = Utils.newFloatArray(8); 85 | uvs = Utils.newFloatArray(8); 86 | 87 | tempColor = new Color(1, 1, 1, 1); 88 | 89 | constructor (name:string) { 90 | super(name); 91 | } 92 | 93 | updateOffset () : void { 94 | let regionScaleX = this.width / this.region.originalWidth * this.scaleX; 95 | let regionScaleY = this.height / this.region.originalHeight * this.scaleY; 96 | let localX = -this.width / 2 * this.scaleX + this.region.offsetX * regionScaleX; 97 | let localY = -this.height / 2 * this.scaleY + this.region.offsetY * regionScaleY; 98 | let localX2 = localX + this.region.width * regionScaleX; 99 | let localY2 = localY + this.region.height * regionScaleY; 100 | let radians = this.rotation * Math.PI / 180; 101 | let cos = Math.cos(radians); 102 | let sin = Math.sin(radians); 103 | let localXCos = localX * cos + this.x; 104 | let localXSin = localX * sin; 105 | let localYCos = localY * cos + this.y; 106 | let localYSin = localY * sin; 107 | let localX2Cos = localX2 * cos + this.x; 108 | let localX2Sin = localX2 * sin; 109 | let localY2Cos = localY2 * cos + this.y; 110 | let localY2Sin = localY2 * sin; 111 | let offset = this.offset; 112 | offset[RegionAttachment.OX1] = localXCos - localYSin; 113 | offset[RegionAttachment.OY1] = localYCos + localXSin; 114 | offset[RegionAttachment.OX2] = localXCos - localY2Sin; 115 | offset[RegionAttachment.OY2] = localY2Cos + localXSin; 116 | offset[RegionAttachment.OX3] = localX2Cos - localY2Sin; 117 | offset[RegionAttachment.OY3] = localY2Cos + localX2Sin; 118 | offset[RegionAttachment.OX4] = localX2Cos - localYSin; 119 | offset[RegionAttachment.OY4] = localYCos + localX2Sin; 120 | } 121 | 122 | setRegion (region: TextureRegion) : void { 123 | this.region = region; 124 | let uvs = this.uvs; 125 | if (region.rotate) { 126 | uvs[2] = region.u; 127 | uvs[3] = region.v2; 128 | uvs[4] = region.u; 129 | uvs[5] = region.v; 130 | uvs[6] = region.u2; 131 | uvs[7] = region.v; 132 | uvs[0] = region.u2; 133 | uvs[1] = region.v2; 134 | } else { 135 | uvs[0] = region.u; 136 | uvs[1] = region.v2; 137 | uvs[2] = region.u; 138 | uvs[3] = region.v; 139 | uvs[4] = region.u2; 140 | uvs[5] = region.v; 141 | uvs[6] = region.u2; 142 | uvs[7] = region.v2; 143 | } 144 | } 145 | 146 | computeWorldVertices (bone: Bone, worldVertices: ArrayLike, offset: number, stride: number) { 147 | let vertexOffset = this.offset; 148 | let x = bone.worldX, y = bone.worldY; 149 | let a = bone.a, b = bone.b, c = bone.c, d = bone.d; 150 | let offsetX = 0, offsetY = 0; 151 | 152 | offsetX = vertexOffset[RegionAttachment.OX1]; 153 | offsetY = vertexOffset[RegionAttachment.OY1]; 154 | worldVertices[offset] = offsetX * a + offsetY * b + x; // br 155 | worldVertices[offset + 1] = offsetX * c + offsetY * d + y; 156 | offset += stride; 157 | 158 | offsetX = vertexOffset[RegionAttachment.OX2]; 159 | offsetY = vertexOffset[RegionAttachment.OY2]; 160 | worldVertices[offset] = offsetX * a + offsetY * b + x; // bl 161 | worldVertices[offset + 1] = offsetX * c + offsetY * d + y; 162 | offset += stride; 163 | 164 | offsetX = vertexOffset[RegionAttachment.OX3]; 165 | offsetY = vertexOffset[RegionAttachment.OY3]; 166 | worldVertices[offset] = offsetX * a + offsetY * b + x; // ul 167 | worldVertices[offset + 1] = offsetX * c + offsetY * d + y; 168 | offset += stride; 169 | 170 | offsetX = vertexOffset[RegionAttachment.OX4]; 171 | offsetY = vertexOffset[RegionAttachment.OY4]; 172 | worldVertices[offset] = offsetX * a + offsetY * b + x; // ur 173 | worldVertices[offset + 1] = offsetX * c + offsetY * d + y; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/core/polyfills.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | interface Math { 31 | fround(n: number): number; 32 | } 33 | 34 | (() => { 35 | if (!Math.fround) { 36 | Math.fround = (function (array) { 37 | return function (x: number) { 38 | return array[0] = x, array[0]; 39 | }; 40 | })(new Float32Array(1)); 41 | } 42 | })(); 43 | -------------------------------------------------------------------------------- /src/core/vertexeffects/JitterEffect.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class JitterEffect implements VertexEffect { 32 | jitterX = 0; 33 | jitterY = 0; 34 | 35 | constructor (jitterX: number, jitterY: number) { 36 | this.jitterX = jitterX; 37 | this.jitterY = jitterY; 38 | } 39 | 40 | begin(skeleton: Skeleton): void { 41 | } 42 | 43 | transform(position: Vector2, uv: Vector2, light: Color, dark: Color): void { 44 | position.x += MathUtils.randomTriangular(-this.jitterX, this.jitterY); 45 | position.y += MathUtils.randomTriangular(-this.jitterX, this.jitterY); 46 | } 47 | 48 | end(): void { 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/core/vertexeffects/SwirlEffect.ts: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated May 1, 2019. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2019, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 21 | * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 24 | * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 27 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | module spine { 31 | export class SwirlEffect implements VertexEffect { 32 | static interpolation = new PowOut(2); 33 | centerX = 0; 34 | centerY = 0; 35 | radius = 0; 36 | angle = 0; 37 | private worldX = 0; 38 | private worldY = 0; 39 | 40 | constructor (radius: number) { 41 | this.radius = radius; 42 | } 43 | 44 | begin(skeleton: Skeleton): void { 45 | this.worldX = skeleton.x + this.centerX; 46 | this.worldY = skeleton.y + this.centerY; 47 | } 48 | 49 | transform(position: Vector2, uv: Vector2, light: Color, dark: Color): void { 50 | let radAngle = this.angle * MathUtils.degreesToRadians; 51 | let x = position.x - this.worldX; 52 | let y = position.y - this.worldY; 53 | let dist = Math.sqrt(x * x + y * y); 54 | if (dist < this.radius) { 55 | let theta = SwirlEffect.interpolation.apply(0, radAngle, (this.radius - dist) / this.radius); 56 | let cos = Math.cos(theta); 57 | let sin = Math.sin(theta); 58 | position.x = cos * x - sin * y + this.worldX; 59 | position.y = sin * x + cos * y + this.worldY; 60 | } 61 | } 62 | 63 | end(): void { 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/impl/EventEmitter.ts: -------------------------------------------------------------------------------- 1 | namespace spine { 2 | export class EventEmitter { 3 | private events = new Map(); 4 | 5 | public on(event: T, fn: Function, context?: any): this { 6 | let listeners = this.events.get(event); 7 | 8 | if (listeners) { 9 | listeners.push({ once: false, fn, context }); 10 | } 11 | else { 12 | this.events.set(event, [{ once: false, fn, context }]); 13 | } 14 | return this; 15 | } 16 | 17 | public once(event: T, fn: Function, context?: any): this { 18 | let listeners = this.events.get(event); 19 | 20 | if (listeners) { 21 | listeners.push({ once: true, fn, context }); 22 | } 23 | else { 24 | this.events.set(event, [{ once: true, fn, context }]); 25 | } 26 | return this; 27 | } 28 | 29 | public off(event: T, fn?: Function, context?: any, once?: boolean): this { 30 | let listeners = this.events.get(event); 31 | 32 | if (listeners) { 33 | if (fn) { 34 | for (let i = 0; i < listeners.length; i++) { 35 | let l = listeners[i]; 36 | 37 | if ((fn === l.fn) && (!once || l.once) && (!context || l.context === context)) { 38 | listeners.splice(i--, 1); 39 | } 40 | } 41 | } 42 | else { 43 | this.events.delete(event); 44 | } 45 | } 46 | return this; 47 | } 48 | 49 | public offAll(): this { 50 | this.events.clear(); 51 | return this; 52 | } 53 | 54 | public emit(event: T, ...args: any[]): this { 55 | let listeners = this.events.get(event); 56 | 57 | if (listeners) { 58 | for (let i = 0; i < listeners.length; i++) { 59 | let listener = listeners[i]; 60 | 61 | if (listener.once) listeners.splice(i--, 1); 62 | listener.fn.apply(listener.context, args); 63 | } 64 | } 65 | return this; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/impl/SkeletonAnimation.ts: -------------------------------------------------------------------------------- 1 | namespace spine { 2 | export class SkeletonAnimation extends egret.DisplayObjectContainer { 3 | public readonly renderer: SkeletonRenderer; 4 | public readonly state: AnimationState; 5 | public readonly stateData: AnimationStateData; 6 | public readonly skeleton: Skeleton; 7 | public readonly skeletonData: SkeletonData; 8 | private lastTime: number = 0; 9 | 10 | public constructor(skeletonData: SkeletonData) { 11 | super(); 12 | this.renderer = new SkeletonRenderer(skeletonData); 13 | this.state = this.renderer.state; 14 | this.stateData = this.renderer.stateData; 15 | this.skeleton = this.renderer.skeleton; 16 | this.skeletonData = this.renderer.skeletonData; 17 | this.addChild(this.renderer); 18 | this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddedToStage, this); 19 | } 20 | 21 | public get flipX(): boolean { 22 | return this.renderer.scaleX == -1; 23 | } 24 | 25 | public set flipX(flip: boolean) { 26 | this.renderer.scaleX = flip ? -1 : 1; 27 | } 28 | 29 | public get flipY(): boolean { 30 | return this.renderer.scaleY == 1; 31 | } 32 | 33 | public set flipY(flip: boolean) { 34 | this.renderer.scaleY = flip ? 1 : -1; 35 | } 36 | 37 | public setTimeScale(scale: number) { 38 | this.state.timeScale = scale; 39 | } 40 | 41 | public play(anim: string, loop = 0, trackID = 0): Track { 42 | return this.start(trackID).add(anim, loop); 43 | } 44 | 45 | public start(trackID = 0): Track { 46 | this.skeleton.setToSetupPose(); 47 | return new Track(this, trackID); 48 | } 49 | 50 | public stop(track: number) { 51 | this.state.clearTrack(track); 52 | } 53 | 54 | public stopAll(reset?: boolean) { 55 | this.state.clearTracks(); 56 | if (reset) this.skeleton.setToSetupPose(); 57 | } 58 | 59 | private onAddedToStage() { 60 | this.lastTime = Date.now() / 1000; 61 | this.addEventListener(egret.Event.ENTER_FRAME, this.onEnterFrame, this); 62 | this.addEventListener(egret.Event.REMOVED_FROM_STAGE, this.onRemovedFromStage, this); 63 | } 64 | 65 | private onRemovedFromStage() { 66 | this.removeEventListener(egret.Event.ENTER_FRAME, this.onEnterFrame, this); 67 | this.removeEventListener(egret.Event.REMOVED_FROM_STAGE, this.onRemovedFromStage, this); 68 | } 69 | 70 | private onEnterFrame() { 71 | let now = Date.now() / 1000; 72 | this.renderer.update(now - this.lastTime); 73 | this.lastTime = now; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/impl/Track.ts: -------------------------------------------------------------------------------- 1 | namespace spine { 2 | interface AnimationRecord { 3 | name: string; 4 | loop: number; 5 | listener?: AnimationListener; 6 | } 7 | 8 | export const enum SpineEvent { 9 | PlayStart, 10 | PlayEnd, 11 | LoopStart, 12 | LoopEnd, 13 | Interrupt, 14 | Custom, 15 | TrackEnd, 16 | } 17 | 18 | export interface AnimationListener { 19 | playStart?: () => void; 20 | playEnd?: () => void; 21 | loopStart?: () => void; 22 | loopEnd?: () => void; 23 | interrupt?: () => void; 24 | custom?: (event: Event) => void; 25 | } 26 | 27 | export class Track extends EventEmitter { 28 | public readonly trackID: number; 29 | public readonly skelAnimation: SkeletonAnimation; 30 | private stateListener: AnimationStateListener2; 31 | private trackEntry: TrackEntry | undefined; 32 | private animations: AnimationRecord[] = []; 33 | private disposed: boolean = false; 34 | private loop: number = 0; 35 | 36 | public constructor(skelAnimation: SkeletonAnimation, trackID: number) { 37 | super(); 38 | this.trackID = trackID; 39 | this.skelAnimation = skelAnimation; 40 | this.stateListener = { 41 | complete: () => this.onComplete(), 42 | interrupt: () => this.onInterrupt(), 43 | event: (_, event) => this.onCustomEvent(event), 44 | start: undefined!, end: undefined!, dispose: undefined! 45 | }; 46 | } 47 | 48 | public waitPlayStart() { 49 | return new Promise(resolve => this.once(SpineEvent.PlayStart, resolve)); 50 | } 51 | 52 | public waitPlayEnd() { 53 | return new Promise(resolve => this.once(SpineEvent.PlayEnd, resolve)); 54 | } 55 | 56 | public waitLoopStart() { 57 | return new Promise(resolve => this.once(SpineEvent.LoopStart, resolve)); 58 | } 59 | 60 | public waitLoopEnd() { 61 | return new Promise(resolve => this.once(SpineEvent.LoopEnd, resolve)); 62 | } 63 | 64 | public waitInterrupt() { 65 | return new Promise(resolve => this.once(SpineEvent.Interrupt, resolve)); 66 | } 67 | 68 | public waitTrackEnd() { 69 | return new Promise(resolve => this.once(SpineEvent.TrackEnd, resolve)); 70 | } 71 | 72 | public waitEvent() { 73 | return new Promise(resolve => this.once(SpineEvent.Custom, resolve)); 74 | } 75 | 76 | public waitNamedEvent(name: string) { 77 | return new Promise(resolve => { 78 | const callback = (event: Event) => { 79 | if (event.data.name == name) { 80 | this.off(SpineEvent.Custom, callback); 81 | resolve(event); 82 | } 83 | } 84 | this.on(SpineEvent.Custom, callback); 85 | }); 86 | } 87 | 88 | public add(name: string, loop: number = 1, listener?: AnimationListener) { 89 | if (!this.disposed) { 90 | this.animations.push({ name, loop, listener }); 91 | if (this.animations.length == 1) { 92 | this.playNextAnimation(); 93 | } 94 | } 95 | return this; 96 | } 97 | 98 | private setAnimation(name: string, loop: boolean) { 99 | if (this.trackEntry) this.trackEntry.listener = null; 100 | this.trackEntry = this.skelAnimation.state.setAnimation(this.trackID, name, loop); 101 | this.trackEntry.listener = this.stateListener; 102 | this.skelAnimation.renderer.update(0); 103 | } 104 | 105 | private playNextAnimation() { 106 | if (!this.disposed && this.animations.length > 0) { 107 | const { name, listener } = this.animations[0]; 108 | 109 | if (listener) { 110 | if (listener.playStart) this.on(SpineEvent.PlayStart, listener.playStart, listener); 111 | if (listener.playEnd) this.on(SpineEvent.PlayEnd, listener.playEnd, listener); 112 | if (listener.loopStart) this.on(SpineEvent.LoopStart, listener.loopStart, listener); 113 | if (listener.loopEnd) this.on(SpineEvent.LoopEnd, listener.loopEnd, listener); 114 | if (listener.interrupt) this.on(SpineEvent.Interrupt, listener.interrupt, listener); 115 | if (listener.custom) this.on(SpineEvent.Custom, listener.custom, listener); 116 | } 117 | this.loop = 0; 118 | this.setAnimation(name, false); 119 | this.emit(SpineEvent.PlayStart); 120 | this.emit(SpineEvent.LoopStart); 121 | } 122 | } 123 | 124 | private onComplete() { 125 | if (!this.disposed) { 126 | const animation = this.animations[0]; 127 | 128 | this.emit(SpineEvent.LoopEnd); 129 | 130 | if (++this.loop != animation.loop) { 131 | this.setAnimation(animation.name, false); 132 | this.emit(SpineEvent.LoopStart); 133 | } 134 | else { 135 | const listener = animation.listener; 136 | 137 | this.emit(SpineEvent.PlayEnd); 138 | this.animations.shift(); 139 | 140 | if (listener) { 141 | this.off(SpineEvent.PlayStart, listener.playStart); 142 | this.off(SpineEvent.PlayEnd, listener.playEnd); 143 | this.off(SpineEvent.LoopStart, listener.loopStart); 144 | this.off(SpineEvent.LoopEnd, listener.loopEnd); 145 | this.off(SpineEvent.Interrupt, listener.interrupt); 146 | this.off(SpineEvent.Custom, listener.custom); 147 | } 148 | if (this.animations.length > 0) { 149 | this.playNextAnimation(); 150 | } 151 | else { 152 | this.disposed = true; 153 | this.trackEntry.listener = null; 154 | this.trackEntry = null; 155 | this.emit(SpineEvent.TrackEnd); 156 | } 157 | } 158 | } 159 | } 160 | 161 | private onInterrupt() { 162 | if (!this.disposed) { 163 | this.disposed = true; 164 | this.animations.length = 0; 165 | this.emit(SpineEvent.Interrupt); 166 | } 167 | } 168 | 169 | private onCustomEvent(event: Event) { 170 | if (!this.disposed) { 171 | this.emit(SpineEvent.Custom, event); 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "noImplicitAny": false, 5 | "sourceMap": true, 6 | "declaration": true, 7 | "outFile": "bin/spine-egret-runtimes.js", 8 | "lib": [ 9 | "dom", 10 | "es6" 11 | ] 12 | }, 13 | "include": [ 14 | "src/egret", 15 | "src/core", 16 | "src/impl" 17 | ] 18 | } --------------------------------------------------------------------------------