[ts] Added spine-ts runtime for TypeScript and JavaScript

This commit is contained in:
Nathan Sweet 2016-08-12 09:28:50 +02:00 committed by Mario Zechner
parent b7c9a1cc20
commit 698c4a1661
82 changed files with 57178 additions and 15 deletions

28
LICENSE
View File

@ -1,17 +1,17 @@
Spine Runtimes Software License
Version 2.4
Version 2.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 (the "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 otherwise create derivative works, improvements of
the Software or develop new applications using the Software or (b) remove,
delete, alter or obscure any trademarks or any copyright, trademark, patent
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.
@ -21,8 +21,8 @@ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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.

View File

@ -3,7 +3,7 @@
<AS3LibraryFolder>lib</AS3LibraryFolder>
<AS3Classpath generateProblems="true" sdkBased="true" type="lib" useAsSharedCode="false">frameworks/libs/player/{playerVersion}/playerglobal.swc</AS3Classpath>
<AS3Classpath generateProblems="true" sdkBased="false" type="source" useAsSharedCode="false">src</AS3Classpath>
<AS3Classpath generateProblems="true" sdkBased="false" type="lib" useAsSharedCode="false">lib/starling-2.0.1.swc</AS3Classpath>
<AS3Classpath generateProblems="true" sdkBased="false" type="lib" useAsSharedCode="false">lib/spine-as3.swc</AS3Classpath>
<AS3Classpath generateProblems="true" sdkBased="false" type="lib" useAsSharedCode="false">lib/spine-starling.swc</AS3Classpath>
<AS3Classpath generateProblems="true" sdkBased="false" type="lib" useAsSharedCode="false">lib/starling-2.0.1.swc</AS3Classpath>
</AS3Classpath>

69
spine-ts/README.md Normal file
View File

@ -0,0 +1,69 @@
# spine-ts
The spine-ts runtime provides functionality to load and manipulate [Spine](http://esotericsoftware.com) skeletal animation data using TypeScript and JavaScript. spine-ts is split
up into multiple modules:
1. **Core**: `core/`, the core classes to load and process Spine models
1. **WebGL**: `webgl/`, a self-contained WebGL backend, build on the core classes
1. **Widget**: `widget/`, a self-contained widget to easily display Spine animations on your website, build on core classes & WebGL backend.
While the source code for the core library and backends is written in TypeScript, all code is compiled to easily consumable JavaScript.
## Licensing
This Spine Runtime may only be used for personal or internal use, typically to evaluate Spine before purchasing. If you would like to incorporate a Spine Runtime into your applications, distribute software containing a Spine Runtime, or modify a Spine Runtime, then you will need a valid [Spine license](https://esotericsoftware.com/spine-purchase). Please see the [Spine Runtimes Software License](https://github.com/EsotericSoftware/spine-runtimes/blob/master/LICENSE) for detailed information.
The Spine Runtimes are developed with the intent to be used with data exported from Spine. By purchasing Spine, `Section 2` of the [Spine Software License](https://esotericsoftware.com/files/license.txt) grants the right to create and distribute derivative works of the Spine Runtimes.
## Spine version
spine-ts works with data exported from the latest Spine version.
spine-ts supports all Spine features.
spine-ts does not yet support loading the binary format.
## Usage
1. Download the Spine Runtimes source using [git](https://help.github.com/articles/set-up-git) or by downloading it [as a zip](https://github.com/EsotericSoftware/spine-runtimes/archive/master.zip).
2. To use only the core library without rendering support, include the `build/spine-core.js` file in your project.
3. To use the WebGL backend, include the `spine-webgl.js` file in your project.
4. To use the Widget, include `spine-widget.js` file in your project.
All `*.js` files are self-contained and include both the core and respective backend classes.
If you write your app with TypeScript, additionally copy the corresponding `build/spine-*.d.ts` file to your project.
## Example
To run the examples, spawn a light-weight web server in the root of spine-ts, then navigate to the respective
`index.html` file. E.g.:
```
cd spine-ts
python -m SimpleHTTPServer
````
Then open `http://localhost:8000/webgl/example` or `http://localhost:8000/widget/example` in your browser.
## Development Setup
The spine-ts runtime and the various backends are implemented in TypeScript for greater maintainability and better tooling support. To
setup a development environment, follow these steps.
1. Install [NPM](https://nodejs.org/en/download/) and make sure it's available on the command line
2. On the command line, Install the TypeScript compiler via `npm install -g typescript`
3. Install [Visual Studio Code](https://code.visualstudio.com/)
4. On the command line, change into the `spine-ts` directory
5. Start the TypeScript compiler in watcher mode for the backend you want to work on:
1. **Core**: `tsc -w -p tsconfig.core.json`, builds `core/src`, outputs `build/spine-core.js|d.ts|js.map`
2. **WebGL**: `tsc -w -p tsconfig.webgl.json`, builds `core/src` and `webgl/src`, outputs `build/spine-webgl.js|d.ts|js.map`
3. **Widget**: `tsc -w -p tsconfig.widget.json`, builds `core/src` and `widget/src`, outputs `build/spine-widget.js|d.ts|js.map`
6. Open the `spine-ts` folder in Visual Studio Code. VS Code will use the `tsconfig.json` file all source files from core and all
backends for your development pleasure. The actual JavaScript output is still created by the command line TypeScript compiler process from the previous step.
Each backend contains an `example/` folder with an `index.html` file that demonstrates the respective backend. For development, we
suggest to run a HTTP server in the root of `spine-ts`, e.g.
```
cd spine-ts
python -m SimpleHTTPServer
```
Then navigate to `http://localhost:8000/webgl/example` or `http://localhost:8000/widget/example`

2502
spine-ts/build/spine-all.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

6877
spine-ts/build/spine-all.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2502
spine-ts/build/spine-core.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

6877
spine-ts/build/spine-core.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2502
spine-ts/build/spine-webgl.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2502
spine-ts/build/spine-widget.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,816 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 Animation {
name: string;
timelines: Array<Timeline>;
duration: number;
constructor (name: string, timelines: Array<Timeline>, duration: number) {
if (name == null) throw new Error("name cannot be null.");
if (timelines == null) throw new Error("timelines cannot be null.");
this.name = name;
this.timelines = timelines;
this.duration = duration;
}
apply (skeleton: Skeleton, lastTime: number, time: number, loop: boolean, events: Array<Event>) {
if (skeleton == null) throw new Error("skeleton cannot be null.");
if (loop && this.duration != 0) {
time %= this.duration;
if (lastTime > 0) lastTime %= this.duration;
}
let timelines = this.timelines;
for (let i = 0, n = timelines.length; i < n; i++)
timelines[i].apply(skeleton, lastTime, time, events, 1);
}
mix (skeleton: Skeleton, lastTime: number, time: number, loop: boolean, events: Array<Event>, alpha: number) {
if (skeleton == null) throw new Error("skeleton cannot be null.");
if (loop && this.duration != 0) {
time %= this.duration;
if (lastTime > 0) lastTime %= this.duration;
}
let timelines = this.timelines;
for (let i = 0, n = timelines.length; i < n; i++)
timelines[i].apply(skeleton, lastTime, time, events, alpha);
}
static binarySearch (values: ArrayLike<number>, target: number, step: number = 1) {
let low = 0;
let high = values.length / step - 2;
if (high == 0) return step;
let current = high >>> 1;
while (true) {
if (values[(current + 1) * step] <= target)
low = current + 1;
else
high = current;
if (low == high) return (low + 1) * step;
current = (low + high) >>> 1;
}
}
static linearSearch (values: ArrayLike<number>, target: number, step: number) {
for (let i = 0, last = values.length - step; i <= last; i += step)
if (values[i] > target) return i;
return -1;
}
}
export interface Timeline {
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number): void;
}
export abstract class CurveTimeline implements Timeline {
static LINEAR = 0; static STEPPED = 1; static BEZIER = 2;
static BEZIER_SIZE = 10 * 2 - 1;
private curves: ArrayLike<number>; // type, x, y, ...
constructor (frameCount: number) {
if (frameCount <= 0) throw new Error("frameCount must be > 0: " + frameCount);
this.curves = Utils.newFloatArray((frameCount - 1) * CurveTimeline.BEZIER_SIZE);
}
getFrameCount () {
return this.curves.length / CurveTimeline.BEZIER_SIZE + 1;
}
setLinear (frameIndex: number) {
this.curves[frameIndex * CurveTimeline.BEZIER_SIZE] = CurveTimeline.LINEAR;
}
setStepped (frameIndex: number) {
this.curves[frameIndex * CurveTimeline.BEZIER_SIZE] = CurveTimeline.STEPPED;
}
getCurveType (frameIndex: number): number {
let index = frameIndex * CurveTimeline.BEZIER_SIZE;
if (index == this.curves.length) return CurveTimeline.LINEAR;
let type = this.curves[index];
if (type == CurveTimeline.LINEAR) return CurveTimeline.LINEAR;
if (type == CurveTimeline.STEPPED) return CurveTimeline.STEPPED;
return CurveTimeline.BEZIER;
}
/** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
* cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
* the difference between the keyframe's values. */
setCurve (frameIndex: number, cx1: number, cy1: number, cx2: number, cy2: number) {
let tmpx = (-cx1 * 2 + cx2) * 0.03, tmpy = (-cy1 * 2 + cy2) * 0.03;
let dddfx = ((cx1 - cx2) * 3 + 1) * 0.006, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006;
let ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy;
let dfx = cx1 * 0.3 + tmpx + dddfx * 0.16666667, dfy = cy1 * 0.3 + tmpy + dddfy * 0.16666667;
let i = frameIndex * CurveTimeline.BEZIER_SIZE;
let curves = this.curves;
curves[i++] = CurveTimeline.BEZIER;
let x = dfx, y = dfy;
for (let n = i + CurveTimeline.BEZIER_SIZE - 1; i < n; i += 2) {
curves[i] = x;
curves[i + 1] = y;
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
x += dfx;
y += dfy;
}
}
getCurvePercent (frameIndex: number, percent: number) {
percent = MathUtils.clamp(percent, 0, 1);
let curves = this.curves;
let i = frameIndex * CurveTimeline.BEZIER_SIZE;
let type = curves[i];
if (type == CurveTimeline.LINEAR) return percent;
if (type == CurveTimeline.STEPPED) return 0;
i++;
let x = 0;
for (let start = i, n = i + CurveTimeline.BEZIER_SIZE - 1; i < n; i += 2) {
x = curves[i];
if (x >= percent) {
let prevX: number, prevY: number;
if (i == start) {
prevX = 0;
prevY = 0;
} else {
prevX = curves[i - 2];
prevY = curves[i - 1];
}
return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX);
}
}
let y = curves[i - 1];
return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
}
abstract apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number): void;
}
export class RotateTimeline extends CurveTimeline {
static ENTRIES = 2;
static PREV_TIME = -2; static PREV_ROTATION = -1;
static ROTATION = 1;
boneIndex: number;
frames: ArrayLike<number>; // time, degrees, ...
constructor (frameCount: number) {
super(frameCount);
this.frames = Utils.newFloatArray(frameCount << 1);
}
/** Sets the time and angle of the specified keyframe. */
setFrame (frameIndex: number, time: number, degrees: number) {
frameIndex <<= 1;
this.frames[frameIndex] = time;
this.frames[frameIndex + RotateTimeline.ROTATION] = degrees;
}
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let bone = skeleton.bones[this.boneIndex];
if (time >= frames[frames.length - RotateTimeline.ENTRIES]) { // Time is after last frame.
let amount = bone.data.rotation + frames[frames.length + RotateTimeline.PREV_ROTATION] - bone.rotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
bone.rotation += amount * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, RotateTimeline.ENTRIES);
let prevRotation = frames[frame + RotateTimeline.PREV_ROTATION];
let frameTime = frames[frame];
let percent = this.getCurvePercent((frame >> 1) - 1,
1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime));
let amount = frames[frame + RotateTimeline.ROTATION] - prevRotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
bone.rotation += amount * alpha;
}
}
export class TranslateTimeline extends CurveTimeline {
static ENTRIES = 3;
static PREV_TIME = -3; static PREV_X = -2; static PREV_Y = -1;
static X = 1; static Y = 2;
boneIndex: number;
frames: ArrayLike<number>; // time, x, y, ...
constructor (frameCount: number) {
super(frameCount);
this.frames = Utils.newFloatArray(frameCount * TranslateTimeline.ENTRIES);
}
/** Sets the time and value of the specified keyframe. */
setFrame (frameIndex: number, time: number, x: number, y: number) {
frameIndex *= TranslateTimeline.ENTRIES;
this.frames[frameIndex] = time;
this.frames[frameIndex + TranslateTimeline.X] = x;
this.frames[frameIndex + TranslateTimeline.Y] = y;
}
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let bone = skeleton.bones[this.boneIndex];
if (time >= frames[frames.length - TranslateTimeline.ENTRIES]) { // Time is after last frame.
bone.x += (bone.data.x + frames[frames.length + TranslateTimeline.PREV_X] - bone.x) * alpha;
bone.y += (bone.data.y + frames[frames.length + TranslateTimeline.PREV_Y] - bone.y) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, TranslateTimeline.ENTRIES);
let prevX = frames[frame + TranslateTimeline.PREV_X];
let prevY = frames[frame + TranslateTimeline.PREV_Y];
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame / TranslateTimeline.ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + TranslateTimeline.PREV_TIME] - frameTime));
bone.x += (bone.data.x + prevX + (frames[frame + TranslateTimeline.X] - prevX) * percent - bone.x) * alpha;
bone.y += (bone.data.y + prevY + (frames[frame + TranslateTimeline.Y] - prevY) * percent - bone.y) * alpha;
}
}
export class ScaleTimeline extends TranslateTimeline {
constructor (frameCount: number) {
super(frameCount);
}
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let bone = skeleton.bones[this.boneIndex];
if (time >= frames[frames.length - ScaleTimeline.ENTRIES]) { // Time is after last frame.
bone.scaleX += (bone.data.scaleX * frames[frames.length + ScaleTimeline.PREV_X] - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY * frames[frames.length + ScaleTimeline.PREV_Y] - bone.scaleY) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, ScaleTimeline.ENTRIES);
let prevX = frames[frame + ScaleTimeline.PREV_X];
let prevY = frames[frame + ScaleTimeline.PREV_Y];
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame / ScaleTimeline.ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + ScaleTimeline.PREV_TIME] - frameTime));
bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + ScaleTimeline.X] - prevX) * percent) - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + ScaleTimeline.Y] - prevY) * percent) - bone.scaleY) * alpha;
}
}
export class ShearTimeline extends TranslateTimeline {
constructor (frameCount: number) {
super(frameCount);
}
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let bone = skeleton.bones[this.boneIndex];
if (time >= frames[frames.length - ShearTimeline.ENTRIES]) { // Time is after last frame.
bone.shearX += (bone.data.shearX + frames[frames.length + ShearTimeline.PREV_X] - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY + frames[frames.length + ShearTimeline.PREV_Y] - bone.shearY) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, ShearTimeline.ENTRIES);
let prevX = frames[frame + ShearTimeline.PREV_X];
let prevY = frames[frame + ShearTimeline.PREV_Y];
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame / ShearTimeline.ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + ShearTimeline.PREV_TIME] - frameTime));
bone.shearX += (bone.data.shearX + (prevX + (frames[frame + ShearTimeline.X] - prevX) * percent) - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY + (prevY + (frames[frame + ShearTimeline.Y] - prevY) * percent) - bone.shearY) * alpha;
}
}
export class ColorTimeline extends CurveTimeline {
static ENTRIES = 5;
static PREV_TIME = -5; static PREV_R = -4; static PREV_G = -3; static PREV_B = -2; static PREV_A = -1;
static R = 1; static G = 2; static B = 3; static A = 4;
slotIndex: number;
frames: ArrayLike<number>; // time, r, g, b, a, ...
constructor (frameCount: number) {
super(frameCount);
this.frames = Utils.newFloatArray(frameCount * ColorTimeline.ENTRIES);
}
/** Sets the time and value of the specified keyframe. */
setFrame (frameIndex: number, time: number, r: number, g: number, b: number, a: number) {
frameIndex *= ColorTimeline.ENTRIES;
this.frames[frameIndex] = time;
this.frames[frameIndex + ColorTimeline.R] = r;
this.frames[frameIndex + ColorTimeline.G] = g;
this.frames[frameIndex + ColorTimeline.B] = b;
this.frames[frameIndex + ColorTimeline.A] = a;
}
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let r = 0, g = 0, b = 0, a = 0;
if (time >= frames[frames.length - ColorTimeline.ENTRIES]) { // Time is after last frame.
let i = frames.length;
r = frames[i + ColorTimeline.PREV_R];
g = frames[i + ColorTimeline.PREV_G];
b = frames[i + ColorTimeline.PREV_B];
a = frames[i + ColorTimeline.PREV_A];
} else {
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, ColorTimeline.ENTRIES);
r = frames[frame + ColorTimeline.PREV_R];
g = frames[frame + ColorTimeline.PREV_G];
b = frames[frame + ColorTimeline.PREV_B];
a = frames[frame + ColorTimeline.PREV_A];
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame / ColorTimeline.ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + ColorTimeline.PREV_TIME] - frameTime));
r += (frames[frame + ColorTimeline.R] - r) * percent;
g += (frames[frame + ColorTimeline.G] - g) * percent;
b += (frames[frame + ColorTimeline.B] - b) * percent;
a += (frames[frame + ColorTimeline.A] - a) * percent;
}
let color: Color = skeleton.slots[this.slotIndex].color;
if (alpha < 1)
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
else
color.set(r, g, b, a);
}
}
export class AttachmentTimeline implements Timeline {
slotIndex: number;
frames: ArrayLike<number> // time, ...
attachmentNames: Array<string>;
constructor (frameCount: number) {
this.frames = Utils.newFloatArray(frameCount);
this.attachmentNames = new Array<string>(frameCount);
}
getFrameCount () {
return this.frames.length;
}
/** Sets the time and value of the specified keyframe. */
setFrame (frameIndex: number, time: number, attachmentName: string) {
this.frames[frameIndex] = time;
this.attachmentNames[frameIndex] = attachmentName;
}
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let frameIndex = 0;
if (time >= frames[frames.length - 1]) // Time is after last frame.
frameIndex = frames.length - 1;
else
frameIndex = Animation.binarySearch(frames, time, 1) - 1;
let attachmentName = this.attachmentNames[frameIndex];
skeleton.slots[this.slotIndex]
.setAttachment(attachmentName == null ? null : skeleton.getAttachment(this.slotIndex, attachmentName));
}
}
export class EventTimeline implements Timeline {
frames: ArrayLike<number>; // time, ...
events: Array<Event>;
constructor (frameCount: number) {
this.frames = Utils.newFloatArray(frameCount);
this.events = new Array<Event>(frameCount);
}
getFrameCount () {
return this.frames.length;
}
/** Sets the time of the specified keyframe. */
setFrame (frameIndex: number, event: Event) {
this.frames[frameIndex] = event.time;
this.events[frameIndex] = event;
}
/** Fires events for frames > lastTime and <= time. */
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number) {
if (firedEvents == null) return;
let frames = this.frames;
let frameCount = this.frames.length;
if (lastTime > time) { // Fire events after last time for looped animations.
this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha);
lastTime = -1;
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
return;
if (time < frames[0]) return; // Time is before first frame.
let frame = 0;
if (lastTime < frames[0])
frame = 0;
else {
frame = Animation.binarySearch(frames, lastTime);
let frameTime = frames[frame];
while (frame > 0) { // Fire multiple events with the same frame.
if (frames[frame - 1] != frameTime) break;
frame--;
}
}
for (; frame < frameCount && time >= frames[frame]; frame++)
firedEvents.push(this.events[frame]);
}
}
export class DrawOrderTimeline implements Timeline {
frames: ArrayLike<number>; // time, ...
drawOrders: Array<Array<number>>;
constructor (frameCount: number) {
this.frames = Utils.newFloatArray(frameCount);
this.drawOrders = new Array<Array<number>>(frameCount);
}
getFrameCount () {
return this.frames.length;
}
/** Sets the time of the specified keyframe.
* @param drawOrder May be null to use bind pose draw order. */
setFrame (frameIndex: number, time: number, drawOrder: Array<number>) {
this.frames[frameIndex] = time;
this.drawOrders[frameIndex] = drawOrder;
}
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let frame = 0;
if (time >= frames[frames.length - 1]) // Time is after last frame.
frame = frames.length - 1;
else
frame = Animation.binarySearch(frames, time) - 1;
let drawOrder: Array<Slot> = skeleton.drawOrder;
let slots: Array<Slot> = skeleton.slots;
let drawOrderToSetupIndex = this.drawOrders[frame];
if (drawOrderToSetupIndex == null)
Utils.arrayCopy(slots, 0, drawOrder, 0, slots.length);
else {
for (let i = 0, n = drawOrderToSetupIndex.length; i < n; i++)
drawOrder[i] = slots[drawOrderToSetupIndex[i]];
}
}
}
export class DeformTimeline extends CurveTimeline {
frames: ArrayLike<number>; // time, ...
frameVertices: Array<ArrayLike<number>>;
slotIndex: number;
attachment: VertexAttachment;
constructor (frameCount: number) {
super(frameCount);
this.frames = Utils.newFloatArray(frameCount);
this.frameVertices = new Array<ArrayLike<number>>(frameCount);
}
/** Sets the time of the specified keyframe. */
setFrame (frameIndex: number, time: number, vertices: ArrayLike<number>) {
this.frames[frameIndex] = time;
this.frameVertices[frameIndex] = vertices;
}
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number) {
let slot: Slot = skeleton.slots[this.slotIndex];
let slotAttachment: Attachment = slot.getAttachment();
if (!(slotAttachment instanceof VertexAttachment) || !(<VertexAttachment>slotAttachment).applyDeform(this.attachment)) return;
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let frameVertices = this.frameVertices;
let vertexCount = frameVertices[0].length;
let verticesArray: Array<number> = slot.attachmentVertices;
if (verticesArray.length != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices.
let vertices: Array<number> = Utils.setArraySize(verticesArray, vertexCount);
if (time >= frames[frames.length - 1]) { // Time is after last frame.
let lastVertices = frameVertices[frames.length - 1];
if (alpha < 1) {
for (let i = 0; i < vertexCount; i++)
vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
} else
Utils.arrayCopy(lastVertices, 0, vertices, 0, vertexCount);
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time);
let prevVertices = frameVertices[frame - 1];
let nextVertices = frameVertices[frame];
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
if (alpha < 1) {
for (let i = 0; i < vertexCount; i++) {
let prev = prevVertices[i];
vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha;
}
} else {
for (let i = 0; i < vertexCount; i++) {
let prev = prevVertices[i];
vertices[i] = prev + (nextVertices[i] - prev) * percent;
}
}
}
}
export class IkConstraintTimeline extends CurveTimeline {
static ENTRIES = 3;
static PREV_TIME = -3; static PREV_MIX = -2; static PREV_BEND_DIRECTION = -1;
static MIX = 1; static BEND_DIRECTION = 2;
ikConstraintIndex: number;
frames: ArrayLike<number>; // time, mix, bendDirection, ...
constructor (frameCount: number) {
super(frameCount);
this.frames = Utils.newFloatArray(frameCount * IkConstraintTimeline.ENTRIES);
}
/** Sets the time, mix and bend direction of the specified keyframe. */
setFrame (frameIndex: number, time: number, mix: number, bendDirection: number) {
frameIndex *= IkConstraintTimeline.ENTRIES;
this.frames[frameIndex] = time;
this.frames[frameIndex + IkConstraintTimeline.MIX] = mix;
this.frames[frameIndex + IkConstraintTimeline.BEND_DIRECTION] = bendDirection;
}
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let constraint: IkConstraint = skeleton.ikConstraints[this.ikConstraintIndex];
if (time >= frames[frames.length - IkConstraintTimeline.ENTRIES]) { // Time is after last frame.
constraint.mix += (frames[frames.length + IkConstraintTimeline.PREV_MIX] - constraint.mix) * alpha;
constraint.bendDirection = Math.floor(frames[frames.length + IkConstraintTimeline.PREV_BEND_DIRECTION]);
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, IkConstraintTimeline.ENTRIES);
let mix = frames[frame + IkConstraintTimeline.PREV_MIX];
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame / IkConstraintTimeline.ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + IkConstraintTimeline.PREV_TIME] - frameTime));
constraint.mix += (mix + (frames[frame + IkConstraintTimeline.MIX] - mix) * percent - constraint.mix) * alpha;
constraint.bendDirection = Math.floor(frames[frame + IkConstraintTimeline.PREV_BEND_DIRECTION]);
}
}
export class TransformConstraintTimeline extends CurveTimeline {
static ENTRIES = 5;
static PREV_TIME = -5; static PREV_ROTATE = -4; static PREV_TRANSLATE = -3; static PREV_SCALE = -2; static PREV_SHEAR = -1;
static ROTATE = 1; static TRANSLATE = 2; static SCALE = 3; static SHEAR = 4;
transformConstraintIndex: number;
frames: ArrayLike<number>; // time, rotate mix, translate mix, scale mix, shear mix, ...
constructor (frameCount: number) {
super(frameCount);
this.frames = Utils.newFloatArray(frameCount * TransformConstraintTimeline.ENTRIES);
}
/** Sets the time and mixes of the specified keyframe. */
setFrame (frameIndex: number, time: number, rotateMix: number, translateMix: number, scaleMix: number, shearMix: number) {
frameIndex *= TransformConstraintTimeline.ENTRIES;
this.frames[frameIndex] = time;
this.frames[frameIndex + TransformConstraintTimeline.ROTATE] = rotateMix;
this.frames[frameIndex + TransformConstraintTimeline.TRANSLATE] = translateMix;
this.frames[frameIndex + TransformConstraintTimeline.SCALE] = scaleMix;
this.frames[frameIndex + TransformConstraintTimeline.SHEAR] = shearMix;
}
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let constraint: TransformConstraint = skeleton.transformConstraints[this.transformConstraintIndex];
if (time >= frames[frames.length - TransformConstraintTimeline.ENTRIES]) { // Time is after last frame.
let i = frames.length;
constraint.rotateMix += (frames[i + TransformConstraintTimeline.PREV_ROTATE] - constraint.rotateMix) * alpha;
constraint.translateMix += (frames[i + TransformConstraintTimeline.PREV_TRANSLATE] - constraint.translateMix) * alpha;
constraint.scaleMix += (frames[i + TransformConstraintTimeline.PREV_SCALE] - constraint.scaleMix) * alpha;
constraint.shearMix += (frames[i + TransformConstraintTimeline.PREV_SHEAR] - constraint.shearMix) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, TransformConstraintTimeline.ENTRIES);
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame / TransformConstraintTimeline.ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + TransformConstraintTimeline.PREV_TIME] - frameTime));
let rotate = frames[frame + TransformConstraintTimeline.PREV_ROTATE];
let translate = frames[frame + TransformConstraintTimeline.PREV_TRANSLATE];
let scale = frames[frame + TransformConstraintTimeline.PREV_SCALE];
let shear = frames[frame + TransformConstraintTimeline.PREV_SHEAR];
constraint.rotateMix += (rotate + (frames[frame + TransformConstraintTimeline.ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
constraint.translateMix += (translate + (frames[frame + TransformConstraintTimeline.TRANSLATE] - translate) * percent - constraint.translateMix)
* alpha;
constraint.scaleMix += (scale + (frames[frame + TransformConstraintTimeline.SCALE] - scale) * percent - constraint.scaleMix) * alpha;
constraint.shearMix += (shear + (frames[frame + TransformConstraintTimeline.SHEAR] - shear) * percent - constraint.shearMix) * alpha;
}
}
export class PathConstraintPositionTimeline extends CurveTimeline {
static ENTRIES = 2;
static PREV_TIME = -2; static PREV_VALUE = -1;
static VALUE = 1;
pathConstraintIndex: number;
frames: ArrayLike<number>; // time, position, ...
constructor (frameCount: number) {
super(frameCount);
this.frames = Utils.newFloatArray(frameCount * PathConstraintPositionTimeline.ENTRIES);
}
/** Sets the time and value of the specified keyframe. */
setFrame (frameIndex: number, time: number, value: number) {
frameIndex *= PathConstraintPositionTimeline.ENTRIES;
this.frames[frameIndex] = time;
this.frames[frameIndex + PathConstraintPositionTimeline.VALUE] = value;
}
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
if (time >= frames[frames.length - PathConstraintPositionTimeline.ENTRIES]) { // Time is after last frame.
let i = frames.length;
constraint.position += (frames[i + PathConstraintPositionTimeline.PREV_VALUE] - constraint.position) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, PathConstraintPositionTimeline.ENTRIES);
let position = frames[frame + PathConstraintPositionTimeline.PREV_VALUE];
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame / PathConstraintPositionTimeline.ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + PathConstraintPositionTimeline.PREV_TIME] - frameTime));
constraint.position += (position + (frames[frame + PathConstraintPositionTimeline.VALUE] - position) * percent - constraint.position) * alpha;
}
}
export class PathConstraintSpacingTimeline extends PathConstraintPositionTimeline {
constructor (frameCount: number) {
super(frameCount);
}
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
if (time >= frames[frames.length - PathConstraintSpacingTimeline.ENTRIES]) { // Time is after last frame.
let i = frames.length;
constraint.spacing += (frames[i + PathConstraintSpacingTimeline.PREV_VALUE] - constraint.spacing) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, PathConstraintSpacingTimeline.ENTRIES);
let spacing = frames[frame + PathConstraintSpacingTimeline.PREV_VALUE];
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame / PathConstraintSpacingTimeline.ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + PathConstraintSpacingTimeline.PREV_TIME] - frameTime));
constraint.spacing += (spacing + (frames[frame + PathConstraintSpacingTimeline.VALUE] - spacing) * percent - constraint.spacing) * alpha;
}
}
export class PathConstraintMixTimeline extends CurveTimeline {
static ENTRIES = 3;
static PREV_TIME = -3; static PREV_ROTATE = -2; static PREV_TRANSLATE = -1;
static ROTATE = 1; static TRANSLATE = 2;
pathConstraintIndex: number;
frames: ArrayLike<number>; // time, rotate mix, translate mix, ...
constructor (frameCount: number) {
super(frameCount);
this.frames = Utils.newFloatArray(frameCount * PathConstraintMixTimeline.ENTRIES);
}
/** Sets the time and mixes of the specified keyframe. */
setFrame (frameIndex: number, time: number, rotateMix: number, translateMix: number) {
frameIndex *= PathConstraintMixTimeline.ENTRIES;
this.frames[frameIndex] = time;
this.frames[frameIndex + PathConstraintMixTimeline.ROTATE] = rotateMix;
this.frames[frameIndex + PathConstraintMixTimeline.TRANSLATE] = translateMix;
}
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number) {
let frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
if (time >= frames[frames.length - PathConstraintMixTimeline.ENTRIES]) { // Time is after last frame.
let i = frames.length;
constraint.rotateMix += (frames[i + PathConstraintMixTimeline.PREV_ROTATE] - constraint.rotateMix) * alpha;
constraint.translateMix += (frames[i + PathConstraintMixTimeline.PREV_TRANSLATE] - constraint.translateMix) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
let frame = Animation.binarySearch(frames, time, PathConstraintMixTimeline.ENTRIES);
let rotate = frames[frame + PathConstraintMixTimeline.PREV_ROTATE];
let translate = frames[frame + PathConstraintMixTimeline.PREV_TRANSLATE];
let frameTime = frames[frame];
let percent = this.getCurvePercent(frame / PathConstraintMixTimeline.ENTRIES - 1,
1 - (time - frameTime) / (frames[frame + PathConstraintMixTimeline.PREV_TIME] - frameTime));
constraint.rotateMix += (rotate + (frames[frame + PathConstraintMixTimeline.ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
constraint.translateMix += (translate + (frames[frame + PathConstraintMixTimeline.TRANSLATE] - translate) * percent - constraint.translateMix)
* alpha;
}
}
}

View File

@ -0,0 +1,321 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 AnimationState {
data: AnimationStateData;
tracks = new Array<TrackEntry>();
events = new Array<Event>();
listeners = new Array<AnimationStateListener>();
timeScale = 1;
constructor (data: AnimationStateData = null) {
if (data == null) throw new Error("data cannot be null.");
this.data = data;
}
update (delta: number) {
delta *= this.timeScale;
for (let i = 0; i < this.tracks.length; i++) {
let current = this.tracks[i];
if (current == null) continue;
let next = current.next;
if (next != null) {
let nextTime = current.lastTime - next.delay;
if (nextTime >= 0) {
let nextDelta = delta * next.timeScale;
next.time = nextTime + nextDelta; // For start event to see correct time.
current.time += delta * current.timeScale; // For end event to see correct time.
this.setCurrent(i, next);
next.time -= nextDelta; // Prevent increasing time twice, below.
current = next;
}
} else if (!current.loop && current.lastTime >= current.endTime) {
// End non-looping animation when it reaches its end time and there is no next entry.
this.clearTrack(i);
continue;
}
current.time += delta * current.timeScale;
if (current.previous != null) {
let previousDelta = delta * current.previous.timeScale;
current.previous.time += previousDelta;
current.mixTime += previousDelta;
}
}
}
apply (skeleton: Skeleton) {
let events = this.events;
let listenerCount = this.listeners.length;
for (let i = 0; i < this.tracks.length; i++) {
let current = this.tracks[i];
if (current == null) continue;
events.length = 0;
let time = current.time;
let lastTime = current.lastTime;
let endTime = current.endTime;
let loop = current.loop;
if (!loop && time > endTime) time = endTime;
let previous = current.previous;
if (previous == null)
current.animation.mix(skeleton, lastTime, time, loop, events, current.mix);
else {
let previousTime = previous.time;
if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime;
previous.animation.apply(skeleton, previousTime, previousTime, previous.loop, null);
let alpha = current.mixTime / current.mixDuration * current.mix;
if (alpha >= 1) {
alpha = 1;
current.previous = null;
}
current.animation.mix(skeleton, lastTime, time, loop, events, alpha);
}
for (let ii = 0, nn = events.length; ii < nn; ii++) {
let event = events[ii];
if (current.listener != null) current.listener.event(i, event);
for (let iii = 0; iii < listenerCount; iii++)
this.listeners[iii].event(i, event);
}
// Check if completed the animation or a loop iteration.
if (loop ? (lastTime % endTime > time % endTime) : (lastTime < endTime && time >= endTime)) {
let count = MathUtils.toInt(time / endTime);
if (current.listener != null) current.listener.complete(i, count);
for (let ii = 0, nn = this.listeners.length; ii < nn; ii++)
this.listeners[ii].complete(i, count);
}
current.lastTime = current.time;
}
}
clearTracks () {
for (let i = 0, n = this.tracks.length; i < n; i++)
this.clearTrack(i);
this.tracks.length = 0;
}
clearTrack (trackIndex: number) {
if (trackIndex >= this.tracks.length) return;
let current = this.tracks[trackIndex];
if (current == null) return;
if (current.listener != null) current.listener.end(trackIndex);
for (let i = 0, n = this.listeners.length; i < n; i++)
this.listeners[i].end(trackIndex);
this.tracks[trackIndex] = null;
this.freeAll(current);
}
freeAll (entry: TrackEntry) {
while (entry != null) {
let next = entry.next;
entry = next;
}
}
expandToIndex (index: number) {
if (index < this.tracks.length) return this.tracks[index];
Utils.setArraySize(this.tracks, index - this.tracks.length + 1, null);
this.tracks.length = index + 1;
return null;
}
setCurrent (index: number, entry: TrackEntry) {
let current = this.expandToIndex(index);
if (current != null) {
let previous = current.previous;
current.previous = null;
if (current.listener != null) current.listener.end(index);
for (let i = 0, n = this.listeners.length; i < n; i++)
this.listeners[i].end(index);
entry.mixDuration = this.data.getMix(current.animation, entry.animation);
if (entry.mixDuration > 0) {
entry.mixTime = 0;
// If a mix is in progress, mix from the closest animation.
if (previous != null && current.mixTime / current.mixDuration < 0.5) {
entry.previous = previous;
previous = current;
} else
entry.previous = current;
}
}
this.tracks[index] = entry;
if (entry.listener != null) entry.listener.start(index);
for (let i = 0, n = this.listeners.length; i < n; i++)
this.listeners[i].start(index);
}
/** @see #setAnimation(int, Animation, boolean) */
setAnimation (trackIndex: number, animationName: string, loop: boolean) {
let animation = this.data.skeletonData.findAnimation(animationName);
if (animation == null) throw new Error("Animation not found: " + animationName);
return this.setAnimationWith(trackIndex, animation, loop);
}
/** Set the current animation. Any queued animations are cleared. */
setAnimationWith (trackIndex: number, animation: Animation, loop: boolean) {
let current = this.expandToIndex(trackIndex);
if (current != null) this.freeAll(current.next);
let entry = new TrackEntry();
entry.animation = animation;
entry.loop = loop;
entry.endTime = animation.duration;
this.setCurrent(trackIndex, entry);
return entry;
}
/** {@link #addAnimation(int, Animation, boolean, float)} */
addAnimation (trackIndex: number, animationName: string, loop: boolean, delay: number) {
let animation = this.data.skeletonData.findAnimation(animationName);
if (animation == null) throw new Error("Animation not found: " + animationName);
return this.addAnimationWith(trackIndex, animation, loop, delay);
}
/** Adds an animation to be played delay seconds after the current or last queued animation.
* @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */
addAnimationWith (trackIndex: number, animation: Animation, loop: boolean, delay: number) {
let entry = new TrackEntry();
entry.animation = animation;
entry.loop = loop;
entry.endTime = animation.duration;
let last = this.expandToIndex(trackIndex);
if (last != null) {
while (last.next != null)
last = last.next;
last.next = entry;
} else
this.tracks[trackIndex] = entry;
if (delay <= 0) {
if (last != null)
delay += last.endTime - this.data.getMix(last.animation, animation);
else
delay = 0;
}
entry.delay = delay;
return entry;
}
/** @return May be null. */
getCurrent (trackIndex: number) {
if (trackIndex >= this.tracks.length) return null;
return this.tracks[trackIndex];
}
/** Adds a listener to receive events for all animations. */
addListener (listener: AnimationStateListener) {
if (listener == null) throw new Error("listener cannot be null.");
this.listeners.push(listener);
}
/** Removes the listener added with {@link #addListener(AnimationStateListener)}. */
removeListener (listener: AnimationStateListener) {
let index = this.listeners.indexOf(listener);
if (index >= 0) this.listeners.splice(index, 1);
}
clearListeners () {
this.listeners.length = 0;
}
}
export class TrackEntry {
next: TrackEntry; previous: TrackEntry;
animation: Animation;
loop = false;
delay = 0; time = 0; lastTime = -1; endTime = 0; timeScale = 1;
mixTime = 0; mixDuration = 0;
listener: AnimationStateListener;
mix = 1;
reset () {
this.next = null;
this.previous = null;
this.animation = null;
this.listener = null;
this.timeScale = 1;
this.lastTime = -1; // Trigger events on frame zero.
this.time = 0;
}
/** Returns true if the current time is greater than the end time, regardless of looping. */
isComplete () : boolean {
return this.time >= this.endTime;
}
}
export abstract class AnimationStateAdapter implements AnimationStateListener {
event (trackIndex: number, event: Event) {
}
complete (trackIndex: number, loopCount: number) {
}
start (trackIndex: number) {
}
end (trackIndex: number) {
}
}
export interface AnimationStateListener {
/** Invoked when the current animation triggers an event. */
event (trackIndex: number, event: Event): void;
/** Invoked when the current animation has completed.
* @param loopCount The number of times the animation reached the end. */
complete (trackIndex: number, loopCount: number): void;
/** Invoked just after the current animation is set. */
start (trackIndex: number): void;
/** Invoked just before the current animation is replaced. */
end (trackIndex: number): void;
}
}

View File

@ -0,0 +1,64 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 AnimationStateData {
skeletonData: SkeletonData;
animationToMixTime: Map<number> = { };
defaultMix = 0;
constructor (skeletonData: SkeletonData) {
if (skeletonData == null) throw new Error("skeletonData cannot be null.");
this.skeletonData = skeletonData;
}
setMix (fromName: string, toName: string, duration: number) {
let from = this.skeletonData.findAnimation(fromName);
if (from == null) throw new Error("Animation not found: " + fromName);
let to = this.skeletonData.findAnimation(toName);
if (to == null) throw new Error("Animation not found: " + toName);
this.setMixWith(from, to, duration);
}
setMixWith (from: Animation, to: Animation, duration: number) {
if (from == null) throw new Error("from cannot be null.");
if (to == null) throw new Error("to cannot be null.");
let key = from.name + to.name;
this.animationToMixTime[key] = duration;
}
getMix (from: Animation, to: Animation) {
let key = from.name + to.name;
let value = this.animationToMixTime[key];
return value === undefined ? this.defaultMix : value;
}
}
}

