|
@@ -1,366 +0,0 @@
|
|
|
-/******************************************************************************
|
|
|
- * Spine Runtimes Software License v2.5
|
|
|
- *
|
|
|
- * Copyright (c) 2013-2016, Esoteric Software
|
|
|
- * All rights reserved.
|
|
|
- *
|
|
|
- * You are granted a perpetual, non-exclusive, non-sublicensable, and
|
|
|
- * non-transferable license to use, install, execute, and perform the Spine
|
|
|
- * Runtimes software and derivative works solely for personal or internal
|
|
|
- * use. Without the written permission of Esoteric Software (see Section 2 of
|
|
|
- * the Spine Software License Agreement), you may not (a) modify, translate,
|
|
|
- * adapt, or develop new applications using the Spine Runtimes or otherwise
|
|
|
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
|
|
|
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
|
|
|
- * or other intellectual property or proprietary rights notices on or in the
|
|
|
- * Software, including any copy thereof. Redistributions in binary or source
|
|
|
- * form must include this license and terms.
|
|
|
- *
|
|
|
- * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
|
|
|
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
|
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
|
- * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
|
|
|
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
|
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
- * POSSIBILITY OF SUCH DAMAGE.
|
|
|
- *****************************************************************************/
|
|
|
-
|
|
|
-module spine {
|
|
|
- export class SpineWidget {
|
|
|
- skeleton: Skeleton;
|
|
|
- state: AnimationState;
|
|
|
- context: spine.webgl.ManagedWebGLRenderingContext;
|
|
|
- canvas: HTMLCanvasElement;
|
|
|
- debugRenderer: spine.webgl.SkeletonDebugRenderer;
|
|
|
-
|
|
|
- private config: SpineWidgetConfig;
|
|
|
- private assetManager: spine.webgl.AssetManager;
|
|
|
- private shader: spine.webgl.Shader;
|
|
|
- private batcher: spine.webgl.PolygonBatcher;
|
|
|
- private shapes: spine.webgl.ShapeRenderer;
|
|
|
- private debugShader: spine.webgl.Shader;
|
|
|
- private mvp = new spine.webgl.Matrix4();
|
|
|
- private skeletonRenderer: spine.webgl.SkeletonRenderer;
|
|
|
- private paused = false;
|
|
|
- private lastFrameTime = Date.now() / 1000.0;
|
|
|
- private backgroundColor = new Color();
|
|
|
- private loaded = false;
|
|
|
- private bounds = { offset: new Vector2(), size: new Vector2() };
|
|
|
-
|
|
|
-
|
|
|
- constructor (element: HTMLElement | string, config: SpineWidgetConfig) {
|
|
|
- if (!element) throw new Error("Please provide a DOM element, e.g. document.getElementById('myelement')");
|
|
|
- if (!config) throw new Error("Please provide a configuration, specifying at least the json file, atlas file and animation name");
|
|
|
-
|
|
|
- let elementId = element as string;
|
|
|
- if (typeof(element) === "string") element = document.getElementById(element as string);
|
|
|
- if (element == null) throw new Error(`Element ${elementId} does not exist`);
|
|
|
-
|
|
|
- this.validateConfig(config);
|
|
|
-
|
|
|
- let existingCanvas = <HTMLCanvasElement>element.children[0];
|
|
|
- let canvas = this.canvas = existingCanvas || document.createElement("canvas");
|
|
|
- canvas.style.width = "100%";
|
|
|
- canvas.style.height = "100%";
|
|
|
- if (!existingCanvas) {
|
|
|
- (<HTMLElement> element).appendChild(canvas);
|
|
|
- }
|
|
|
- canvas.width = (<HTMLElement>element).clientWidth;
|
|
|
- canvas.height = (<HTMLElement>element).clientHeight;
|
|
|
- var webglConfig = { alpha: config.alpha };
|
|
|
- this.context = new spine.webgl.ManagedWebGLRenderingContext(canvas, webglConfig);
|
|
|
-
|
|
|
- this.shader = spine.webgl.Shader.newTwoColoredTextured(this.context);
|
|
|
- this.batcher = new spine.webgl.PolygonBatcher(this.context);
|
|
|
- this.mvp.ortho2d(0, 0, canvas.width - 1, canvas.height - 1);
|
|
|
- this.skeletonRenderer = new spine.webgl.SkeletonRenderer(this.context);
|
|
|
- this.debugShader = spine.webgl.Shader.newColored(this.context);
|
|
|
- this.debugRenderer = new spine.webgl.SkeletonDebugRenderer(this.context);
|
|
|
- this.shapes = new spine.webgl.ShapeRenderer(this.context);
|
|
|
-
|
|
|
- let assets = this.assetManager = new spine.webgl.AssetManager(this.context, config.imagesPath ? config.imagesPath : "");
|
|
|
- if (!config.atlasContent) {
|
|
|
- assets.loadText(config.atlas);
|
|
|
- }
|
|
|
- if (!config.jsonContent) {
|
|
|
- assets.loadText(config.json);
|
|
|
- }
|
|
|
- if (config.atlasPages == null) {
|
|
|
- if (config.atlas) {
|
|
|
- var atlasPage = config.atlas.replace(".atlas", ".png");
|
|
|
- if (atlasPage.lastIndexOf(config.imagesPath) == 0) {
|
|
|
- atlasPage = atlasPage.substr(config.imagesPath.length);
|
|
|
- }
|
|
|
- assets.loadTexture(atlasPage);
|
|
|
- } else {
|
|
|
- let firstLine = config.atlasContent.trim().split("\n")[0];
|
|
|
- assets.loadTexture(firstLine);
|
|
|
- }
|
|
|
- } else {
|
|
|
- for (let i = 0; i < config.atlasPages.length; i++) {
|
|
|
- if (config.atlasPagesContent && config.atlasPagesContent[i]) {
|
|
|
- assets.loadTextureData(config.atlasPages[i], config.atlasPagesContent[i]);
|
|
|
- } else {
|
|
|
- assets.loadTexture(config.atlasPages[i]);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- requestAnimationFrame(() => { this.load(); });
|
|
|
- }
|
|
|
-
|
|
|
- private validateConfig (config: SpineWidgetConfig) {
|
|
|
- if (!config.atlas && !config.atlasContent) throw new Error("Please specify config.atlas or config.atlasContent");
|
|
|
- if (!config.json && !config.jsonContent) throw new Error("Please specify config.json or config.jsonContent");
|
|
|
- if (!config.animation) throw new Error("Please specify config.animationName");
|
|
|
-
|
|
|
- if (!config.scale) config.scale = 1.0;
|
|
|
- if (!config.skin) config.skin = "default";
|
|
|
- if (config.loop === undefined) config.loop = true;
|
|
|
- if (!config.x) config.x = 0;
|
|
|
- if (!config.y) config.y = 0;
|
|
|
- if (config.fitToCanvas === undefined) config.fitToCanvas = true;
|
|
|
- if (!config.backgroundColor) config.backgroundColor = "#555555";
|
|
|
- if (!config.imagesPath) {
|
|
|
- if (config.atlas) {
|
|
|
- let index = config.atlas.lastIndexOf("/");
|
|
|
- if (index != -1) {
|
|
|
- config.imagesPath = config.atlas.substr(0, index) + "/";
|
|
|
- } else {
|
|
|
- config.imagesPath = "";
|
|
|
- }
|
|
|
- } else {
|
|
|
- config.imagesPath = "";
|
|
|
- }
|
|
|
- }
|
|
|
- if (config.json && config.json.lastIndexOf(config.imagesPath) == 0) {
|
|
|
- config.json = config.json.substr(config.imagesPath.length);
|
|
|
- }
|
|
|
- if (config.atlas && config.atlas.lastIndexOf(config.imagesPath) == 0) {
|
|
|
- config.atlas = config.atlas.substr(config.imagesPath.length);
|
|
|
- }
|
|
|
- if (!config.premultipliedAlpha === undefined) config.premultipliedAlpha = false;
|
|
|
- if (!config.debug === undefined) config.debug = false;
|
|
|
- if (!config.alpha === undefined) config.alpha = true;
|
|
|
- this.backgroundColor.setFromString(config.backgroundColor);
|
|
|
- this.config = config;
|
|
|
- }
|
|
|
-
|
|
|
- private load () {
|
|
|
- let assetManager = this.assetManager;
|
|
|
- let imagesPath = this.config.imagesPath;
|
|
|
- let config = this.config;
|
|
|
- if (assetManager.isLoadingComplete()) {
|
|
|
- if (assetManager.hasErrors()) {
|
|
|
- if (config.error) config.error(this, "Failed to load assets: " + JSON.stringify(assetManager.getErrors()));
|
|
|
- else throw new Error("Failed to load assets: " + JSON.stringify(assetManager.getErrors()));
|
|
|
- }
|
|
|
-
|
|
|
- let atlasContent = config.atlasContent === undefined ? this.assetManager.get(this.config.atlas) as string : config.atlasContent;
|
|
|
- let atlas = new spine.TextureAtlas(atlasContent, (path: string) => {
|
|
|
- let texture = assetManager.get(path) as spine.webgl.GLTexture;
|
|
|
- return texture;
|
|
|
- });
|
|
|
-
|
|
|
- let atlasLoader = new spine.AtlasAttachmentLoader(atlas);
|
|
|
- var skeletonJson = new spine.SkeletonJson(atlasLoader);
|
|
|
-
|
|
|
- // Set the scale to apply during parsing, parse the file, and create a new skeleton.
|
|
|
- skeletonJson.scale = config.scale;
|
|
|
- let jsonContent = config.jsonContent === undefined ? assetManager.get(config.json) as string : config.jsonContent;
|
|
|
- var skeletonData = skeletonJson.readSkeletonData(jsonContent);
|
|
|
- var skeleton = this.skeleton = new spine.Skeleton(skeletonData);
|
|
|
- var bounds = this.bounds;
|
|
|
- skeleton.setSkinByName(config.skin);
|
|
|
- skeleton.setToSetupPose();
|
|
|
- skeleton.updateWorldTransform();
|
|
|
- skeleton.getBounds(bounds.offset, bounds.size, []);
|
|
|
- if (!config.fitToCanvas) {
|
|
|
- skeleton.x = config.x;
|
|
|
- skeleton.y = config.y;
|
|
|
- }
|
|
|
-
|
|
|
- var animationState = this.state = new spine.AnimationState(new spine.AnimationStateData(skeleton.data));
|
|
|
- animationState.setAnimation(0, config.animation, config.loop);
|
|
|
- this.loaded = true;
|
|
|
- if (config.success) config.success(this);
|
|
|
- requestAnimationFrame(() => { this.render(); });
|
|
|
- } else
|
|
|
- requestAnimationFrame(() => { this.load(); });
|
|
|
- }
|
|
|
-
|
|
|
- private render () {
|
|
|
- var now = Date.now() / 1000;
|
|
|
- var delta = now - this.lastFrameTime;
|
|
|
- if (delta > 0.1) delta = 0;
|
|
|
- this.lastFrameTime = now;
|
|
|
-
|
|
|
- let gl = this.context.gl;
|
|
|
- let color = this.backgroundColor;
|
|
|
- this.resize();
|
|
|
- gl.clearColor(color.r, color.g, color.b, color.a);
|
|
|
- gl.clear(gl.COLOR_BUFFER_BIT);
|
|
|
-
|
|
|
- // Apply the animation state based on the delta time.
|
|
|
- var state = this.state;
|
|
|
- var skeleton = this.skeleton;
|
|
|
- var premultipliedAlpha = this.config.premultipliedAlpha;
|
|
|
- state.update(delta);
|
|
|
- state.apply(skeleton);
|
|
|
- skeleton.updateWorldTransform();
|
|
|
-
|
|
|
- // Draw the skeleton
|
|
|
- let shader = this.shader;
|
|
|
- let batcher = this.batcher;
|
|
|
- let skeletonRenderer = this.skeletonRenderer;
|
|
|
- shader.bind();
|
|
|
- shader.setUniformi(spine.webgl.Shader.SAMPLER, 0);
|
|
|
- shader.setUniform4x4f(spine.webgl.Shader.MVP_MATRIX, this.mvp.values);
|
|
|
- batcher.begin(shader);
|
|
|
- skeletonRenderer.premultipliedAlpha = premultipliedAlpha;
|
|
|
- skeletonRenderer.draw(batcher, skeleton);
|
|
|
- batcher.end();
|
|
|
- shader.unbind();
|
|
|
-
|
|
|
- // Draw debug information if requested via config
|
|
|
- if (this.config.debug) {
|
|
|
- let shader = this.debugShader;
|
|
|
- let shapes = this.shapes;
|
|
|
- let renderer = this.debugRenderer;
|
|
|
- shader.bind();
|
|
|
- shader.setUniform4x4f(spine.webgl.Shader.MVP_MATRIX, this.mvp.values);
|
|
|
- renderer.premultipliedAlpha = premultipliedAlpha;
|
|
|
- shapes.begin(shader);
|
|
|
- renderer.draw(shapes, skeleton);
|
|
|
- shapes.end();
|
|
|
- shader.unbind();
|
|
|
- }
|
|
|
-
|
|
|
- if (!this.paused) requestAnimationFrame(() => { this.render(); });
|
|
|
- }
|
|
|
-
|
|
|
- private resize () {
|
|
|
- let canvas = this.canvas;
|
|
|
- let w = canvas.clientWidth;
|
|
|
- let h = canvas.clientHeight;
|
|
|
- let bounds = this.bounds;
|
|
|
-
|
|
|
- var devicePixelRatio = window.devicePixelRatio || 1;
|
|
|
- if (canvas.width != Math.floor(w * devicePixelRatio) || canvas.height != Math.floor(h * devicePixelRatio)) {
|
|
|
- canvas.width = Math.floor(w * devicePixelRatio);
|
|
|
- canvas.height = Math.floor(h * devicePixelRatio);
|
|
|
- }
|
|
|
-
|
|
|
- // magic
|
|
|
- if (this.config.fitToCanvas) {
|
|
|
- var centerX = bounds.offset.x + bounds.size.x / 2;
|
|
|
- var centerY = bounds.offset.y + bounds.size.y / 2;
|
|
|
- var scaleX = bounds.size.x / w;
|
|
|
- var scaleY = bounds.size.y / h;
|
|
|
- var scale = Math.max(scaleX, scaleY) * 1.2;
|
|
|
- if (scale < 1) scale = 1;
|
|
|
- var width = w * scale;
|
|
|
- var height = h * scale;
|
|
|
- this.skeleton.x = this.skeleton.y = 0;
|
|
|
- this.mvp.ortho2d(centerX - width / 2, centerY - height / 2, width, height);
|
|
|
- } else {
|
|
|
- this.mvp.ortho2d(0, 0, w - 1, h - 1);
|
|
|
- }
|
|
|
-
|
|
|
- this.context.gl.viewport(0, 0, canvas.width, canvas.height);
|
|
|
- }
|
|
|
-
|
|
|
- pause () {
|
|
|
- this.paused = true;
|
|
|
- }
|
|
|
-
|
|
|
- play () {
|
|
|
- this.paused = false;
|
|
|
- requestAnimationFrame(() => { this.render(); });
|
|
|
- }
|
|
|
-
|
|
|
- isPlaying () {
|
|
|
- return !this.paused;
|
|
|
- }
|
|
|
-
|
|
|
- setAnimation (animationName: string, animationStateListener: AnimationStateListener2 = null) {
|
|
|
- if (!this.loaded) throw new Error("Widget isn't loaded yet");
|
|
|
- this.skeleton.setToSetupPose();
|
|
|
- let entry = this.state.setAnimation(0, animationName, this.config.loop);
|
|
|
- entry.listener = animationStateListener
|
|
|
- }
|
|
|
-
|
|
|
- static loadWidgets() {
|
|
|
- let widgets = document.getElementsByClassName("spine-widget");
|
|
|
- for (var i = 0; i < widgets.length; i++) {
|
|
|
- SpineWidget.loadWidget(<HTMLElement>widgets[i]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- static loadWidget(widget: HTMLElement) {
|
|
|
- let config = new SpineWidgetConfig();
|
|
|
- config.atlas = widget.getAttribute("data-atlas");
|
|
|
- config.json = widget.getAttribute("data-json");
|
|
|
- config.animation = widget.getAttribute("data-animation");
|
|
|
- if (widget.getAttribute("data-images-path")) config.imagesPath = widget.getAttribute("data-images-path");
|
|
|
- if (widget.getAttribute("data-atlas-pages")) config.atlasPages = widget.getAttribute("data-atlas-pages").split(",");
|
|
|
- if (widget.getAttribute("data-skin")) config.skin = widget.getAttribute("data-skin");
|
|
|
- if (widget.getAttribute("data-loop")) config.loop = widget.getAttribute("data-loop") === "true";
|
|
|
- if (widget.getAttribute("data-scale")) config.scale = parseFloat(widget.getAttribute("data-scale"));
|
|
|
- if (widget.getAttribute("data-x")) config.x = parseFloat(widget.getAttribute("data-x"));
|
|
|
- if (widget.getAttribute("data-y")) config.y = parseFloat(widget.getAttribute("data-y"));
|
|
|
- if (widget.getAttribute("data-fit-to-canvas")) config.fitToCanvas = widget.getAttribute("data-fit-to-canvas") === "true";
|
|
|
- if (widget.getAttribute("data-background-color")) config.backgroundColor = widget.getAttribute("data-background-color");
|
|
|
- if (widget.getAttribute("data-premultiplied-alpha")) config.premultipliedAlpha = widget.getAttribute("data-premultiplied-alpha") === "true";
|
|
|
- if (widget.getAttribute("data-debug")) config.debug = widget.getAttribute("data-debug") === "true";
|
|
|
- if (widget.getAttribute("data-alpha")) config.alpha = widget.getAttribute("data-alpha") === "true";
|
|
|
-
|
|
|
- new spine.SpineWidget(widget, config);
|
|
|
- }
|
|
|
-
|
|
|
- static pageLoaded = false;
|
|
|
- private static ready () {
|
|
|
- if (SpineWidget.pageLoaded) return;
|
|
|
- SpineWidget.pageLoaded = true;
|
|
|
- SpineWidget.loadWidgets();
|
|
|
- }
|
|
|
-
|
|
|
- static setupDOMListener() {
|
|
|
- if (document.addEventListener) {
|
|
|
- document.addEventListener("DOMContentLoaded", SpineWidget.ready, false);
|
|
|
- window.addEventListener("load", SpineWidget.ready, false);
|
|
|
- } else {
|
|
|
- (<any>document).attachEvent("onreadystatechange", function readyStateChange() {
|
|
|
- if (document.readyState === "complete" ) SpineWidget.ready();
|
|
|
- });
|
|
|
- (<any>window).attachEvent("onload", SpineWidget.ready);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- export class SpineWidgetConfig {
|
|
|
- json: string;
|
|
|
- jsonContent: any;
|
|
|
- atlas: string;
|
|
|
- atlasContent: string;
|
|
|
- animation: string;
|
|
|
- imagesPath: string;
|
|
|
- atlasPages: string[];
|
|
|
- atlasPagesContent: string[];
|
|
|
- skin = "default";
|
|
|
- loop = true;
|
|
|
- scale = 1.0;
|
|
|
- x = 0;
|
|
|
- y = 0;
|
|
|
- alpha = true;
|
|
|
- fitToCanvas = true;
|
|
|
- backgroundColor = "#555555";
|
|
|
- premultipliedAlpha = false;
|
|
|
- debug = false;
|
|
|
- success: (widget: SpineWidget) => void;
|
|
|
- error: (widget: SpineWidget, msg: string) => void;
|
|
|
- }
|
|
|
-}
|
|
|
-spine.SpineWidget.setupDOMListener();
|