View File

@ -0,0 +1,39 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 enum BlendMode {
Normal,
Additive,
Multiply,
Screen
}
}

293
spine-ts/core/src/Bone.ts Normal file
View File

@ -0,0 +1,293 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 Bone implements Updatable {
data: BoneData;
skeleton: Skeleton;
parent: Bone;
children = new Array<Bone>();
x = 0; y = 0; rotation = 0; scaleX = 0; scaleY = 0; shearX = 0; shearY = 0;
appliedRotation = 0;
a = 0; b = 0; worldX = 0;
c = 0; d = 0; worldY = 0;
worldSignX = 0; worldSignY = 0;
sorted = false;
/** @param parent May be null. */
constructor (data: BoneData, skeleton: Skeleton, parent: Bone) {
if (data == null) throw new Error("data cannot be null.");
if (skeleton == null) throw new Error("skeleton cannot be null.");
this.data = data;
this.skeleton = skeleton;
this.parent = parent;
this.setToSetupPose();
}
/** Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. */
update () {
this.updateWorldTransformWith(this.x, this.y, this.rotation, this.scaleX, this.scaleY, this.shearX, this.shearY);
}
/** Computes the world transform using the parent bone and this bone's local transform. */
updateWorldTransform () {
this.updateWorldTransformWith(this.x, this.y, this.rotation, this.scaleX, this.scaleY, this.shearX, this.shearY);
}
/** Computes the world transform using the parent bone and the specified local transform. */
updateWorldTransformWith (x: number, y: number, rotation: number, scaleX: number, scaleY: number, shearX: number, shearY: number) {
this.appliedRotation = rotation;
let rotationY = rotation + 90 + shearY;
let la = MathUtils.cosDeg(rotation + shearX) * scaleX, lb = MathUtils.cosDeg(rotationY) * scaleY;
let lc = MathUtils.sinDeg(rotation + shearX) * scaleX, ld = MathUtils.sinDeg(rotationY) * scaleY;
let parent = this.parent;
if (parent == null) { // Root bone.
let skeleton = this.skeleton;
if (skeleton.flipX) {
x = -x;
la = -la;
lb = -lb;
}
if (skeleton.flipY) {
y = -y;
lc = -lc;
ld = -ld;
}
this.a = la;
this.b = lb;
this.c = lc;
this.d = ld;
this.worldX = x;
this.worldY = y;
this.worldSignX = MathUtils.signum(scaleX);
this.worldSignY = MathUtils.signum(scaleY);
return;
}
let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
this.worldX = pa * x + pb * y + parent.worldX;
this.worldY = pc * x + pd * y + parent.worldY;
this.worldSignX = parent.worldSignX * MathUtils.signum(scaleX);
this.worldSignY = parent.worldSignY * MathUtils.signum(scaleY);
if (this.data.inheritRotation && this.data.inheritScale) {
this.a = pa * la + pb * lc;
this.b = pa * lb + pb * ld;
this.c = pc * la + pd * lc;
this.d = pc * lb + pd * ld;
} else {
if (this.data.inheritRotation) { // No scale inheritance.
pa = 1;
pb = 0;
pc = 0;
pd = 1;
do {
let cos = MathUtils.cosDeg(parent.appliedRotation), sin = MathUtils.sinDeg(parent.appliedRotation);
let temp = pa * cos + pb * sin;
pb = pb * cos - pa * sin;
pa = temp;
temp = pc * cos + pd * sin;
pd = pd * cos - pc * sin;
pc = temp;
if (!parent.data.inheritRotation) break;
parent = parent.parent;
} while (parent != null);
this.a = pa * la + pb * lc;
this.b = pa * lb + pb * ld;
this.c = pc * la + pd * lc;
this.d = pc * lb + pd * ld;
} else if (this.data.inheritScale) { // No rotation inheritance.
pa = 1;
pb = 0;
pc = 0;
pd = 1;
do {
let cos = MathUtils.cosDeg(parent.appliedRotation), sin = MathUtils.sinDeg(parent.appliedRotation);
let psx = parent.scaleX, psy = parent.scaleY;
let za = cos * psx, zb = sin * psy, zc = sin * psx, zd = cos * psy;
let temp = pa * za + pb * zc;
pb = pb * zd - pa * zb;
pa = temp;
temp = pc * za + pd * zc;
pd = pd * zd - pc * zb;
pc = temp;
if (psx >= 0) sin = -sin;
temp = pa * cos + pb * sin;
pb = pb * cos - pa * sin;
pa = temp;
temp = pc * cos + pd * sin;
pd = pd * cos - pc * sin;
pc = temp;
if (!parent.data.inheritScale) break;
parent = parent.parent;
} while (parent != null);
this.a = pa * la + pb * lc;
this.b = pa * lb + pb * ld;
this.c = pc * la + pd * lc;
this.d = pc * lb + pd * ld;
} else {
this.a = la;
this.b = lb;
this.c = lc;
this.d = ld;
}
if (this.skeleton.flipX) {
this.a = -this.a;
this.b = -this.b;
}
if (this.skeleton.flipY) {
this.c = -this.c;
this.d = -this.d;
}
}
}
setToSetupPose () {
let data = this.data;
this.x = data.x;
this.y = data.y;
this.rotation = data.rotation;
this.scaleX = data.scaleX;
this.scaleY = data.scaleY;
this.shearX = data.shearX;
this.shearY = data.shearY;
}
getWorldRotationX () {
return Math.atan2(this.c, this.a) * MathUtils.radDeg;
}
getWorldRotationY () {
return Math.atan2(this.d, this.b) * MathUtils.radDeg;
}
getWorldScaleX () {
return Math.sqrt(this.a * this.a + this.b * this.b) * this.worldSignX;
}
getWorldScaleY () {
return Math.sqrt(this.c * this.c + this.d * this.d) * this.worldSignY;
}
worldToLocalRotationX () {
let parent = this.parent;
if (parent == null) return this.rotation;
let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
return Math.atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.radDeg;
}
worldToLocalRotationY () {
let parent = this.parent;
if (parent == null) return this.rotation;
let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
return Math.atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.radDeg;
}
rotateWorld (degrees: number) {
let a = this.a, b = this.b, c = this.c, d = this.d;
let cos = MathUtils.cosDeg(degrees), sin = MathUtils.sinDeg(degrees);
this.a = cos * a - sin * c;
this.b = cos * b - sin * d;
this.c = sin * a + cos * c;
this.d = sin * b + cos * d;
}
/** Computes the local transform from the world transform. This can be useful to perform processing on the local transform
* after the world transform has been modified directly (eg, by a constraint).
* <p>
* Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local
* transform values may differ from the original values but are functionally the same. */
updateLocalTransform () {
let parent = this.parent;
if (parent == null) {
this.x = this.worldX;
this.y = this.worldY;
this.rotation = Math.atan2(this.c, this.a) * MathUtils.radDeg;
this.scaleX = Math.sqrt(this.a * this.a + this.c * this.c);
this.scaleY = Math.sqrt(this.b * this.b + this.d * this.d);
let det = this.a * this.d - this.b * this.c;
this.shearX = 0;
this.shearY = Math.atan2(this.a * this.b + this.c * this.d, det) * MathUtils.radDeg;
return;
}
let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
let pid = 1 / (pa * pd - pb * pc);
let dx = this.worldX - parent.worldX, dy = this.worldY - parent.worldY;
this.x = (dx * pd * pid - dy * pb * pid);
this.y = (dy * pa * pid - dx * pc * pid);
let ia = pid * pd;
let id = pid * pa;
let ib = pid * pb;
let ic = pid * pc;
let ra = ia * this.a - ib * this.c;
let rb = ia * this.b - ib * this.d;
let rc = id * this.c - ic * this.a;
let rd = id * this.d - ic * this.b;
this.shearX = 0;
this.scaleX = Math.sqrt(ra * ra + rc * rc);
if (this.scaleX > 0.0001) {
let det = ra * rd - rb * rc;
this.scaleY = det / this.scaleX;
this.shearY = Math.atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
this.rotation = Math.atan2(rc, ra) * MathUtils.radDeg;
} else {
this.scaleX = 0;
this.scaleY = Math.sqrt(rb * rb + rd * rd);
this.shearY = 0;
this.rotation = 90 - Math.atan2(rd, rb) * MathUtils.radDeg;
}
this.appliedRotation = this.rotation;
}
worldToLocal (world: Vector2) {
let a = this.a, b = this.b, c = this.c, d = this.d;
let invDet = 1 / (a * d - b * c);
let x = world.x - this.worldX, y = world.y - this.worldY;
world.x = (x * d * invDet - y * b * invDet);
world.y = (y * a * invDet - x * c * invDet);
return world;
}
localToWorld (local: Vector2) {
let x = local.x, y = local.y;
local.x = x * this.a + y * this.b + this.worldX;
local.y = x * this.c + y * this.d + this.worldY;
return local;
}
}
}

View File

@ -0,0 +1,49 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 BoneData {
index: number;
name: string;
parent: BoneData;
length: number;
x = 0; y = 0; rotation = 0; scaleX = 1; scaleY = 1; shearX = 0; shearY = 0;
inheritRotation = true; inheritScale = true;
constructor (index: number, name: string, parent: BoneData) {
if (index < 0) throw new Error("index must be >= 0.");
if (name == null) throw new Error("name cannot be null.");
this.index = index;
this.name = name;
this.parent = parent;
}
}
}

View File

@ -0,0 +1,46 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 Event {
data: EventData;
intValue: number;
floatValue: number;
stringValue: string;
time: number;
constructor (time: number, data: EventData) {
if (data == null) throw new Error("data cannot be null.");
this.time = time;
this.data = data;
}
}
}

View File

@ -0,0 +1,43 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 EventData {
name: string;
intValue: number;
floatValue: number;
stringValue: string;
constructor (name: string) {
this.name = name;
}
}
}

View File

@ -0,0 +1,223 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 IkConstraint implements Updatable {
data: IkConstraintData;
bones: Array<Bone>;
target: Bone;
mix = 1;
bendDirection = 0;
level = 0;
constructor (data: IkConstraintData, skeleton: Skeleton) {
if (data == null) throw new Error("data cannot be null.");
if (skeleton == null) throw new Error("skeleton cannot be null.");
this.data = data;
this.mix = data.mix;
this.bendDirection = data.bendDirection;
this.bones = new Array<Bone>();
for (let i = 0; i < data.bones.length; i++)
this.bones.push(skeleton.findBone(data.bones[i].name));
this.target = skeleton.findBone(data.target.name);
}
apply () {
this.update();
}
update () {
let target = this.target;
let bones = this.bones;
switch (bones.length) {
case 1:
this.apply1(bones[0], target.worldX, target.worldY, this.mix);
break;
case 2:
this.apply2(bones[0], bones[1], target.worldX, target.worldY, this.bendDirection, this.mix);
break;
}
}
/** Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified in the world
* coordinate system. */
apply1 (bone: Bone, targetX: number, targetY: number, alpha: number) {
let pp = bone.parent;
let id = 1 / (pp.a * pp.d - pp.b * pp.c);
let x = targetX - pp.worldX, y = targetY - pp.worldY;
let tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y;
let rotationIK = Math.atan2(ty, tx) * MathUtils.radDeg - bone.shearX - bone.rotation;
if (bone.scaleX < 0) rotationIK += 180;
if (rotationIK > 180)
rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360;
bone.updateWorldTransformWith(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY, bone.shearX,
bone.shearY);
}
/** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The
* target is specified in the world coordinate system.
* @param child A direct descendant of the parent bone. */
apply2 (parent: Bone, child: Bone, targetX: number, targetY: number, bendDir: number, alpha: number) {
if (alpha == 0) {
child.updateWorldTransform();
return;
}
let px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
let os1 = 0, os2 = 0, s2 = 0;
if (psx < 0) {
psx = -psx;
os1 = 180;
s2 = -1;
} else {
os1 = 0;
s2 = 1;
}
if (psy < 0) {
psy = -psy;
s2 = -s2;
}
if (csx < 0) {
csx = -csx;
os2 = 180;
} else
os2 = 0;
let cx = child.x, cy = 0, cwx = 0, cwy = 0, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
let u = Math.abs(psx - psy) <= 0.0001;
if (!u) {
cy = 0;
cwx = a * cx + parent.worldX;
cwy = c * cx + parent.worldY;
} else {
cy = child.y;
cwx = a * cx + b * cy + parent.worldX;
cwy = c * cx + d * cy + parent.worldY;
}
let pp = parent.parent;
a = pp.a;
b = pp.b;
c = pp.c;
d = pp.d;
let id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY;
let tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
x = cwx - pp.worldX;
y = cwy - pp.worldY;
let dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
let l1 = Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1 = 0, a2 = 0;
outer:
if (u) {
l2 *= psx;
let cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
if (cos < -1)
cos = -1;
else if (cos > 1) cos = 1;
a2 = Math.acos(cos) * bendDir;
a = l1 + l2 * cos;
b = l2 * Math.sin(a2);
a1 = Math.atan2(ty * a - tx * b, tx * a + ty * b);
} else {
a = psx * l2;
b = psy * l2;
let aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = Math.atan2(ty, tx);
c = bb * l1 * l1 + aa * dd - aa * bb;
let c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c;
if (d >= 0) {
let q = Math.sqrt(d);
if (c1 < 0) q = -q;
q = -(c1 + q) / 2;
let r0 = q / c2, r1 = c / q;
let r = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
if (r * r <= dd) {
y = Math.sqrt(dd - r * r) * bendDir;
a1 = ta - Math.atan2(y, r);
a2 = Math.atan2(y / psy, (r - l1) / psx);
break outer;
}
}
let minAngle = 0, minDist = Number.MAX_VALUE, minX = 0, minY = 0;
let maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0;
x = l1 + a;
d = x * x;
if (d > maxDist) {
maxAngle = 0;
maxDist = d;
maxX = x;
}
x = l1 - a;
d = x * x;
if (d < minDist) {
minAngle = MathUtils.PI;
minDist = d;
minX = x;
}
let angle = Math.acos(-a * l1 / (aa - bb));
x = a * Math.cos(angle) + l1;
y = b * Math.sin(angle);
d = x * x + y * y;
if (d < minDist) {
minAngle = angle;
minDist = d;
minX = x;
minY = y;
}
if (d > maxDist) {
maxAngle = angle;
maxDist = d;
maxX = x;
maxY = y;
}
if (dd <= (minDist + maxDist) / 2) {
a1 = ta - Math.atan2(minY * bendDir, minX);
a2 = minAngle * bendDir;
} else {
a1 = ta - Math.atan2(maxY * bendDir, maxX);
a2 = maxAngle * bendDir;
}
}
let os = Math.atan2(cy, cx) * s2;
let rotation = parent.rotation;
a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation;
if (a1 > 180)
a1 -= 360;
else if (a1 < -180) a1 += 360;
parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, parent.scaleX, parent.scaleY, 0, 0);
rotation = child.rotation;
a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - rotation;
if (a2 > 180)
a2 -= 360;
else if (a2 < -180) a2 += 360;
child.updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY);
}
}
}

View File

@ -0,0 +1,44 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 IkConstraintData {
name: string;
bones = new Array<BoneData>();
target: BoneData;
bendDirection = 1;
mix = 1;
constructor (name: string) {
this.name = name;
}
}
}

View File

@ -0,0 +1,388 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 PathConstraint implements Updatable {
static NONE = -1; static BEFORE = -2; static AFTER = -3;
data: PathConstraintData;
bones: Array<Bone>;
target: Slot;
position = 0; spacing = 0; rotateMix = 0; translateMix = 0;
spaces = new Array<number>(); positions = new Array<number>();
world = new Array<number>(); curves = new Array<number>(); lengths = new Array<number>();
segments = new Array<number>();
constructor (data: PathConstraintData, skeleton: Skeleton) {
if (data == null) throw new Error("data cannot be null.");
if (skeleton == null) throw new Error("skeleton cannot be null.");
this.data = data;
this.bones = new Array<Bone>();
for (let i = 0, n = data.bones.length; i < n; i++)
this.bones.push(skeleton.findBone(data.bones[i].name));
this.target = skeleton.findSlot(data.target.name);
this.position = data.position;
this.spacing = data.spacing;
this.rotateMix = data.rotateMix;
this.translateMix = data.translateMix;
}
apply () {
this.update();
}
update () {
let attachment = this.target.getAttachment();
if (!(attachment instanceof PathAttachment)) return;
let rotateMix = this.rotateMix, translateMix = this.translateMix;
let translate = translateMix > 0, rotate = rotateMix > 0;
if (!translate && !rotate) return;
let data = this.data;
let spacingMode = data.spacingMode;
let lengthSpacing = spacingMode == SpacingMode.Length;
let rotateMode = data.rotateMode;
let tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale;
let boneCount = this.bones.length, spacesCount = tangents ? boneCount : boneCount + 1;
let bones = this.bones;
let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array<number> = null;
let spacing = this.spacing;
if (scale || lengthSpacing) {
if (scale) lengths = Utils.setArraySize(this.lengths, boneCount);
for (let i = 0, n = spacesCount - 1; i < n;) {
let bone = bones[i];
let length = bone.data.length, x = length * bone.a, y = length * bone.c;
length = Math.sqrt(x * x + y * y);
if (scale) lengths[i] = length;
spaces[++i] = lengthSpacing ? Math.max(0, length + spacing) : spacing;
}
} else {
for (let i = 1; i < spacesCount; i++)
spaces[i] = spacing;
}
let positions = this.computeWorldPositions(<PathAttachment>attachment, spacesCount, tangents,
data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent);
let skeleton = this.target.bone.skeleton;
let skeletonX = skeleton.x, skeletonY = skeleton.y;
let boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
let tip = rotateMode == RotateMode.Chain && offsetRotation == 0;
for (let i = 0, p = 3; i < boneCount; i++, p += 3) {
let bone = bones[i];
bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix;
bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix;
let x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
if (scale) {
let length = lengths[i];
if (length != 0) {
let s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1;
bone.a *= s;
bone.c *= s;
}
}
boneX = x;
boneY = y;
if (rotate) {
let a = bone.a, b = bone.b, c = bone.c, d = bone.d, r = 0, cos = 0, sin = 0;
if (tangents)
r = positions[p - 1];
else if (spaces[i + 1] == 0)
r = positions[p + 2];
else
r = Math.atan2(dy, dx);
r -= Math.atan2(c, a) - offsetRotation * MathUtils.degRad;
if (tip) {
cos = Math.cos(r);
sin = Math.sin(r);
let length = bone.data.length;
boneX += (length * (cos * a - sin * c) - dx) * rotateMix;
boneY += (length * (sin * a + cos * c) - dy) * rotateMix;
}
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) //
r += MathUtils.PI2;
r *= rotateMix;
cos = Math.cos(r);
sin = Math.sin(r);
bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d;
}
}
}
computeWorldPositions (path: PathAttachment, spacesCount: number, tangents: boolean, percentPosition: boolean,
percentSpacing: boolean) {
let target = this.target;
let position = this.position;
let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array<number> = null;
let closed = path.closed;
let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PathConstraint.NONE;
if (!path.constantSpeed) {
let lengths = path.lengths;
curveCount -= closed ? 1 : 2;
let pathLength = lengths[curveCount];
if (percentPosition) position *= pathLength;
if (percentSpacing) {
for (let i = 0; i < spacesCount; i++)
spaces[i] *= pathLength;
}
world = Utils.setArraySize(this.world, 8);
for (let i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
let space = spaces[i];
position += space;
let p = position;
if (closed) {
p %= pathLength;
if (p < 0) p += pathLength;
curve = 0;
} else if (p < 0) {
if (prevCurve != PathConstraint.BEFORE) {
prevCurve = PathConstraint.BEFORE;
path.computeWorldVerticesWith(target, 2, 4, world, 0);
}
this.addBeforePosition(p, world, 0, out, o);
continue;
} else if (p > pathLength) {
if (prevCurve != PathConstraint.AFTER) {
prevCurve = PathConstraint.AFTER;
path.computeWorldVerticesWith(target, verticesLength - 6, 4, world, 0);
}
this.addAfterPosition(p - pathLength, world, 0, out, o);
continue;
}
// Determine curve containing position.
for (;; curve++) {
let length = lengths[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
let prev = lengths[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
if (curve != prevCurve) {
prevCurve = curve;
if (closed && curve == curveCount) {
path.computeWorldVerticesWith(target, verticesLength - 4, 4, world, 0);
path.computeWorldVerticesWith(target, 0, 4, world, 4);
} else
path.computeWorldVerticesWith(target, curve * 6 + 2, 8, world, 0);
}
this.addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
tangents || (i > 0 && space == 0));
}
return out;
}
// World vertices.
if (closed) {
verticesLength += 2;
world = Utils.setArraySize(this.world, verticesLength);
path.computeWorldVerticesWith(target, 2, verticesLength - 4, world, 0);
path.computeWorldVerticesWith(target, 0, 2, world, verticesLength - 4);
world[verticesLength - 2] = world[0];
world[verticesLength - 1] = world[1];
} else {
curveCount--;
verticesLength -= 4;
world = Utils.setArraySize(this.world, verticesLength);
path.computeWorldVerticesWith(target, 2, verticesLength, world, 0);
}
// Curve lengths.
let curves = Utils.setArraySize(this.curves, curveCount);
let pathLength = 0;
let x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
let tmpx = 0, tmpy = 0, dddfx = 0, dddfy = 0, ddfx = 0, ddfy = 0, dfx = 0, dfy = 0;
for (let i = 0, w = 2; i < curveCount; i++, w += 6) {
cx1 = world[w];
cy1 = world[w + 1];
cx2 = world[w + 2];
cy2 = world[w + 3];
x2 = world[w + 4];
y2 = world[w + 5];
tmpx = (x1 - cx1 * 2 + cx2) * 0.1875;
tmpy = (y1 - cy1 * 2 + cy2) * 0.1875;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667;
dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667;
pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
pathLength += Math.sqrt(dfx * dfx + dfy * dfy);
curves[i] = pathLength;
x1 = x2;
y1 = y2;
}
if (percentPosition) position *= pathLength;
if (percentSpacing) {
for (let i = 0; i < spacesCount; i++)
spaces[i] *= pathLength;
}
let segments = this.segments;
let curveLength = 0;
for (let i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
let space = spaces[i];
position += space;
let p = position;
if (closed) {
p %= pathLength;
if (p < 0) p += pathLength;
curve = 0;
} else if (p < 0) {
this.addBeforePosition(p, world, 0, out, o);
continue;
} else if (p > pathLength) {
this.addAfterPosition(p - pathLength, world, verticesLength - 4, out, o);
continue;
}
// Determine curve containing position.
for (;; curve++) {
let length = curves[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
let prev = curves[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
// Curve segment lengths.
if (curve != prevCurve) {
prevCurve = curve;
let ii = curve * 6;
x1 = world[ii];
y1 = world[ii + 1];
cx1 = world[ii + 2];
cy1 = world[ii + 3];
cx2 = world[ii + 4];
cy2 = world[ii + 5];
x2 = world[ii + 6];
y2 = world[ii + 7];
tmpx = (x1 - cx1 * 2 + cx2) * 0.03;
tmpy = (y1 - cy1 * 2 + cy2) * 0.03;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667;
dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667;
curveLength = Math.sqrt(dfx * dfx + dfy * dfy);
segments[0] = curveLength;
for (ii = 1; ii < 8; ii++) {
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
curveLength += Math.sqrt(dfx * dfx + dfy * dfy);
segments[ii] = curveLength;
}
dfx += ddfx;
dfy += ddfy;
curveLength += Math.sqrt(dfx * dfx + dfy * dfy);
segments[8] = curveLength;
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
curveLength += Math.sqrt(dfx * dfx + dfy * dfy);
segments[9] = curveLength;
segment = 0;
}
// Weight by segment length.
p *= curveLength;
for (;; segment++) {
let length = segments[segment];
if (p > length) continue;
if (segment == 0)
p /= length;
else {
let prev = segments[segment - 1];
p = segment + (p - prev) / (length - prev);
}
break;
}
this.addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0));
}
return out;
}
addBeforePosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) {
let x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = Math.atan2(dy, dx);
out[o] = x1 + p * Math.cos(r);
out[o + 1] = y1 + p * Math.sin(r);
out[o + 2] = r;
}
addAfterPosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) {
let x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = Math.atan2(dy, dx);
out[o] = x1 + p * Math.cos(r);
out[o + 1] = y1 + p * Math.sin(r);
out[o + 2] = r;
}
addCurvePosition (p: number, x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number,
out: Array<number>, o: number, tangents: boolean) {
if (p == 0) p = 0.0001;
let tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
let ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
let x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
out[o] = x;
out[o + 1] = y;
if (tangents) out[o + 2] = Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
}
}
}

View File

@ -0,0 +1,59 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 PathConstraintData {
name: string;
bones = new Array<BoneData>();
target: SlotData;
positionMode: PositionMode;
spacingMode: SpacingMode;
rotateMode: RotateMode;
offsetRotation: number;
position: number; spacing: number; rotateMix: number; translateMix: number;
constructor (name: string) {
this.name = name;
}
}
export enum PositionMode {
Fixed, Percent
}
export enum SpacingMode {
Length, Fixed, Percent
}
export enum RotateMode {
Tangent, Chain, ChainScale
}
}

View File

@ -0,0 +1,469 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 Skeleton {
data: SkeletonData;
bones: Array<Bone>;
slots: Array<Slot>;
drawOrder: Array<Slot>;
ikConstraints: Array<IkConstraint>; ikConstraintsSorted: Array<IkConstraint>;
transformConstraints: Array<TransformConstraint>;
pathConstraints: Array<PathConstraint>;
_updateCache = new Array<Updatable>();
skin: Skin;
color: Color;
time = 0;
flipX = false; flipY = false;
x = 0; y = 0;
constructor (data: SkeletonData) {
if (data == null) throw new Error("data cannot be null.");
this.data = data;
this.bones = new Array<Bone>();
for (let i = 0; i < data.bones.length; i++) {
let boneData = data.bones[i];
let bone: Bone;
if (boneData.parent == null)
bone = new Bone(boneData, this, null);
else {
let parent = this.bones[boneData.parent.index];
bone = new Bone(boneData, this, parent);
parent.children.push(bone);
}
this.bones.push(bone);
}
this.slots = new Array<Slot>();
this.drawOrder = new Array<Slot>();
for (let i = 0; i < data.slots.length; i++) {
let slotData = data.slots[i];
let bone = this.bones[slotData.boneData.index];
let slot = new Slot(slotData, bone);
this.slots.push(slot);
this.drawOrder.push(slot);
}
this.ikConstraints = new Array<IkConstraint>();
this.ikConstraintsSorted = new Array<IkConstraint>();
for (let i = 0; i < data.ikConstraints.length; i++) {
let ikConstraintData = data.ikConstraints[i];
this.ikConstraints.push(new IkConstraint(ikConstraintData, this));
}
this.transformConstraints = new Array<TransformConstraint>();
for (let i = 0; i < data.transformConstraints.length; i++) {
let transformConstraintData = data.transformConstraints[i];
this.transformConstraints.push(new TransformConstraint(transformConstraintData, this));
}
this.pathConstraints = new Array<PathConstraint>();
for (let i = 0; i < data.pathConstraints.length; i++) {
let pathConstraintData = data.pathConstraints[i];
this.pathConstraints.push(new PathConstraint(pathConstraintData, this));
}
this.color = new Color(1, 1, 1, 1);
this.updateCache();
}
updateCache () {
let updateCache = this._updateCache;
updateCache.length = 0;
let bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++)
bones[i].sorted = false;
// IK first, lowest hierarchy depth first.
let ikConstraints = this.ikConstraintsSorted;
ikConstraints.length = 0;
for (let i = 0; i < this.ikConstraints.length; i++)
ikConstraints.push(this.ikConstraints[i]);
let ikCount = ikConstraints.length;
for (let i = 0, level = 0, n = ikCount; i < n; i++) {
let ik = ikConstraints[i];
let bone = ik.bones[0].parent;
for (level = 0; bone != null; level++)
bone = bone.parent;
ik.level = level;
}
for (let i = 1, ii = 0; i < ikCount; i++) {
let ik = ikConstraints[i];
let level = ik.level;
for (ii = i - 1; ii >= 0; ii--) {
let other = ikConstraints[ii];
if (other.level < level) break;
ikConstraints[ii + 1] = other;
}
ikConstraints[ii + 1] = ik;
}
for (let i = 0, n = ikConstraints.length; i < n; i++) {
let constraint = ikConstraints[i];
let target = constraint.target;
this.sortBone(target);
let constrained = constraint.bones;
let parent = constrained[0];
this.sortBone(parent);
updateCache.push(constraint);
this.sortReset(parent.children);
constrained[constrained.length - 1].sorted = true;
}
let pathConstraints = this.pathConstraints;
for (let i = 0, n = pathConstraints.length; i < n; i++) {
let constraint = pathConstraints[i];
let slot = constraint.target;
let slotIndex = slot.data.index;
let slotBone = slot.bone;
if (this.skin != null) this.sortPathConstraintAttachment(this.skin, slotIndex, slotBone);
if (this.data.defaultSkin != null && this.data.defaultSkin != this.skin)
this.sortPathConstraintAttachment(this.data.defaultSkin, slotIndex, slotBone);
for (let ii = 0, nn = this.data.skins.length; ii < nn; ii++)
this.sortPathConstraintAttachment(this.data.skins[ii], slotIndex, slotBone);
let attachment = slot.getAttachment();
if (attachment instanceof PathAttachment) this.sortPathConstraintAttachmentWith(attachment, slotBone);
let constrained = constraint.bones;
let boneCount = constrained.length;
for (let ii = 0; ii < boneCount; ii++)
this.sortBone(constrained[ii]);
updateCache.push(constraint);
for (let ii = 0; ii < boneCount; ii++)
this.sortReset(constrained[ii].children);
for (let ii = 0; ii < boneCount; ii++)
constrained[ii].sorted = true;
}
let transformConstraints = this.transformConstraints;
for (let i = 0, n = transformConstraints.length; i < n; i++) {
let constraint = transformConstraints[i];
this.sortBone(constraint.target);
let constrained = constraint.bones;
let boneCount = constrained.length;
for (let ii = 0; ii < boneCount; ii++)
this.sortBone(constrained[ii]);
updateCache.push(constraint);
for (let ii = 0; ii < boneCount; ii++)
this.sortReset(constrained[ii].children);
for (let ii = 0; ii < boneCount; ii++)
constrained[ii].sorted = true;
}
for (let i = 0, n = bones.length; i < n; i++)
this.sortBone(bones[i]);
}
sortPathConstraintAttachment (skin: Skin, slotIndex: number, slotBone: Bone) {
let attachments = skin.attachments[slotIndex];
if (!attachments) return;
for (let key in attachments) {
this.sortPathConstraintAttachmentWith(attachments[key], slotBone);
}
}
sortPathConstraintAttachmentWith (attachment: Attachment, slotBone: Bone) {
if (!(attachment instanceof PathAttachment)) return;
let pathBones = (<PathAttachment>attachment).bones;
if (pathBones == null)
this.sortBone(slotBone);
else {
let bones = this.bones;
for (let i = 0; i < pathBones.length; i++) {
let boneIndex = pathBones[i];
this.sortBone(bones[boneIndex]);
}
}
}
sortBone (bone: Bone) {
if (bone.sorted) return;
let parent = bone.parent;
if (parent != null) this.sortBone(parent);
bone.sorted = true;
this._updateCache.push(bone);
}
sortReset (bones: Array<Bone>) {
for (let i = 0, n = bones.length; i < n; i++) {
let bone = bones[i];
if (bone.sorted) this.sortReset(bone.children);
bone.sorted = false;
}
}
/** Updates the world transform for each bone and applies constraints. */
updateWorldTransform () {
let updateCache = this._updateCache;
for (let i = 0, n = updateCache.length; i < n; i++)
updateCache[i].update();
}
/** Sets the bones, constraints, and slots to their setup pose values. */
setToSetupPose () {
this.setBonesToSetupPose();
this.setSlotsToSetupPose();
}
/** Sets the bones and constraints to their setup pose values. */
setBonesToSetupPose () {
let bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++)
bones[i].setToSetupPose();
let ikConstraints = this.ikConstraints;
for (let i = 0, n = ikConstraints.length; i < n; i++) {
let constraint = ikConstraints[i];
constraint.bendDirection = constraint.data.bendDirection;
constraint.mix = constraint.data.mix;
}
let transformConstraints = this.transformConstraints;
for (let i = 0, n = transformConstraints.length; i < n; i++) {
let constraint = transformConstraints[i];
let data = constraint.data;
constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix;
constraint.scaleMix = data.scaleMix;
constraint.shearMix = data.shearMix;
}
let pathConstraints = this.pathConstraints;
for (let i = 0, n = pathConstraints.length; i < n; i++) {
let constraint = pathConstraints[i];
let data = constraint.data;
constraint.position = data.position;
constraint.spacing = data.spacing;
constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix;
}
}
setSlotsToSetupPose () {
let slots = this.slots;
Utils.arrayCopy(slots, 0, this.drawOrder, 0, slots.length);
for (let i = 0, n = slots.length; i < n; i++)
slots[i].setToSetupPose();
}
/** @return May return null. */
getRootBone () {
if (this.bones.length == 0) return null;
return this.bones[0];
}
/** @return May be null. */
findBone (boneName: string) {
if (boneName == null) throw new Error("boneName cannot be null.");
let bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++) {
let bone = bones[i];
if (bone.data.name == boneName) return bone;
}
return null;
}
/** @return -1 if the bone was not found. */
findBoneIndex (boneName: string) {
if (boneName == null) throw new Error("boneName cannot be null.");
let bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++)
if (bones[i].data.name == boneName) return i;
return -1;
}
/** @return May be null. */
findSlot (slotName: string) {
if (slotName == null) throw new Error("slotName cannot be null.");
let slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i];
if (slot.data.name == slotName) return slot;
}
return null;
}
/** @return -1 if the bone was not found. */
findSlotIndex (slotName: string) {
if (slotName == null) throw new Error("slotName cannot be null.");
let slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++)
if (slots[i].data.name == slotName) return i;
return -1;
}
/** Sets a skin by name.
* @see #setSkin(Skin) */
setSkinByName (skinName: string) {
let skin = this.data.findSkin(skinName);
if (skin == null) throw new Error("Skin not found: " + skinName);
this.setSkin(skin);
}
/** Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default skin}.
* Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was no
* old skin, each slot's setup mode attachment is attached from the new skin.
* @param newSkin May be null. */
setSkin (newSkin: Skin) {
if (newSkin != null) {
if (this.skin != null)
newSkin.attachAll(this, this.skin);
else {
let slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i];
let name = slot.data.attachmentName;
if (name != null) {
let attachment: Attachment = newSkin.getAttachment(i, name);
if (attachment != null) slot.setAttachment(attachment);
}
}
}
}
this.skin = newSkin;
}
/** @return May be null. */
getAttachmentByName (slotName: string, attachmentName: string): Attachment {
return this.getAttachment(this.data.findSlotIndex(slotName), attachmentName);
}
/** @return May be null. */
getAttachment (slotIndex: number, attachmentName: string): Attachment {
if (attachmentName == null) throw new Error("attachmentName cannot be null.");
if (this.skin != null) {
let attachment: Attachment = this.skin.getAttachment(slotIndex, attachmentName);
if (attachment != null) return attachment;
}
if (this.data.defaultSkin != null) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName);
return null;
}
/** @param attachmentName May be null. */
setAttachment (slotName: string, attachmentName: string) {
if (slotName == null) throw new Error("slotName cannot be null.");
let slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i];
if (slot.data.name == slotName) {
let attachment: Attachment = null;
if (attachmentName != null) {
attachment = this.getAttachment(i, attachmentName);
if (attachment == null)
throw new Error("Attachment not found: " + attachmentName + ", for slot: " + slotName);
}
slot.setAttachment(attachment);
return;
}
}
throw new Error("Slot not found: " + slotName);
}
/** @return May be null. */
findIkConstraint (constraintName: string) {
if (constraintName == null) throw new Error("constraintName cannot be null.");
let ikConstraints = this.ikConstraints;
for (let i = 0, n = ikConstraints.length; i < n; i++) {
let ikConstraint = ikConstraints[i];
if (ikConstraint.data.name == constraintName) return ikConstraint;
}
return null;
}
/** @return May be null. */
findTransformConstraint (constraintName: string) {
if (constraintName == null) throw new Error("constraintName cannot be null.");
let transformConstraints = this.transformConstraints;
for (let i = 0, n = transformConstraints.length; i < n; i++) {
let constraint = transformConstraints[i];
if (constraint.data.name == constraintName) return constraint;
}
return null;
}
/** @return May be null. */
findPathConstraint (constraintName: string) {
if (constraintName == null) throw new Error("constraintName cannot be null.");
let pathConstraints = this.pathConstraints;
for (let i = 0, n = pathConstraints.length; i < n; i++) {
let constraint = pathConstraints[i];
if (constraint.data.name == constraintName) return constraint;
}
return null;
}
/** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose.
* @param offset The distance from the skeleton origin to the bottom left corner of the AABB.
* @param size The width and height of the AABB. */
getBounds (offset: Vector2, size: Vector2) {
if (offset == null) throw new Error("offset cannot be null.");
if (size == null) throw new Error("size cannot be null.");
let drawOrder = this.drawOrder;
let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
for (let i = 0, n = drawOrder.length; i < n; i++) {
let slot = drawOrder[i];
let vertices: ArrayLike<number> = null;
let attachment = slot.getAttachment();
if (attachment instanceof RegionAttachment)
vertices = (<RegionAttachment>attachment).updateWorldVertices(slot, false);
else if (attachment instanceof MeshAttachment) //
vertices = (<MeshAttachment>attachment).updateWorldVertices(slot, true);
if (vertices != null) {
for (let ii = 0, nn = vertices.length; ii < nn; ii += 5) {
let x = vertices[ii], y = vertices[ii + 1];
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
}
offset.set(minX, minY);
size.set(maxX - minX, maxY - minY);
}
update (delta: number) {
this.time += delta;
}
}
}

View File

@ -0,0 +1,193 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 SkeletonBounds {
minX = 0; minY = 0; maxX = 0; maxY = 0;
boundingBoxes = new Array<BoundingBoxAttachment>();
polygons = new Array<ArrayLike<number>>();
private _polygonPool = new Pool<ArrayLike<number>>(() => {
return Utils.newFloatArray(16);
});
update (skeleton: Skeleton, updateAabb: boolean) {
if (skeleton == null) throw new Error("skeleton cannot be null.");
let boundingBoxes = this.boundingBoxes;
let polygons = this.polygons;
let polygonPool = this._polygonPool;
let slots = skeleton.slots;
let slotCount = slots.length;
boundingBoxes.length = 0;
polygonPool.freeAll(polygons);
polygons.length = 0;
for (let i = 0; i < slotCount; i++) {
let slot = slots[i];
let attachment = slot.getAttachment();
if (attachment instanceof BoundingBoxAttachment) {
let boundingBox = attachment as BoundingBoxAttachment;
boundingBoxes.push(boundingBox);
let polygon = polygonPool.obtain();
if (polygon.length != boundingBox.worldVerticesLength) {
polygon = Utils.newFloatArray(boundingBox.worldVerticesLength);
}
polygons.push(polygon);
boundingBox.computeWorldVertices(slot, polygon);
}
}
if (updateAabb) this.aabbCompute();
}
aabbCompute () {
let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
let polygons = this.polygons;
for (let i = 0, n = polygons.length; i < n; i++) {
let polygon = polygons[i];
let vertices = polygon;
for (let ii = 0, nn = polygon.length; ii < nn; ii += 2) {
let x = vertices[ii];
let y = vertices[ii + 1];
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
/** Returns true if the axis aligned bounding box contains the point. */
aabbContainsPoint (x: number, y: number) {
return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY;
}
/** Returns true if the axis aligned bounding box intersects the line segment. */
aabbIntersectsSegment (x1: number, y1: number, x2: number, y2: number) {
let minX = this.minX;
let minY = this.minY;
let maxX = this.maxX;
let maxY = this.maxY;
if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
return false;
let m = (y2 - y1) / (x2 - x1);
let y = m * (minX - x1) + y1;
if (y > minY && y < maxY) return true;
y = m * (maxX - x1) + y1;
if (y > minY && y < maxY) return true;
let x = (minY - y1) / m + x1;
if (x > minX && x < maxX) return true;
x = (maxY - y1) / m + x1;
if (x > minX && x < maxX) return true;
return false;
}
/** Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. */
aabbIntersectsSkeleton (bounds: SkeletonBounds) {
return this.minX < bounds.maxX && this.maxX > bounds.minX && this.minY < bounds.maxY && this.maxY > bounds.minY;
}
/** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
* efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */
containsPoint (x: number, y: number): BoundingBoxAttachment {
let polygons = this.polygons;
for (let i = 0, n = polygons.length; i < n; i++)
if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i];
return null;
}
/** Returns true if the polygon contains the point. */
containsPointPolygon (polygon: ArrayLike<number>, x: number, y: number) {
let vertices = polygon;
let nn = polygon.length;
let prevIndex = nn - 2;
let inside = false;
for (let ii = 0; ii < nn; ii += 2) {
let vertexY = vertices[ii + 1];
let prevY = vertices[prevIndex + 1];
if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
let vertexX = vertices[ii];
if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
}
prevIndex = ii;
}
return inside;
}
/** Returns the first bounding box attachment that contains any part of the line segment, or null. When doing many checks, it
* is usually more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns
* true. */
intersectsSegment (x1: number, y1: number, x2: number, y2: number) {
let polygons = this.polygons;
for (let i = 0, n = polygons.length; i < n; i++)
if (this.intersectsSegmentPolygon(polygons[i], x1, y1, x2, y2)) return this.boundingBoxes[i];
return null;
}
/** Returns true if the polygon contains any part of the line segment. */
intersectsSegmentPolygon (polygon: ArrayLike<number>, x1: number, y1: number, x2: number, y2: number) {
let vertices = polygon;
let nn = polygon.length;
let width12 = x1 - x2, height12 = y1 - y2;
let det1 = x1 * y2 - y1 * x2;
let x3 = vertices[nn - 2], y3 = vertices[nn - 1];
for (let ii = 0; ii < nn; ii += 2) {
let x4 = vertices[ii], y4 = vertices[ii + 1];
let det2 = x3 * y4 - y3 * x4;
let width34 = x3 - x4, height34 = y3 - y4;
let det3 = width12 * height34 - height12 * width34;
let x = (det1 * width34 - width12 * det2) / det3;
if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
let y = (det1 * height34 - height12 * det2) / det3;
if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true;
}
x3 = x4;
y3 = y4;
}
return false;
}
/** Returns the polygon for the specified bounding box, or null. */
getPolygon (boundingBox: BoundingBoxAttachment) {
if (boundingBox == null) throw new Error("boundingBox cannot be null.");
let index = this.boundingBoxes.indexOf(boundingBox);
return index == -1 ? null : this.polygons[index];
}
}
}

View File

@ -0,0 +1,151 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 SkeletonData {
name: string;
bones = new Array<BoneData>(); // Ordered parents first.
slots = new Array<SlotData>(); // Setup pose draw order.
skins = new Array<Skin>();
defaultSkin: Skin;
events = new Array<EventData>();
animations = new Array<Animation>();
ikConstraints = new Array<IkConstraintData>();
transformConstraints = new Array<TransformConstraintData>();
pathConstraints = new Array<PathConstraintData>();
width: number; height: number;
version: string; hash: string; imagesPath: string;
findBone (boneName: string) {
if (boneName == null) throw new Error("boneName cannot be null.");
let bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++) {
let bone = bones[i];
if (bone.name == boneName) return bone;
}
return null;
}
findBoneIndex (boneName: string) {
if (boneName == null) throw new Error("boneName cannot be null.");
let bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++)
if (bones[i].name == boneName) return i;
return -1;
}
findSlot (slotName: string) {
if (slotName == null) throw new Error("slotName cannot be null.");
let slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i];
if (slot.name == slotName) return slot;
}
return null;
}
findSlotIndex (slotName: string) {
if (slotName == null) throw new Error("slotName cannot be null.");
let slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++)
if (slots[i].name == slotName) return i;
return -1;
}
findSkin (skinName: string) {
if (skinName == null) throw new Error("skinName cannot be null.");
let skins = this.skins;
for (let i = 0, n = skins.length; i < n; i++) {
let skin = skins[i];
if (skin.name == skinName) return skin;
}
return null;
}
findEvent (eventDataName: string) {
if (eventDataName == null) throw new Error("eventDataName cannot be null.");
let events = this.events;
for (let i = 0, n = events.length; i < n; i++) {
let event = events[i];
if (event.name == eventDataName) return event;
}
return null;
}
findAnimation (animationName: string) {
if (animationName == null) throw new Error("animationName cannot be null.");
let animations = this.animations;
for (let i = 0, n = animations.length; i < n; i++) {
let animation = animations[i];
if (animation.name == animationName) return animation;
}
return null;
}
findIkConstraint (constraintName: string) {
if (constraintName == null) throw new Error("constraintName cannot be null.");
let ikConstraints = this.ikConstraints;
for (let i = 0, n = ikConstraints.length; i < n; i++) {
let constraint = ikConstraints[i];
if (constraint.name == constraintName) return constraint;
}
return null;
}
findTransformConstraint (constraintName: string) {
if (constraintName == null) throw new Error("constraintName cannot be null.");
let transformConstraints = this.transformConstraints;
for (let i = 0, n = transformConstraints.length; i < n; i++) {
let constraint = transformConstraints[i];
if (constraint.name == constraintName) return constraint;
}
return null;
}
findPathConstraint (constraintName: string) {
if (constraintName == null) throw new Error("constraintName cannot be null.");
let pathConstraints = this.pathConstraints;
for (let i = 0, n = pathConstraints.length; i < n; i++) {
let constraint = pathConstraints[i];
if (constraint.name == constraintName) return constraint;
}
return null;
}
findPathConstraintIndex (pathConstraintName: string) {
if (pathConstraintName == null) throw new Error("pathConstraintName cannot be null.");
let pathConstraints = this.pathConstraints;
for (let i = 0, n = pathConstraints.length; i < n; i++)
if (pathConstraints[i].name == pathConstraintName) return i;
return -1;
}
}
}

View File

@ -0,0 +1,715 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 SkeletonJson {
attachmentLoader: AttachmentLoader;
scale = 1;
private linkedMeshes = new Array<LinkedMesh>();
constructor (attachmentLoader: AttachmentLoader) {
this.attachmentLoader = attachmentLoader;
}
readSkeletonData (json: string): SkeletonData {
let scale = this.scale;
let skeletonData = new SkeletonData();
let root = JSON.parse(json);
// Skeleton
let skeletonMap = root.skeleton;
if (skeletonMap != null) {
skeletonData.hash = skeletonMap.hash;
skeletonData.version = skeletonMap.spine;
skeletonData.width = skeletonMap.width;
skeletonData.height = skeletonMap.height;
skeletonData.imagesPath = skeletonMap.images;
}
// Bones
if (root.bones) {
for (let i = 0; i < root.bones.length; i++) {
let boneMap = root.bones[i];
let parent: BoneData = null;
let parentName: string = this.getValue(boneMap, "parent", null);
if (parentName != null) {
parent = skeletonData.findBone(parentName);
if (parent == null) throw new Error("Parent bone not found: " + parentName);
}
let data = new BoneData(skeletonData.bones.length, boneMap.name, parent);
data.length = this.getValue(boneMap, "length", 0) * scale;
data.x = this.getValue(boneMap, "x", 0) * scale;
data.y = this.getValue(boneMap, "y", 0) * scale;
data.rotation = this.getValue(boneMap, "rotation", 0);
data.scaleX = this.getValue(boneMap, "scaleX", 1);
data.scaleY = this.getValue(boneMap, "scaleY", 1);
data.shearX = this.getValue(boneMap, "shearX", 0);
data.shearY = this.getValue(boneMap, "shearY", 0);
data.inheritRotation = this.getValue(boneMap, "inheritRotation", true);
data.inheritScale = this.getValue(boneMap, "inheritScale", true);
skeletonData.bones.push(data);
}
}
// Slots.
if (root.slots) {
for (let i = 0; i < root.slots.length; i++) {
let slotMap = root.slots[i];
let slotName: string = slotMap.name;
let boneName: string = slotMap.bone;
let boneData = skeletonData.findBone(boneName);
if (boneData == null) throw new Error("Slot bone not found: " + boneName);
let data = new SlotData(skeletonData.slots.length, slotName, boneData);
let color: string = slotMap.color ? slotMap.color : null;
if (color != null) data.color.setFromString(color);
data.attachmentName = this.getValue(slotMap, "attachment", null);
data.blendMode = SkeletonJson.blendModeFromString(this.getValue(slotMap, "blend", "normal"));
skeletonData.slots.push(data);
}
}
// IK constraints
if (root.ik) {
for (let i = 0; i < root.ik.length; i++) {
let constraintMap = root.ik[i];
let data = new IkConstraintData(constraintMap.name);
for (let j = 0; j < constraintMap.bones.length; j++) {
let boneName = constraintMap.bones[j];
let bone = skeletonData.findBone(boneName);
if (bone == null) throw new Error("IK bone not found: " + boneName);
data.bones.push(bone);
}
let targetName: string = constraintMap.target;
data.target = skeletonData.findBone(targetName);
if (data.target == null) throw new Error("IK target bone not found: " + targetName);
data.bendDirection = this.getValue(constraintMap, "bendPositive", true) ? 1 : -1;
data.mix = constraintMap.mix ? constraintMap.mix : 1;
skeletonData.ikConstraints.push(data);
}
}
// Transform constraints.
if (root.transform) {
for (let i = 0; i < root.transform.length; i++) {
let constraintMap = root.transform[i];
let data = new TransformConstraintData(constraintMap.name);
for (let j = 0; j < constraintMap.bones.length; j++) {
let boneName = constraintMap.bones[j];
let bone = skeletonData.findBone(boneName);
if (bone == null) throw new Error("Transform constraint bone not found: " + boneName);
data.bones.push(bone);
}
let targetName: string = constraintMap.target;
data.target = skeletonData.findBone(targetName);
if (data.target == null) throw new Error("Transform constraint target bone not found: " + targetName);
data.offsetRotation = this.getValue(constraintMap, "rotation", 0);
data.offsetX = this.getValue(constraintMap, "x", 0) * scale;
data.offsetY = this.getValue(constraintMap, "y", 0) * scale;
data.offsetScaleX = this.getValue(constraintMap, "scaleX", 0);
data.offsetScaleY = this.getValue(constraintMap, "scaleY", 0);
data.offsetShearY = this.getValue(constraintMap, "shearY", 0);
data.rotateMix = this.getValue(constraintMap, "rotateMix", 1);
data.translateMix = this.getValue(constraintMap, "translateMix", 1);
data.scaleMix = this.getValue(constraintMap, "scaleMix", 1);
data.shearMix = this.getValue(constraintMap, "shearMix", 1);
skeletonData.transformConstraints.push(data);
}
}
// Path constraints.
if (root.path) {
for (let i = 0; i < root.path.length; i++) {
let constraintMap = root.path[i];
let data = new PathConstraintData(constraintMap.name);
for (let j = 0; j < constraintMap.bones.length; j++) {
let boneName = constraintMap.bones[j];
let bone = skeletonData.findBone(boneName);
if (bone == null) throw new Error("Transform constraint bone not found: " + boneName);
data.bones.push(bone);
}
let targetName: string = constraintMap.target;
data.target = skeletonData.findSlot(targetName);
if (data.target == null) throw new Error("Path target slot not found: " + targetName);
data.positionMode = SkeletonJson.positionModeFromString(this.getValue(constraintMap, "positionMode", "percent"));
data.spacingMode = SkeletonJson.spacingModeFromString(this.getValue(constraintMap, "spacingMode", "length"));
data.rotateMode = SkeletonJson.rotateModeFromString(this.getValue(constraintMap, "rotateMode", "tangent"));
data.offsetRotation = this.getValue(constraintMap, "rotation", 0);
data.position = this.getValue(constraintMap, "position", 0);
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
data.spacing = this.getValue(constraintMap, "spacing", 0);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
data.rotateMix = this.getValue(constraintMap, "rotateMix", 1);
data.translateMix = this.getValue(constraintMap, "translateMix", 1);
skeletonData.pathConstraints.push(data);
}
}
// Skins.
if (root.skins) {
for (let skinName in root.skins) {
let skinMap = root.skins[skinName]
let skin = new Skin(skinName);
for (let slotName in skinMap) {
let slotIndex = skeletonData.findSlotIndex(slotName);
if (slotIndex == -1) throw new Error("Slot not found: " + slotName);
let slotMap = skinMap[slotName];
for (let entryName in slotMap) {
let attachment = this.readAttachment(slotMap[entryName], skin, slotIndex, entryName);
if (attachment != null) skin.addAttachment(slotIndex, entryName, attachment);
}
}
skeletonData.skins.push(skin);
if (skin.name == "default") skeletonData.defaultSkin = skin;
}
}
// Linked meshes.
for (let i = 0, n = this.linkedMeshes.length; i < n; i++) {
let linkedMesh = this.linkedMeshes[i];
let skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
if (skin == null) throw new Error("Skin not found: " + linkedMesh.skin);
let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new Error("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.setParentMesh(<MeshAttachment> parent);
linkedMesh.mesh.updateUVs();
}
this.linkedMeshes.length = 0;
// Events.
if (root.events) {
for (let eventName in root.events) {
let eventMap = root.events[eventName];
let data = new EventData(eventName);
data.intValue = this.getValue(eventMap, "int", 0);
data.floatValue = this.getValue(eventMap, "float", 0);
data.stringValue = this.getValue(eventMap, "string", null);
skeletonData.events.push(data);
}
}
// Animations.
if (root.animations) {
for (let animationName in root.animations) {
let animationMap = root.animations[animationName];
this.readAnimation(animationMap, animationName, skeletonData);
}
}
return skeletonData;
}
readAttachment (map: any, skin: Skin, slotIndex: number, name: string): Attachment {
let scale = this.scale;
name = this.getValue(map, "name", name);
let type = this.getValue(map, "type", "region");
switch (type) {
case "region": {
let path = this.getValue(map, "path", name);
let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
if (region == null) return null;
region.path = path;
region.x = this.getValue(map, "x", 0) * scale;
region.y = this.getValue(map, "y", 0) * scale;
region.scaleX = this.getValue(map, "scaleX", 1);
region.scaleY = this.getValue(map, "scaleY", 1);
region.rotation = this.getValue(map, "rotation", 0);
region.width = map.width * scale;
region.height = map.height * scale;
let color: string = this.getValue(map, "color", null);
if (color != null) region.color.setFromString(color);
region.updateOffset();
return region;
}
case "boundingbox": {
let box = this.attachmentLoader.newBoundingBoxAttachment(skin, name);
if (box == null) return null;
this.readVertices(map, box, map.vertexCount << 1);
return box;
}
case "mesh":
case "linkedmesh": {
let path = this.getValue(map, "path", name);
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.path = path;
let color = this.getValue(map, "color", null);
if (color != null) mesh.color.setFromString(color);
let parent: string = this.getValue(map, "parent", null);
if (parent != null) {
mesh.inheritDeform = this.getValue(map, "deform", true);
this.linkedMeshes.push(new LinkedMesh(mesh, <string> this.getValue(map, "skin", null), slotIndex, parent));
return mesh;
}
let uvs: Array<number> = map.uvs;
this.readVertices(map, mesh, uvs.length);
mesh.triangles = map.triangles;
mesh.regionUVs = uvs;
mesh.updateUVs();
mesh.hullLength = this.getValue(map, "hull", 0) * 2;
return mesh;
}
case "path": {
let path = this.attachmentLoader.newPathAttachment(skin, name);
if (path == null) return null;
path.closed = this.getValue(map, "closed", false);
path.constantSpeed = this.getValue(map, "constantSpeed", true);
let vertexCount = map.vertexCount;
this.readVertices(map, path, vertexCount << 1);
let lengths: Array<number> = Utils.newArray(vertexCount / 3, 0);
for (let i = 0; i < map.lengths.length; i++)
lengths[i++] = map.lengths[i] * scale;
path.lengths = lengths;
return path;
}
}
return null;
}
readVertices (map: any, attachment: VertexAttachment, verticesLength: number) {
let scale = this.scale;
attachment.worldVerticesLength = verticesLength;
let vertices: Array<number> = map.vertices;
if (verticesLength == vertices.length) {
if (scale != 1) {
for (let i = 0, n = vertices.length; i < n; i++)
vertices[i] *= scale;
}
attachment.vertices = Utils.toFloatArray(vertices);
return;
}
let weights = new Array<number>();
let bones = new Array<number>();
for (let i = 0, n = vertices.length; i < n;) {
let boneCount = vertices[i++];
bones.push(boneCount);
for (let nn = i + boneCount * 4; i < nn; i += 4) {
bones.push(vertices[i]);
weights.push(vertices[i + 1] * scale);
weights.push(vertices[i + 2] * scale);
weights.push(vertices[i + 3]);
}
}
attachment.bones = bones;
attachment.vertices = Utils.toFloatArray(weights);
}
readAnimation (map: any, name: string, skeletonData: SkeletonData) {
let scale = this.scale;
let timelines = new Array<Timeline>();
let duration = 0;
// Slot timelines.
if (map.slots) {
for (let slotName in map.slots) {
let slotMap = map.slots[slotName];
let slotIndex = skeletonData.findSlotIndex(slotName);
if (slotIndex == -1) throw new Error("Slot not found: " + slotName);
for (let timelineName in slotMap) {
let timelineMap = slotMap[timelineName];
if (timelineName == "color") {
let timeline = new ColorTimeline(timelineMap.length);
timeline.slotIndex = slotIndex;
let frameIndex = 0;
for (let i = 0; i < timelineMap.length; i++) {
let valueMap = timelineMap[i];
let color = new Color();
color.setFromString(valueMap.color);
timeline.setFrame(frameIndex, valueMap.time, color.r, color.g, color.b, color.a);
this.readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.push(timeline);
duration = Math.max(duration, timeline.frames[(timeline.getFrameCount() - 1) * ColorTimeline.ENTRIES]);
} else if (timelineName = "attachment") {
let timeline = new AttachmentTimeline(timelineMap.length);
timeline.slotIndex = slotIndex;
let frameIndex = 0;
for (let i = 0; i < timelineMap.length; i++) {
let valueMap = timelineMap[i];
timeline.setFrame(frameIndex++, valueMap.time, valueMap.name);
}
timelines.push(timeline);
duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);
} else
throw new Error("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
}
}
}
// Bone timelines.
if (map.bones) {
for (let boneName in map.bones) {
let boneMap = map.bones[boneName];
let boneIndex = skeletonData.findBoneIndex(boneName);
if (boneIndex == -1) throw new Error("Bone not found: " + boneName);
for (let timelineName in boneMap) {
let timelineMap = boneMap[timelineName];
if (timelineName === "rotate") {
let timeline = new RotateTimeline(timelineMap.length);
timeline.boneIndex = boneIndex;
let frameIndex = 0;
for (let i = 0; i < timelineMap.length; i++) {
let valueMap = timelineMap[i];
timeline.setFrame(frameIndex, valueMap.time, valueMap.angle);
this.readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.push(timeline);
duration = Math.max(duration, timeline.frames[(timeline.getFrameCount() - 1) * RotateTimeline.ENTRIES]);
} else if (timelineName === "translate" || timelineName === "scale" || timelineName === "shear") {
let timeline: TranslateTimeline = null;
let timelineScale = 1;
if (timelineName === "scale")
timeline = new ScaleTimeline(timelineMap.length);
else if (timelineName === "shear")
timeline = new ShearTimeline(timelineMap.length);
else {
timeline = new TranslateTimeline(timelineMap.length);
timelineScale = scale;
}
timeline.boneIndex = boneIndex;
let frameIndex = 0;
for (let i = 0; i < timelineMap.length; i++) {
let valueMap = timelineMap[i];
let x = this.getValue(valueMap, "x", 0), y = this.getValue(valueMap, "y", 0);
timeline.setFrame(frameIndex, valueMap.time, x * timelineScale, y * timelineScale);
this.readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.push(timeline);
duration = Math.max(duration, timeline.frames[(timeline.getFrameCount() - 1) * TranslateTimeline.ENTRIES]);
} else
throw new Error("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
}
}
}
// IK constraint timelines.
if (map.ik) {
for (let constraintName in map.ik) {
let constraintMap = map.ik[constraintName];
let constraint = skeletonData.findIkConstraint(constraintName);
let timeline = new IkConstraintTimeline(constraintMap.length);
timeline.ikConstraintIndex = skeletonData.ikConstraints.indexOf(constraint);
let frameIndex = 0;
for (let i = 0; i < constraintMap.length; i++) {
let valueMap = constraintMap[i];
timeline.setFrame(frameIndex, valueMap.time, this.getValue(valueMap, "mix", 1),
this.getValue(valueMap, "bendPositive", true) ? 1 : -1);
this.readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.push(timeline);
duration = Math.max(duration, timeline.frames[(timeline.getFrameCount() - 1) * IkConstraintTimeline.ENTRIES]);
}
}
// Transform constraint timelines.
if (map.transform) {
for (let constraintName in map.transform) {
let constraintMap = map.transform[constraintName];
let constraint = skeletonData.findTransformConstraint(constraintName);
let timeline = new TransformConstraintTimeline(constraintMap.length);
timeline.transformConstraintIndex = skeletonData.transformConstraints.indexOf(constraint);
let frameIndex = 0;
for (let i = 0; i < constraintMap.length; i++) {
let valueMap = constraintMap[i];
timeline.setFrame(frameIndex, valueMap.time, this.getValue(valueMap, "rotateMix", 1),
this.getValue(valueMap, "translateMix", 1), this.getValue(valueMap, "scaleMix", 1), this.getValue(valueMap, "shearMix", 1));
this.readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.push(timeline);
duration = Math.max(duration,
timeline.frames[(timeline.getFrameCount() - 1) * TransformConstraintTimeline.ENTRIES]);
}
}
// Path constraint timelines.
if (map.paths) {
for (let constraintName in map.paths) {
let constraintMap = map.paths[constraintName];
let index = skeletonData.findPathConstraintIndex(constraintName);
if (index == -1) throw new Error("Path constraint not found: " + constraintName);
let data = skeletonData.pathConstraints[index];
for (let timelineName in constraintMap) {
let timelineMap = constraintMap[timelineName];
if (timelineName === "position" || timelineName === "spacing") {
let timeline: PathConstraintPositionTimeline = null;
let timelineScale = 1;
if (timelineName === "spacing") {
timeline = new PathConstraintSpacingTimeline(timelineMap.length);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
} else {
timeline = new PathConstraintPositionTimeline(timelineMap.length);
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
}
timeline.pathConstraintIndex = index;
let frameIndex = 0;
for (let i = 0; i < timelineMap.length; i++) {
let valueMap = timelineMap[i];
timeline.setFrame(frameIndex, valueMap.time, this.getValue(valueMap, timelineName, 0) * timelineScale);
this.readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.push(timeline);
duration = Math.max(duration,
timeline.frames[(timeline.getFrameCount() - 1) * PathConstraintPositionTimeline.ENTRIES]);
} else if (timelineName === "mix") {
let timeline = new PathConstraintMixTimeline(timelineMap.length);
timeline.pathConstraintIndex = index;
let frameIndex = 0;
for (let i = 0; i < timelineMap.length; i++) {
let valueMap = timelineMap[i];
timeline.setFrame(frameIndex, valueMap.time, this.getValue(valueMap, "rotateMix", 1),
this.getValue(valueMap, "translateMix", 1));
this.readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.push(timeline);
duration = Math.max(duration,
timeline.frames[(timeline.getFrameCount() - 1) * PathConstraintMixTimeline.ENTRIES]);
}
}
}
}
// Deform timelines.
if (map.deform) {
for (let deformName in map.deform) {
let deformMap = map.deform[deformName];
let skin = skeletonData.findSkin(deformName);
if (skin == null) throw new Error("Skin not found: " + deformName);
for (let slotName in deformMap) {
let slotMap = deformMap[slotName];
let slotIndex = skeletonData.findSlotIndex(slotName);
if (slotIndex == -1) throw new Error("Slot not found: " + slotMap.name);
for (let timelineName in slotMap) {
let timelineMap = slotMap[timelineName];
let attachment = <VertexAttachment>skin.getAttachment(slotIndex, timelineName);
if (attachment == null) throw new Error("Deform attachment not found: " + timelineMap.name);
let weighted = attachment.bones != null;
let vertices = attachment.vertices;
let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
let timeline = new DeformTimeline(timelineMap.length);
timeline.slotIndex = slotIndex;
timeline.attachment = attachment;
let frameIndex = 0;
for (let j = 0; j < timelineMap.length; j++) {
let valueMap = timelineMap[j];
let deform: ArrayLike<number>;
let verticesValue: Array<Number> = this.getValue(valueMap, "vertices", null);
if (verticesValue == null)
deform = weighted ? Utils.newFloatArray(deformLength) : vertices;
else {
deform = Utils.newFloatArray(deformLength);
let start = <number>this.getValue(valueMap, "offset", 0);
Utils.arrayCopy(verticesValue, 0, deform, start, verticesValue.length);
if (scale != 1) {
for (let i = start, n = i + verticesValue.length; i < n; i++)
deform[i] *= scale;
}
if (!weighted) {
for (let i = 0; i < deformLength; i++)
deform[i] += vertices[i];
}
}
timeline.setFrame(frameIndex, valueMap.time, deform);
this.readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.push(timeline);
duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);
}
}
}
}
// Draw order timeline.
let drawOrderNode = map.drawOrder;
if (drawOrderNode == null) drawOrderNode = map.draworder;
if (drawOrderNode != null) {
let timeline = new DrawOrderTimeline(drawOrderNode.length);
let slotCount = skeletonData.slots.length;
let frameIndex = 0;
for (let j = 0; j < drawOrderNode.length; j++) {
let drawOrderMap = drawOrderNode[j];
let drawOrder: Array<number> = null;
let offsets = this.getValue(drawOrderMap, "offsets", null);
if (offsets != null) {
drawOrder = Utils.newArray<number>(slotCount, -1);
let unchanged = Utils.newArray<number>(slotCount - offsets.length, 0);
let originalIndex = 0, unchangedIndex = 0;
for (let i = 0; i < offsets.length; i++) {
let offsetMap = offsets[i];
let slotIndex = skeletonData.findSlotIndex(offsetMap.slot);
if (slotIndex == -1) throw new Error("Slot not found: " + offsetMap.slot);
// Collect unchanged items.
while (originalIndex != slotIndex)
unchanged[unchangedIndex++] = originalIndex++;
// Set changed items.
drawOrder[originalIndex + offsetMap.offset] = originalIndex++;
}
// Collect remaining unchanged items.
while (originalIndex < slotCount)
unchanged[unchangedIndex++] = originalIndex++;
// Fill in unchanged items.
for (let i = slotCount - 1; i >= 0; i--)
if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
}
timeline.setFrame(frameIndex++, drawOrderMap.time, drawOrder);
}
timelines.push(timeline);
duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);
}
// Event timeline.
if (map.events) {
let timeline = new EventTimeline(map.events.length);
let frameIndex = 0;
for (let i = 0; i < map.events.length; i++) {
let eventMap = map.events[i];
let eventData = skeletonData.findEvent(eventMap.name);
if (eventData == null) throw new Error("Event not found: " + eventMap.name);
let event = new Event(eventMap.time, eventData);
event.intValue = this.getValue(eventMap, "int", eventData.intValue);
event.floatValue = this.getValue(eventMap, "float", eventData.floatValue);
event.stringValue = this.getValue(eventMap, "string", eventData.stringValue);
timeline.setFrame(frameIndex++, event);
}
timelines.push(timeline);
duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);
}
if (isNaN(duration)) {
throw new Error("Error while parsing animation, duration is NaN");
}
skeletonData.animations.push(new Animation(name, timelines, duration));
}
readCurve (map: any, timeline: CurveTimeline, frameIndex: number) {
if (!map.curve) return;
if (map.curve === "stepped")
timeline.setStepped(frameIndex);
else if (Object.prototype.toString.call(map.curve) === '[object Array]') {
let curve: Array<number> = map.curve;
timeline.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]);
}
}
getValue (map: any, prop: string, defaultValue: any) {
return map[prop] !== undefined ? map[prop] : defaultValue;
}
static blendModeFromString (str: string) {
str = str.toLowerCase();
if (str == "normal") return BlendMode.Normal;
if (str == "additive") return BlendMode.Additive;
if (str == "multiply") return BlendMode.Multiply;
if (str == "screen") return BlendMode.Screen;
throw new Error(`Unknown blend mode: ${str}`);
}
static positionModeFromString (str: string) {
str = str.toLowerCase();
if (str == "fixed") return PositionMode.Fixed;
if (str == "percent") return PositionMode.Percent;
throw new Error(`Unknown position mode: ${str}`);
}
static spacingModeFromString (str: string) {
str = str.toLowerCase();
if (str == "length") return SpacingMode.Length;
if (str == "fixed") return SpacingMode.Fixed;
if (str == "percent") return SpacingMode.Percent;
throw new Error(`Unknown position mode: ${str}`);
}
static rotateModeFromString (str: string) {
str = str.toLowerCase();
if (str == "tangent") return RotateMode.Tangent;
if (str == "chain") return RotateMode.Chain;
if (str == "chainscale") return RotateMode.ChainScale;
throw new Error(`Unknown rotate mode: ${str}`);
}
}
class LinkedMesh {
parent: string; skin: string;
slotIndex: number;
mesh: MeshAttachment;
constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string) {
this.mesh = mesh;
this.skin = skin;
this.slotIndex = slotIndex;
this.parent = parent;
}
}
}

77
spine-ts/core/src/Skin.ts Normal file
View File

@ -0,0 +1,77 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 Skin {
name: string;
attachments = new Array<Map<Attachment>>();
constructor (name: string) {
if (name == null) throw new Error("name cannot be null.");
this.name = name;
}
addAttachment (slotIndex: number, name: string, attachment: Attachment) {
if (attachment == null) throw new Error("attachment cannot be null.");
let attachments = this.attachments;
if (slotIndex >= attachments.length) attachments.length = slotIndex + 1;
if (!attachments[slotIndex]) attachments[slotIndex] = { };
attachments[slotIndex][name] = attachment;
}
/** @return May be null. */
getAttachment (slotIndex: number, name: string): Attachment {
let dictionary = this.attachments[slotIndex];
return dictionary ? dictionary[name] : null;
}
/** Attach each attachment in this skin if the corresponding attachment in the old skin is currently attached. */
attachAll (skeleton: Skeleton, oldSkin: Skin) {
let slotIndex = 0;
for (let i = 0; i < skeleton.slots.length; i++) {
let slot = skeleton.slots[i];
let slotAttachment = slot.getAttachment();
if (slotAttachment && slotIndex < oldSkin.attachments.length) {
let dictionary = oldSkin.attachments[slotIndex];
for (let key in dictionary) {
let skinAttachment:Attachment = dictionary[key];
if (slotAttachment == skinAttachment) {
let attachment = this.getAttachment(slotIndex, name);
if (attachment != null) slot.setAttachment(attachment);
break;
}
}
}
slotIndex++;
}
}
}
}

83
spine-ts/core/src/Slot.ts Normal file
View File

@ -0,0 +1,83 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 Slot {
data: SlotData;
bone: Bone;
color: Color;
private attachment: Attachment;
private attachmentTime: number;
attachmentVertices = new Array<number>();
constructor (data: SlotData, bone: Bone) {
if (data == null) throw new Error("data cannot be null.");
if (bone == null) throw new Error("bone cannot be null.");
this.data = data;
this.bone = bone;
this.color = new Color();
this.setToSetupPose();
}
/** @return May be null. */
getAttachment (): Attachment {
return this.attachment;
}
/** Sets the attachment and if it changed, resets {@link #getAttachmentTime()} and clears {@link #getAttachmentVertices()}.
* @param attachment May be null. */
setAttachment (attachment: Attachment) {
if (this.attachment == attachment) return;
this.attachment = attachment;
this.attachmentTime = this.bone.skeleton.time;
this.attachmentVertices.length = 0;
}
setAttachmentTime (time: number) {
this.attachmentTime = this.bone.skeleton.time - time;
}
/** Returns the time since the attachment was set. */
getAttachmentTime (): number {
return this.bone.skeleton.time - this.attachmentTime;
}
setToSetupPose () {
this.color.setFromColor(this.data.color);
if (this.data.attachmentName == null)
this.attachment = null;
else {
this.attachment = null;
this.setAttachment(this.bone.skeleton.getAttachment(this.data.index, this.data.attachmentName));
}
}
}
}

View File

@ -0,0 +1,50 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 SlotData {
index: number;
name: string;
boneData: BoneData;
color = new Color(1, 1, 1, 1);
attachmentName: string;
blendMode: BlendMode;
constructor (index: number, name: string, boneData: BoneData) {
if (index < 0) throw new Error("index must be >= 0.");
if (name == null) throw new Error("name cannot be null.");
if (boneData == null) throw new Error("boneData cannot be null.");
this.index = index;
this.name = name;
this.boneData = boneData;
}
}
}

View File

@ -0,0 +1,117 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 TransformConstraint implements Updatable {
data: TransformConstraintData;
bones: Array<Bone>;
target: Bone;
rotateMix = 0; translateMix = 0; scaleMix = 0; shearMix = 0;
temp = new Vector2();
constructor (data: TransformConstraintData, skeleton: Skeleton) {
if (data == null) throw new Error("data cannot be null.");
if (skeleton == null) throw new Error("skeleton cannot be null.");
this.data = data;
this.rotateMix = data.rotateMix;
this.translateMix = data.translateMix;
this.scaleMix = data.scaleMix;
this.shearMix = data.shearMix;
this.bones = new Array<Bone>();
for (let i = 0; i < data.bones.length; i++)
this.bones.push(skeleton.findBone(data.bones[i].name));
this.target = skeleton.findBone(data.target.name);
}
apply () {
this.update();
}
update () {
let rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
let target = this.target;
let ta = target.a, tb = target.b, tc = target.c, td = target.d;
let bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++) {
let bone = bones[i];
if (rotateMix > 0) {
let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
let r = Math.atan2(tc, ta) - Math.atan2(c, a) + this.data.offsetRotation * MathUtils.degRad;
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI)
r += MathUtils.PI2;
r *= rotateMix;
let cos = Math.cos(r), sin = Math.sin(r);
bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d;
}
if (translateMix > 0) {
let temp = this.temp;
target.localToWorld(temp.set(this.data.offsetX, this.data.offsetY));
bone.worldX += (temp.x - bone.worldX) * translateMix;
bone.worldY += (temp.y - bone.worldY) * translateMix;
}
if (scaleMix > 0) {
let bs = Math.sqrt(bone.a * bone.a + bone.c * bone.c);
let ts = Math.sqrt(ta * ta + tc * tc);
let s = bs > 0.00001 ? (bs + (ts - bs + this.data.offsetScaleX) * scaleMix) / bs : 0;
bone.a *= s;
bone.c *= s;
bs = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
ts = Math.sqrt(tb * tb + td * td);
s = bs > 0.00001 ? (bs + (ts - bs + this.data.offsetScaleY) * scaleMix) / bs : 0;
bone.b *= s;
bone.d *= s;
}
if (shearMix > 0) {
let b = bone.b, d = bone.d;
let by = Math.atan2(d, b);
let r = Math.atan2(td, tb) - Math.atan2(tc, ta) - (by - Math.atan2(bone.c, bone.a));
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI)
r += MathUtils.PI2;
r = by + (r + this.data.offsetShearY * MathUtils.degRad) * shearMix;
let s = Math.sqrt(b * b + d * d);
bone.b = Math.cos(r) * s;
bone.d = Math.sin(r) * s;
}
}
}
}
}

View File

@ -0,0 +1,45 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 TransformConstraintData {
name: string;
bones = new Array<BoneData>();
target: BoneData;
rotateMix = 0; translateMix = 0; scaleMix = 0; shearMix = 0;
offsetRotation = 0; offsetX = 0; offsetY = 0; offsetScaleX = 0; offsetScaleY = 0; offsetShearY = 0;
constructor (name: string) {
if (name == null) throw new Error("name cannot be null.");
this.name = name;
}
}
}

View File

@ -0,0 +1,36 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 interface Updatable {
update(): void;
}
}

198
spine-ts/core/src/Utils.ts Normal file
View File

@ -0,0 +1,198 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 interface Map<T> {
[key: string]: T;
}
export interface Disposable {
dispose (): void;
}
export class Color {
constructor (public r: number = 0, public g: number = 0, public b: number = 0, public a: number = 0) {
}
set (r: number, g: number, b: number, a: number) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
this.clamp();
}
setFromColor (c: Color) {
this.r = c.r;
this.g = c.g;
this.b = c.b;
this.a = c.a;
}
setFromString (hex: string) {
hex = hex.charAt(0) == '#' ? hex.substr(1) : hex;
this.r = parseInt(hex.substr(0, 2), 16) / 255.0;
this.g = parseInt(hex.substr(2, 2), 16) / 255.0;
this.b = parseInt(hex.substr(4, 2), 16) / 255.0;
this.a = (hex.length != 8 ? 255 : parseInt(hex.substr(6, 2), 16)) / 255.0;
}
add (r: number, g: number, b: number, a: number) {
this.r += r;
this.g += g;
this.b += b;
this.a += a;
this.clamp();
}
clamp () {
if (this.r < 0) this.r = 0;
else if (this.r > 1) this.r = 1;
if (this.g < 0) this.g = 0;
else if (this.g > 1) this.g = 1;
if (this.b < 0) this.b = 0;
else if (this.b > 1) this.b = 1;
if (this.a < 0) this.a = 0;
else if (this.a > 1) this.a = 1;
return this;
}
}
export class MathUtils {
static PI = 3.1415927;
static PI2 = MathUtils.PI * 2;
static radiansToDegrees = 180 / MathUtils.PI;
static radDeg = MathUtils.radiansToDegrees;
static degreesToRadians = MathUtils.PI / 180;
static degRad = MathUtils.degreesToRadians;
static clamp (value: number, min: number, max: number) {
if (value < min) return min;
if (value > max) return max;
return value;
}
static cosDeg (degrees: number) {
return Math.cos(degrees * MathUtils.degRad);
}
static sinDeg (degrees: number) {
return Math.sin(degrees * MathUtils.degRad);
}
static signum (value: number): number {
return value >= 0 ? 1 : -1;
}
static toInt (x: number) {
return x > 0 ? Math.floor(x) : Math.ceil(x);
}
}
export class Utils {
static SUPPORTS_TYPED_ARRAYS = typeof(Float32Array) !== "undefined";
static arrayCopy<T> (source: ArrayLike<T>, sourceStart: number, dest: ArrayLike<T>, destStart: number, numElements: number) {
for (let i = sourceStart, j = destStart; i < sourceStart + numElements; i++, j++) {
dest[j] = source[i];
}
}
static setArraySize<T> (array: Array<T>, size: number, value: any = 0): Array<T> {
let oldSize = array.length;
if (oldSize == size) return array;
array.length = size;
if (oldSize < size) {
for (let i = oldSize; i < size; i++) array[i] = value;
}
return array;
}
static newArray<T> (size: number, defaultValue: T): Array<T> {
let array = new Array<T>(size);
for (let i = 0; i < size; i++) array[i] = defaultValue;
return array;
}
static newFloatArray (size: number): ArrayLike<number> {
if (Utils.SUPPORTS_TYPED_ARRAYS) {
return new Float32Array(size)
} else {
let array = new Array<number>(size);
for (let i = 0; i < array.length; i++) array[i] = 0;
return array;
}
}
static toFloatArray (array: Array<number>) {
return Utils.SUPPORTS_TYPED_ARRAYS ? new Float32Array(array) : array;
}
}
export class Pool<T> {
private _items = new Array<T>(16);
private _instantiator: () => T;
constructor (instantiator: () => T) {
this._instantiator = instantiator;
}
obtain () {
return this._items.length > 0 ? this._items.pop() : this._instantiator();
}
free (item: T) {
this._items.push(item);
}
freeAll (items: ArrayLike<T>) {
for (let i = 0; i < items.length; i++) this._items[i] = items[i];
}
clear () {
this._items.length = 0;
}
}
export class Vector2 {
constructor (public x = 0, public y = 0) {
}
set (x: number, y: number): Vector2 {
this.x = x;
this.y = y;
return this;
}
}
}

View File

@ -0,0 +1,124 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 abstract class Attachment {
name: string;
constructor (name: string) {
if (name == null) throw new Error("name cannot be null.");
this.name = name;
}
}
export abstract class VertexAttachment extends Attachment {
bones: Array<number>;
vertices: ArrayLike<number>;
worldVerticesLength = 0;
constructor (name: string) {
super(name);
}
computeWorldVertices (slot: Slot, worldVertices: ArrayLike<number>) {
this.computeWorldVerticesWith(slot, 0, this.worldVerticesLength, worldVertices, 0);
}
/** Transforms local vertices to world coordinates.
* @param start The index of the first local vertex value to transform. Each vertex has 2 values, x and y.
* @param count The number of world vertex values to output. Must be <= {@link #getWorldVerticesLength()} - start.
* @param worldVertices The output world vertices. Must have a length >= offset + count.
* @param offset The worldVertices index to begin writing values. */
computeWorldVerticesWith (slot: Slot, start: number, count: number, worldVertices: ArrayLike<number>, offset: number) {
count += offset;
let skeleton = slot.bone.skeleton;
let x = skeleton.x, y = skeleton.y;
let deformArray = slot.attachmentVertices;
let vertices = this.vertices;
let bones = this.bones;
if (bones == null) {
if (deformArray.length > 0) vertices = deformArray;
let bone = slot.bone;
x += bone.worldX;
y += bone.worldY;
let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
for (let v = start, w = offset; w < count; v += 2, w += 2) {
let vx = vertices[v], vy = vertices[v + 1];
worldVertices[w] = vx * a + vy * b + x;
worldVertices[w + 1] = vx * c + vy * d + y;
}
return;
}
let v = 0, skip = 0;
for (let i = 0; i < start; i += 2) {
let n = bones[v];
v += n + 1;
skip += n;
}
let skeletonBones = skeleton.bones;
if (deformArray.length == 0) {
for (let w = offset, b = skip * 3; w < count; w += 2) {
let wx = x, wy = y;
let n = bones[v++];
n += v;
for (; v < n; v++, b += 3) {
let bone = skeletonBones[bones[v]];
let vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
} else {
let deform = deformArray;
for (let w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) {
let wx = x, wy = y;
let n = bones[v++];
n += v;
for (; v < n; v++, b += 3, f += 2) {
let bone = skeletonBones[bones[v]];
let vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
}
}
/** Returns true if a deform originally applied to the specified attachment should be applied to this attachment. */
applyDeform (sourceAttachment: VertexAttachment) {
return this == sourceAttachment;
}
}
}

View File

@ -0,0 +1,46 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 interface AttachmentLoader {
/** @return May be null to not load an attachment. */
newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment;
/** @return May be null to not load an attachment. */
newMeshAttachment (skin: Skin, name: string, path: string) : MeshAttachment;
/** @return May be null to not load an attachment. */
newBoundingBoxAttachment (skin: Skin, name: string) : BoundingBoxAttachment;
/** @return May be null to not load an attachment */
newPathAttachment(skin: Skin, name: string): PathAttachment;
}
}

View File

@ -0,0 +1,36 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 enum AttachmentType {
Region, BoundingBox, Mesh, LinkedMesh, Path
}
}

View File

@ -0,0 +1,38 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 BoundingBoxAttachment extends VertexAttachment {
constructor (name: string) {
super(name);
}
}
}

View File

@ -0,0 +1,173 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 MeshAttachment extends VertexAttachment {
region: TextureRegion;
path: string;
regionUVs: ArrayLike<number>; worldVertices: ArrayLike<number>;
triangles: Array<number>;
color = new Color(1, 1, 1, 1);
hullLength: number;
private _parentMesh: MeshAttachment;
inheritDeform = false;
tempColor = new Color(0, 0, 0, 0);
constructor (name: string) {
super(name);
}
updateUVs () {
let regionUVs = this.regionUVs;
let verticesLength = regionUVs.length;
let worldVerticesLength = (verticesLength >> 1) * 8;
if (this.worldVertices == null || this.worldVertices.length != worldVerticesLength)
this.worldVertices = Utils.newFloatArray(worldVerticesLength);
let u = 0, v = 0, width = 0, height = 0;
if (this.region == null) {
u = v = 0;
width = height = 1;
} else {
u = this.region.u;
v = this.region.v;
width = this.region.u2 - u;
height = this.region.v2 - v;
}
if (this.region.rotate) {
for (let i = 0, w = 6; i < verticesLength; i += 2, w += 8) {
this.worldVertices[w] = u + regionUVs[i + 1] * width;
this.worldVertices[w + 1] = v + height - regionUVs[i] * height;
}
} else {
for (let i = 0, w = 6; i < verticesLength; i += 2, w += 8) {
this.worldVertices[w] = u + regionUVs[i] * width;
this.worldVertices[w + 1] = v + regionUVs[i + 1] * height;
}
}
}
/** @return The updated world vertices. */
updateWorldVertices (slot: Slot, premultipliedAlpha: boolean) {
let skeleton = slot.bone.skeleton;
let skeletonColor = skeleton.color, slotColor = slot.color, meshColor = this.color;
let alpha = skeletonColor.a * slotColor.a * meshColor.a;
let multiplier = premultipliedAlpha ? alpha : 1;
let color = this.tempColor;
color.set(skeletonColor.r * slotColor.r * meshColor.r * multiplier,
skeletonColor.g * slotColor.g * meshColor.g * multiplier,
skeletonColor.b * slotColor.b * meshColor.b * multiplier,
alpha);
let x = skeleton.x, y = skeleton.y;
let deformArray = slot.attachmentVertices;
let vertices = this.vertices, worldVertices = this.worldVertices;
let bones = this.bones;
if (bones == null) {
let verticesLength = vertices.length;
if (deformArray.length > 0) vertices = deformArray;
let bone = slot.bone;
x += bone.worldX;
y += bone.worldY;
let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
for (let v = 0, w = 0; v < verticesLength; v += 2, w += 8) {
let vx = vertices[v], vy = vertices[v + 1];
worldVertices[w] = vx * a + vy * b + x;
worldVertices[w + 1] = vx * c + vy * d + y;
worldVertices[w + 2] = color.r;
worldVertices[w + 3] = color.g;
worldVertices[w + 4] = color.b;
worldVertices[w + 5] = color.a;
}
return worldVertices;
}
let skeletonBones = skeleton.bones;
if (deformArray.length == 0) {
for (let w = 0, v = 0, b = 0, n = bones.length; v < n; w += 8) {
let wx = x, wy = y;
let nn = bones[v++] + v;
for (; v < nn; v++, b += 3) {
let bone = skeletonBones[bones[v]];
let vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
worldVertices[w + 2] = color.r;
worldVertices[w + 3] = color.g;
worldVertices[w + 4] = color.b;
worldVertices[w + 5] = color.a;
}
} else {
let deform = deformArray;
for (let w = 0, v = 0, b = 0, f = 0, n = bones.length; v < n; w += 8) {
let wx = x, wy = y;
let nn = bones[v++] + v;
for (; v < nn; v++, b += 3, f += 2) {
let bone = skeletonBones[bones[v]];
let vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
worldVertices[w + 2] = color.r;
worldVertices[w + 3] = color.g;
worldVertices[w + 4] = color.b;
worldVertices[w + 5] = color.a;
}
}
return worldVertices;
}
applyDeform (sourceAttachment: VertexAttachment): boolean {
return this == sourceAttachment || (this.inheritDeform && this._parentMesh == sourceAttachment);
}
getParentMesh () {
return this._parentMesh;
}
/** @param parentMesh May be null. */
setParentMesh (parentMesh: MeshAttachment) {
this._parentMesh = parentMesh;
if (parentMesh != null) {
this.bones = parentMesh.bones;
this.vertices = parentMesh.vertices;
this.regionUVs = parentMesh.regionUVs;
this.triangles = parentMesh.triangles;
this.hullLength = parentMesh.hullLength;
}
}
}
}

View File

@ -0,0 +1,41 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 PathAttachment extends VertexAttachment {
lengths: Array<number>;
closed = false; constantSpeed = false;
constructor (name: string) {
super(name);
}
}
}

View File

@ -0,0 +1,206 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 RegionAttachment extends Attachment {
static OX1 = 0;
static OY1 = 1;
static OX2 = 2;
static OY2 = 3;
static OX3 = 4;
static OY3 = 5;
static OX4 = 6;
static OY4 = 7;
static X1 = 0;
static Y1 = 1;
static C1R = 2;
static C1G = 3;
static C1B = 4;
static C1A = 5;
static U1 = 6;
static V1 = 7;
static X2 = 8;
static Y2 = 9;
static C2R = 10;
static C2G = 11;
static C2B = 12;
static C2A = 13;
static U2 = 14;
static V2 = 15;
static X3 = 16;
static Y3 = 17;
static C3R = 18;
static C3G = 19;
static C3B = 20;
static C3A = 21;
static U3 = 22;
static V3 = 23;
static X4 = 24;
static Y4 = 25;
static C4R = 26;
static C4G = 27;
static C4B = 28;
static C4A = 29;
static U4 = 30;
static V4 = 31;
x = 0; y = 0; scaleX = 1; scaleY = 1; rotation = 0; width = 0; height = 0;
color = new Color(1, 1, 1, 1);
path: string;
rendererObject: any;
region: TextureRegion;
offset = Utils.newFloatArray(8);
vertices = Utils.newFloatArray(8 * 4);
tempColor = new Color(1, 1, 1, 1);
constructor (name:string) {
super(name);
}
setRegion (region: TextureRegion) : void {
let vertices = this.vertices;
if (region.rotate) {
vertices[RegionAttachment.U2] = region.u;
vertices[RegionAttachment.V2] = region.v2;
vertices[RegionAttachment.U3] = region.u;
vertices[RegionAttachment.V3] = region.v;
vertices[RegionAttachment.U4] = region.u2;
vertices[RegionAttachment.V4] = region.v;
vertices[RegionAttachment.U1] = region.u2;
vertices[RegionAttachment.V1] = region.v2;
} else {
vertices[RegionAttachment.U1] = region.u;
vertices[RegionAttachment.V1] = region.v2;
vertices[RegionAttachment.U2] = region.u;
vertices[RegionAttachment.V2] = region.v;
vertices[RegionAttachment.U3] = region.u2;
vertices[RegionAttachment.V3] = region.v;
vertices[RegionAttachment.U4] = region.u2;
vertices[RegionAttachment.V4] = region.v2;
}
}
updateOffset () : void {
let regionScaleX = this.width / this.region.originalWidth * this.scaleX;
let regionScaleY = this.height / this.region.originalHeight * this.scaleY;
let localX = -this.width / 2 * this.scaleX + this.region.offsetX * regionScaleX;
let localY = -this.height / 2 * this.scaleY + this.region.offsetY * regionScaleY;
let localX2 = localX + this.region.width * regionScaleX;
let localY2 = localY + this.region.height * regionScaleY;
let radians = this.rotation * Math.PI / 180;
let cos = Math.cos(radians);
let sin = Math.sin(radians);
let localXCos = localX * cos + this.x;
let localXSin = localX * sin;
let localYCos = localY * cos + this.y;
let localYSin = localY * sin;
let localX2Cos = localX2 * cos + this.x;
let localX2Sin = localX2 * sin;
let localY2Cos = localY2 * cos + this.y;
let localY2Sin = localY2 * sin;
let offset = this.offset;
offset[RegionAttachment.OX1] = localXCos - localYSin;
offset[RegionAttachment.OY1] = localYCos + localXSin;
offset[RegionAttachment.OX2] = localXCos - localY2Sin;
offset[RegionAttachment.OY2] = localY2Cos + localXSin;
offset[RegionAttachment.OX3] = localX2Cos - localY2Sin;
offset[RegionAttachment.OY3] = localY2Cos + localX2Sin;
offset[RegionAttachment.OX4] = localX2Cos - localYSin;
offset[RegionAttachment.OY4] = localYCos + localX2Sin;
}
updateWorldVertices (slot: Slot, premultipliedAlpha: boolean) {
let skeleton = slot.bone.skeleton;
let skeletonColor = skeleton.color;
let slotColor = slot.color;
let regionColor = this.color;
let alpha = skeletonColor.a * slotColor.a * regionColor.a;
let multiplier = premultipliedAlpha ? alpha : 1;
let color = this.tempColor;
color.set(skeletonColor.r * slotColor.r * regionColor.r * multiplier,
skeletonColor.g * slotColor.g * regionColor.g * multiplier,
skeletonColor.b * slotColor.b * regionColor.b * multiplier,
alpha);
let vertices = this.vertices;
let offset = this.offset;
let bone = slot.bone;
let x = skeleton.x + bone.worldX, y = skeleton.y + bone.worldY;
let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
let offsetX = 0, offsetY = 0;
offsetX = offset[RegionAttachment.OX1];
offsetY = offset[RegionAttachment.OY1];
vertices[RegionAttachment.X1] = offsetX * a + offsetY * b + x; // br
vertices[RegionAttachment.Y1] = offsetX * c + offsetY * d + y;
vertices[RegionAttachment.C1R] = color.r;
vertices[RegionAttachment.C1G] = color.g;
vertices[RegionAttachment.C1B] = color.b;
vertices[RegionAttachment.C1A] = color.a;
offsetX = offset[RegionAttachment.OX2];
offsetY = offset[RegionAttachment.OY2];
vertices[RegionAttachment.X2] = offsetX * a + offsetY * b + x; // bl
vertices[RegionAttachment.Y2] = offsetX * c + offsetY * d + y;
vertices[RegionAttachment.C2R] = color.r;
vertices[RegionAttachment.C2G] = color.g;
vertices[RegionAttachment.C2B] = color.b;
vertices[RegionAttachment.C2A] = color.a;
offsetX = offset[RegionAttachment.OX3];
offsetY = offset[RegionAttachment.OY3];
vertices[RegionAttachment.X3] = offsetX * a + offsetY * b + x; // ul
vertices[RegionAttachment.Y3] = offsetX * c + offsetY * d + y;
vertices[RegionAttachment.C3R] = color.r;
vertices[RegionAttachment.C3G] = color.g;
vertices[RegionAttachment.C3B] = color.b;
vertices[RegionAttachment.C3A] = color.a;
offsetX = offset[RegionAttachment.OX4];
offsetY = offset[RegionAttachment.OY4];
vertices[RegionAttachment.X4] = offsetX * a + offsetY * b + x; // ur
vertices[RegionAttachment.Y4] = offsetX * c + offsetY * d + y;
vertices[RegionAttachment.C4R] = color.r;
vertices[RegionAttachment.C4G] = color.g;
vertices[RegionAttachment.C4B] = color.b;
vertices[RegionAttachment.C4A] = color.a;
return vertices;
}
}
}

View File

@ -0,0 +1,42 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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 TextureRegion {
renderObject: any;
u = 0; v = 0;
u2 = 0; v2 = 0;
width = 0; height = 0;
rotate = false;
offsetX = 0; offsetY = 0;
originalWidth = 0; originalHeight = 0;
}
}

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "none",
"noImplicitAny": true,
"removeComments": false,
"preserveConstEnums": true,
"outFile": "build/spine-core.js",
"sourceMap": true,
"declaration": true
},
"include": [
"core/src/**/*"
],
"exclude": [
"webgl/src/*.ts",
"build",
"build/*.d.ts"
]
}

22
spine-ts/tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "none",
"noImplicitAny": true,
"removeComments": false,
"preserveConstEnums": true,
"outFile": "build/spine-all.js",
"sourceMap": true,
"declaration": true
},
"include": [
"core/src/**/*",
"webgl/src/**/*",
"widget/src/**/*"
],
"exclude": [
"build",
"build/*.js",
"build/*.d.ts",
"build/*.js.map"
]
}

View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "none",
"noImplicitAny": true,
"removeComments": false,
"preserveConstEnums": true,
"outFile": "build/spine-webgl.js",
"sourceMap": true,
"declaration": true
},
"include": [
"core/src/**/*",
"webgl/src/**/*"
],
"exclude": [
"build",
"build/*.d.ts",
"build/*.js",
"build/*.js.map"
]
}

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "none",
"noImplicitAny": true,
"removeComments": false,
"preserveConstEnums": true,
"outFile": "build/spine-widget.js",
"sourceMap": true,
"declaration": true
},
"include": [
"core/src/**/*",
"webgl/src/**/*",
"widget/src/**/*"
],
"exclude": [
"build",
"build/*.d.ts",
"build/*.js",
"build/*.js.map"
]
}

View File

@ -0,0 +1,293 @@
goblins-mesh.png
size: 1024,128
format: RGBA8888
filter: Linear,Linear
repeat: none
dagger
rotate: true
xy: 372, 100
size: 26, 108
orig: 26, 108
offset: 0, 0
index: -1
goblin/eyes-closed
rotate: false
xy: 2, 7
size: 34, 12
orig: 34, 12
offset: 0, 0
index: -1
goblin/head
rotate: false
xy: 107, 36
size: 103, 66
orig: 103, 66
offset: 0, 0
index: -1
goblin/left-arm
rotate: false
xy: 901, 56
size: 37, 35
orig: 37, 35
offset: 0, 0
index: -1
goblin/left-foot
rotate: false
xy: 929, 95
size: 65, 31
orig: 65, 31
offset: 0, 0
index: -1
goblin/left-hand
rotate: false
xy: 452, 2
size: 36, 41
orig: 36, 41
offset: 0, 0
index: -1
goblin/left-lower-leg
rotate: true
xy: 713, 93
size: 33, 70
orig: 33, 70
offset: 0, 0
index: -1
goblin/left-shoulder
rotate: false
xy: 610, 44
size: 29, 44
orig: 29, 44
offset: 0, 0
index: -1
goblin/left-upper-leg
rotate: true
xy: 638, 93
size: 33, 73
orig: 33, 73
offset: 0, 0
index: -1
goblin/neck
rotate: false
xy: 490, 2
size: 36, 41
orig: 36, 41
offset: 0, 0
index: -1
goblin/pelvis
rotate: false
xy: 482, 45
size: 62, 43
orig: 62, 43
offset: 0, 0
index: -1
goblin/right-arm
rotate: true
xy: 690, 2
size: 23, 50
orig: 23, 50
offset: 0, 0
index: -1
goblin/right-foot
rotate: false
xy: 771, 58
size: 63, 33
orig: 63, 33
offset: 0, 0
index: -1
goblin/right-hand
rotate: false
xy: 940, 56
size: 36, 37
orig: 36, 37
offset: 0, 0
index: -1
goblin/right-lower-leg
rotate: true
xy: 482, 90
size: 36, 76
orig: 36, 76
offset: 0, 0
index: -1
goblin/right-shoulder
rotate: true
xy: 602, 3
size: 39, 45
orig: 39, 45
offset: 0, 0
index: -1
goblin/right-upper-leg
rotate: true
xy: 641, 57
size: 34, 63
orig: 34, 63
offset: 0, 0
index: -1
goblin/torso
rotate: true
xy: 212, 34
size: 68, 96
orig: 68, 96
offset: 0, 0
index: -1
goblin/undie-straps
rotate: false
xy: 380, 5
size: 55, 19
orig: 55, 19
offset: 0, 0
index: -1
goblin/undies
rotate: false
xy: 174, 5
size: 36, 29
orig: 36, 29
offset: 0, 0
index: -1
goblingirl/eyes-closed
rotate: false
xy: 269, 11
size: 37, 21
orig: 37, 21
offset: 0, 0
index: -1
goblingirl/head
rotate: false
xy: 2, 21
size: 103, 81
orig: 103, 81
offset: 0, 0
index: -1
goblingirl/left-arm
rotate: true
xy: 978, 56
size: 37, 35
orig: 37, 35
offset: 0, 0
index: -1
goblingirl/left-foot
rotate: false
xy: 107, 3
size: 65, 31
orig: 65, 31
offset: 0, 0
index: -1
goblingirl/left-hand
rotate: false
xy: 565, 2
size: 35, 40
orig: 35, 40
offset: 0, 0
index: -1
goblingirl/left-lower-leg
rotate: true
xy: 785, 93
size: 33, 70
orig: 33, 70
offset: 0, 0
index: -1
goblingirl/left-shoulder
rotate: true
xy: 690, 27
size: 28, 46
orig: 28, 46
offset: 0, 0
index: -1
goblingirl/left-upper-leg
rotate: true
xy: 857, 93
size: 33, 70
orig: 33, 70
offset: 0, 0
index: -1
goblingirl/neck
rotate: false
xy: 528, 2
size: 35, 41
orig: 35, 41
offset: 0, 0
index: -1
goblingirl/pelvis
rotate: false
xy: 546, 45
size: 62, 43
orig: 62, 43
offset: 0, 0
index: -1
goblingirl/right-arm
rotate: false
xy: 452, 48
size: 28, 50
orig: 28, 50
offset: 0, 0
index: -1
goblingirl/right-foot
rotate: false
xy: 836, 58
size: 63, 33
orig: 63, 33
offset: 0, 0
index: -1
goblingirl/right-hand
rotate: true
xy: 771, 20
size: 36, 37
orig: 36, 37
offset: 0, 0
index: -1
goblingirl/right-lower-leg
rotate: true
xy: 560, 90
size: 36, 76
orig: 36, 76
offset: 0, 0
index: -1
goblingirl/right-shoulder
rotate: false
xy: 649, 10
size: 39, 45
orig: 39, 45
offset: 0, 0
index: -1
goblingirl/right-upper-leg
rotate: true
xy: 706, 57
size: 34, 63
orig: 34, 63
offset: 0, 0
index: -1
goblingirl/torso
rotate: false
xy: 310, 2
size: 68, 96
orig: 68, 96
offset: 0, 0
index: -1
goblingirl/undie-straps
rotate: false
xy: 212, 13
size: 55, 19
orig: 55, 19
offset: 0, 0
index: -1
goblingirl/undies
rotate: false
xy: 810, 27
size: 36, 29
orig: 36, 29
offset: 0, 0
index: -1
shield
rotate: false
xy: 380, 26
size: 70, 72
orig: 70, 72
offset: 0, 0
index: -1
spear
rotate: true
xy: 2, 104
size: 22, 368
orig: 22, 368
offset: 0, 0
index: -1

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View File

@ -0,0 +1,279 @@
raptor.png
size: 1024,1024
format: RGBA8888
filter: Linear,Linear
repeat: none
back_arm
rotate: true
xy: 140, 191
size: 46, 29
orig: 46, 29
offset: 0, 0
index: -1
back_bracer
rotate: true
xy: 167, 317
size: 39, 28
orig: 39, 28
offset: 0, 0
index: -1
back_hand
rotate: false
xy: 167, 358
size: 36, 34
orig: 36, 34
offset: 0, 0
index: -1
back_knee
rotate: false
xy: 299, 478
size: 49, 67
orig: 49, 67
offset: 0, 0
index: -1
back_thigh
rotate: true
xy: 167, 437
size: 39, 24
orig: 39, 24
offset: 0, 0
index: -1
eyes_closed
rotate: true
xy: 2, 2
size: 47, 45
orig: 47, 45
offset: 0, 0
index: -1
eyes_open
rotate: true
xy: 49, 2
size: 47, 45
orig: 47, 45
offset: 0, 0
index: -1
eyes_surprised
rotate: true
xy: 96, 2
size: 47, 45
orig: 47, 45
offset: 0, 0
index: -1
front_arm
rotate: false
xy: 419, 544
size: 48, 30
orig: 48, 30
offset: 0, 0
index: -1
front_bracer
rotate: false
xy: 880, 695
size: 41, 29
orig: 41, 29
offset: 0, 0
index: -1
front_hand
rotate: true
xy: 167, 394
size: 41, 38
orig: 41, 38
offset: 0, 0
index: -1
front_open_hand
rotate: false
xy: 880, 726
size: 43, 44
orig: 43, 44
offset: 0, 0
index: -1
front_thigh
rotate: false
xy: 360, 545
size: 57, 29
orig: 57, 29
offset: 0, 0
index: -1
gun
rotate: false
xy: 785, 774
size: 107, 103
orig: 107, 103
offset: 0, 0
index: -1
gun_nohand
rotate: false
xy: 614, 703
size: 105, 102
orig: 105, 102
offset: 0, 0
index: -1
head
rotate: false
xy: 2, 137
size: 136, 149
orig: 136, 149
offset: 0, 0
index: -1
lower_leg
rotate: true
xy: 780, 699
size: 73, 98
orig: 73, 98
offset: 0, 0
index: -1
mouth_grind
rotate: false
xy: 469, 544
size: 47, 30
orig: 47, 30
offset: 0, 0
index: -1
mouth_oooo
rotate: true
xy: 894, 772
size: 105, 30
orig: 105, 30
offset: 0, 0
index: -1
mouth_smile
rotate: true
xy: 140, 239
size: 47, 30
orig: 47, 30
offset: 0, 0
index: -1
neck
rotate: true
xy: 538, 577
size: 18, 21
orig: 18, 21
offset: 0, 0
index: -1
raptor_arm_back
rotate: false
xy: 940, 936
size: 82, 86
orig: 82, 86
offset: 0, 0
index: -1
raptor_body
rotate: false
xy: 2, 737
size: 610, 285
orig: 610, 285
offset: 0, 0
index: -1
raptor_front_arm
rotate: true
xy: 195, 464
size: 81, 102
orig: 81, 102
offset: 0, 0
index: -1
raptor_front_leg
rotate: false
xy: 2, 478
size: 191, 257
orig: 191, 257
offset: 0, 0
index: -1
raptor_hindleg_back
rotate: false
xy: 614, 807
size: 169, 215
orig: 169, 215
offset: 0, 0
index: -1
raptor_horn
rotate: false
xy: 360, 655
size: 182, 80
orig: 182, 80
offset: 0, 0
index: -1
raptor_horn_back
rotate: false
xy: 360, 576
size: 176, 77
orig: 176, 77
offset: 0, 0
index: -1
raptor_jaw
rotate: false
xy: 785, 879
size: 153, 143
orig: 153, 143
offset: 0, 0
index: -1
raptor_saddle_noshadow
rotate: false
xy: 2, 288
size: 163, 188
orig: 163, 188
offset: 0, 0
index: -1
raptor_saddle_strap_front
rotate: false
xy: 721, 710
size: 57, 95
orig: 57, 95
offset: 0, 0
index: -1
raptor_saddle_strap_rear
rotate: true
xy: 940, 880
size: 54, 74
orig: 54, 74
offset: 0, 0
index: -1
raptor_saddle_w_shadow
rotate: false
xy: 195, 547
size: 163, 188
orig: 163, 188
offset: 0, 0
index: -1
raptor_tongue
rotate: true
xy: 544, 649
size: 86, 64
orig: 86, 64
offset: 0, 0
index: -1
stirrup_back
rotate: true
xy: 140, 145
size: 44, 35
orig: 44, 35
offset: 0, 0
index: -1
stirrup_front
rotate: false
xy: 538, 597
size: 45, 50
orig: 45, 50
offset: 0, 0
index: -1
stirrup_strap
rotate: false
xy: 350, 497
size: 49, 46
orig: 49, 46
offset: 0, 0
index: -1
torso
rotate: true
xy: 610, 647
size: 54, 91
orig: 54, 91
offset: 0, 0
index: -1
visor
rotate: false
xy: 2, 51
size: 131, 84
orig: 131, 84
offset: 0, 0
index: -1

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

View File

@ -0,0 +1,195 @@
spineboy.png
size: 1024,1024
format: RGBA8888
filter: Linear,Linear
repeat: none
eye_indifferent
rotate: false
xy: 550, 694
size: 93, 89
orig: 93, 89
offset: 0, 0
index: -1
eye_surprised
rotate: false
xy: 834, 856
size: 93, 89
orig: 93, 89
offset: 0, 0
index: -1
front_bracer
rotate: false
xy: 678, 774
size: 58, 80
orig: 58, 80
offset: 0, 0
index: -1
front_fist_closed
rotate: true
xy: 466, 593
size: 75, 82
orig: 75, 82
offset: 0, 0
index: -1
front_fist_open
rotate: false
xy: 550, 605
size: 86, 87
orig: 86, 87
offset: 0, 0
index: -1
front_foot
rotate: false
xy: 550, 785
size: 126, 69
orig: 126, 69
offset: 0, 0
index: -1
front_foot_bend1
rotate: true
xy: 375, 492
size: 128, 70
orig: 128, 70
offset: 0, 0
index: -1
front_foot_bend2
rotate: true
xy: 275, 330
size: 108, 93
orig: 108, 93
offset: 0, 0
index: -1
front_shin
rotate: false
xy: 466, 670
size: 82, 184
orig: 82, 184
offset: 0, 0
index: -1
front_thigh
rotate: false
xy: 214, 208
size: 48, 112
orig: 48, 112
offset: 0, 0
index: -1
front_upper_arm
rotate: false
xy: 214, 109
size: 54, 97
orig: 54, 97
offset: 0, 0
index: -1
goggles
rotate: false
xy: 466, 856
size: 261, 166
orig: 261, 166
offset: 0, 0
index: -1
gun
rotate: false
xy: 2, 117
size: 210, 203
orig: 210, 203
offset: 0, 0
index: -1
head
rotate: false
xy: 2, 322
size: 271, 298
orig: 271, 298
offset: 0, 0
index: -1
mouth_grind
rotate: false
xy: 929, 896
size: 93, 59
orig: 93, 59
offset: 0, 0
index: -1
mouth_oooo
rotate: false
xy: 929, 835
size: 93, 59
orig: 93, 59
offset: 0, 0
index: -1
mouth_smile
rotate: false
xy: 447, 532
size: 93, 59
orig: 93, 59
offset: 0, 0
index: -1
muzzle
rotate: false
xy: 2, 622
size: 462, 400
orig: 462, 400
offset: 0, 0
index: -1
neck
rotate: false
xy: 796, 819
size: 36, 41
orig: 36, 41
offset: 0, 0
index: -1
rear_bracer
rotate: false
xy: 738, 788
size: 56, 72
orig: 56, 72
offset: 0, 0
index: -1
rear_foot
rotate: true
xy: 2, 2
size: 113, 60
orig: 113, 60
offset: 0, 0
index: -1
rear_foot_bend1
rotate: false
xy: 64, 49
size: 117, 66
orig: 117, 66
offset: 0, 0
index: -1
rear_foot_bend2
rotate: false
xy: 729, 862
size: 103, 83
orig: 103, 83
offset: 0, 0
index: -1
rear_shin
rotate: true
xy: 729, 947
size: 75, 178
orig: 75, 178
offset: 0, 0
index: -1
rear_thigh
rotate: true
xy: 909, 957
size: 65, 104
orig: 65, 104
offset: 0, 0
index: -1
rear_upper_arm
rotate: true
xy: 447, 483
size: 47, 87
orig: 47, 87
offset: 0, 0
index: -1
torso
rotate: false
xy: 275, 440
size: 98, 180
orig: 98, 180
offset: 0, 0
index: -1

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

View File

@ -0,0 +1,125 @@
tank.png
size: 2048,1024
format: RGBA8888
filter: Linear,Linear
repeat: none
images/antenna
rotate: true
xy: 1295, 683
size: 22, 303
orig: 22, 303
offset: 0, 0
index: -1
images/cannon
rotate: false
xy: 2, 93
size: 931, 58
orig: 931, 58
offset: 0, 0
index: -1
images/cannonConnector
rotate: true
xy: 1676, 455
size: 112, 135
orig: 112, 135
offset: 0, 0
index: -1
images/guntower
rotate: false
xy: 1295, 707
size: 730, 289
orig: 730, 289
offset: 0, 0
index: -1
images/machinegun
rotate: false
xy: 2, 34
size: 331, 57
orig: 331, 57
offset: 0, 0
index: -1
images/machinegun-mount
rotate: false
xy: 1952, 609
size: 72, 96
orig: 72, 96
offset: 0, 0
index: -1
images/rock
rotate: false
xy: 935, 96
size: 252, 55
orig: 252, 55
offset: 0, 0
index: -1
images/tankBottom
rotate: false
xy: 2, 377
size: 1285, 276
orig: 1285, 276
offset: 0, 0
index: -1
images/tankBottom-shadow
rotate: false
xy: 2, 655
size: 1291, 341
orig: 1291, 341
offset: 0, 0
index: -1
images/tankTop
rotate: false
xy: 2, 153
size: 1407, 222
orig: 1407, 222
offset: 0, 0
index: -1
images/tread
rotate: false
xy: 2, 2
size: 96, 30
orig: 96, 30
offset: 0, 0
index: -1
images/tread-inside
rotate: false
xy: 335, 63
size: 25, 28
orig: 25, 28
offset: 0, 0
index: -1
images/wheel-big
rotate: false
xy: 1295, 490
size: 191, 191
orig: 191, 191
offset: 0, 0
index: -1
images/wheel-big-overlay
rotate: false
xy: 1488, 495
size: 186, 186
orig: 186, 186
offset: 0, 0
index: -1
images/wheel-mid
rotate: false
xy: 1676, 569
size: 136, 136
orig: 136, 136
offset: 0, 0
index: -1
images/wheel-mid-overlay
rotate: false
xy: 1814, 569
size: 136, 136
orig: 136, 136
offset: 0, 0
index: -1
images/wheel-small
rotate: false
xy: 1813, 496
size: 71, 71
orig: 71, 71
offset: 0, 0
index: -1

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,13 @@
vine.png
size: 128,1024
format: RGBA8888
filter: Linear,Linear
repeat: none
images/vine
rotate: false
xy: 2, 2
size: 68, 962
orig: 68, 962
offset: 0, 0
index: -1

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View File

@ -0,0 +1,219 @@
<html>
<script src="../../build/spine-webgl.js"></script>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<body>
<center>
<canvas id="canvas" style="width: 640; height: 480"></canvas></br>
<span>Skeleton:</span><select id="skeletonList"></select>
<span>Animation:</span><select id="animationList"></select>
<span>Skin:</span><select id="skinList"></select>
</center>
</body>
<script>
var lastFrameTime = Date.now() / 1000;
var canvas;
var shader;
var batcher;
var gl;
var mvp = new spine.webgl.Matrix4();
var assetManager;
var skeletonRenderer;
var skeletons = {};
var activeSkeleton = "raptor";
function init () {
// Setup canvas and WebGL context. We pass alpha: false to canvas.getContext() so we don't use premultiplied alpha when
// loading textures. That is handled separately by PolygonBatcher.
canvas = document.getElementById("canvas");
canvas.width = 640;
canvas.height = 480;
var config = { alpha: false };
gl = canvas.getContext("webgl", config) || canvas.getContext("experimental-webgl", config);
// Create a simple shader, mesh, model-view-projection matrix and SkeletonRenderer.
shader = spine.webgl.Shader.newColoredTextured(gl);
batcher = new spine.webgl.PolygonBatcher(gl);
mvp.ortho2d(0, 0, 639, 479);
skeletonRenderer = new spine.webgl.SkeletonRenderer(gl);
assetManager = new spine.webgl.AssetManager(gl);
// Tell AssetManager to load the resources for each model, including the exported .json file, the .atlas file and the .png
// file for the atlas. We then wait until all resources are loaded in the load() method.
assetManager.loadText("assets/spineboy.json");
assetManager.loadText("assets/spineboy.atlas");
assetManager.loadTexture("assets/spineboy.png");
assetManager.loadText("assets/raptor.json");
assetManager.loadText("assets/raptor.atlas");
assetManager.loadTexture("assets/raptor.png");
assetManager.loadText("assets/tank.json");
assetManager.loadText("assets/tank.atlas");
assetManager.loadTexture("assets/tank.png");
assetManager.loadText("assets/goblins-mesh.json");
assetManager.loadText("assets/goblins-mesh.atlas");
assetManager.loadTexture("assets/goblins-mesh.png");
assetManager.loadText("assets/vine.json");
assetManager.loadText("assets/vine.atlas");
assetManager.loadTexture("assets/vine.png");
requestAnimationFrame(load);
}
function load () {
// Wait until the AssetManager has loaded all resources, then load the skeletons.
if (assetManager.isLoadingComplete()) {
skeletons["spineboy"] = loadSkeleton("spineboy", 0.5, "test", 320, 20, false);
skeletons["raptor"] = loadSkeleton("raptor", 0.4, "walk", 320, 20, false);
skeletons["tank"] = loadSkeleton("tank", 0.3, "drive", 400, 20, false);
skeletons["goblins"] = loadSkeleton("goblins-mesh", 1, "walk", 320, 20, true, "goblin");
skeletons["vine"] = loadSkeleton("vine", 0.5, "animation", 320, 20, false);
setupUI();
requestAnimationFrame(render);
} else {
requestAnimationFrame(load);
}
}
function loadSkeleton (name, scale, initialAnimation, positionX, positionY, premultipliedAlpha, skin) {
if (skin === undefined) skin = "default";
// Load the texture atlas using name.atlas and name.png from the AssetManager.
// The function passed to TextureAtlas is used to resolve relative paths.
atlas = new spine.webgl.TextureAtlas(assetManager.get("assets/" + name + ".atlas"), function(path) {
return assetManager.get("assets/" + path);
});
// Create a TextureAtlasAttachmentLoader, which is specific to the WebGL backend.
atlasLoader = new spine.webgl.TextureAtlasAttachmentLoader(atlas);
// Create a SkeletonJson instance for parsing the .json file.
var skeletonJson = new spine.SkeletonJson(atlasLoader);
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
skeletonJson.scale = scale;
var skeletonData = skeletonJson.readSkeletonData(assetManager.get("assets/" + name + ".json"));
var skeleton = new spine.Skeleton(skeletonData);
skeleton.x = positionX;
skeleton.y = positionY;
skeleton.setSkinByName(skin);
// Create an AnimationState, and set the initial animation in looping mode.
var animationState = new spine.AnimationState(new spine.AnimationStateData(skeleton.data));
animationState.setAnimation(0, initialAnimation, true);
animationState.addListener({
event: function(trackIndex, event) {
// console.log("Event on track " + trackIndex + ": " + JSON.stringify(event));
},
complete: function(trackIndex, loopCount) {
// console.log("Animation on track " + trackIndex + " completed, loop count: " + loopCount);
},
start: function(trackIndex) {
// console.log("Animation on track " + trackIndex + " started");
},
end: function(trackIndex) {
// console.log("Animation on track " + trackIndex + " ended");
}
})
// Pack everything up and return to caller.
return { skeleton: skeleton, state: animationState, premultipliedAlpha: premultipliedAlpha };
}
function setupUI () {
var skeletonList = $("#skeletonList");
for (var skeletonName in skeletons) {
var option = $("<option></option>");
option.attr("value", skeletonName).text(skeletonName);
if (skeletonName === activeSkeleton) option.attr("selected", "selected");
skeletonList.append(option);
}
var setupAnimationUI = function() {
var animationList = $("#animationList");
animationList.empty();
var skeleton = skeletons[activeSkeleton].skeleton;
var state = skeletons[activeSkeleton].state;
var activeAnimation = state.tracks[0].animation.name;
for (var i = 0; i < skeleton.data.animations.length; i++) {
var name = skeleton.data.animations[i].name;
var option = $("<option></option>");
option.attr("value", name).text(name);
if (name === activeAnimation) option.attr("selected", "selected");
animationList.append(option);
}
animationList.change(function() {
var state = skeletons[activeSkeleton].state;
var skeleton = skeletons[activeSkeleton].skeleton;
var animationName = $("#animationList option:selected").text();
skeleton.setToSetupPose();
state.setAnimation(0, animationName, true);
})
}
var setupSkinUI = function() {
var skinList = $("#skinList");
skinList.empty();
var skeleton = skeletons[activeSkeleton].skeleton;
var activeSkin = skeleton.skin == null ? "default" : skeleton.skin.name;
for (var i = 0; i < skeleton.data.skins.length; i++) {
var name = skeleton.data.skins[i].name;
var option = $("<option></option>");
option.attr("value", name).text(name);
if (name === activeSkin) option.attr("selected", "selected");
skinList.append(option);
}
skinList.change(function() {
var skeleton = skeletons[activeSkeleton].skeleton;
var skinName = $("#skinList option:selected").text();
skeleton.setSkinByName(skinName);
skeleton.setSlotsToSetupPose();
})
}
skeletonList.change(function() {
activeSkeleton = $("#skeletonList option:selected").text();
setupAnimationUI();
setupSkinUI();
})
setupAnimationUI();
setupSkinUI();
}
function render () {
var now = Date.now() / 1000;
var delta = now - lastFrameTime;
lastFrameTime = now;
gl.clearColor(0.3, 0.3, 0.3, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Apply the animation state based on the delta time.
var state = skeletons[activeSkeleton].state;
var skeleton = skeletons[activeSkeleton].skeleton;
var premultipliedAlpha = skeletons[activeSkeleton].premultipliedAlpha;
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
// Bind the shader and set the texture and model-view-projection matrix.
shader.bind();
shader.setUniformi(spine.webgl.Shader.SAMPLER, 0);
shader.setUniform4x4f(spine.webgl.Shader.MVP_MATRIX, mvp.values);
// Start the batch and tell the SkeletonRenderer to render the active skeleton.
batcher.begin(shader);
skeletonRenderer.premultipliedAlpha = premultipliedAlpha;
skeletonRenderer.draw(batcher, skeleton);
batcher.end();
shader.unbind();
requestAnimationFrame(render);
}
(function() {
init();
})();
</script>
</html>

View File

@ -0,0 +1,133 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export class AssetManager implements Disposable {
private _gl: WebGLRenderingContext;
private _assets: Map<string | Texture> = {};
private _errors: Map<string> = {};
private _toLoad = 0;
private _loaded = 0;
constructor (gl: WebGLRenderingContext) {
this._gl = gl;
}
loadText(path: string,
success: (path: string, text: string) => void = null,
error: (path: string, error: string) => void = null
) {
this._toLoad++;
let request = new XMLHttpRequest();
request.onreadystatechange = () => {
if (request.readyState == XMLHttpRequest.DONE) {
if (request.status >= 200 && request.status < 300) {
if (success) success(path, request.responseText);
this._assets[path] = request.responseText;
} else {
if (error) error(path, `Couldn't load text ${path}: status ${request.status}, ${request.responseText}`);
this._errors[path] = `Couldn't load text ${path}: status ${request.status}, ${request.responseText}`;
}
this._toLoad--;
this._loaded++;
}
};
request.open("GET", path, true);
request.send();
}
loadTexture (path: string,
success: (path: string, image: HTMLImageElement) => void = null,
error: (path: string, error: string) => void = null
) {
this._toLoad++;
let img = new Image();
img.src = path;
img.onload = (ev) => {
if (success) success(path, img);
let texture = new Texture(this._gl, img);
this._assets[path] = texture;
this._toLoad--;
this._loaded++;
}
img.onerror = (ev) => {
if (error) error(path, `Couldn't load image ${path}`);
this._errors[path] = `Couldn't load image ${path}`;
this._toLoad--;
this._loaded++;
}
}
get (path: string) {
return this._assets[path];
}
remove (path: string) {
let asset = this._assets[path];
if (asset instanceof Texture) {
asset.dispose();
}
this._assets[path] = null;
}
removeAll () {
for (let key in this._assets) {
let asset = this._assets[key];
if (asset instanceof Texture) asset.dispose();
}
this._assets = {};
}
isLoadingComplete (): boolean {
return this._toLoad == 0;
}
toLoad (): number {
return this._toLoad;
}
loaded (): number {
return this._loaded;
}
dispose () {
this.removeAll();
}
hasErrors() {
return Object.keys(this._errors).length > 0;
}
errors() {
return this._errors;
}
}
}

View File

@ -0,0 +1,303 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export const M00 = 0;
export const M01 = 4;
export const M02 = 8;
export const M03 = 12;
export const M10 = 1;
export const M11 = 5;
export const M12 = 9;
export const M13 = 13;
export const M20 = 2;
export const M21 = 6;
export const M22 = 10;
export const M23 = 14;
export const M30 = 3;
export const M31 = 7;
export const M32 = 11;
export const M33 = 15;
export class Matrix4 {
temp: Float32Array = new Float32Array(16);
values: Float32Array = new Float32Array(16);
constructor () {
let v = this.values;
v[M00] = 1;
v[M11] = 1;
v[M22] = 1;
v[M33] = 1;
}
set (values: ArrayLike<number>): Matrix4 {
this.values.set(values);
return this;
}
transpose (): Matrix4 {
let t = this.temp;
let v = this.values;
t[M00] = v[M00];
t[M01] = v[M10];
t[M02] = v[M20];
t[M03] = v[M30];
t[M10] = v[M01];
t[M11] = v[M11];
t[M12] = v[M21];
t[M13] = v[M31];
t[M20] = v[M02];
t[M21] = v[M12];
t[M22] = v[M22];
t[M23] = v[M32];
t[M30] = v[M03];
t[M31] = v[M13];
t[M32] = v[M23];
t[M33] = v[M33];
return this.set(t);
}
identity (): Matrix4 {
let v = this.values;
v[M00] = 1;
v[M01] = 0;
v[M02] = 0;
v[M03] = 0;
v[M10] = 0;
v[M11] = 1;
v[M12] = 0;
v[M13] = 0;
v[M20] = 0;
v[M21] = 0;
v[M22] = 1;
v[M23] = 0;
v[M30] = 0;
v[M31] = 0;
v[M32] = 0;
v[M33] = 1;
return this;
}
invert (): Matrix4 {
let v = this.values;
let t = this.temp;
let l_det = v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03]
+ v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03]
- v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13]
- v[M00] * v[M31] * v[M22] * v[M13] - v[M20] * v[M01] * v[M32] * v[M13] + v[M00] * v[M21] * v[M32] * v[M13]
+ v[M30] * v[M11] * v[M02] * v[M23] - v[M10] * v[M31] * v[M02] * v[M23] - v[M30] * v[M01] * v[M12] * v[M23]
+ v[M00] * v[M31] * v[M12] * v[M23] + v[M10] * v[M01] * v[M32] * v[M23] - v[M00] * v[M11] * v[M32] * v[M23]
- v[M20] * v[M11] * v[M02] * v[M33] + v[M10] * v[M21] * v[M02] * v[M33] + v[M20] * v[M01] * v[M12] * v[M33]
- v[M00] * v[M21] * v[M12] * v[M33] - v[M10] * v[M01] * v[M22] * v[M33] + v[M00] * v[M11] * v[M22] * v[M33];
if (l_det == 0) throw new Error("non-invertible matrix");
let inv_det = 1.0 / l_det;
t[M00] = v[M12] * v[M23] * v[M31] - v[M13] * v[M22] * v[M31] + v[M13] * v[M21] * v[M32]
- v[M11] * v[M23] * v[M32] - v[M12] * v[M21] * v[M33] + v[M11] * v[M22] * v[M33];
t[M01] = v[M03] * v[M22] * v[M31] - v[M02] * v[M23] * v[M31] - v[M03] * v[M21] * v[M32]
+ v[M01] * v[M23] * v[M32] + v[M02] * v[M21] * v[M33] - v[M01] * v[M22] * v[M33];
t[M02] = v[M02] * v[M13] * v[M31] - v[M03] * v[M12] * v[M31] + v[M03] * v[M11] * v[M32]
- v[M01] * v[M13] * v[M32] - v[M02] * v[M11] * v[M33] + v[M01] * v[M12] * v[M33];
t[M03] = v[M03] * v[M12] * v[M21] - v[M02] * v[M13] * v[M21] - v[M03] * v[M11] * v[M22]
+ v[M01] * v[M13] * v[M22] + v[M02] * v[M11] * v[M23] - v[M01] * v[M12] * v[M23];
t[M10] = v[M13] * v[M22] * v[M30] - v[M12] * v[M23] * v[M30] - v[M13] * v[M20] * v[M32]
+ v[M10] * v[M23] * v[M32] + v[M12] * v[M20] * v[M33] - v[M10] * v[M22] * v[M33];
t[M11] = v[M02] * v[M23] * v[M30] - v[M03] * v[M22] * v[M30] + v[M03] * v[M20] * v[M32]
- v[M00] * v[M23] * v[M32] - v[M02] * v[M20] * v[M33] + v[M00] * v[M22] * v[M33];
t[M12] = v[M03] * v[M12] * v[M30] - v[M02] * v[M13] * v[M30] - v[M03] * v[M10] * v[M32]
+ v[M00] * v[M13] * v[M32] + v[M02] * v[M10] * v[M33] - v[M00] * v[M12] * v[M33];
t[M13] = v[M02] * v[M13] * v[M20] - v[M03] * v[M12] * v[M20] + v[M03] * v[M10] * v[M22]
- v[M00] * v[M13] * v[M22] - v[M02] * v[M10] * v[M23] + v[M00] * v[M12] * v[M23];
t[M20] = v[M11] * v[M23] * v[M30] - v[M13] * v[M21] * v[M30] + v[M13] * v[M20] * v[M31]
- v[M10] * v[M23] * v[M31] - v[M11] * v[M20] * v[M33] + v[M10] * v[M21] * v[M33];
t[M21] = v[M03] * v[M21] * v[M30] - v[M01] * v[M23] * v[M30] - v[M03] * v[M20] * v[M31]
+ v[M00] * v[M23] * v[M31] + v[M01] * v[M20] * v[M33] - v[M00] * v[M21] * v[M33];
t[M22] = v[M01] * v[M13] * v[M30] - v[M03] * v[M11] * v[M30] + v[M03] * v[M10] * v[M31]
- v[M00] * v[M13] * v[M31] - v[M01] * v[M10] * v[M33] + v[M00] * v[M11] * v[M33];
t[M23] = v[M03] * v[M11] * v[M20] - v[M01] * v[M13] * v[M20] - v[M03] * v[M10] * v[M21]
+ v[M00] * v[M13] * v[M21] + v[M01] * v[M10] * v[M23] - v[M00] * v[M11] * v[M23];
t[M30] = v[M12] * v[M21] * v[M30] - v[M11] * v[M22] * v[M30] - v[M12] * v[M20] * v[M31]
+ v[M10] * v[M22] * v[M31] + v[M11] * v[M20] * v[M32] - v[M10] * v[M21] * v[M32];
t[M31] = v[M01] * v[M22] * v[M30] - v[M02] * v[M21] * v[M30] + v[M02] * v[M20] * v[M31]
- v[M00] * v[M22] * v[M31] - v[M01] * v[M20] * v[M32] + v[M00] * v[M21] * v[M32];
t[M32] = v[M02] * v[M11] * v[M30] - v[M01] * v[M12] * v[M30] - v[M02] * v[M10] * v[M31]
+ v[M00] * v[M12] * v[M31] + v[M01] * v[M10] * v[M32] - v[M00] * v[M11] * v[M32];
t[M33] = v[M01] * v[M12] * v[M20] - v[M02] * v[M11] * v[M20] + v[M02] * v[M10] * v[M21]
- v[M00] * v[M12] * v[M21] - v[M01] * v[M10] * v[M22] + v[M00] * v[M11] * v[M22];
v[M00] = t[M00] * inv_det;
v[M01] = t[M01] * inv_det;
v[M02] = t[M02] * inv_det;
v[M03] = t[M03] * inv_det;
v[M10] = t[M10] * inv_det;
v[M11] = t[M11] * inv_det;
v[M12] = t[M12] * inv_det;
v[M13] = t[M13] * inv_det;
v[M20] = t[M20] * inv_det;
v[M21] = t[M21] * inv_det;
v[M22] = t[M22] * inv_det;
v[M23] = t[M23] * inv_det;
v[M30] = t[M30] * inv_det;
v[M31] = t[M31] * inv_det;
v[M32] = t[M32] * inv_det;
v[M33] = t[M33] * inv_det;
return this;
}
determinant (): number {
let v = this.values;
return v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03]
+ v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03]
- v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13]
- v[M00] * v[M31] * v[M22] * v[M13] - v[M20] * v[M01] * v[M32] * v[M13] + v[M00] * v[M21] * v[M32] * v[M13]
+ v[M30] * v[M11] * v[M02] * v[M23] - v[M10] * v[M31] * v[M02] * v[M23] - v[M30] * v[M01] * v[M12] * v[M23]
+ v[M00] * v[M31] * v[M12] * v[M23] + v[M10] * v[M01] * v[M32] * v[M23] - v[M00] * v[M11] * v[M32] * v[M23]
- v[M20] * v[M11] * v[M02] * v[M33] + v[M10] * v[M21] * v[M02] * v[M33] + v[M20] * v[M01] * v[M12] * v[M33]
- v[M00] * v[M21] * v[M12] * v[M33] - v[M10] * v[M01] * v[M22] * v[M33] + v[M00] * v[M11] * v[M22] * v[M33];
}
translate (x: number, y: number, z: number): Matrix4 {
let v = this.values;
v[M03] += x;
v[M13] += y;
v[M23] += z;
return this;
}
copy (): Matrix4 {
return new Matrix4().set(this.values);
}
projection (near: number, far: number, fovy: number, aspectRatio: number): Matrix4 {
this.identity();
let l_fd = (1.0 / Math.tan((fovy * (Math.PI / 180)) / 2.0));
let l_a1 = (far + near) / (near - far);
let l_a2 = (2 * far * near) / (near - far);
let v = this.values;
v[M00] = l_fd / aspectRatio;
v[M10] = 0;
v[M20] = 0;
v[M30] = 0;
v[M01] = 0;
v[M11] = l_fd;
v[M21] = 0;
v[M31] = 0;
v[M02] = 0;
v[M12] = 0;
v[M22] = l_a1;
v[M32] = -1;
v[M03] = 0;
v[M13] = 0;
v[M23] = l_a2;
v[M33] = 0;
return this;
}
ortho2d (x: number, y: number, width: number, height: number): Matrix4 {
return this.ortho(x, x + width, y, y + height, 0, 1);
}
ortho (left: number, right: number, bottom: number, top: number, near: number, far: number): Matrix4 {
this.identity();
let x_orth = 2 / (right - left);
let y_orth = 2 / (top - bottom);
let z_orth = -2 / (far - near);
let tx = -(right + left) / (right - left);
let ty = -(top + bottom) / (top - bottom);
let tz = -(far + near) / (far - near);
let v = this.values;
v[M00] = x_orth;
v[M10] = 0;
v[M20] = 0;
v[M30] = 0;
v[M01] = 0;
v[M11] = y_orth;
v[M21] = 0;
v[M31] = 0;
v[M02] = 0;
v[M12] = 0;
v[M22] = z_orth;
v[M32] = 0;
v[M03] = tx;
v[M13] = ty;
v[M23] = tz;
v[M33] = 1;
return this;
}
multiply (matrix: Matrix4): Matrix4 {
let t = this.temp;
let v = this.values;
let m = matrix.values;
t[M00] = v[M00] * m[M00] + v[M01] * m[M10] + v[M02] * m[M20] + v[M03] * m[M30];
t[M01] = v[M00] * m[M01] + v[M01] * m[M11] + v[M02] * m[M21] + v[M03] * m[M31];
t[M02] = v[M00] * m[M02] + v[M01] * m[M12] + v[M02] * m[M22] + v[M03] * m[M32];
t[M03] = v[M00] * m[M03] + v[M01] * m[M13] + v[M02] * m[M23] + v[M03] * m[M33];
t[M10] = v[M10] * m[M00] + v[M11] * m[M10] + v[M12] * m[M20] + v[M13] * m[M30];
t[M11] = v[M10] * m[M01] + v[M11] * m[M11] + v[M12] * m[M21] + v[M13] * m[M31];
t[M12] = v[M10] * m[M02] + v[M11] * m[M12] + v[M12] * m[M22] + v[M13] * m[M32];
t[M13] = v[M10] * m[M03] + v[M11] * m[M13] + v[M12] * m[M23] + v[M13] * m[M33];
t[M20] = v[M20] * m[M00] + v[M21] * m[M10] + v[M22] * m[M20] + v[M23] * m[M30];
t[M21] = v[M20] * m[M01] + v[M21] * m[M11] + v[M22] * m[M21] + v[M23] * m[M31];
t[M22] = v[M20] * m[M02] + v[M21] * m[M12] + v[M22] * m[M22] + v[M23] * m[M32];
t[M23] = v[M20] * m[M03] + v[M21] * m[M13] + v[M22] * m[M23] + v[M23] * m[M33];
t[M30] = v[M30] * m[M00] + v[M31] * m[M10] + v[M32] * m[M20] + v[M33] * m[M30];
t[M31] = v[M30] * m[M01] + v[M31] * m[M11] + v[M32] * m[M21] + v[M33] * m[M31];
t[M32] = v[M30] * m[M02] + v[M31] * m[M12] + v[M32] * m[M22] + v[M33] * m[M32];
t[M33] = v[M30] * m[M03] + v[M31] * m[M13] + v[M32] * m[M23] + v[M33] * m[M33];
return this.set(this.temp);
}
multiplyLeft (matrix: Matrix4): Matrix4 {
let t = this.temp;
let v = this.values;
let m = matrix.values;
t[M00] = m[M00] * v[M00] + m[M01] * v[M10] + m[M02] * v[M20] + m[M03] * v[M30];
t[M01] = m[M00] * v[M01] + m[M01] * v[M11] + m[M02] * v[M21] + m[M03] * v[M31];
t[M02] = m[M00] * v[M02] + m[M01] * v[M12] + m[M02] * v[M22] + m[M03] * v[M32];
t[M03] = m[M00] * v[M03] + m[M01] * v[M13] + m[M02] * v[M23] + m[M03] * v[M33];
t[M10] = m[M10] * v[M00] + m[M11] * v[M10] + m[M12] * v[M20] + m[M13] * v[M30];
t[M11] = m[M10] * v[M01] + m[M11] * v[M11] + m[M12] * v[M21] + m[M13] * v[M31];
t[M12] = m[M10] * v[M02] + m[M11] * v[M12] + m[M12] * v[M22] + m[M13] * v[M32];
t[M13] = m[M10] * v[M03] + m[M11] * v[M13] + m[M12] * v[M23] + m[M13] * v[M33];
t[M20] = m[M20] * v[M00] + m[M21] * v[M10] + m[M22] * v[M20] + m[M23] * v[M30];
t[M21] = m[M20] * v[M01] + m[M21] * v[M11] + m[M22] * v[M21] + m[M23] * v[M31];
t[M22] = m[M20] * v[M02] + m[M21] * v[M12] + m[M22] * v[M22] + m[M23] * v[M32];
t[M23] = m[M20] * v[M03] + m[M21] * v[M13] + m[M22] * v[M23] + m[M23] * v[M33];
t[M30] = m[M30] * v[M00] + m[M31] * v[M10] + m[M32] * v[M20] + m[M33] * v[M30];
t[M31] = m[M30] * v[M01] + m[M31] * v[M11] + m[M32] * v[M21] + m[M33] * v[M31];
t[M32] = m[M30] * v[M02] + m[M31] * v[M12] + m[M32] * v[M22] + m[M33] * v[M32];
t[M33] = m[M30] * v[M03] + m[M31] * v[M13] + m[M32] * v[M23] + m[M33] * v[M33];
return this.set(this.temp);
}
}
}

184
spine-ts/webgl/src/Mesh.ts Normal file
View File

@ -0,0 +1,184 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export class Mesh implements Disposable {
private _gl: WebGLRenderingContext;
private _vertices:Float32Array;
private _verticesBuffer: WebGLBuffer;
private _verticesLength = 0;
private _dirtyVertices = false;
private _indices:Uint16Array;
private _indicesBuffer: WebGLBuffer;
private _indicesLength = 0;
private _dirtyIndices = false;
private _elementsPerVertex = 0;
attributes (): VertexAttribute[] { return this._attributes; }
maxVertices (): number { return this._vertices.length / this._elementsPerVertex; }
numVertices (): number { return this._verticesLength / this._elementsPerVertex; }
setVerticesLength (length: number) {
this._dirtyVertices = true;
this._verticesLength = length;
}
vertices (): Float32Array { return this._vertices; }
maxIndices (): number { return this._indices.length; }
numIndices (): number { return this._indicesLength; }
setIndicesLength (length: number) {
this._dirtyIndices = true;
this._indicesLength = length;
}
indices (): Uint16Array { return this._indices };
constructor (gl: WebGLRenderingContext, private _attributes: VertexAttribute[], maxVertices: number, maxIndices: number) {
this._gl = gl;
this._elementsPerVertex = 0;
for (let i = 0; i < _attributes.length; i++) {
this._elementsPerVertex += _attributes[i].numElements;
}
this._vertices = new Float32Array(maxVertices * this._elementsPerVertex);
this._indices = new Uint16Array(maxIndices);
}
setVertices (vertices: Array<number>) {
this._dirtyVertices = true;
if (vertices.length > this._vertices.length) throw Error("Mesh can't store more than " + this.maxVertices() + " vertices");
this._vertices.set(vertices, 0);
this._verticesLength = vertices.length;
}
setIndices (indices: Array<number>) {
this._dirtyIndices = true;
if (indices.length > this._indices.length) throw Error("Mesh can't store more than " + this.maxIndices() + " indices");
this._indices.set(indices, 0);
this._indicesLength = indices.length;
}
draw (shader: Shader, primitiveType: number) {
this.drawWithOffset(shader, primitiveType, 0, this._indicesLength > 0? this._indicesLength: this._verticesLength);
}
drawWithOffset (shader: Shader, primitiveType: number, offset: number, count: number) {
let gl = this._gl;
if (this._dirtyVertices || this._dirtyIndices) this.update();
this.bind(shader);
if (this._indicesLength > 0) gl.drawElements(primitiveType, count, gl.UNSIGNED_SHORT, offset * 2);
else gl.drawArrays(primitiveType, offset, count);
this.unbind(shader);
}
bind (shader: Shader) {
let gl = this._gl;
gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesBuffer);
let offset = 0;
for (let i = 0; i < this._attributes.length; i++) {
let attrib = this._attributes[i];
let location = shader.getAttributeLocation(attrib.name);
gl.enableVertexAttribArray(location);
gl.vertexAttribPointer(location, attrib.numElements, gl.FLOAT, false, this._elementsPerVertex * 4, offset * 4);
offset += attrib.numElements;
}
if (this._indicesLength > 0) gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indicesBuffer);
}
unbind (shader: Shader) {
let gl = this._gl;
for (let i = 0; i < this._attributes.length; i++) {
let attrib = this._attributes[i];
let location = shader.getAttributeLocation(attrib.name);
gl.disableVertexAttribArray(location);
}
gl.bindBuffer(gl.ARRAY_BUFFER, null);
if (this._indicesLength > 0) gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
private update () {
let gl = this._gl;
if (this._dirtyVertices) {
if (!this._verticesBuffer) {
this._verticesBuffer = gl.createBuffer();
}
gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this._vertices.subarray(0, this._verticesLength), gl.STATIC_DRAW);
this._dirtyVertices = false;
}
if (this._dirtyIndices) {
if (!this._indicesBuffer) {
this._indicesBuffer = gl.createBuffer();
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this._indices.subarray(0, this._indicesLength), gl.STATIC_DRAW);
this._dirtyIndices = false;
}
}
dispose () {
let gl = this._gl;
gl.deleteBuffer(this._verticesBuffer);
gl.deleteBuffer(this._indicesBuffer);
}
}
export class VertexAttribute {
constructor (public name: string, public type: VertexAttributeType, public numElements: number) { }
}
export class Position2Attribute extends VertexAttribute {
constructor () {
super(Shader.POSITION, VertexAttributeType.Float, 2);
}
}
export class Position3Attribute extends VertexAttribute {
constructor () {
super(Shader.POSITION, VertexAttributeType.Float, 3);
}
}
export class TexCoordAttribute extends VertexAttribute {
constructor (unit: number = 0) {
super(Shader.TEXCOORDS + (unit == 0? "": unit), VertexAttributeType.Float, 2);
}
}
export class ColorAttribute extends VertexAttribute {
constructor () {
super(Shader.COLOR, VertexAttributeType.Float, 4);
}
}
export enum VertexAttributeType {
Float
}
}

View File

@ -0,0 +1,121 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export class PolygonBatcher {
private _gl: WebGLRenderingContext;
private _drawCalls: number;
private _drawing = false;
private _mesh: Mesh;
private _shader: Shader = null;
private _lastTexture: Texture = null;
private _verticesLength = 0;
private _indicesLength = 0;
private _srcBlend: number = WebGLRenderingContext.SRC_ALPHA;
private _dstBlend: number = WebGLRenderingContext.ONE_MINUS_SRC_ALPHA;
constructor (gl: WebGLRenderingContext, maxVertices: number = 10920) {
if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
this._gl = gl;
this._mesh = new Mesh(gl, [new Position2Attribute(), new ColorAttribute(), new TexCoordAttribute()], maxVertices, maxVertices * 3);
}
begin (shader: Shader) {
let gl = this._gl;
if (this._drawing) throw new Error("PolygonBatch is already drawing. Call PolygonBatch.end() before calling PolygonBatch.begin()");
this._drawCalls = 0;
this._shader = shader;
this._lastTexture = null;
this._drawing = true;
gl.enable(gl.BLEND);
gl.blendFunc(this._srcBlend, this._dstBlend);
}
setBlendMode (srcBlend: number, dstBlend: number) {
let gl = this._gl;
this._srcBlend = srcBlend;
this._dstBlend = dstBlend;
if (this._drawing) {
this.flush();
gl.blendFunc(this._srcBlend, this._dstBlend);
}
}
draw (texture: Texture, vertices: ArrayLike<number>, indices: Array<number>) {
if (texture != this._lastTexture) {
this.flush();
this._lastTexture = texture;
texture.bind();
} else if (this._verticesLength + vertices.length > this._mesh.vertices().length ||
this._indicesLength + indices.length > this._mesh.indices().length) {
this.flush();
}
let indexStart = this._mesh.numVertices();
this._mesh.vertices().set(vertices, this._verticesLength);
this._verticesLength += vertices.length;
this._mesh.setVerticesLength(this._verticesLength)
let indicesArray = this._mesh.indices();
for (let i = this._indicesLength, j = 0; j < indices.length; i++, j++)
indicesArray[i] = indices[j] + indexStart;
this._indicesLength += indices.length;
this._mesh.setIndicesLength(this._indicesLength);
}
private flush () {
let gl = this._gl;
if (this._verticesLength == 0) return;
this._mesh.draw(this._shader, gl.TRIANGLES);
this._verticesLength = 0;
this._indicesLength = 0;
this._mesh.setVerticesLength(0);
this._mesh.setIndicesLength(0);
this._drawCalls++;
}
end () {
let gl = this._gl;
if (!this._drawing) throw new Error("PolygonBatch is not drawing. Call PolygonBatch.begin() before calling PolygonBatch.end()");
if (this._verticesLength > 0 || this._indicesLength > 0) this.flush();
this._shader = null;
this._lastTexture = null;
this._drawing = false;
gl.disable(gl.BLEND);
}
drawCalls () { return this._drawCalls; }
}
}

View File

@ -0,0 +1,240 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export class Shader implements Disposable {
public static MVP_MATRIX = "u_projTrans";
public static POSITION = "a_position";
public static COLOR = "a_color";
public static TEXCOORDS = "a_texCoords";
public static SAMPLER = "u_texture";
private _gl: WebGLRenderingContext;
private _vs: WebGLShader = null;
private _fs: WebGLShader = null;
private _program: WebGLProgram = null;
private _tmp2x2: Float32Array = new Float32Array(2 * 2);
private _tmp3x3: Float32Array = new Float32Array(3 * 3);
private _tmp4x4: Float32Array = new Float32Array(4 * 4);
public program () { return this._program; }
public vertexShader () { return this._vertexShader; }
public fragmentShader () { return this._fragmentShader; }
constructor (gl: WebGLRenderingContext, private _vertexShader: string, private _fragmentShader: string) {
this._gl = gl;
this.compile();
}
private compile () {
let gl = this._gl;
try {
this._vs = this.compileShader(gl.VERTEX_SHADER, this._vertexShader);
this._fs = this.compileShader(gl.FRAGMENT_SHADER, this._fragmentShader);
this._program = this.compileProgram(this._vs, this._fs);
} catch (e) {
this.dispose();
throw e;
}
}
private compileShader (type: number, source: string) {
let gl = this._gl;
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
let error = "Couldn't compile shader: " + gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error(error);
}
return shader;
}
private compileProgram (vs: WebGLShader, fs: WebGLShader) {
let gl = this._gl;
let program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
let error = "Couldn't compile shader program: " + gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(error);
}
return program;
}
public bind () {
this._gl.useProgram(this._program);
}
public unbind () {
this._gl.useProgram(null);
}
public setUniformi (uniform: string, value: number) {
this._gl.uniform1i(this.getUniformLocation(uniform), value);
}
public setUniformf (uniform: string, value: number) {
this._gl.uniform1f(this.getUniformLocation(uniform), value);
}
public setUniform2f (uniform: string, value: number, value2: number) {
this._gl.uniform2f(this.getUniformLocation(uniform), value, value2);
}
public setUniform3f (uniform: string, value: number, value2: number, value3: number) {
this._gl.uniform3f(this.getUniformLocation(uniform), value, value2, value3);
}
public setUniform4f (uniform: string, value: number, value2: number, value3: number, value4: number) {
this._gl.uniform4f(this.getUniformLocation(uniform), value, value2, value3, value4);
}
public setUniform2x2f (uniform: string, value: ArrayLike<number>) {
let gl = this._gl;
this._tmp2x2.set(value);
gl.uniformMatrix2fv(this.getUniformLocation(uniform), false, this._tmp2x2);
}
public setUniform3x3f (uniform: string, value: ArrayLike<number>) {
let gl = this._gl;
this._tmp3x3.set(value);
gl.uniformMatrix3fv(this.getUniformLocation(uniform), false, this._tmp3x3);
}
public setUniform4x4f (uniform: string, value: ArrayLike<number>) {
let gl = this._gl;
this._tmp4x4.set(value);
gl.uniformMatrix4fv(this.getUniformLocation(uniform), false, this._tmp4x4);
}
public getUniformLocation (uniform: string): WebGLUniformLocation {
let gl = this._gl;
let location = gl.getUniformLocation(this._program, uniform);
if (!location) throw new Error(`Couldn't find location for uniform ${uniform}`);
return location;
}
public getAttributeLocation (attribute: string): number {
let gl = this._gl;
let location = gl.getAttribLocation(this._program, attribute);
if (location == -1) throw new Error(`Couldn't find location for attribute ${attribute}`);
return location;
}
public dispose () {
let gl = this._gl;
if (this._vs) {
gl.deleteShader(this._vs);
this._vs = null;
}
if (this._fs) {
gl.deleteShader(this._fs);
this._fs = null;
}
if (this._program) {
gl.deleteProgram(this._program);
this._program = null;
}
}
public static newColoredTextured (gl: WebGLRenderingContext): Shader {
let vs = `
attribute vec4 ${Shader.POSITION};
attribute vec4 ${Shader.COLOR};
attribute vec2 ${Shader.TEXCOORDS};
uniform mat4 ${Shader.MVP_MATRIX};
varying vec4 v_color;
varying vec2 v_texCoords;
void main () {
v_color = ${Shader.COLOR};
v_texCoords = ${Shader.TEXCOORDS};
gl_Position = ${Shader.MVP_MATRIX} * ${Shader.POSITION};
}
`;
let fs = `
#ifdef GL_ES
#define LOWP lowp
precision mediump float;
#else
#define LOWP
#endif
varying LOWP vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
void main () {
gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
}
`;
return new Shader(gl, vs, fs);
}
public static newColored (gl: WebGLRenderingContext): Shader {
let vs = `
attribute vec4 ${Shader.POSITION};
attribute vec4 ${Shader.COLOR};
uniform mat4 ${Shader.MVP_MATRIX};
varying vec4 v_color;
void main () {
v_color = ${Shader.COLOR};
gl_Position = ${Shader.MVP_MATRIX} * ${Shader.POSITION};
}
`;
let fs = `
#ifdef GL_ES
#define LOWP lowp
precision mediump float;
#else
#define LOWP
#endif
varying LOWP vec4 v_color;
void main () {
gl_FragColor = v_color;
}
`;
return new Shader(gl, vs, fs);
}
}
}

View File

@ -0,0 +1,78 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export class SkeletonRenderer {
static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
premultipliedAlpha = false;
private _gl: WebGLRenderingContext;
constructor (gl: WebGLRenderingContext) {
this._gl = gl;
}
draw (batcher: PolygonBatcher, skeleton: Skeleton) {
let premultipliedAlpha = this.premultipliedAlpha;
let blendMode: BlendMode = null;
let vertices: ArrayLike<number> = null;
let triangles: Array<number> = null;
let drawOrder = skeleton.drawOrder;
for (let i = 0, n = drawOrder.length; i < n; i++) {
let slot = drawOrder[i];
let attachment = slot.getAttachment();
let texture: Texture = null;
if (attachment instanceof RegionAttachment) {
let region = <RegionAttachment>attachment;
vertices = region.updateWorldVertices(slot, premultipliedAlpha);
triangles = SkeletonRenderer.QUAD_TRIANGLES;
texture = (<TextureAtlasRegion>region.region.renderObject).texture;
} else if (attachment instanceof MeshAttachment) {
let mesh = <MeshAttachment>attachment;
vertices = mesh.updateWorldVertices(slot, premultipliedAlpha);
triangles = mesh.triangles;
texture = (<TextureAtlasRegion>mesh.region.renderObject).texture;
}
if (texture != null) {
let slotBlendMode = slot.data.blendMode;
if (slotBlendMode != blendMode) {
blendMode = slotBlendMode;
batcher.setBlendMode(getSourceGLBlendMode(this._gl, blendMode, premultipliedAlpha), getDestGLBlendMode(this._gl, blendMode));
}
batcher.draw(texture, vertices, triangles);
}
}
}
}
}

View File

@ -0,0 +1,131 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export class Texture implements Disposable {
private _gl: WebGLRenderingContext;
private _texture: WebGLTexture;
private _image: HTMLImageElement;
private _boundUnit = 0;
constructor (gl: WebGLRenderingContext, image: HTMLImageElement, useMipMaps: boolean = false) {
this._gl = gl;
this._texture = gl.createTexture();
this._image = image;
this.update(useMipMaps);
}
getImage (): HTMLImageElement {
return this._image;
}
setFilters (minFilter: TextureFilter, magFilter: TextureFilter) {
let gl = this._gl;
this.bind();
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter);
}
setWraps (uWrap: TextureWrap, vWrap: TextureWrap) {
let gl = this._gl;
this.bind();
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, uWrap);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, vWrap);
}
update (useMipMaps: boolean) {
let gl = this._gl;
this.bind();
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, useMipMaps? gl.LINEAR_MIPMAP_LINEAR: gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
if (useMipMaps) gl.generateMipmap(gl.TEXTURE_2D);
}
bind (unit: number = 0) {
let gl = this._gl;
this._boundUnit = unit;
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, this._texture);
}
unbind () {
let gl = this._gl;
gl.activeTexture(gl.TEXTURE0 + this._boundUnit);
gl.bindTexture(gl.TEXTURE_2D, null);
}
dispose () {
let gl = this._gl;
gl.deleteTexture(this._texture);
}
public static filterFromString (text: string): TextureFilter {
switch (text.toLowerCase()) {
case "nearest": return TextureFilter.Nearest;
case "linear": return TextureFilter.Linear;
case "mipmap": return TextureFilter.MipMap;
case "mipmapnearestnearest": return TextureFilter.MipMapNearestNearest;
case "mipmaplinearnearest": return TextureFilter.MipMapLinearNearest;
case "mipmapnearestlinear": return TextureFilter.MipMapNearestLinear;
case "mipmaplinearlinear": return TextureFilter.MipMapLinearLinear;
default: throw new Error(`Unknown texture filter ${text}`);
}
}
public static wrapFromString (text: string): TextureWrap {
switch (text.toLowerCase()) {
case "mirroredtepeat": return TextureWrap.MirroredRepeat;
case "clamptoedge": return TextureWrap.ClampToEdge;
case "repeat": return TextureWrap.Repeat;
default: throw new Error(`Unknown texture wrap ${text}`);
}
}
}
export enum TextureFilter {
Nearest = WebGLRenderingContext.NEAREST,
Linear = WebGLRenderingContext.LINEAR,
MipMap = WebGLRenderingContext.LINEAR_MIPMAP_LINEAR,
MipMapNearestNearest = WebGLRenderingContext.NEAREST_MIPMAP_NEAREST,
MipMapLinearNearest = WebGLRenderingContext.LINEAR_MIPMAP_NEAREST,
MipMapNearestLinear = WebGLRenderingContext.NEAREST_MIPMAP_LINEAR,
MipMapLinearLinear = WebGLRenderingContext.LINEAR_MIPMAP_LINEAR
}
export enum TextureWrap {
MirroredRepeat = WebGLRenderingContext.MIRRORED_REPEAT,
ClampToEdge = WebGLRenderingContext.CLAMP_TO_EDGE,
Repeat = WebGLRenderingContext.REPEAT
}
}

View File

@ -0,0 +1,213 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export class TextureAtlas implements Disposable {
pages = new Array<TextureAtlasPage>();
regions = new Array<TextureAtlasRegion>();
constructor (atlasText: string, textureLoader: (path: string) => Texture) {
this.load(atlasText, textureLoader);
}
private load (atlasText: string, textureLoader: (path: string) => Texture) {
if (textureLoader == null)
throw new Error("textureLoader cannot be null.");
let reader = new TextureAtlasReader(atlasText);
let tuple = new Array<string>(4);
let page:TextureAtlasPage = null;
while (true) {
let line = reader.readLine();
if (line == null)
break;
line = line.trim();
if (line.length == 0)
page = null;
else if (!page) {
page = new TextureAtlasPage();
page.name = line;
if (reader.readTuple(tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker.
page.width = parseInt(tuple[0]);
page.height = parseInt(tuple[1]);
reader.readTuple(tuple);
}
// page.format = Format[tuple[0]]; we don't need format in WebGL
reader.readTuple(tuple);
page.minFilter = Texture.filterFromString(tuple[0]);
page.magFilter = Texture.filterFromString(tuple[1]);
let direction= reader.readValue();
page.uWrap = TextureWrap.ClampToEdge;
page.vWrap = TextureWrap.ClampToEdge;
if (direction == "x")
page.uWrap = TextureWrap.Repeat;
else if (direction == "y")
page.vWrap = TextureWrap.Repeat;
else if (direction == "xy")
page.uWrap = page.vWrap = TextureWrap.Repeat;
page.texture = textureLoader(line);
page.texture.setFilters(page.minFilter, page.magFilter);
page.texture.setWraps(page.uWrap, page.vWrap);
page.width = page.texture.getImage().width;
page.height = page.texture.getImage().height;
this.pages.push(page);
} else {
let region:TextureAtlasRegion = new TextureAtlasRegion();
region.name = line;
region.page = page;
region.rotate = reader.readValue() == "true";
reader.readTuple(tuple);
let x = parseInt(tuple[0]);
let y = parseInt(tuple[1]);
reader.readTuple(tuple);
let width = parseInt(tuple[0]);
let height = parseInt(tuple[1]);
region.u = x / page.width;
region.v = y / page.height;
if (region.rotate) {
region.u2 = (x + height) / page.width;
region.v2 = (y + width) / page.height;
} else {
region.u2 = (x + width) / page.width;
region.v2 = (y + height) / page.height;
}
region.x = x;
region.y = y;
region.width = Math.abs(width);
region.height = Math.abs(height);
if (reader.readTuple(tuple) == 4) { // split is optional
// region.splits = new Vector.<int>(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3]));
if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits
//region.pads = Vector.<int>(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3]));
reader.readTuple(tuple);
}
}
region.originalWidth = parseInt(tuple[0]);
region.originalHeight = parseInt(tuple[1]);
reader.readTuple(tuple);
region.offsetX = parseInt(tuple[0]);
region.offsetY = parseInt(tuple[1]);
region.index = parseInt(reader.readValue());
region.texture = page.texture;
this.regions.push(region);
}
}
}
findRegion (name: string): TextureAtlasRegion {
for (let i = 0; i < this.regions.length; i++) {
if (this.regions[i].name == name) {
return this.regions[i];
}
}
return null;
}
dispose () {
for (let i = 0; i < this.pages.length; i++) {
this.pages[i].texture.dispose();
}
}
}
class TextureAtlasReader {
lines: Array<string>;
index: number = 0;
constructor (text: string) {
this.lines = text.split(/\r\n|\r|\n/);
}
readLine (): string {
if (this.index >= this.lines.length)
return null;
return this.lines[this.index++];
}
readValue (): string {
let line = this.readLine();
let colon= line.indexOf(":");
if (colon == -1)
throw new Error("Invalid line: " + line);
return line.substring(colon + 1).trim();
}
readTuple (tuple: Array<string>): number {
let line = this.readLine();
let colon = line.indexOf(":");
if (colon == -1)
throw new Error("Invalid line: " + line);
let i = 0, lastMatch = colon + 1;
for (; i < 3; i++) {
let comma = line.indexOf(",", lastMatch);
if (comma == -1) break;
tuple[i] = line.substr(lastMatch, comma - lastMatch).trim();
lastMatch = comma + 1;
}
tuple[i] = line.substring(lastMatch).trim();
return i + 1;
}
}
export class TextureAtlasPage {
name: string;
minFilter: TextureFilter;
magFilter: TextureFilter;
uWrap: TextureWrap;
vWrap: TextureWrap;
texture: Texture;
width: number;
height: number;
}
export class TextureAtlasRegion extends TextureRegion {
page: TextureAtlasPage;
name: string;
x: number;
y: number;
index: number;
rotate: boolean;
texture: Texture;
}
}

View File

@ -0,0 +1,71 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export class TextureAtlasAttachmentLoader implements AttachmentLoader {
atlas: TextureAtlas;
constructor (atlas: TextureAtlas) {
this.atlas = atlas;
}
/** @return May be null to not load an attachment. */
newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment {
let region = this.atlas.findRegion(path);
region.renderObject = region;
if (region == null) throw new Error("Region not found in atlas: " + path + " (region attachment: " + name + ")");
let attachment = new RegionAttachment(name);
attachment.setRegion(region);
attachment.region = region;
return attachment;
}
/** @return May be null to not load an attachment. */
newMeshAttachment (skin: Skin, name: string, path: string) : MeshAttachment {
let region = this.atlas.findRegion(path);
region.renderObject = region;
if (region == null) throw new Error("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
let attachment = new MeshAttachment(name);
attachment.region = region;
return attachment;
}
/** @return May be null to not load an attachment. */
newBoundingBoxAttachment (skin: Skin, name: string) : BoundingBoxAttachment {
return new BoundingBoxAttachment(name);
}
/** @return May be null to not load an attachment */
newPathAttachment (skin: Skin, name: string): PathAttachment {
return new PathAttachment(name);
}
}
}

View File

@ -0,0 +1,110 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export class Vector3 {
x = 0;
y = 0;
z = 0;
set (x: number, y: number, z: number): Vector3 {
this.x = x;
this.y = y;
this.z = z;
return this;
}
add (v: Vector3): Vector3 {
this.x += v.x;
this.y += v.y;
this.z += v.z;
return this;
}
sub (v: Vector3): Vector3 {
this.x -= v.x;
this.y -= v.y;
this.z -= v.z;
return this;
}
scale (s: number): Vector3 {
this.x *= s;
this.y *= s;
this.z *= s;
return this;
}
normalize (): Vector3 {
let len = this.length();
if (len == 0) return this;
len = 1 / len;
this.x *= len;
this.y *= len;
this.z *= len;
return this;
}
cross (v: Vector3): Vector3 {
return this.set(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x)
}
multiply (matrix: Matrix4): Vector3 {
let l_mat = matrix.values;
return this.set(this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03],
this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13],
this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]);
}
project (matrix: Matrix4): Vector3 {
let l_mat = matrix.values;
let l_w = 1 / (this.x * l_mat[M30] + this.y * l_mat[M31] + this.z * l_mat[M32] + l_mat[M33]);
return this.set((this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03]) * l_w,
(this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13]) * l_w,
(this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]) * l_w);
}
dot (v: Vector3): number {
return this.x * v.x + this.y * v.y + this.z * v.z;
}
length (): number {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
distance (v: Vector3): number {
let a = v.x - this.x;
let b = v.y - this.y;
let c = v.z - this.z;
return Math.sqrt(a * a + b * b + c * c);
}
}
}

View File

@ -0,0 +1,52 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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.webgl {
export function getSourceGLBlendMode (gl: WebGLRenderingContext, blendMode: BlendMode, premultipliedAlpha: boolean = false) {
switch(blendMode) {
case BlendMode.Normal: return premultipliedAlpha? gl.ONE : gl.SRC_ALPHA;
case BlendMode.Additive: return premultipliedAlpha? gl.ONE : gl.SRC_ALPHA;
case BlendMode.Multiply: return gl.DST_COLOR;
case BlendMode.Screen: return gl.ONE;
default: throw new Error("Unknown blend mode: " + blendMode);
}
}
export function getDestGLBlendMode (gl: WebGLRenderingContext, blendMode: BlendMode) {
switch(blendMode) {
case BlendMode.Normal: return gl.ONE_MINUS_SRC_ALPHA;
case BlendMode.Additive: return gl.ONE;
case BlendMode.Multiply: return gl.ONE_MINUS_SRC_ALPHA;
case BlendMode.Screen: return gl.ONE_MINUS_SRC_ALPHA;
default: throw new Error("Unknown blend mode: " + blendMode);
}
}
}

View File

@ -0,0 +1,279 @@
raptor.png
size: 1024,1024
format: RGBA8888
filter: Linear,Linear
repeat: none
back_arm
rotate: true
xy: 140, 191
size: 46, 29
orig: 46, 29
offset: 0, 0
index: -1
back_bracer
rotate: true
xy: 167, 317
size: 39, 28
orig: 39, 28
offset: 0, 0
index: -1
back_hand
rotate: false
xy: 167, 358
size: 36, 34
orig: 36, 34
offset: 0, 0
index: -1
back_knee
rotate: false
xy: 299, 478
size: 49, 67
orig: 49, 67
offset: 0, 0
index: -1
back_thigh
rotate: true
xy: 167, 437
size: 39, 24
orig: 39, 24
offset: 0, 0
index: -1
eyes_closed
rotate: true
xy: 2, 2
size: 47, 45
orig: 47, 45
offset: 0, 0
index: -1
eyes_open
rotate: true
xy: 49, 2
size: 47, 45
orig: 47, 45
offset: 0, 0
index: -1
eyes_surprised
rotate: true
xy: 96, 2
size: 47, 45
orig: 47, 45
offset: 0, 0
index: -1
front_arm
rotate: false
xy: 419, 544
size: 48, 30
orig: 48, 30
offset: 0, 0
index: -1
front_bracer
rotate: false
xy: 880, 695
size: 41, 29
orig: 41, 29
offset: 0, 0
index: -1
front_hand
rotate: true
xy: 167, 394
size: 41, 38
orig: 41, 38
offset: 0, 0
index: -1
front_open_hand
rotate: false
xy: 880, 726
size: 43, 44
orig: 43, 44
offset: 0, 0
index: -1
front_thigh
rotate: false
xy: 360, 545
size: 57, 29
orig: 57, 29
offset: 0, 0
index: -1
gun
rotate: false
xy: 785, 774
size: 107, 103
orig: 107, 103
offset: 0, 0
index: -1
gun_nohand
rotate: false
xy: 614, 703
size: 105, 102
orig: 105, 102
offset: 0, 0
index: -1
head
rotate: false
xy: 2, 137
size: 136, 149
orig: 136, 149
offset: 0, 0
index: -1
lower_leg
rotate: true
xy: 780, 699
size: 73, 98
orig: 73, 98
offset: 0, 0
index: -1
mouth_grind
rotate: false
xy: 469, 544
size: 47, 30
orig: 47, 30
offset: 0, 0
index: -1
mouth_oooo
rotate: true
xy: 894, 772
size: 105, 30
orig: 105, 30
offset: 0, 0
index: -1
mouth_smile
rotate: true
xy: 140, 239
size: 47, 30
orig: 47, 30
offset: 0, 0
index: -1
neck
rotate: true
xy: 538, 577
size: 18, 21
orig: 18, 21
offset: 0, 0
index: -1
raptor_arm_back
rotate: false
xy: 940, 936
size: 82, 86
orig: 82, 86
offset: 0, 0
index: -1
raptor_body
rotate: false
xy: 2, 737
size: 610, 285
orig: 610, 285
offset: 0, 0
index: -1
raptor_front_arm
rotate: true
xy: 195, 464
size: 81, 102
orig: 81, 102
offset: 0, 0
index: -1
raptor_front_leg
rotate: false
xy: 2, 478
size: 191, 257
orig: 191, 257
offset: 0, 0
index: -1
raptor_hindleg_back
rotate: false
xy: 614, 807
size: 169, 215
orig: 169, 215
offset: 0, 0
index: -1
raptor_horn
rotate: false
xy: 360, 655
size: 182, 80
orig: 182, 80
offset: 0, 0
index: -1
raptor_horn_back
rotate: false
xy: 360, 576
size: 176, 77
orig: 176, 77
offset: 0, 0
index: -1
raptor_jaw
rotate: false
xy: 785, 879
size: 153, 143
orig: 153, 143
offset: 0, 0
index: -1
raptor_saddle_noshadow
rotate: false
xy: 2, 288
size: 163, 188
orig: 163, 188
offset: 0, 0
index: -1
raptor_saddle_strap_front
rotate: false
xy: 721, 710
size: 57, 95
orig: 57, 95
offset: 0, 0
index: -1
raptor_saddle_strap_rear
rotate: true
xy: 940, 880
size: 54, 74
orig: 54, 74
offset: 0, 0
index: -1
raptor_saddle_w_shadow
rotate: false
xy: 195, 547
size: 163, 188
orig: 163, 188
offset: 0, 0
index: -1
raptor_tongue
rotate: true
xy: 544, 649
size: 86, 64
orig: 86, 64
offset: 0, 0
index: -1
stirrup_back
rotate: true
xy: 140, 145
size: 44, 35
orig: 44, 35
offset: 0, 0
index: -1
stirrup_front
rotate: false
xy: 538, 597
size: 45, 50
orig: 45, 50
offset: 0, 0
index: -1
stirrup_strap
rotate: false
xy: 350, 497
size: 49, 46
orig: 49, 46
offset: 0, 0
index: -1
torso
rotate: true
xy: 610, 647
size: 54, 91
orig: 54, 91
offset: 0, 0
index: -1
visor
rotate: false
xy: 2, 51
size: 131, 84
orig: 131, 84
offset: 0, 0
index: -1

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

View File

@ -0,0 +1,30 @@
<html>
<script src="../../build/spine-widget.js"></script>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<body>
<center>
<div id="spine-widget" style="margin-bottom: 20px"></div>
<div style="margin-bottom: 20px" class="spine-widget" data-json="assets/raptor.json" data-atlas="assets/raptor.atlas" data-animation="Jump" data-scale="0.25" data-x="40"></div>
<div style="margin-bottom: 20px" class="spine-widget" data-json="assets/raptor.json" data-atlas="assets/raptor.atlas" data-animation="walk" data-scale="0.2" data-width="320" data-height="240" data-background-color="#cc0000"></div>
</center>
</body>
<script>
new spine.SpineWidget("spine-widget", {
json: "assets/raptor.json",
atlas: "assets/raptor.atlas",
animation: "walk",
scale: 0.4,
backgroundColor: "#000000",
success: function (widget) {
var animIndex = 0;
widget.canvas.onclick = function () {
animIndex++;
let animations = widget.skeleton.data.animations;
if (animIndex >= animations.length) animIndex = 0;
widget.setAnimation(animations[animIndex].name);
}
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,261 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.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;
gl: WebGLRenderingContext;
canvas: HTMLCanvasElement;
private _config: SpineWidgetConfig;
private _assetManager: spine.webgl.AssetManager;
private _shader: spine.webgl.Shader;
private _batcher: spine.webgl.PolygonBatcher;
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;
constructor (element: Element | 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 canvas = this.canvas = document.createElement("canvas");
(<Element> element).appendChild(canvas);
canvas.width = config.width;
canvas.height = config.height;
var webglConfig = { alpha: false };
let gl = this.gl = <WebGLRenderingContext> (canvas.getContext("webgl", webglConfig) || canvas.getContext("experimental-webgl", webglConfig));
this._shader = spine.webgl.Shader.newColoredTextured(gl);
this._batcher = new spine.webgl.PolygonBatcher(gl);
this._mvp.ortho2d(0, 0, 639, 479);
this._skeletonRenderer = new spine.webgl.SkeletonRenderer(gl);
let assets = this._assetManager = new spine.webgl.AssetManager(gl);
assets.loadText(config.atlas);
assets.loadText(config.json);
assets.loadTexture(config.atlas.replace(".atlas", ".png"));
requestAnimationFrame(() => { this.load(); });
}
private validateConfig (config: SpineWidgetConfig) {
if (!config.atlas) throw new Error("Please specify config.atlas");
if (!config.json) throw new Error("Please specify config.json");
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.y) config.y = 20;
if (!config.width) config.width = 640;
if (!config.height) config.height = 480;
if (!config.x) config.x = config.width / 2;
if (!config.backgroundColor) config.backgroundColor = "#555555";
if (!config.imagesPath) {
let index = config.atlas.lastIndexOf("/");
if (index != -1) {
config.imagesPath = config.atlas.substr(0, index) + "/";
} else {
config.imagesPath = "";
}
}
if (!config.premultipliedAlpha === undefined) config.premultipliedAlpha = false;
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.errors));
else throw new Error("Failed to load assets: " + JSON.stringify(assetManager.errors));
}
let atlas = new spine.webgl.TextureAtlas(this._assetManager.get(this._config.atlas) as string, (path) => {
return assetManager.get(imagesPath + path) as spine.webgl.Texture;
});
let atlasLoader = new spine.webgl.TextureAtlasAttachmentLoader(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;
var skeletonData = skeletonJson.readSkeletonData(assetManager.get(config.json) as string);
var skeleton = this.skeleton = new spine.Skeleton(skeletonData);
skeleton.x = config.x;
skeleton.y = config.y;
skeleton.setSkinByName(config.skin);
var animationState = this.state = new spine.AnimationState(new spine.AnimationStateData(skeleton.data));
animationState.setAnimation(0, config.animation, true);
if (config.success) config.success(this);
this._loaded = true;
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.gl;
let color = this._backgroundColor;
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();
// Bind the shader and set the texture and model-view-projection matrix.
let shader = this._shader;
shader.bind();
shader.setUniformi(spine.webgl.Shader.SAMPLER, 0);
shader.setUniform4x4f(spine.webgl.Shader.MVP_MATRIX, this._mvp.values);
// Start the batch and tell the SkeletonRenderer to render the active skeleton.
let batcher = this._batcher;
let skeletonRenderer = this._skeletonRenderer;
batcher.begin(shader);
skeletonRenderer.premultipliedAlpha = premultipliedAlpha;
skeletonRenderer.draw(batcher, skeleton);
batcher.end();
shader.unbind();
if (!this._paused) requestAnimationFrame(() => { this.render(); });
}
pause () {
this._paused = true;
}
play () {
this._paused = false;
requestAnimationFrame(() => { this.render(); });
}
isPlaying () {
return !this._paused;
}
setAnimation (animationName: string) {
if (!this._loaded) throw new Error("Widget isn't loaded yet");
this.skeleton.setToSetupPose();
this.state.setAnimation(0, animationName, this._config.loop);
}
static loadWidgets() {
let widgets = document.getElementsByClassName("spine-widget");
for (var i = 0; i < widgets.length; i++) {
SpineWidget.loadWidget(widgets[i]);
}
}
static loadWidget(widget: Element) {
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-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.x = parseFloat(widget.getAttribute("data-y"));
if (widget.getAttribute("data-width")) config.width = parseInt(widget.getAttribute("data-width"));
if (widget.getAttribute("data-height")) config.height = parseInt(widget.getAttribute("data-height"));
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";
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;
atlas: string;
animation: string;
imagesPath: string;
skin = "default";
loop = true;
scale = 1.0;
x = 0;
y = 0;
width = 640;
height = 480;
backgroundColor = "#555555";
premultipliedAlpha = false;
success: (widget: SpineWidget) => void;
error: (widget: SpineWidget, msg: string) => void;
}
}
spine.SpineWidget.setupDOMListener();