mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
Merge branch '4.1-beta' into 4.2-beta
This commit is contained in:
commit
d297e1b4ce
@ -91,10 +91,7 @@ public class SkeletonRenderer {
|
||||
Object[] drawOrder = skeleton.drawOrder.items;
|
||||
for (int i = 0, n = skeleton.drawOrder.size; i < n; i++) {
|
||||
Slot slot = (Slot)drawOrder[i];
|
||||
if (!slot.bone.active) {
|
||||
clipper.clipEnd(slot);
|
||||
continue;
|
||||
}
|
||||
if (!slot.bone.active) continue;
|
||||
Attachment attachment = slot.attachment;
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
RegionAttachment region = (RegionAttachment)attachment;
|
||||
@ -129,8 +126,8 @@ public class SkeletonRenderer {
|
||||
batch.draw(region.getRegion().getTexture(), vertices, 0, 20);
|
||||
|
||||
} else if (attachment instanceof ClippingAttachment) {
|
||||
clipper.clipStart(slot, (ClippingAttachment)attachment);
|
||||
continue;
|
||||
throw new RuntimeException(batch.getClass().getSimpleName()
|
||||
+ " cannot perform clipping, PolygonSpriteBatch or TwoColorPolygonBatch is required.");
|
||||
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
throw new RuntimeException(batch.getClass().getSimpleName()
|
||||
@ -140,10 +137,7 @@ public class SkeletonRenderer {
|
||||
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
|
||||
if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
|
||||
}
|
||||
|
||||
clipper.clipEnd(slot);
|
||||
}
|
||||
clipper.clipEnd();
|
||||
if (vertexEffect != null) vertexEffect.end();
|
||||
}
|
||||
|
||||
|
||||
@ -56,8 +56,8 @@ You can include a module in your project via a `<script>` tag from the [unpkg](h
|
||||
<script src="https://unpkg.com/@esotericsoftware/spine-player@4.0.*/dist/iife/spine-player.js">
|
||||
<link rel="stylesheet" href="https://unpkg.com/@esotericsoftware/spine-player@4.0.*/dist/spine-player.css">
|
||||
|
||||
// spine-ts WebGL
|
||||
<script src="https://unpkg.com/@esotericsoftware/spine-threejs@4.0.*/dist/iife/spine-webgl.js">
|
||||
// spine-ts ThreeJS
|
||||
<script src="https://unpkg.com/@esotericsoftware/spine-threejs@4.0.*/dist/iife/spine-threejs.js">
|
||||
```
|
||||
|
||||
We also provide `js.map` source maps. They will be automatically fetched from unpkg when debugging code of a spine-module in Chrome, Firefox, or Safari, mapping the JavaScript code back to its original TypeScript sources.
|
||||
|
||||
@ -31,7 +31,7 @@ import { AssetManagerBase, Downloader } from "@esotericsoftware/spine-core"
|
||||
import { CanvasTexture } from "./CanvasTexture";
|
||||
|
||||
export class AssetManager extends AssetManagerBase {
|
||||
constructor (pathPrefix: string = "", downloader: Downloader = null) {
|
||||
super((image: HTMLImageElement) => { return new CanvasTexture(image); }, pathPrefix, downloader);
|
||||
constructor (pathPrefix: string = "", downloader: Downloader = new Downloader()) {
|
||||
super((image: HTMLImageElement | ImageBitmap) => { return new CanvasTexture(image); }, pathPrefix, downloader);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
import { Texture, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core";
|
||||
|
||||
export class CanvasTexture extends Texture {
|
||||
constructor (image: HTMLImageElement) {
|
||||
constructor (image: HTMLImageElement | ImageBitmap) {
|
||||
super(image);
|
||||
}
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ export class SkeletonRenderer {
|
||||
|
||||
let w = region.width, h = region.height;
|
||||
ctx.translate(w / 2, h / 2);
|
||||
if (attachment.region.degrees == 90) {
|
||||
if (attachment.region!.degrees == 90) {
|
||||
let t = w;
|
||||
w = h;
|
||||
h = t;
|
||||
@ -107,9 +107,9 @@ export class SkeletonRenderer {
|
||||
let skeletonColor = skeleton.color;
|
||||
let drawOrder = skeleton.drawOrder;
|
||||
|
||||
let blendMode: BlendMode = null;
|
||||
let blendMode: BlendMode | null = null;
|
||||
let vertices: ArrayLike<number> = this.vertices;
|
||||
let triangles: Array<number> = null;
|
||||
let triangles: Array<number> | null = null;
|
||||
|
||||
for (let i = 0, n = drawOrder.length; i < n; i++) {
|
||||
let slot = drawOrder[i];
|
||||
@ -127,7 +127,8 @@ export class SkeletonRenderer {
|
||||
let mesh = <MeshAttachment>attachment;
|
||||
vertices = this.computeMeshVertices(slot, mesh, false);
|
||||
triangles = mesh.triangles;
|
||||
texture = (<TextureAtlasRegion>mesh.region.renderObject).page.texture.getImage() as HTMLImageElement;
|
||||
let region = (<TextureAtlasRegion>mesh.region!.renderObject);
|
||||
texture = region.page.texture!.getImage() as HTMLImageElement;
|
||||
} else
|
||||
continue;
|
||||
|
||||
|
||||
@ -42,8 +42,8 @@ import { SequenceMode, SequenceModeValues } from "./attachments/Sequence";
|
||||
export class Animation {
|
||||
/** The animation's name, which is unique across all animations in the skeleton. */
|
||||
name: string;
|
||||
timelines: Array<Timeline> = null;
|
||||
timelineIds: StringSet = null;
|
||||
timelines: Array<Timeline> = [];
|
||||
timelineIds: StringSet = new StringSet();
|
||||
|
||||
/** The duration of the animation in seconds, which is the highest time of all keys in the timeline. */
|
||||
duration: number;
|
||||
@ -58,7 +58,7 @@ export class Animation {
|
||||
setTimelines (timelines: Array<Timeline>) {
|
||||
if (!timelines) throw new Error("timelines cannot be null.");
|
||||
this.timelines = timelines;
|
||||
this.timelineIds = new StringSet();
|
||||
this.timelineIds.clear();
|
||||
for (var i = 0; i < timelines.length; i++)
|
||||
this.timelineIds.addAll(timelines[i].getPropertyIds());
|
||||
}
|
||||
@ -155,8 +155,8 @@ const Property = {
|
||||
|
||||
/** The interface for all timelines. */
|
||||
export abstract class Timeline {
|
||||
propertyIds: string[] = null;
|
||||
frames: NumberArrayLike = null;
|
||||
propertyIds: string[];
|
||||
frames: NumberArrayLike;
|
||||
|
||||
constructor (frameCount: number, propertyIds: string[]) {
|
||||
this.propertyIds = propertyIds;
|
||||
@ -179,7 +179,7 @@ export abstract class Timeline {
|
||||
return this.frames[this.frames.length - this.getFrameEntries()];
|
||||
}
|
||||
|
||||
abstract apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection): void;
|
||||
abstract apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event> | null, alpha: number, blend: MixBlend, direction: MixDirection): void;
|
||||
|
||||
static search1 (frames: NumberArrayLike, time: number) {
|
||||
let n = frames.length;
|
||||
@ -208,7 +208,7 @@ export interface SlotTimeline {
|
||||
|
||||
/** The base class for timelines that use interpolation between key frame values. */
|
||||
export abstract class CurveTimeline extends Timeline {
|
||||
protected curves: NumberArrayLike = null; // type, x, y, ...
|
||||
protected curves: NumberArrayLike; // type, x, y, ...
|
||||
|
||||
constructor (frameCount: number, bezierCount: number, propertyIds: string[]) {
|
||||
super(frameCount, propertyIds);
|
||||
@ -369,7 +369,7 @@ export class RotateTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
this.boneIndex = boneIndex;
|
||||
}
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event> | null, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let bone = skeleton.bones[this.boneIndex];
|
||||
if (!bone.active) return;
|
||||
|
||||
@ -1176,9 +1176,9 @@ export class RGBA2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
if (!slot.bone.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
let light = slot.color, dark = slot.darkColor;
|
||||
let light = slot.color, dark = slot.darkColor!;
|
||||
if (time < frames[0]) {
|
||||
let setupLight = slot.data.color, setupDark = slot.data.darkColor;
|
||||
let setupLight = slot.data.color, setupDark = slot.data.darkColor!;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
light.setFromColor(setupLight);
|
||||
@ -1245,7 +1245,7 @@ export class RGBA2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
} else {
|
||||
if (blend == MixBlend.setup) {
|
||||
light.setFromColor(slot.data.color);
|
||||
let setupDark = slot.data.darkColor;
|
||||
let setupDark = slot.data.darkColor!;
|
||||
dark.r = setupDark.r;
|
||||
dark.g = setupDark.g;
|
||||
dark.b = setupDark.b;
|
||||
@ -1291,9 +1291,9 @@ export class RGB2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
if (!slot.bone.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
let light = slot.color, dark = slot.darkColor;
|
||||
let light = slot.color, dark = slot.darkColor!;
|
||||
if (time < frames[0]) {
|
||||
let setupLight = slot.data.color, setupDark = slot.data.darkColor;
|
||||
let setupLight = slot.data.color, setupDark = slot.data.darkColor!;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
light.r = setupLight.r;
|
||||
@ -1360,7 +1360,7 @@ export class RGB2Timeline extends CurveTimeline implements SlotTimeline {
|
||||
dark.b = b2;
|
||||
} else {
|
||||
if (blend == MixBlend.setup) {
|
||||
let setupLight = slot.data.color, setupDark = slot.data.darkColor;
|
||||
let setupLight = slot.data.color, setupDark = slot.data.darkColor!;
|
||||
light.r = setupLight.r;
|
||||
light.g = setupLight.g;
|
||||
light.b = setupLight.b;
|
||||
@ -1383,7 +1383,7 @@ export class AttachmentTimeline extends Timeline implements SlotTimeline {
|
||||
slotIndex = 0;
|
||||
|
||||
/** The attachment name for each key frame. May contain null values to clear the attachment. */
|
||||
attachmentNames: Array<string>;
|
||||
attachmentNames: Array<string | null>;
|
||||
|
||||
constructor (frameCount: number, slotIndex: number) {
|
||||
super(frameCount, [
|
||||
@ -1398,7 +1398,7 @@ export class AttachmentTimeline extends Timeline implements SlotTimeline {
|
||||
}
|
||||
|
||||
/** Sets the time in seconds and the attachment name for the specified key frame. */
|
||||
setFrame (frame: number, time: number, attachmentName: string) {
|
||||
setFrame (frame: number, time: number, attachmentName: string | null) {
|
||||
this.frames[frame] = time;
|
||||
this.attachmentNames[frame] = attachmentName;
|
||||
}
|
||||
@ -1420,7 +1420,7 @@ export class AttachmentTimeline extends Timeline implements SlotTimeline {
|
||||
this.setAttachment(skeleton, slot, this.attachmentNames[Timeline.search1(this.frames, time)]);
|
||||
}
|
||||
|
||||
setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string) {
|
||||
setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string | null) {
|
||||
slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(this.slotIndex, attachmentName));
|
||||
}
|
||||
}
|
||||
@ -1430,10 +1430,10 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
slotIndex = 0;
|
||||
|
||||
/** The attachment that will be deformed. */
|
||||
attachment: VertexAttachment = null;
|
||||
attachment: VertexAttachment;
|
||||
|
||||
/** The vertices for each key frame. */
|
||||
vertices: Array<NumberArrayLike> = null;
|
||||
vertices: Array<NumberArrayLike>;
|
||||
|
||||
constructor (frameCount: number, bezierCount: number, slotIndex: number, attachment: VertexAttachment) {
|
||||
super(frameCount, bezierCount, [
|
||||
@ -1508,7 +1508,8 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let slot: Slot = skeleton.slots[this.slotIndex];
|
||||
if (!slot.bone.active) return;
|
||||
let slotAttachment: Attachment = slot.getAttachment();
|
||||
let slotAttachment: Attachment | null = slot.getAttachment();
|
||||
if (!slotAttachment) return;
|
||||
if (!(slotAttachment instanceof VertexAttachment) || (<VertexAttachment>slotAttachment).timelineAttahment != this.attachment) return;
|
||||
|
||||
let deform: Array<number> = slot.deform;
|
||||
@ -1685,7 +1686,7 @@ export class EventTimeline extends Timeline {
|
||||
static propertyIds = ["" + Property.event];
|
||||
|
||||
/** The event for each key frame. */
|
||||
events: Array<Event> = null;
|
||||
events: Array<Event>;
|
||||
|
||||
constructor (frameCount: number) {
|
||||
super(frameCount, EventTimeline.propertyIds);
|
||||
@ -1738,11 +1739,11 @@ export class DrawOrderTimeline extends Timeline {
|
||||
static propertyIds = ["" + Property.drawOrder];
|
||||
|
||||
/** The draw order for each key frame. See {@link #setFrame(int, float, int[])}. */
|
||||
drawOrders: Array<Array<number>> = null;
|
||||
drawOrders: Array<Array<number> | null>;
|
||||
|
||||
constructor (frameCount: number) {
|
||||
super(frameCount, DrawOrderTimeline.propertyIds);
|
||||
this.drawOrders = new Array<Array<number>>(frameCount);
|
||||
this.drawOrders = new Array<Array<number> | null>(frameCount);
|
||||
}
|
||||
|
||||
getFrameCount () {
|
||||
@ -1752,7 +1753,7 @@ export class DrawOrderTimeline extends Timeline {
|
||||
/** Sets the time in seconds and the draw order for the specified key frame.
|
||||
* @param drawOrder For each slot in {@link Skeleton#slots}, the index of the new draw order. May be null to use setup pose
|
||||
* draw order. */
|
||||
setFrame (frame: number, time: number, drawOrder: Array<number>) {
|
||||
setFrame (frame: number, time: number, drawOrder: Array<number> | null) {
|
||||
this.frames[frame] = time;
|
||||
this.drawOrders[frame] = drawOrder;
|
||||
}
|
||||
@ -1768,7 +1769,8 @@ export class DrawOrderTimeline extends Timeline {
|
||||
return;
|
||||
}
|
||||
|
||||
let drawOrderToSetupIndex = this.drawOrders[Timeline.search1(this.frames, time)];
|
||||
let idx = Timeline.search1(this.frames, time);
|
||||
let drawOrderToSetupIndex = this.drawOrders[idx];
|
||||
if (!drawOrderToSetupIndex)
|
||||
Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length);
|
||||
else {
|
||||
@ -2157,7 +2159,7 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
|
||||
constructor (frameCount: number, slotIndex: number, attachment: HasTextureRegion) {
|
||||
super(frameCount, [
|
||||
Property.sequence + "|" + slotIndex + "|" + attachment.sequence.id
|
||||
Property.sequence + "|" + slotIndex + "|" + attachment.sequence!.id
|
||||
]);
|
||||
this.slotIndex = slotIndex;
|
||||
this.attachment = attachment;
|
||||
@ -2207,7 +2209,7 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
let modeAndIndex = frames[i + SequenceTimeline.MODE];
|
||||
let delay = frames[i + SequenceTimeline.DELAY];
|
||||
|
||||
let index = modeAndIndex >> 4, count = this.attachment.sequence.regions.length;
|
||||
let index = modeAndIndex >> 4, count = this.attachment.sequence!.regions.length;
|
||||
let mode = SequenceModeValues[modeAndIndex & 0xf];
|
||||
if (mode != SequenceMode.hold) {
|
||||
index += (((time - before) / delay + 0.00001) | 0);
|
||||
|
||||
@ -40,16 +40,16 @@ import { Event } from "./Event";
|
||||
*
|
||||
* See [Applying Animations](http://esotericsoftware.com/spine-applying-animations/) in the Spine Runtimes Guide. */
|
||||
export class AnimationState {
|
||||
static _emptyAnimation = new Animation("<empty>", [], 0);
|
||||
private static emptyAnimation (): Animation {
|
||||
if (!_emptyAnimation) _emptyAnimation = new Animation("<empty>", [], 0);
|
||||
return _emptyAnimation;
|
||||
return AnimationState._emptyAnimation;
|
||||
}
|
||||
|
||||
/** The AnimationStateData to look up mix durations. */
|
||||
data: AnimationStateData = null;
|
||||
data: AnimationStateData;
|
||||
|
||||
/** The list of tracks that currently have animations, which may contain null entries. */
|
||||
tracks = new Array<TrackEntry>();
|
||||
tracks = new Array<TrackEntry | null>();
|
||||
|
||||
/** Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower
|
||||
* or faster. Defaults to 1.
|
||||
@ -113,7 +113,7 @@ export class AnimationState {
|
||||
}
|
||||
if (current.mixingFrom && this.updateMixingFrom(current, delta)) {
|
||||
// End mixing from entries once all have completed.
|
||||
let from = current.mixingFrom;
|
||||
let from: TrackEntry | null = current.mixingFrom;
|
||||
current.mixingFrom = null;
|
||||
if (from) from.mixingTo = null;
|
||||
while (from) {
|
||||
@ -181,12 +181,12 @@ export class AnimationState {
|
||||
|
||||
// Apply current entry.
|
||||
let animationLast = current.animationLast, animationTime = current.getAnimationTime(), applyTime = animationTime;
|
||||
let applyEvents = events;
|
||||
let applyEvents: Event[] | null = events;
|
||||
if (current.reverse) {
|
||||
applyTime = current.animation.duration - applyTime;
|
||||
applyTime = current.animation!.duration - applyTime;
|
||||
applyEvents = null;
|
||||
}
|
||||
let timelines = current.animation.timelines;
|
||||
let timelines = current.animation!.timelines;
|
||||
let timelineCount = timelines.length;
|
||||
if ((i == 0 && mix == 1) || blend == MixBlend.add) {
|
||||
for (let ii = 0; ii < timelineCount; ii++) {
|
||||
@ -246,7 +246,7 @@ export class AnimationState {
|
||||
}
|
||||
|
||||
applyMixingFrom (to: TrackEntry, skeleton: Skeleton, blend: MixBlend) {
|
||||
let from = to.mixingFrom;
|
||||
let from = to.mixingFrom!;
|
||||
if (from.mixingFrom) this.applyMixingFrom(from, skeleton, blend);
|
||||
|
||||
let mix = 0;
|
||||
@ -260,13 +260,13 @@ export class AnimationState {
|
||||
}
|
||||
|
||||
let attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
|
||||
let timelines = from.animation.timelines;
|
||||
let timelines = from.animation!.timelines;
|
||||
let timelineCount = timelines.length;
|
||||
let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
|
||||
let animationLast = from.animationLast, animationTime = from.getAnimationTime(), applyTime = animationTime;
|
||||
let events = null;
|
||||
if (from.reverse)
|
||||
applyTime = from.animation.duration - applyTime;
|
||||
applyTime = from.animation!.duration - applyTime;
|
||||
else if (mix < from.eventThreshold)
|
||||
events = this.events;
|
||||
|
||||
@ -349,7 +349,7 @@ export class AnimationState {
|
||||
if (slot.attachmentState <= this.unkeyedState) slot.attachmentState = this.unkeyedState + SETUP;
|
||||
}
|
||||
|
||||
setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string, attachments: boolean) {
|
||||
setAttachment (skeleton: Skeleton, slot: Slot, attachmentName: string | null, attachments: boolean) {
|
||||
slot.setAttachment(!attachmentName ? null : skeleton.getAttachment(slot.data.index, attachmentName));
|
||||
if (attachments) slot.attachmentState = this.unkeyedState + CURRENT;
|
||||
}
|
||||
@ -645,7 +645,7 @@ export class AnimationState {
|
||||
}
|
||||
|
||||
/** @param last May be null. */
|
||||
trackEntry (trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry) {
|
||||
trackEntry (trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry | null) {
|
||||
let entry = this.trackEntryPool.obtain();
|
||||
entry.reset();
|
||||
entry.trackIndex = trackIndex;
|
||||
@ -674,7 +674,7 @@ export class AnimationState {
|
||||
|
||||
entry.alpha = 1;
|
||||
entry.mixTime = 0;
|
||||
entry.mixDuration = !last ? 0 : this.data.getMix(last.animation, animation);
|
||||
entry.mixDuration = !last ? 0 : this.data.getMix(last.animation!, animation);
|
||||
entry.interruptAlpha = 1;
|
||||
entry.totalAlpha = 0;
|
||||
entry.mixBlend = MixBlend.replace;
|
||||
@ -710,8 +710,8 @@ export class AnimationState {
|
||||
|
||||
computeHold (entry: TrackEntry) {
|
||||
let to = entry.mixingTo;
|
||||
let timelines = entry.animation.timelines;
|
||||
let timelinesCount = entry.animation.timelines.length;
|
||||
let timelines = entry.animation!.timelines;
|
||||
let timelinesCount = entry.animation!.timelines.length;
|
||||
let timelineMode = entry.timelineMode;
|
||||
timelineMode.length = timelinesCount;
|
||||
let timelineHoldMix = entry.timelineHoldMix;
|
||||
@ -731,11 +731,11 @@ export class AnimationState {
|
||||
if (!propertyIDs.addAll(ids))
|
||||
timelineMode[i] = SUBSEQUENT;
|
||||
else if (!to || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline
|
||||
|| timeline instanceof EventTimeline || !to.animation.hasTimeline(ids)) {
|
||||
|| timeline instanceof EventTimeline || !to.animation!.hasTimeline(ids)) {
|
||||
timelineMode[i] = FIRST;
|
||||
} else {
|
||||
for (let next = to.mixingTo; next; next = next.mixingTo) {
|
||||
if (next.animation.hasTimeline(ids)) continue;
|
||||
for (let next = to.mixingTo; next; next = next!.mixingTo) {
|
||||
if (next.animation!.hasTimeline(ids)) continue;
|
||||
if (entry.mixDuration > 0) {
|
||||
timelineMode[i] = HOLD_MIX;
|
||||
timelineHoldMix[i] = next;
|
||||
@ -784,26 +784,26 @@ export class AnimationState {
|
||||
* References to a track entry must not be kept after the {@link AnimationStateListener#dispose()} event occurs. */
|
||||
export class TrackEntry {
|
||||
/** The animation to apply for this track entry. */
|
||||
animation: Animation = null;
|
||||
animation: Animation | null = null;
|
||||
|
||||
previous: TrackEntry = null;
|
||||
previous: TrackEntry | null = null;
|
||||
|
||||
/** The animation queued to start after this animation, or null. `next` makes up a linked list. */
|
||||
next: TrackEntry = null;
|
||||
next: TrackEntry | null = null;
|
||||
|
||||
/** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no
|
||||
* mixing is currently occuring. When mixing from multiple animations, `mixingFrom` makes up a linked list. */
|
||||
mixingFrom: TrackEntry = null;
|
||||
mixingFrom: TrackEntry | null = null;
|
||||
|
||||
/** The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is
|
||||
* currently occuring. When mixing to multiple animations, `mixingTo` makes up a linked list. */
|
||||
mixingTo: TrackEntry = null;
|
||||
mixingTo: TrackEntry | null = null;
|
||||
|
||||
/** The listener for events generated by this track entry, or null.
|
||||
*
|
||||
* A track entry returned from {@link AnimationState#setAnimation()} is already the current animation
|
||||
* for the track, so the track entry listener {@link AnimationStateListener#start()} will not be called. */
|
||||
listener: AnimationStateListener = null;
|
||||
listener: AnimationStateListener | null = null;
|
||||
|
||||
/** The index of the track where this track entry is either current or queued.
|
||||
*
|
||||
@ -999,7 +999,7 @@ export class TrackEntry {
|
||||
export class EventQueue {
|
||||
objects: Array<any> = [];
|
||||
drainDisabled = false;
|
||||
animState: AnimationState = null;
|
||||
animState: AnimationState;
|
||||
|
||||
constructor (animState: AnimationState) {
|
||||
this.animState = animState;
|
||||
@ -1051,35 +1051,47 @@ export class EventQueue {
|
||||
switch (type) {
|
||||
case EventType.start:
|
||||
if (entry.listener && entry.listener.start) entry.listener.start(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++)
|
||||
if (listeners[ii].start) listeners[ii].start(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++) {
|
||||
let listener = listeners[ii];
|
||||
if (listener.start) listener.start(entry);
|
||||
}
|
||||
break;
|
||||
case EventType.interrupt:
|
||||
if (entry.listener && entry.listener.interrupt) entry.listener.interrupt(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++)
|
||||
if (listeners[ii].interrupt) listeners[ii].interrupt(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++) {
|
||||
let listener = listeners[ii];
|
||||
if (listener.interrupt) listener.interrupt(entry);
|
||||
}
|
||||
break;
|
||||
case EventType.end:
|
||||
if (entry.listener && entry.listener.end) entry.listener.end(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++)
|
||||
if (listeners[ii].end) listeners[ii].end(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++) {
|
||||
let listener = listeners[ii];
|
||||
if (listener.end) listener.end(entry);
|
||||
}
|
||||
// Fall through.
|
||||
case EventType.dispose:
|
||||
if (entry.listener && entry.listener.dispose) entry.listener.dispose(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++)
|
||||
if (listeners[ii].dispose) listeners[ii].dispose(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++) {
|
||||
let listener = listeners[ii];
|
||||
if (listener.dispose) listener.dispose(entry);
|
||||
}
|
||||
this.animState.trackEntryPool.free(entry);
|
||||
break;
|
||||
case EventType.complete:
|
||||
if (entry.listener && entry.listener.complete) entry.listener.complete(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++)
|
||||
if (listeners[ii].complete) listeners[ii].complete(entry);
|
||||
for (let ii = 0; ii < listeners.length; ii++) {
|
||||
let listener = listeners[ii];
|
||||
if (listener.complete) listener.complete(entry);
|
||||
}
|
||||
break;
|
||||
case EventType.event:
|
||||
let event = objects[i++ + 2] as Event;
|
||||
if (entry.listener && entry.listener.event) entry.listener.event(entry, event);
|
||||
for (let ii = 0; ii < listeners.length; ii++)
|
||||
if (listeners[ii].event) listeners[ii].event(entry, event);
|
||||
for (let ii = 0; ii < listeners.length; ii++) {
|
||||
let listener = listeners[ii];
|
||||
if (listener.event) listener.event(entry, event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1104,24 +1116,24 @@ export enum EventType {
|
||||
* {@link AnimationState#addListener()}. */
|
||||
export interface AnimationStateListener {
|
||||
/** Invoked when this entry has been set as the current entry. */
|
||||
start?(entry: TrackEntry): void;
|
||||
start?: (entry: TrackEntry) => void;
|
||||
|
||||
/** Invoked when another entry has replaced this entry as the current entry. This entry may continue being applied for
|
||||
* mixing. */
|
||||
interrupt?(entry: TrackEntry): void;
|
||||
interrupt?: (entry: TrackEntry) => void;
|
||||
|
||||
/** Invoked when this entry is no longer the current entry and will never be applied again. */
|
||||
end?(entry: TrackEntry): void;
|
||||
end?: (entry: TrackEntry) => void;
|
||||
|
||||
/** Invoked when this entry will be disposed. This may occur without the entry ever being set as the current entry.
|
||||
* References to the entry should not be kept after dispose is called, as it may be destroyed or reused. */
|
||||
dispose?(entry: TrackEntry): void;
|
||||
dispose?: (entry: TrackEntry) => void;
|
||||
|
||||
/** Invoked every time this entry's animation completes a loop. */
|
||||
complete?(entry: TrackEntry): void;
|
||||
complete?: (entry: TrackEntry) => void;
|
||||
|
||||
/** Invoked when this entry's animation triggers an event. */
|
||||
event?(entry: TrackEntry, event: Event): void;
|
||||
event?: (entry: TrackEntry, event: Event) => void;
|
||||
}
|
||||
|
||||
export abstract class AnimationStateAdapter implements AnimationStateListener {
|
||||
@ -1181,5 +1193,3 @@ export const HOLD_MIX = 4;
|
||||
|
||||
export const SETUP = 1;
|
||||
export const CURRENT = 2;
|
||||
|
||||
let _emptyAnimation: Animation = null;
|
||||
|
||||
@ -35,7 +35,7 @@ import { StringMap } from "./Utils";
|
||||
/** Stores mix (crossfade) durations to be applied when {@link AnimationState} animations are changed. */
|
||||
export class AnimationStateData {
|
||||
/** The SkeletonData to look up animations when they are specified by name. */
|
||||
skeletonData: SkeletonData = null;
|
||||
skeletonData: SkeletonData;
|
||||
|
||||
animationToMixTime: StringMap<number> = {};
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ import { TextureAtlas } from "./TextureAtlas";
|
||||
import { Disposable, StringMap } from "./Utils";
|
||||
|
||||
export class AssetManagerBase implements Disposable {
|
||||
private pathPrefix: string = null;
|
||||
private pathPrefix: string = "";
|
||||
private textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture;
|
||||
private downloader: Downloader;
|
||||
private assets: StringMap<any> = {};
|
||||
@ -40,10 +40,10 @@ export class AssetManagerBase implements Disposable {
|
||||
private toLoad = 0;
|
||||
private loaded = 0;
|
||||
|
||||
constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader: Downloader = null) {
|
||||
constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader: Downloader = new Downloader()) {
|
||||
this.textureLoader = textureLoader;
|
||||
this.pathPrefix = pathPrefix;
|
||||
this.downloader = downloader || new Downloader();
|
||||
this.downloader = downloader;
|
||||
}
|
||||
|
||||
private start (path: string): string {
|
||||
@ -85,8 +85,8 @@ export class AssetManagerBase implements Disposable {
|
||||
}
|
||||
|
||||
loadBinary (path: string,
|
||||
success: (path: string, binary: Uint8Array) => void = null,
|
||||
error: (path: string, message: string) => void = null) {
|
||||
success: (path: string, binary: Uint8Array) => void = () => { },
|
||||
error: (path: string, message: string) => void = () => { }) {
|
||||
path = this.start(path);
|
||||
|
||||
this.downloader.downloadBinary(path, (data: Uint8Array): void => {
|
||||
@ -97,8 +97,8 @@ export class AssetManagerBase implements Disposable {
|
||||
}
|
||||
|
||||
loadText (path: string,
|
||||
success: (path: string, text: string) => void = null,
|
||||
error: (path: string, message: string) => void = null) {
|
||||
success: (path: string, text: string) => void = () => { },
|
||||
error: (path: string, message: string) => void = () => { }) {
|
||||
path = this.start(path);
|
||||
|
||||
this.downloader.downloadText(path, (data: string): void => {
|
||||
@ -109,8 +109,8 @@ export class AssetManagerBase implements Disposable {
|
||||
}
|
||||
|
||||
loadJson (path: string,
|
||||
success: (path: string, object: object) => void = null,
|
||||
error: (path: string, message: string) => void = null) {
|
||||
success: (path: string, object: object) => void = () => { },
|
||||
error: (path: string, message: string) => void = () => { }) {
|
||||
path = this.start(path);
|
||||
|
||||
this.downloader.downloadJson(path, (data: object): void => {
|
||||
@ -121,8 +121,8 @@ export class AssetManagerBase implements Disposable {
|
||||
}
|
||||
|
||||
loadTexture (path: string,
|
||||
success: (path: string, texture: Texture) => void = null,
|
||||
error: (path: string, message: string) => void = null) {
|
||||
success: (path: string, texture: Texture) => void = () => { },
|
||||
error: (path: string, message: string) => void = () => { }) {
|
||||
path = this.start(path);
|
||||
|
||||
let isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document);
|
||||
@ -152,9 +152,9 @@ export class AssetManagerBase implements Disposable {
|
||||
}
|
||||
|
||||
loadTextureAtlas (path: string,
|
||||
success: (path: string, atlas: TextureAtlas) => void = null,
|
||||
error: (path: string, message: string) => void = null,
|
||||
fileAlias: { [keyword: string]: string } = null
|
||||
success: (path: string, atlas: TextureAtlas) => void = () => { },
|
||||
error: (path: string, message: string) => void = () => { },
|
||||
fileAlias?: { [keyword: string]: string }
|
||||
) {
|
||||
let index = path.lastIndexOf("/");
|
||||
let parent = index >= 0 ? path.substring(0, index + 1) : "";
|
||||
@ -165,7 +165,7 @@ export class AssetManagerBase implements Disposable {
|
||||
let atlas = new TextureAtlas(atlasText);
|
||||
let toLoad = atlas.pages.length, abort = false;
|
||||
for (let page of atlas.pages) {
|
||||
this.loadTexture(fileAlias == null ? parent + page.name : fileAlias[page.name],
|
||||
this.loadTexture(!fileAlias ? parent + page.name : fileAlias[page.name!],
|
||||
(imagePath: string, texture: Texture) => {
|
||||
if (!abort) {
|
||||
page.setTexture(texture);
|
||||
@ -179,7 +179,7 @@ export class AssetManagerBase implements Disposable {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.error(error, path, `Couldn't parse texture atlas ${path}: ${e.message}`);
|
||||
this.error(error, path, `Couldn't parse texture atlas ${path}: ${(e as any).message}`);
|
||||
}
|
||||
}, (status: number, responseText: string): void => {
|
||||
this.error(error, path, `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`);
|
||||
|
||||
@ -43,7 +43,7 @@ import { Sequence } from "./attachments/Sequence"
|
||||
* See [Loading skeleton data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the
|
||||
* Spine Runtimes Guide. */
|
||||
export class AtlasAttachmentLoader implements AttachmentLoader {
|
||||
atlas: TextureAtlas = null;
|
||||
atlas: TextureAtlas;
|
||||
|
||||
constructor (atlas: TextureAtlas) {
|
||||
this.atlas = atlas;
|
||||
@ -53,14 +53,15 @@ export class AtlasAttachmentLoader implements AttachmentLoader {
|
||||
let regions = sequence.regions;
|
||||
for (let i = 0, n = regions.length; i < n; i++) {
|
||||
let path = sequence.getPath(basePath, i);
|
||||
regions[i] = this.atlas.findRegion(path);
|
||||
let region = this.atlas.findRegion(path);
|
||||
if (region == null) throw new Error("Region not found in atlas: " + path + " (sequence: " + name + ")");
|
||||
regions[i] = region;
|
||||
regions[i].renderObject = regions[i];
|
||||
if (regions[i] == null) throw new Error("Region not found in atlas: " + path + " (sequence: " + name + ")");
|
||||
}
|
||||
}
|
||||
|
||||
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment {
|
||||
let attachment = new RegionAttachment(name);
|
||||
let attachment = new RegionAttachment(name, path);
|
||||
if (sequence != null) {
|
||||
this.loadSequence(name, path, sequence);
|
||||
} else {
|
||||
@ -73,7 +74,7 @@ export class AtlasAttachmentLoader implements AttachmentLoader {
|
||||
}
|
||||
|
||||
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment {
|
||||
let attachment = new MeshAttachment(name);
|
||||
let attachment = new MeshAttachment(name, path);
|
||||
if (sequence != null) {
|
||||
this.loadSequence(name, path, sequence);
|
||||
} else {
|
||||
|
||||
@ -39,13 +39,13 @@ import { MathUtils, Vector2 } from "./Utils";
|
||||
* constraint or application code modifies the world transform after it was computed from the local transform. */
|
||||
export class Bone implements Updatable {
|
||||
/** The bone's setup pose data. */
|
||||
data: BoneData = null;
|
||||
data: BoneData;
|
||||
|
||||
/** The skeleton this bone belongs to. */
|
||||
skeleton: Skeleton = null;
|
||||
skeleton: Skeleton;
|
||||
|
||||
/** The parent bone, or null if this is the root bone. */
|
||||
parent: Bone = null;
|
||||
parent: Bone | null = null;
|
||||
|
||||
/** The immediate children of this bone. */
|
||||
children = new Array<Bone>();
|
||||
@ -114,7 +114,7 @@ export class Bone implements Updatable {
|
||||
active = false;
|
||||
|
||||
/** @param parent May be null. */
|
||||
constructor (data: BoneData, skeleton: Skeleton, parent: Bone) {
|
||||
constructor (data: BoneData, skeleton: Skeleton, parent: Bone | null) {
|
||||
if (!data) throw new Error("data cannot be null.");
|
||||
if (!skeleton) throw new Error("skeleton cannot be null.");
|
||||
this.data = data;
|
||||
|
||||
@ -35,10 +35,10 @@ export class BoneData {
|
||||
index: number = 0;
|
||||
|
||||
/** The name of the bone, which is unique across all bones in the skeleton. */
|
||||
name: string = null;
|
||||
name: string;
|
||||
|
||||
/** @returns May be null. */
|
||||
parent: BoneData = null;
|
||||
parent: BoneData | null = null;
|
||||
|
||||
/** The bone's length. */
|
||||
length: number = 0;
|
||||
@ -76,7 +76,7 @@ export class BoneData {
|
||||
* rendered at runtime. */
|
||||
color = new Color();
|
||||
|
||||
constructor (index: number, name: string, parent: BoneData) {
|
||||
constructor (index: number, name: string, parent: BoneData | null) {
|
||||
if (index < 0) throw new Error("index must be >= 0.");
|
||||
if (!name) throw new Error("name cannot be null.");
|
||||
this.index = index;
|
||||
|
||||
@ -35,10 +35,10 @@ import { EventData } from "./EventData";
|
||||
* AnimationStateListener {@link AnimationStateListener#event()}, and
|
||||
* [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */
|
||||
export class Event {
|
||||
data: EventData = null;
|
||||
data: EventData;
|
||||
intValue: number = 0;
|
||||
floatValue: number = 0;
|
||||
stringValue: string = null;
|
||||
stringValue: string | null = null;
|
||||
time: number = 0;
|
||||
volume: number = 0;
|
||||
balance: number = 0;
|
||||
|
||||
@ -31,11 +31,11 @@
|
||||
*
|
||||
* See [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */
|
||||
export class EventData {
|
||||
name: string = null;
|
||||
name: string;
|
||||
intValue: number = 0;
|
||||
floatValue: number = 0;
|
||||
stringValue: string = null;
|
||||
audioPath: string = null;
|
||||
stringValue: string | null = null;
|
||||
audioPath: string | null = null;
|
||||
volume: number = 0;
|
||||
balance: number = 0;
|
||||
|
||||
|
||||
@ -40,13 +40,13 @@ import { MathUtils } from "./Utils";
|
||||
* See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */
|
||||
export class IkConstraint implements Updatable {
|
||||
/** The IK constraint's setup pose data. */
|
||||
data: IkConstraintData = null;
|
||||
data: IkConstraintData;
|
||||
|
||||
/** The bones that will be modified by this IK constraint. */
|
||||
bones: Array<Bone> = null;
|
||||
bones: Array<Bone>;
|
||||
|
||||
/** The bone that is the IK target. */
|
||||
target: Bone = null;
|
||||
target: Bone;
|
||||
|
||||
/** Controls the bend direction of the IK bones, either 1 or -1. */
|
||||
bendDirection = 0;
|
||||
@ -76,9 +76,14 @@ export class IkConstraint implements Updatable {
|
||||
this.stretch = data.stretch;
|
||||
|
||||
this.bones = new Array<Bone>();
|
||||
for (let i = 0; i < data.bones.length; i++)
|
||||
this.bones.push(skeleton.findBone(data.bones[i].name));
|
||||
this.target = skeleton.findBone(data.target.name);
|
||||
for (let i = 0; i < data.bones.length; i++) {
|
||||
let bone = skeleton.findBone(data.bones[i].name);
|
||||
if (!bone) throw new Error(`Couldn't find bone ${data.bones[i].name}`);
|
||||
this.bones.push(bone);
|
||||
}
|
||||
let target = skeleton.findBone(data.target.name);
|
||||
if (!target) throw new Error(`Couldn't find bone ${data.target.name}`);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
isActive () {
|
||||
@ -102,6 +107,7 @@ export class IkConstraint implements Updatable {
|
||||
/** Applies 1 bone IK. The target is specified in the world coordinate system. */
|
||||
apply1 (bone: Bone, targetX: number, targetY: number, compress: boolean, stretch: boolean, uniform: boolean, alpha: number) {
|
||||
let p = bone.parent;
|
||||
if (!p) throw new Error("IK bone must have parent.");
|
||||
let pa = p.a, pb = p.b, pc = p.c, pd = p.d;
|
||||
let rotationIK = -bone.ashearX - bone.arotation, tx = 0, ty = 0;
|
||||
|
||||
@ -183,6 +189,7 @@ export class IkConstraint implements Updatable {
|
||||
cwy = c * cx + d * cy + parent.worldY;
|
||||
}
|
||||
let pp = parent.parent;
|
||||
if (!pp) throw new Error("IK parent must itself have a parent.");
|
||||
a = pp.a;
|
||||
b = pp.b;
|
||||
c = pp.c;
|
||||
|
||||
@ -39,7 +39,12 @@ export class IkConstraintData extends ConstraintData {
|
||||
bones = new Array<BoneData>();
|
||||
|
||||
/** The bone that is the IK target. */
|
||||
target: BoneData = null;
|
||||
private _target: BoneData | null = null;
|
||||
public set target (boneData: BoneData) { this._target = boneData; }
|
||||
public get target () {
|
||||
if (!this._target) throw new Error("BoneData not set.")
|
||||
else return this._target;
|
||||
}
|
||||
|
||||
/** Controls the bend direction of the IK bones, either 1 or -1. */
|
||||
bendDirection = 1;
|
||||
|
||||
@ -45,13 +45,13 @@ export class PathConstraint implements Updatable {
|
||||
static epsilon = 0.00001;
|
||||
|
||||
/** The path constraint's setup pose data. */
|
||||
data: PathConstraintData = null;
|
||||
data: PathConstraintData;
|
||||
|
||||
/** The bones that will be modified by this path constraint. */
|
||||
bones: Array<Bone> = null;
|
||||
bones: Array<Bone>;
|
||||
|
||||
/** The slot whose path attachment will be used to constrained the bones. */
|
||||
target: Slot = null;
|
||||
target: Slot;
|
||||
|
||||
/** The position along the path. */
|
||||
position = 0;
|
||||
@ -76,9 +76,14 @@ export class PathConstraint implements Updatable {
|
||||
if (!skeleton) throw new Error("skeleton cannot be null.");
|
||||
this.data = data;
|
||||
this.bones = new Array<Bone>();
|
||||
for (let i = 0, n = data.bones.length; i < n; i++)
|
||||
this.bones.push(skeleton.findBone(data.bones[i].name));
|
||||
this.target = skeleton.findSlot(data.target.name);
|
||||
for (let i = 0, n = data.bones.length; i < n; i++) {
|
||||
let bone = skeleton.findBone(data.bones[i].name);
|
||||
if (!bone) throw new Error(`Couldn't find bone ${data.bones[i].name}.`);
|
||||
this.bones.push(bone);
|
||||
}
|
||||
let target = skeleton.findSlot(data.target.name);
|
||||
if (!target) throw new Error(`Couldn't find target bone ${data.target.name}`);
|
||||
this.target = target;
|
||||
this.position = data.position;
|
||||
this.spacing = data.spacing;
|
||||
this.mixRotate = data.mixRotate;
|
||||
@ -102,7 +107,7 @@ export class PathConstraint implements Updatable {
|
||||
|
||||
let bones = this.bones;
|
||||
let boneCount = bones.length, spacesCount = tangents ? boneCount : boneCount + 1;
|
||||
let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array<number> = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : null;
|
||||
let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array<number> = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : [];
|
||||
let spacing = this.spacing;
|
||||
|
||||
switch (data.spacingMode) {
|
||||
@ -222,7 +227,7 @@ export class PathConstraint implements Updatable {
|
||||
computeWorldPositions (path: PathAttachment, spacesCount: number, tangents: boolean) {
|
||||
let target = this.target;
|
||||
let position = this.position;
|
||||
let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array<number> = null;
|
||||
let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array<number> = this.world;
|
||||
let closed = path.closed;
|
||||
let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PathConstraint.NONE;
|
||||
|
||||
|
||||
@ -41,16 +41,21 @@ export class PathConstraintData extends ConstraintData {
|
||||
bones = new Array<BoneData>();
|
||||
|
||||
/** The slot whose path attachment will be used to constrained the bones. */
|
||||
target: SlotData = null;
|
||||
private _target: SlotData | null = null;
|
||||
public set target (slotData: SlotData) { this._target = slotData; }
|
||||
public get target () {
|
||||
if (!this._target) throw new Error("SlotData not set.")
|
||||
else return this._target;
|
||||
}
|
||||
|
||||
/** The mode for positioning the first bone on the path. */
|
||||
positionMode: PositionMode = null;
|
||||
positionMode: PositionMode = PositionMode.Fixed;
|
||||
|
||||
/** The mode for positioning the bones after the first bone on the path. */
|
||||
spacingMode: SpacingMode = null;
|
||||
spacingMode: SpacingMode = SpacingMode.Fixed;
|
||||
|
||||
/** The mode for adjusting the rotation of the bones. */
|
||||
rotateMode: RotateMode = null;
|
||||
rotateMode: RotateMode = RotateMode.Chain;
|
||||
|
||||
/** An offset added to the constrained bone rotation. */
|
||||
offsetRotation: number = 0;
|
||||
|
||||
@ -46,34 +46,34 @@ import { Color, Utils, MathUtils, Vector2, NumberArrayLike } from "./Utils";
|
||||
* See [Instance objects](http://esotericsoftware.com/spine-runtime-architecture#Instance-objects) in the Spine Runtimes Guide. */
|
||||
export class Skeleton {
|
||||
/** The skeleton's setup pose data. */
|
||||
data: SkeletonData = null;
|
||||
data: SkeletonData;
|
||||
|
||||
/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
|
||||
bones: Array<Bone> = null;
|
||||
bones: Array<Bone>;
|
||||
|
||||
/** The skeleton's slots. */
|
||||
slots: Array<Slot> = null;
|
||||
slots: Array<Slot>;
|
||||
|
||||
/** The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order. */
|
||||
drawOrder: Array<Slot> = null;
|
||||
drawOrder: Array<Slot>;
|
||||
|
||||
/** The skeleton's IK constraints. */
|
||||
ikConstraints: Array<IkConstraint> = null;
|
||||
ikConstraints: Array<IkConstraint>;
|
||||
|
||||
/** The skeleton's transform constraints. */
|
||||
transformConstraints: Array<TransformConstraint> = null;
|
||||
transformConstraints: Array<TransformConstraint>;
|
||||
|
||||
/** The skeleton's path constraints. */
|
||||
pathConstraints: Array<PathConstraint> = null;
|
||||
pathConstraints: Array<PathConstraint>;
|
||||
|
||||
/** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */
|
||||
_updateCache = new Array<Updatable>();
|
||||
|
||||
/** The skeleton's current skin. May be null. */
|
||||
skin: Skin = null;
|
||||
skin: Skin | null = null;
|
||||
|
||||
/** The color to tint all the skeleton's attachments. */
|
||||
color: Color = null;
|
||||
color: Color;
|
||||
|
||||
/** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale
|
||||
* inheritance. */
|
||||
@ -155,7 +155,7 @@ export class Skeleton {
|
||||
if (this.skin) {
|
||||
let skinBones = this.skin.bones;
|
||||
for (let i = 0, n = this.skin.bones.length; i < n; i++) {
|
||||
let bone = this.bones[skinBones[i].index];
|
||||
let bone: Bone | null = this.bones[skinBones[i].index];
|
||||
do {
|
||||
bone.sorted = false;
|
||||
bone.active = true;
|
||||
@ -201,7 +201,7 @@ export class Skeleton {
|
||||
}
|
||||
|
||||
sortIkConstraint (constraint: IkConstraint) {
|
||||
constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)));
|
||||
constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!;
|
||||
if (!constraint.active) return;
|
||||
|
||||
let target = constraint.target;
|
||||
@ -226,7 +226,7 @@ export class Skeleton {
|
||||
}
|
||||
|
||||
sortPathConstraint (constraint: PathConstraint) {
|
||||
constraint.active = constraint.target.bone.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)));
|
||||
constraint.active = constraint.target.bone.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!;
|
||||
if (!constraint.active) return;
|
||||
|
||||
let slot = constraint.target;
|
||||
@ -255,7 +255,7 @@ export class Skeleton {
|
||||
}
|
||||
|
||||
sortTransformConstraint (constraint: TransformConstraint) {
|
||||
constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)));
|
||||
constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!;
|
||||
if (!constraint.active) return;
|
||||
|
||||
this.sortBone(constraint.target);
|
||||
@ -265,7 +265,7 @@ export class Skeleton {
|
||||
if (constraint.data.local) {
|
||||
for (let i = 0; i < boneCount; i++) {
|
||||
let child = constrained[i];
|
||||
this.sortBone(child.parent);
|
||||
this.sortBone(child.parent!);
|
||||
this.sortBone(child);
|
||||
}
|
||||
} else {
|
||||
@ -307,6 +307,7 @@ export class Skeleton {
|
||||
}
|
||||
|
||||
sortBone (bone: Bone) {
|
||||
if (!bone) return;
|
||||
if (bone.sorted) return;
|
||||
let parent = bone.parent;
|
||||
if (parent) this.sortBone(parent);
|
||||
@ -348,6 +349,7 @@ export class Skeleton {
|
||||
updateWorldTransformWith (parent: Bone) {
|
||||
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
|
||||
let rootBone = this.getRootBone();
|
||||
if (!rootBone) throw new Error("Root bone must not be null.");
|
||||
let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
|
||||
rootBone.worldX = pa * this.x + pb * this.y + parent.worldX;
|
||||
rootBone.worldY = pc * this.x + pd * this.y + parent.worldY;
|
||||
@ -484,7 +486,7 @@ export class Skeleton {
|
||||
let slot = slots[i];
|
||||
let name = slot.data.attachmentName;
|
||||
if (name) {
|
||||
let attachment: Attachment = newSkin.getAttachment(i, name);
|
||||
let attachment = newSkin.getAttachment(i, name);
|
||||
if (attachment) slot.setAttachment(attachment);
|
||||
}
|
||||
}
|
||||
@ -500,8 +502,10 @@ export class Skeleton {
|
||||
*
|
||||
* See {@link #getAttachment()}.
|
||||
* @returns May be null. */
|
||||
getAttachmentByName (slotName: string, attachmentName: string): Attachment {
|
||||
return this.getAttachment(this.data.findSlot(slotName).index, attachmentName);
|
||||
getAttachmentByName (slotName: string, attachmentName: string): Attachment | null {
|
||||
let slot = this.data.findSlot(slotName);
|
||||
if (!slot) throw new Error(`Can't find slot with name ${slotName}`);
|
||||
return this.getAttachment(slot.index, attachmentName);
|
||||
}
|
||||
|
||||
/** Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot index and
|
||||
@ -509,10 +513,10 @@ export class Skeleton {
|
||||
*
|
||||
* See [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide.
|
||||
* @returns May be null. */
|
||||
getAttachment (slotIndex: number, attachmentName: string): Attachment {
|
||||
getAttachment (slotIndex: number, attachmentName: string): Attachment | null {
|
||||
if (!attachmentName) throw new Error("attachmentName cannot be null.");
|
||||
if (this.skin) {
|
||||
let attachment: Attachment = this.skin.getAttachment(slotIndex, attachmentName);
|
||||
let attachment = this.skin.getAttachment(slotIndex, attachmentName);
|
||||
if (attachment) return attachment;
|
||||
}
|
||||
if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName);
|
||||
@ -528,7 +532,7 @@ export class Skeleton {
|
||||
for (let i = 0, n = slots.length; i < n; i++) {
|
||||
let slot = slots[i];
|
||||
if (slot.data.name == slotName) {
|
||||
let attachment: Attachment = null;
|
||||
let attachment: Attachment | null = null;
|
||||
if (attachmentName) {
|
||||
attachment = this.getAttachment(i, attachmentName);
|
||||
if (!attachment) throw new Error("Attachment not found: " + attachmentName + ", for slot: " + slotName);
|
||||
@ -602,7 +606,7 @@ export class Skeleton {
|
||||
let slot = drawOrder[i];
|
||||
if (!slot.bone.active) continue;
|
||||
let verticesLength = 0;
|
||||
let vertices: NumberArrayLike = null;
|
||||
let vertices: NumberArrayLike | null = null;
|
||||
let attachment = slot.getAttachment();
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
verticesLength = 8;
|
||||
|
||||
@ -56,7 +56,7 @@ export class SkeletonBinary {
|
||||
* See [Scaling](http://esotericsoftware.com/spine-loading-skeleton-data#Scaling) in the Spine Runtimes Guide. */
|
||||
scale = 1;
|
||||
|
||||
attachmentLoader: AttachmentLoader = null;
|
||||
attachmentLoader: AttachmentLoader;
|
||||
private linkedMeshes = new Array<LinkedMesh>();
|
||||
|
||||
constructor (attachmentLoader: AttachmentLoader) {
|
||||
@ -91,13 +91,17 @@ export class SkeletonBinary {
|
||||
let n = 0;
|
||||
// Strings.
|
||||
n = input.readInt(true)
|
||||
for (let i = 0; i < n; i++)
|
||||
input.strings.push(input.readString());
|
||||
for (let i = 0; i < n; i++) {
|
||||
let str = input.readString();
|
||||
if (!str) throw new Error("String in string table must not be null.");
|
||||
input.strings.push(str);
|
||||
}
|
||||
|
||||
// Bones.
|
||||
n = input.readInt(true)
|
||||
for (let i = 0; i < n; i++) {
|
||||
let name = input.readString();
|
||||
if (!name) throw new Error("Bone name must not be null.");
|
||||
let parent = i == 0 ? null : skeletonData.bones[input.readInt(true)];
|
||||
let data = new BoneData(i, name, parent);
|
||||
data.rotation = input.readFloat();
|
||||
@ -118,6 +122,7 @@ export class SkeletonBinary {
|
||||
n = input.readInt(true);
|
||||
for (let i = 0; i < n; i++) {
|
||||
let slotName = input.readString();
|
||||
if (!slotName) throw new Error("Slot name must not be null.");
|
||||
let boneData = skeletonData.bones[input.readInt(true)];
|
||||
let data = new SlotData(i, slotName, boneData);
|
||||
Color.rgba8888ToColor(data.color, input.readInt32());
|
||||
@ -133,7 +138,9 @@ export class SkeletonBinary {
|
||||
// IK constraints.
|
||||
n = input.readInt(true);
|
||||
for (let i = 0, nn; i < n; i++) {
|
||||
let data = new IkConstraintData(input.readString());
|
||||
let name = input.readString();
|
||||
if (!name) throw new Error("IK constraint data name must not be null.");
|
||||
let data = new IkConstraintData(name);
|
||||
data.order = input.readInt(true);
|
||||
data.skinRequired = input.readBoolean();
|
||||
nn = input.readInt(true);
|
||||
@ -152,7 +159,9 @@ export class SkeletonBinary {
|
||||
// Transform constraints.
|
||||
n = input.readInt(true);
|
||||
for (let i = 0, nn; i < n; i++) {
|
||||
let data = new TransformConstraintData(input.readString());
|
||||
let name = input.readString();
|
||||
if (!name) throw new Error("Transform constraint data name must not be null.");
|
||||
let data = new TransformConstraintData(name);
|
||||
data.order = input.readInt(true);
|
||||
data.skinRequired = input.readBoolean();
|
||||
nn = input.readInt(true);
|
||||
@ -179,7 +188,9 @@ export class SkeletonBinary {
|
||||
// Path constraints.
|
||||
n = input.readInt(true);
|
||||
for (let i = 0, nn; i < n; i++) {
|
||||
let data = new PathConstraintData(input.readString());
|
||||
let name = input.readString();
|
||||
if (!name) throw new Error("Path constraint data name must not be null.");
|
||||
let data = new PathConstraintData(name);
|
||||
data.order = input.readInt(true);
|
||||
data.skinRequired = input.readBoolean();
|
||||
nn = input.readInt(true);
|
||||
@ -211,8 +222,11 @@ export class SkeletonBinary {
|
||||
{
|
||||
let i = skeletonData.skins.length;
|
||||
Utils.setArraySize(skeletonData.skins, n = i + input.readInt(true));
|
||||
for (; i < n; i++)
|
||||
skeletonData.skins[i] = this.readSkin(input, skeletonData, false, nonessential);
|
||||
for (; i < n; i++) {
|
||||
let skin = this.readSkin(input, skeletonData, false, nonessential);
|
||||
if (!skin) throw new Error("readSkin() should not have returned null.");
|
||||
skeletonData.skins[i] = skin;
|
||||
}
|
||||
}
|
||||
|
||||
// Linked meshes.
|
||||
@ -220,7 +234,10 @@ export class SkeletonBinary {
|
||||
for (let i = 0; i < n; i++) {
|
||||
let linkedMesh = this.linkedMeshes[i];
|
||||
let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
|
||||
if (!skin) throw new Error("Not skin found for linked mesh.");
|
||||
if (!linkedMesh.parent) throw new Error("Linked mesh parent must not be null");
|
||||
let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
|
||||
if (!parent) throw new Error(`Parent mesh not found: ${linkedMesh.parent}`);
|
||||
linkedMesh.mesh.timelineAttahment = linkedMesh.inheritTimeline ? parent as VertexAttachment : linkedMesh.mesh;
|
||||
linkedMesh.mesh.setParentMesh(parent as MeshAttachment);
|
||||
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
|
||||
@ -230,7 +247,9 @@ export class SkeletonBinary {
|
||||
// Events.
|
||||
n = input.readInt(true);
|
||||
for (let i = 0; i < n; i++) {
|
||||
let data = new EventData(input.readStringRef());
|
||||
let eventName = input.readStringRef();
|
||||
if (!eventName) throw new Error
|
||||
let data = new EventData(eventName);
|
||||
data.intValue = input.readInt(false);
|
||||
data.floatValue = input.readFloat();
|
||||
data.stringValue = input.readString();
|
||||
@ -244,12 +263,15 @@ export class SkeletonBinary {
|
||||
|
||||
// Animations.
|
||||
n = input.readInt(true);
|
||||
for (let i = 0; i < n; i++)
|
||||
skeletonData.animations.push(this.readAnimation(input, input.readString(), skeletonData));
|
||||
for (let i = 0; i < n; i++) {
|
||||
let animationName = input.readString();
|
||||
if (!animationName) throw new Error("Animatio name must not be null.");
|
||||
skeletonData.animations.push(this.readAnimation(input, animationName, skeletonData));
|
||||
}
|
||||
return skeletonData;
|
||||
}
|
||||
|
||||
private readSkin (input: BinaryInput, skeletonData: SkeletonData, defaultSkin: boolean, nonessential: boolean): Skin {
|
||||
private readSkin (input: BinaryInput, skeletonData: SkeletonData, defaultSkin: boolean, nonessential: boolean): Skin | null {
|
||||
let skin = null;
|
||||
let slotCount = 0;
|
||||
|
||||
@ -258,7 +280,9 @@ export class SkeletonBinary {
|
||||
if (slotCount == 0) return null;
|
||||
skin = new Skin("default");
|
||||
} else {
|
||||
skin = new Skin(input.readStringRef());
|
||||
let skinName = input.readStringRef();
|
||||
if (!skinName) throw new Error("Skin name must not be null.");
|
||||
skin = new Skin(skinName);
|
||||
skin.bones.length = input.readInt(true);
|
||||
for (let i = 0, n = skin.bones.length; i < n; i++)
|
||||
skin.bones[i] = skeletonData.bones[input.readInt(true)];
|
||||
@ -277,6 +301,7 @@ export class SkeletonBinary {
|
||||
let slotIndex = input.readInt(true);
|
||||
for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) {
|
||||
let name = input.readStringRef();
|
||||
if (!name) throw new Error("Attachment name must not be null");
|
||||
let attachment = this.readAttachment(input, skeletonData, skin, slotIndex, name, nonessential);
|
||||
if (attachment) skin.setAttachment(slotIndex, name, attachment);
|
||||
}
|
||||
@ -284,7 +309,7 @@ export class SkeletonBinary {
|
||||
return skin;
|
||||
}
|
||||
|
||||
private readAttachment (input: BinaryInput, skeletonData: SkeletonData, skin: Skin, slotIndex: number, attachmentName: string, nonessential: boolean): Attachment {
|
||||
private readAttachment (input: BinaryInput, skeletonData: SkeletonData, skin: Skin, slotIndex: number, attachmentName: string, nonessential: boolean): Attachment | null {
|
||||
let scale = this.scale;
|
||||
|
||||
let name = input.readStringRef();
|
||||
@ -327,7 +352,7 @@ export class SkeletonBinary {
|
||||
let box = this.attachmentLoader.newBoundingBoxAttachment(skin, name);
|
||||
if (!box) return null;
|
||||
box.worldVerticesLength = vertexCount << 1;
|
||||
box.vertices = vertices.vertices;
|
||||
box.vertices = vertices.vertices!;
|
||||
box.bones = vertices.bones;
|
||||
if (nonessential) Color.rgba8888ToColor(box.color, color);
|
||||
return box;
|
||||
@ -341,7 +366,7 @@ export class SkeletonBinary {
|
||||
let vertices = this.readVertices(input, vertexCount);
|
||||
let hullLength = input.readInt(true);
|
||||
let sequence = this.readSequence(input);
|
||||
let edges = null;
|
||||
let edges: number[] = [];
|
||||
let width = 0, height = 0;
|
||||
if (nonessential) {
|
||||
edges = this.readShortArray(input);
|
||||
@ -355,7 +380,7 @@ export class SkeletonBinary {
|
||||
mesh.path = path;
|
||||
Color.rgba8888ToColor(mesh.color, color);
|
||||
mesh.bones = vertices.bones;
|
||||
mesh.vertices = vertices.vertices;
|
||||
mesh.vertices = vertices.vertices!;
|
||||
mesh.worldVerticesLength = vertexCount << 1;
|
||||
mesh.triangles = triangles;
|
||||
mesh.regionUVs = uvs;
|
||||
@ -410,7 +435,7 @@ export class SkeletonBinary {
|
||||
path.closed = closed;
|
||||
path.constantSpeed = constantSpeed;
|
||||
path.worldVerticesLength = vertexCount << 1;
|
||||
path.vertices = vertices.vertices;
|
||||
path.vertices = vertices.vertices!;
|
||||
path.bones = vertices.bones;
|
||||
path.lengths = lengths;
|
||||
if (nonessential) Color.rgba8888ToColor(path.color, color);
|
||||
@ -440,7 +465,7 @@ export class SkeletonBinary {
|
||||
if (!clip) return null;
|
||||
clip.endSlot = skeletonData.slots[endSlotIndex];
|
||||
clip.worldVerticesLength = vertexCount << 1;
|
||||
clip.vertices = vertices.vertices;
|
||||
clip.vertices = vertices.vertices!;
|
||||
clip.bones = vertices.bones;
|
||||
if (nonessential) Color.rgba8888ToColor(clip.color, color);
|
||||
return clip;
|
||||
@ -866,6 +891,7 @@ export class SkeletonBinary {
|
||||
let slotIndex = input.readInt(true);
|
||||
for (let iii = 0, nnn = input.readInt(true); iii < nnn; iii++) {
|
||||
let attachmentName = input.readStringRef();
|
||||
if (!attachmentName) throw new Error("attachmentName must not be null.");
|
||||
let attachment = skin.getAttachment(slotIndex, attachmentName);
|
||||
let timelineType = input.readByte();
|
||||
let frameCount = input.readInt(true);
|
||||
@ -1041,12 +1067,12 @@ export class BinaryInput {
|
||||
return optimizePositive ? result : ((result >>> 1) ^ -(result & 1));
|
||||
}
|
||||
|
||||
readStringRef (): string {
|
||||
readStringRef (): string | null {
|
||||
let index = this.readInt(true);
|
||||
return index == 0 ? null : this.strings[index - 1];
|
||||
}
|
||||
|
||||
readString (): string {
|
||||
readString (): string | null {
|
||||
let byteCount = this.readInt(true);
|
||||
switch (byteCount) {
|
||||
case 0:
|
||||
@ -1089,12 +1115,12 @@ export class BinaryInput {
|
||||
}
|
||||
|
||||
class LinkedMesh {
|
||||
parent: string; skin: string;
|
||||
parent: string | null; skin: string | null;
|
||||
slotIndex: number;
|
||||
mesh: MeshAttachment;
|
||||
inheritTimeline: boolean;
|
||||
|
||||
constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) {
|
||||
constructor (mesh: MeshAttachment, skin: string | null, slotIndex: number, parent: string | null, inheritDeform: boolean) {
|
||||
this.mesh = mesh;
|
||||
this.skin = skin;
|
||||
this.slotIndex = slotIndex;
|
||||
@ -1104,7 +1130,7 @@ class LinkedMesh {
|
||||
}
|
||||
|
||||
class Vertices {
|
||||
constructor (public bones: Array<number> = null, public vertices: Array<number> | Float32Array = null) { }
|
||||
constructor (public bones: Array<number> | null = null, public vertices: Array<number> | Float32Array | null = null) { }
|
||||
}
|
||||
|
||||
enum AttachmentType { Region, BoundingBox, Mesh, LinkedMesh, Path, Point, Clipping }
|
||||
|
||||
@ -153,7 +153,7 @@ export class SkeletonBounds {
|
||||
|
||||
/** 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 {
|
||||
containsPoint (x: number, y: number): BoundingBoxAttachment | null {
|
||||
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];
|
||||
|
||||
@ -40,8 +40,8 @@ export class SkeletonClipping {
|
||||
clippedTriangles = new Array<number>();
|
||||
private scratch = new Array<number>();
|
||||
|
||||
private clipAttachment: ClippingAttachment;
|
||||
private clippingPolygons: Array<Array<number>>;
|
||||
private clipAttachment: ClippingAttachment | null = null;
|
||||
private clippingPolygons: Array<Array<number>> | null = null;
|
||||
|
||||
clipStart (slot: Slot, clip: ClippingAttachment): number {
|
||||
if (this.clipAttachment) return 0;
|
||||
@ -85,8 +85,8 @@ export class SkeletonClipping {
|
||||
|
||||
let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
|
||||
let clippedTriangles = this.clippedTriangles;
|
||||
let polygons = this.clippingPolygons;
|
||||
let polygonsCount = this.clippingPolygons.length;
|
||||
let polygons = this.clippingPolygons!;
|
||||
let polygonsCount = polygons.length;
|
||||
let vertexSize = twoColor ? 12 : 8;
|
||||
|
||||
let index = 0;
|
||||
@ -234,7 +234,7 @@ export class SkeletonClipping {
|
||||
let clipped = false;
|
||||
|
||||
// Avoid copy at the end.
|
||||
let input: Array<number> = null;
|
||||
let input: Array<number>;
|
||||
if (clippingArea.length % 4 >= 2) {
|
||||
input = output;
|
||||
output = this.scratch;
|
||||
|
||||
@ -43,7 +43,7 @@ import { TransformConstraintData } from "./TransformConstraintData";
|
||||
export class SkeletonData {
|
||||
|
||||
/** The skeleton's name, which by default is the name of the skeleton data file, if possible. May be null. */
|
||||
name: string = null;
|
||||
name: string | null = null;
|
||||
|
||||
/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
|
||||
bones = new Array<BoneData>(); // Ordered parents first.
|
||||
@ -56,7 +56,7 @@ export class SkeletonData {
|
||||
*
|
||||
* See {@link Skeleton#getAttachmentByName()}.
|
||||
* May be null. */
|
||||
defaultSkin: Skin = null;
|
||||
defaultSkin: Skin | null = null;
|
||||
|
||||
/** The skeleton's events. */
|
||||
events = new Array<EventData>();
|
||||
@ -86,20 +86,20 @@ export class SkeletonData {
|
||||
height: number = 0;
|
||||
|
||||
/** The Spine version used to export the skeleton data, or null. */
|
||||
version: string = null;
|
||||
version: string | null = null;
|
||||
|
||||
/** The skeleton data hash. This value will change if any of the skeleton data has changed. May be null. */
|
||||
hash: string = null;
|
||||
hash: string | null = null;
|
||||
|
||||
// Nonessential
|
||||
/** The dopesheet FPS in Spine. Available only when nonessential data was exported. */
|
||||
fps = 0;
|
||||
|
||||
/** The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null. */
|
||||
imagesPath: string = null;
|
||||
imagesPath: string | null = null;
|
||||
|
||||
/** The path to the audio directory as defined in Spine. Available only when nonessential data was exported. May be null. */
|
||||
audioPath: string = null;
|
||||
audioPath: string | null = null;
|
||||
|
||||
/** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
|
||||
* multiple times.
|
||||
|
||||
@ -51,7 +51,7 @@ import { HasTextureRegion } from "./attachments/HasTextureRegion";
|
||||
* [JSON and binary data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the Spine
|
||||
* Runtimes Guide. */
|
||||
export class SkeletonJson {
|
||||
attachmentLoader: AttachmentLoader = null;
|
||||
attachmentLoader: AttachmentLoader;
|
||||
|
||||
/** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at
|
||||
* runtime than were used in Spine.
|
||||
@ -87,7 +87,7 @@ export class SkeletonJson {
|
||||
for (let i = 0; i < root.bones.length; i++) {
|
||||
let boneMap = root.bones[i];
|
||||
|
||||
let parent: BoneData = null;
|
||||
let parent: BoneData | null = null;
|
||||
let parentName: string = getValue(boneMap, "parent", null);
|
||||
if (parentName) parent = skeletonData.findBone(parentName);
|
||||
let data = new BoneData(skeletonData.bones.length, boneMap.name, parent);
|
||||
@ -114,6 +114,7 @@ export class SkeletonJson {
|
||||
for (let i = 0; i < root.slots.length; i++) {
|
||||
let slotMap = root.slots[i];
|
||||
let boneData = skeletonData.findBone(slotMap.bone);
|
||||
if (!boneData) throw new Error(`Couldn't find bone ${slotMap.bone} for slot ${slotMap.name}`);
|
||||
let data = new SlotData(skeletonData.slots.length, slotMap.name, boneData);
|
||||
|
||||
let color: string = getValue(slotMap, "color", null);
|
||||
@ -136,10 +137,15 @@ export class SkeletonJson {
|
||||
data.order = getValue(constraintMap, "order", 0);
|
||||
data.skinRequired = getValue(constraintMap, "skin", false);
|
||||
|
||||
for (let ii = 0; ii < constraintMap.bones.length; ii++)
|
||||
data.bones.push(skeletonData.findBone(constraintMap.bones[ii]));
|
||||
for (let ii = 0; ii < constraintMap.bones.length; ii++) {
|
||||
let bone = skeletonData.findBone(constraintMap.bones[ii]);
|
||||
if (!bone) throw new Error(`Couldn't find bone ${constraintMap.bones[ii]} for IK constraint ${constraintMap.name}.`);
|
||||
data.bones.push(bone);
|
||||
}
|
||||
|
||||
data.target = skeletonData.findBone(constraintMap.target);
|
||||
let target = skeletonData.findBone(constraintMap.target);;
|
||||
if (!target) throw new Error(`Couldn't find target bone ${constraintMap.target} for IK constraint ${constraintMap.name}.`);
|
||||
data.target = target;
|
||||
|
||||
data.mix = getValue(constraintMap, "mix", 1);
|
||||
data.softness = getValue(constraintMap, "softness", 0) * scale;
|
||||
@ -160,11 +166,17 @@ export class SkeletonJson {
|
||||
data.order = getValue(constraintMap, "order", 0);
|
||||
data.skinRequired = getValue(constraintMap, "skin", false);
|
||||
|
||||
for (let ii = 0; ii < constraintMap.bones.length; ii++)
|
||||
data.bones.push(skeletonData.findBone(constraintMap.bones[ii]));
|
||||
for (let ii = 0; ii < constraintMap.bones.length; ii++) {
|
||||
let boneName = constraintMap.bones[ii];
|
||||
let bone = skeletonData.findBone(boneName);
|
||||
if (!bone) throw new Error(`Couldn't find bone ${boneName} for transform constraint ${constraintMap.name}.`);
|
||||
data.bones.push(bone);
|
||||
}
|
||||
|
||||
let targetName: string = constraintMap.target;
|
||||
data.target = skeletonData.findBone(targetName);
|
||||
let target = skeletonData.findBone(targetName);
|
||||
if (!target) throw new Error(`Couldn't find target bone ${targetName} for transform constraint ${constraintMap.name}.`);
|
||||
data.target = target;
|
||||
|
||||
data.local = getValue(constraintMap, "local", false);
|
||||
data.relative = getValue(constraintMap, "relative", false);
|
||||
@ -194,11 +206,17 @@ export class SkeletonJson {
|
||||
data.order = getValue(constraintMap, "order", 0);
|
||||
data.skinRequired = getValue(constraintMap, "skin", false);
|
||||
|
||||
for (let ii = 0; ii < constraintMap.bones.length; ii++)
|
||||
data.bones.push(skeletonData.findBone(constraintMap.bones[ii]));
|
||||
for (let ii = 0; ii < constraintMap.bones.length; ii++) {
|
||||
let boneName = constraintMap.bones[ii];
|
||||
let bone = skeletonData.findBone(boneName);
|
||||
if (!bone) throw new Error(`Couldn't find bone ${boneName} for path constraint ${constraintMap.name}.`);
|
||||
data.bones.push(bone);
|
||||
}
|
||||
|
||||
let targetName: string = constraintMap.target;
|
||||
data.target = skeletonData.findSlot(targetName);
|
||||
let target = skeletonData.findSlot(targetName);
|
||||
if (!target) throw new Error(`Couldn't find target slot ${targetName} for path constraint ${constraintMap.name}.`);
|
||||
data.target = target;
|
||||
|
||||
data.positionMode = Utils.enumValue(PositionMode, getValue(constraintMap, "positionMode", "Percent"));
|
||||
data.spacingMode = Utils.enumValue(SpacingMode, getValue(constraintMap, "spacingMode", "Length"));
|
||||
@ -223,27 +241,44 @@ export class SkeletonJson {
|
||||
let skin = new Skin(skinMap.name);
|
||||
|
||||
if (skinMap.bones) {
|
||||
for (let ii = 0; ii < skinMap.bones.length; ii++)
|
||||
skin.bones.push(skeletonData.findBone(skinMap.bones[ii]));
|
||||
for (let ii = 0; ii < skinMap.bones.length; ii++) {
|
||||
let boneName = skinMap.bones[ii];
|
||||
let bone = skeletonData.findBone(boneName);
|
||||
if (!bone) throw new Error(`Couldn't find bone ${boneName} for skin ${skinMap.name}.`);
|
||||
skin.bones.push(bone);
|
||||
}
|
||||
}
|
||||
|
||||
if (skinMap.ik) {
|
||||
for (let ii = 0; ii < skinMap.ik.length; ii++)
|
||||
skin.constraints.push(skeletonData.findIkConstraint(skinMap.ik[ii]));
|
||||
for (let ii = 0; ii < skinMap.ik.length; ii++) {
|
||||
let constraintName = skinMap.ik[ii];
|
||||
let constraint = skeletonData.findIkConstraint(constraintName);
|
||||
if (!constraint) throw new Error(`Couldn't find IK constraint ${constraintName} for skin ${skinMap.name}.`);
|
||||
skin.constraints.push(constraint);
|
||||
}
|
||||
}
|
||||
|
||||
if (skinMap.transform) {
|
||||
for (let ii = 0; ii < skinMap.transform.length; ii++)
|
||||
skin.constraints.push(skeletonData.findTransformConstraint(skinMap.transform[ii]));
|
||||
for (let ii = 0; ii < skinMap.transform.length; ii++) {
|
||||
let constraintName = skinMap.transform[ii];
|
||||
let constraint = skeletonData.findTransformConstraint(constraintName);
|
||||
if (!constraint) throw new Error(`Couldn't find transform constraint ${constraintName} for skin ${skinMap.name}.`);
|
||||
skin.constraints.push(constraint);
|
||||
}
|
||||
}
|
||||
|
||||
if (skinMap.path) {
|
||||
for (let ii = 0; ii < skinMap.path.length; ii++)
|
||||
skin.constraints.push(skeletonData.findPathConstraint(skinMap.path[ii]));
|
||||
for (let ii = 0; ii < skinMap.path.length; ii++) {
|
||||
let constraintName = skinMap.path[ii];
|
||||
let constraint = skeletonData.findPathConstraint(constraintName);
|
||||
if (!constraint) throw new Error(`Couldn't find path constraint ${constraintName} for skin ${skinMap.name}.`);
|
||||
skin.constraints.push(constraint);
|
||||
}
|
||||
}
|
||||
|
||||
for (let slotName in skinMap.attachments) {
|
||||
let slot = skeletonData.findSlot(slotName);
|
||||
if (!slot) throw new Error(`Couldn't find slot ${slotName} for skin ${skinMap.name}.`);
|
||||
let slotMap = skinMap.attachments[slotName];
|
||||
for (let entryName in slotMap) {
|
||||
let attachment = this.readAttachment(slotMap[entryName], skin, slot.index, entryName, skeletonData);
|
||||
@ -259,7 +294,9 @@ export class SkeletonJson {
|
||||
for (let i = 0, n = this.linkedMeshes.length; i < n; i++) {
|
||||
let linkedMesh = this.linkedMeshes[i];
|
||||
let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
|
||||
if (!skin) throw new Error(`Skin not found: ${linkedMesh.skin}`);
|
||||
let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
|
||||
if (!parent) throw new Error(`Parent mesh not found: ${linkedMesh.parent}`);
|
||||
linkedMesh.mesh.timelineAttahment = linkedMesh.inheritTimeline ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
|
||||
linkedMesh.mesh.setParentMesh(<MeshAttachment>parent);
|
||||
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
|
||||
@ -294,7 +331,7 @@ export class SkeletonJson {
|
||||
return skeletonData;
|
||||
}
|
||||
|
||||
readAttachment (map: any, skin: Skin, slotIndex: number, name: string, skeletonData: SkeletonData): Attachment {
|
||||
readAttachment (map: any, skin: Skin, slotIndex: number, name: string, skeletonData: SkeletonData): Attachment | null {
|
||||
let scale = this.scale;
|
||||
name = getValue(map, "name", name);
|
||||
|
||||
@ -452,7 +489,9 @@ export class SkeletonJson {
|
||||
if (map.slots) {
|
||||
for (let slotName in map.slots) {
|
||||
let slotMap = map.slots[slotName];
|
||||
let slotIndex = skeletonData.findSlot(slotName).index;
|
||||
let slot = skeletonData.findSlot(slotName);
|
||||
if (!slot) throw new Error("Slot not found: " + slotName);
|
||||
let slotIndex = slot.index;
|
||||
for (let timelineName in slotMap) {
|
||||
let timelineMap = slotMap[timelineName];
|
||||
if (!timelineMap) continue;
|
||||
@ -603,7 +642,9 @@ export class SkeletonJson {
|
||||
if (map.bones) {
|
||||
for (let boneName in map.bones) {
|
||||
let boneMap = map.bones[boneName];
|
||||
let boneIndex = skeletonData.findBone(boneName).index;
|
||||
let bone = skeletonData.findBone(boneName);
|
||||
if (!bone) throw new Error("Bone not found: " + boneName);
|
||||
let boneIndex = bone.index;
|
||||
for (let timelineName in boneMap) {
|
||||
let timelineMap = boneMap[timelineName];
|
||||
let frames = timelineMap.length;
|
||||
@ -651,6 +692,7 @@ export class SkeletonJson {
|
||||
if (!keyMap) continue;
|
||||
|
||||
let constraint = skeletonData.findIkConstraint(constraintName);
|
||||
if (!constraint) throw new Error("IK Constraint not found: " + constraintName);
|
||||
let constraintIndex = skeletonData.ikConstraints.indexOf(constraint);
|
||||
let timeline = new IkConstraintTimeline(constraintMap.length, constraintMap.length << 1, constraintIndex);
|
||||
|
||||
@ -692,6 +734,7 @@ export class SkeletonJson {
|
||||
if (!keyMap) continue;
|
||||
|
||||
let constraint = skeletonData.findTransformConstraint(constraintName);
|
||||
if (!constraint) throw new Error("Transform constraint not found: " + constraintName);
|
||||
let constraintIndex = skeletonData.transformConstraints.indexOf(constraint);
|
||||
let timeline = new TransformConstraintTimeline(timelineMap.length, timelineMap.length * 6, constraintIndex);
|
||||
|
||||
@ -746,6 +789,7 @@ export class SkeletonJson {
|
||||
for (let constraintName in map.path) {
|
||||
let constraintMap = map.path[constraintName];
|
||||
let constraint = skeletonData.findPathConstraint(constraintName);
|
||||
if (!constraint) throw new Error("Path constraint not found: " + constraintName);
|
||||
let constraintIndex = skeletonData.pathConstraints.indexOf(constraint);
|
||||
for (let timelineName in constraintMap) {
|
||||
let timelineMap = constraintMap[timelineName];
|
||||
@ -799,9 +843,12 @@ export class SkeletonJson {
|
||||
for (let attachmentsName in map.attachments) {
|
||||
let attachmentsMap = map.attachments[attachmentsName];
|
||||
let skin = skeletonData.findSkin(attachmentsName);
|
||||
if (!skin) throw new Error("Skin not found: " + attachmentsName);
|
||||
for (let slotMapName in attachmentsMap) {
|
||||
let slotMap = attachmentsMap[slotMapName];
|
||||
let slotIndex = skeletonData.findSlot(slotMapName).index;
|
||||
let slot = skeletonData.findSlot(slotMapName);
|
||||
if (!slot) throw new Error("Slot not found: " + slotMapName);
|
||||
let slotIndex = slot.index;
|
||||
for (let attachmentMapName in slotMap) {
|
||||
let attachmentMap = slotMap[attachmentMapName];
|
||||
let attachment = <VertexAttachment>skin.getAttachment(slotIndex, attachmentMapName);
|
||||
@ -877,7 +924,7 @@ export class SkeletonJson {
|
||||
let frame = 0;
|
||||
for (let i = 0; i < map.drawOrder.length; i++, frame++) {
|
||||
let drawOrderMap = map.drawOrder[i];
|
||||
let drawOrder: Array<number> = null;
|
||||
let drawOrder: Array<number> | null = null;
|
||||
let offsets = getValue(drawOrderMap, "offsets", null);
|
||||
if (offsets) {
|
||||
drawOrder = Utils.newArray<number>(slotCount, -1);
|
||||
@ -885,7 +932,9 @@ export class SkeletonJson {
|
||||
let originalIndex = 0, unchangedIndex = 0;
|
||||
for (let ii = 0; ii < offsets.length; ii++) {
|
||||
let offsetMap = offsets[ii];
|
||||
let slotIndex = skeletonData.findSlot(offsetMap.slot).index;
|
||||
let slot = skeletonData.findSlot(offsetMap.slot);
|
||||
if (!slot) throw new Error("Slot not found: " + slot);
|
||||
let slotIndex = slot.index;
|
||||
// Collect unchanged items.
|
||||
while (originalIndex != slotIndex)
|
||||
unchanged[unchangedIndex++] = originalIndex++;
|
||||
@ -911,6 +960,7 @@ export class SkeletonJson {
|
||||
for (let i = 0; i < map.events.length; i++, frame++) {
|
||||
let eventMap = map.events[i];
|
||||
let eventData = skeletonData.findEvent(eventMap.name);
|
||||
if (!eventData) throw new Error("Event not found: " + eventMap.name);
|
||||
let event = new Event(Utils.toSinglePrecision(getValue(eventMap, "time", 0)), eventData);
|
||||
event.intValue = getValue(eventMap, "int", eventData.intValue);
|
||||
event.floatValue = getValue(eventMap, "float", eventData.floatValue);
|
||||
|
||||
@ -36,7 +36,7 @@ import { StringMap } from "./Utils";
|
||||
|
||||
/** Stores an entry in the skin consisting of the slot index, name, and attachment **/
|
||||
export class SkinEntry {
|
||||
constructor (public slotIndex: number = 0, public name: string = null, public attachment: Attachment = null) { }
|
||||
constructor (public slotIndex: number = 0, public name: string, public attachment: Attachment) { }
|
||||
}
|
||||
|
||||
/** Stores attachments by slot index and attachment name.
|
||||
@ -45,7 +45,7 @@ export class SkinEntry {
|
||||
* [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide. */
|
||||
export class Skin {
|
||||
/** The skin's name, which is unique across all skins in the skeleton. */
|
||||
name: string = null;
|
||||
name: string;
|
||||
|
||||
attachments = new Array<StringMap<Attachment>>();
|
||||
bones = Array<BoneData>();
|
||||
@ -140,7 +140,7 @@ export class Skin {
|
||||
}
|
||||
|
||||
/** Returns the attachment for the specified slot index and name, or null. */
|
||||
getAttachment (slotIndex: number, name: string): Attachment {
|
||||
getAttachment (slotIndex: number, name: string): Attachment | null {
|
||||
let dictionary = this.attachments[slotIndex];
|
||||
return dictionary ? dictionary[name] : null;
|
||||
}
|
||||
@ -148,7 +148,7 @@ export class Skin {
|
||||
/** Removes the attachment in the skin for the specified slot index and name, if any. */
|
||||
removeAttachment (slotIndex: number, name: string) {
|
||||
let dictionary = this.attachments[slotIndex];
|
||||
if (dictionary) dictionary[name] = null;
|
||||
if (dictionary) delete dictionary[name];
|
||||
}
|
||||
|
||||
/** Returns all attachments in this skin. */
|
||||
|
||||
@ -38,26 +38,26 @@ import { Color } from "./Utils";
|
||||
* across multiple skeletons. */
|
||||
export class Slot {
|
||||
/** The slot's setup pose data. */
|
||||
data: SlotData = null;
|
||||
data: SlotData;
|
||||
|
||||
/** The bone this slot belongs to. */
|
||||
bone: Bone = null;
|
||||
bone: Bone;
|
||||
|
||||
/** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two
|
||||
* color tinting. */
|
||||
color: Color = null;
|
||||
color: Color;
|
||||
|
||||
/** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
|
||||
* color's alpha is not used. */
|
||||
darkColor: Color = null;
|
||||
darkColor: Color | null = null;
|
||||
|
||||
attachment: Attachment = null;
|
||||
attachment: Attachment | null = null;
|
||||
|
||||
attachmentState: number = 0;
|
||||
|
||||
/** The index of the texture region to display when the slot's attachment has a {@link Sequence}. -1 represents the
|
||||
* {@link Sequence#getSetupIndex()}. */
|
||||
sequenceIndex: number;
|
||||
sequenceIndex: number = -1;
|
||||
|
||||
/** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
|
||||
* weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
|
||||
@ -81,14 +81,14 @@ export class Slot {
|
||||
}
|
||||
|
||||
/** The current attachment for the slot, or null if the slot has no attachment. */
|
||||
getAttachment (): Attachment {
|
||||
getAttachment (): Attachment | null {
|
||||
return this.attachment;
|
||||
}
|
||||
|
||||
/** Sets the slot's attachment and, if the attachment changed, resets {@link #sequenceIndex} and clears the {@link #deform}.
|
||||
* The deform is not cleared if the old attachment has the same {@link VertexAttachment#getTimelineAttachment()} as the
|
||||
* specified attachment. */
|
||||
setAttachment (attachment: Attachment) {
|
||||
setAttachment (attachment: Attachment | null) {
|
||||
if (this.attachment == attachment) return;
|
||||
if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment)
|
||||
|| (<VertexAttachment>attachment).timelineAttahment != (<VertexAttachment>this.attachment).timelineAttahment) {
|
||||
@ -101,7 +101,7 @@ export class Slot {
|
||||
/** Sets this slot to the setup pose. */
|
||||
setToSetupPose () {
|
||||
this.color.setFromColor(this.data.color);
|
||||
if (this.darkColor) this.darkColor.setFromColor(this.data.darkColor);
|
||||
if (this.darkColor) this.darkColor.setFromColor(this.data.darkColor!);
|
||||
if (!this.data.attachmentName)
|
||||
this.attachment = null;
|
||||
else {
|
||||
|
||||
@ -36,10 +36,10 @@ export class SlotData {
|
||||
index: number = 0;
|
||||
|
||||
/** The name of the slot, which is unique across all slots in the skeleton. */
|
||||
name: string = null;
|
||||
name: string;
|
||||
|
||||
/** The bone this slot belongs to. */
|
||||
boneData: BoneData = null;
|
||||
boneData: BoneData;
|
||||
|
||||
/** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two
|
||||
* color tinting. */
|
||||
@ -47,13 +47,13 @@ export class SlotData {
|
||||
|
||||
/** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
|
||||
* color's alpha is not used. */
|
||||
darkColor: Color = null;
|
||||
darkColor: Color | null = null;
|
||||
|
||||
/** The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. */
|
||||
attachmentName: string = null;
|
||||
attachmentName: string | null = null;
|
||||
|
||||
/** The blend mode for drawing the slot's attachment. */
|
||||
blendMode: BlendMode = null;
|
||||
blendMode: BlendMode = BlendMode.Normal;
|
||||
|
||||
constructor (index: number, name: string, boneData: BoneData) {
|
||||
if (index < 0) throw new Error("index must be >= 0.");
|
||||
|
||||
@ -38,66 +38,64 @@ export class TextureAtlas implements Disposable {
|
||||
constructor (atlasText: string) {
|
||||
let reader = new TextureAtlasReader(atlasText);
|
||||
let entry = new Array<string>(4);
|
||||
let page: TextureAtlasPage = null;
|
||||
let region: TextureAtlasRegion = null;
|
||||
|
||||
let pageFields: StringMap<Function> = {};
|
||||
pageFields["size"] = () => {
|
||||
page.width = parseInt(entry[1]);
|
||||
page.height = parseInt(entry[2]);
|
||||
let pageFields: StringMap<(page: TextureAtlasPage) => void> = {};
|
||||
pageFields["size"] = (page: TextureAtlasPage) => {
|
||||
page!.width = parseInt(entry[1]);
|
||||
page!.height = parseInt(entry[2]);
|
||||
};
|
||||
pageFields["format"] = () => {
|
||||
// page.format = Format[tuple[0]]; we don't need format in WebGL
|
||||
};
|
||||
pageFields["filter"] = () => {
|
||||
page.minFilter = Utils.enumValue(TextureFilter, entry[1]);
|
||||
page.magFilter = Utils.enumValue(TextureFilter, entry[2]);
|
||||
pageFields["filter"] = (page: TextureAtlasPage) => {
|
||||
page!.minFilter = Utils.enumValue(TextureFilter, entry[1]);
|
||||
page!.magFilter = Utils.enumValue(TextureFilter, entry[2]);
|
||||
};
|
||||
pageFields["repeat"] = () => {
|
||||
if (entry[1].indexOf('x') != -1) page.uWrap = TextureWrap.Repeat;
|
||||
if (entry[1].indexOf('y') != -1) page.vWrap = TextureWrap.Repeat;
|
||||
pageFields["repeat"] = (page: TextureAtlasPage) => {
|
||||
if (entry[1].indexOf('x') != -1) page!.uWrap = TextureWrap.Repeat;
|
||||
if (entry[1].indexOf('y') != -1) page!.vWrap = TextureWrap.Repeat;
|
||||
};
|
||||
pageFields["pma"] = () => {
|
||||
page.pma = entry[1] == "true";
|
||||
pageFields["pma"] = (page: TextureAtlasPage) => {
|
||||
page!.pma = entry[1] == "true";
|
||||
};
|
||||
|
||||
var regionFields: StringMap<Function> = {};
|
||||
regionFields["xy"] = () => { // Deprecated, use bounds.
|
||||
var regionFields: StringMap<(region: TextureAtlasRegion) => void> = {};
|
||||
regionFields["xy"] = (region: TextureAtlasRegion) => { // Deprecated, use bounds.
|
||||
region.x = parseInt(entry[1]);
|
||||
region.y = parseInt(entry[2]);
|
||||
};
|
||||
regionFields["size"] = () => { // Deprecated, use bounds.
|
||||
regionFields["size"] = (region: TextureAtlasRegion) => { // Deprecated, use bounds.
|
||||
region.width = parseInt(entry[1]);
|
||||
region.height = parseInt(entry[2]);
|
||||
};
|
||||
regionFields["bounds"] = () => {
|
||||
regionFields["bounds"] = (region: TextureAtlasRegion) => {
|
||||
region.x = parseInt(entry[1]);
|
||||
region.y = parseInt(entry[2]);
|
||||
region.width = parseInt(entry[3]);
|
||||
region.height = parseInt(entry[4]);
|
||||
};
|
||||
regionFields["offset"] = () => { // Deprecated, use offsets.
|
||||
regionFields["offset"] = (region: TextureAtlasRegion) => { // Deprecated, use offsets.
|
||||
region.offsetX = parseInt(entry[1]);
|
||||
region.offsetY = parseInt(entry[2]);
|
||||
};
|
||||
regionFields["orig"] = () => { // Deprecated, use offsets.
|
||||
regionFields["orig"] = (region: TextureAtlasRegion) => { // Deprecated, use offsets.
|
||||
region.originalWidth = parseInt(entry[1]);
|
||||
region.originalHeight = parseInt(entry[2]);
|
||||
};
|
||||
regionFields["offsets"] = () => {
|
||||
regionFields["offsets"] = (region: TextureAtlasRegion) => {
|
||||
region.offsetX = parseInt(entry[1]);
|
||||
region.offsetY = parseInt(entry[2]);
|
||||
region.originalWidth = parseInt(entry[3]);
|
||||
region.originalHeight = parseInt(entry[4]);
|
||||
};
|
||||
regionFields["rotate"] = () => {
|
||||
regionFields["rotate"] = (region: TextureAtlasRegion) => {
|
||||
let value = entry[1];
|
||||
if (value == "true")
|
||||
region.degrees = 90;
|
||||
else if (value != "false")
|
||||
region.degrees = parseInt(value);
|
||||
};
|
||||
regionFields["index"] = () => {
|
||||
regionFields["index"] = (region: TextureAtlasRegion) => {
|
||||
region.index = parseInt(entry[1]);
|
||||
};
|
||||
|
||||
@ -113,38 +111,34 @@ export class TextureAtlas implements Disposable {
|
||||
}
|
||||
|
||||
// Page and region entries.
|
||||
let names: string[] = null;
|
||||
let values: number[][] = null;
|
||||
let page: TextureAtlasPage | null = null;
|
||||
let names: string[] | null = null;
|
||||
let values: number[][] | null = null;
|
||||
while (true) {
|
||||
if (line === null) break;
|
||||
if (line.trim().length == 0) {
|
||||
page = null;
|
||||
line = reader.readLine();
|
||||
} else if (!page) {
|
||||
page = new TextureAtlasPage();
|
||||
page.name = line.trim();
|
||||
page = new TextureAtlasPage(line.trim());
|
||||
while (true) {
|
||||
if (reader.readEntry(entry, line = reader.readLine()) == 0) break;
|
||||
let field: Function = pageFields[entry[0]];
|
||||
if (field) field();
|
||||
let field = pageFields[entry[0]];
|
||||
if (field) field(page);
|
||||
}
|
||||
this.pages.push(page);
|
||||
} else {
|
||||
region = new TextureAtlasRegion();
|
||||
let region = new TextureAtlasRegion(page, line);
|
||||
|
||||
region.page = page;
|
||||
region.name = line;
|
||||
while (true) {
|
||||
let count = reader.readEntry(entry, line = reader.readLine());
|
||||
if (count == 0) break;
|
||||
let field: Function = regionFields[entry[0]];
|
||||
let field = regionFields[entry[0]];
|
||||
if (field)
|
||||
field();
|
||||
field(region);
|
||||
else {
|
||||
if (!names) {
|
||||
names = [];
|
||||
values = [];
|
||||
}
|
||||
if (!names) names = [];
|
||||
if (!values) values = [];
|
||||
names.push(entry[0]);
|
||||
let entryValues: number[] = [];
|
||||
for (let i = 0; i < count; i++)
|
||||
@ -156,7 +150,7 @@ export class TextureAtlas implements Disposable {
|
||||
region.originalWidth = region.width;
|
||||
region.originalHeight = region.height;
|
||||
}
|
||||
if (names && names.length > 0) {
|
||||
if (names && names.length > 0 && values && values.length > 0) {
|
||||
region.names = names;
|
||||
region.values = values;
|
||||
names = null;
|
||||
@ -176,7 +170,7 @@ export class TextureAtlas implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
findRegion (name: string): TextureAtlasRegion {
|
||||
findRegion (name: string): TextureAtlasRegion | null {
|
||||
for (let i = 0; i < this.regions.length; i++) {
|
||||
if (this.regions[i].name == name) {
|
||||
return this.regions[i];
|
||||
@ -192,26 +186,26 @@ export class TextureAtlas implements Disposable {
|
||||
|
||||
dispose () {
|
||||
for (let i = 0; i < this.pages.length; i++) {
|
||||
this.pages[i].texture.dispose();
|
||||
this.pages[i].texture?.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextureAtlasReader {
|
||||
lines: Array<string> = null;
|
||||
lines: Array<string>;
|
||||
index: number = 0;
|
||||
|
||||
constructor (text: string) {
|
||||
this.lines = text.split(/\r\n|\r|\n/);
|
||||
}
|
||||
|
||||
readLine (): string {
|
||||
readLine (): string | null {
|
||||
if (this.index >= this.lines.length)
|
||||
return null;
|
||||
return this.lines[this.index++];
|
||||
}
|
||||
|
||||
readEntry (entry: string[], line: string): number {
|
||||
readEntry (entry: string[], line: string | null): number {
|
||||
if (!line) return 0;
|
||||
line = line.trim();
|
||||
if (line.length == 0) return 0;
|
||||
@ -233,16 +227,20 @@ class TextureAtlasReader {
|
||||
}
|
||||
|
||||
export class TextureAtlasPage {
|
||||
name: string = null;
|
||||
name: string;
|
||||
minFilter: TextureFilter = TextureFilter.Nearest;
|
||||
magFilter: TextureFilter = TextureFilter.Nearest;
|
||||
uWrap: TextureWrap = TextureWrap.ClampToEdge;
|
||||
vWrap: TextureWrap = TextureWrap.ClampToEdge;
|
||||
texture: Texture = null;
|
||||
texture: Texture | null = null;
|
||||
width: number = 0;
|
||||
height: number = 0;
|
||||
pma: boolean = false;
|
||||
|
||||
constructor (name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
setTexture (texture: Texture) {
|
||||
this.texture = texture;
|
||||
texture.setFilters(this.minFilter, this.magFilter);
|
||||
@ -251,8 +249,8 @@ export class TextureAtlasPage {
|
||||
}
|
||||
|
||||
export class TextureAtlasRegion extends TextureRegion {
|
||||
page: TextureAtlasPage = null;
|
||||
name: string = null;
|
||||
page: TextureAtlasPage;
|
||||
name: string;
|
||||
x: number = 0;
|
||||
y: number = 0;
|
||||
offsetX: number = 0;
|
||||
@ -261,6 +259,12 @@ export class TextureAtlasRegion extends TextureRegion {
|
||||
originalHeight: number = 0;
|
||||
index: number = 0;
|
||||
degrees: number = 0;
|
||||
names: string[] = null;
|
||||
values: number[][] = null;
|
||||
names: string[] | null = null;
|
||||
values: number[][] | null = null;
|
||||
|
||||
constructor (page: TextureAtlasPage, name: string) {
|
||||
super();
|
||||
this.page = page;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,13 +41,13 @@ import { Vector2, MathUtils } from "./Utils";
|
||||
export class TransformConstraint implements Updatable {
|
||||
|
||||
/** The transform constraint's setup pose data. */
|
||||
data: TransformConstraintData = null;
|
||||
data: TransformConstraintData;
|
||||
|
||||
/** The bones that will be modified by this transform constraint. */
|
||||
bones: Array<Bone> = null;
|
||||
bones: Array<Bone>;
|
||||
|
||||
/** The target bone whose world transform will be copied to the constrained bones. */
|
||||
target: Bone = null;
|
||||
target: Bone;
|
||||
|
||||
mixRotate = 0; mixX = 0; mixY = 0; mixScaleX = 0; mixScaleY = 0; mixShearY = 0;
|
||||
|
||||
@ -65,9 +65,14 @@ export class TransformConstraint implements Updatable {
|
||||
this.mixScaleY = data.mixScaleY;
|
||||
this.mixShearY = data.mixShearY;
|
||||
this.bones = new Array<Bone>();
|
||||
for (let i = 0; i < data.bones.length; i++)
|
||||
this.bones.push(skeleton.findBone(data.bones[i].name));
|
||||
this.target = skeleton.findBone(data.target.name);
|
||||
for (let i = 0; i < data.bones.length; i++) {
|
||||
let bone = skeleton.findBone(data.bones[i].name);
|
||||
if (!bone) throw new Error(`Couldn't find bone ${data.bones[i].name}.`);
|
||||
this.bones.push(bone);
|
||||
}
|
||||
let target = skeleton.findBone(data.target.name);
|
||||
if (!target) throw new Error(`Couldn't find target bone ${data.target.name}.`);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
isActive () {
|
||||
|
||||
@ -39,7 +39,12 @@ export class TransformConstraintData extends ConstraintData {
|
||||
bones = new Array<BoneData>();
|
||||
|
||||
/** The target bone whose world transform will be copied to the constrained bones. */
|
||||
target: BoneData = null;
|
||||
private _target: BoneData | null = null;
|
||||
public set target (boneData: BoneData) { this._target = boneData; }
|
||||
public get target () {
|
||||
if (!this._target) throw new Error("BoneData not set.")
|
||||
else return this._target;
|
||||
}
|
||||
|
||||
mixRotate = 0;
|
||||
mixX = 0;
|
||||
|
||||
@ -35,7 +35,7 @@ export interface StringMap<T> {
|
||||
}
|
||||
|
||||
export class IntSet {
|
||||
array = new Array<number>();
|
||||
array = new Array<number | undefined>();
|
||||
|
||||
add (value: number): boolean {
|
||||
let contains = this.contains(value);
|
||||
@ -354,7 +354,7 @@ export class Pool<T> {
|
||||
}
|
||||
|
||||
obtain () {
|
||||
return this.items.length > 0 ? this.items.pop() : this.instantiator();
|
||||
return this.items.length > 0 ? this.items.pop()! : this.instantiator();
|
||||
}
|
||||
|
||||
free (item: T) {
|
||||
|
||||
@ -53,12 +53,12 @@ export abstract class VertexAttachment extends Attachment {
|
||||
/** The bones which affect the {@link #getVertices()}. The array entries are, for each vertex, the number of bones affecting
|
||||
* the vertex followed by that many bone indices, which is the index of the bone in {@link Skeleton#bones}. Will be null
|
||||
* if this attachment has no weights. */
|
||||
bones: Array<number> = null;
|
||||
bones: Array<number> | null = null;
|
||||
|
||||
/** The vertex positions in the bone's coordinate system. For a non-weighted attachment, the values are `x,y`
|
||||
* entries for each vertex. For a weighted attachment, the values are `x,y,weight` entries for each bone affecting
|
||||
* each vertex. */
|
||||
vertices: NumberArrayLike = null;
|
||||
vertices: NumberArrayLike = [];
|
||||
|
||||
/** The maximum number of world vertex values that can be output by
|
||||
* {@link #computeWorldVertices()} using the `count` parameter. */
|
||||
@ -152,8 +152,7 @@ export abstract class VertexAttachment extends Attachment {
|
||||
if (this.vertices) {
|
||||
attachment.vertices = Utils.newFloatArray(this.vertices.length);
|
||||
Utils.arrayCopy(this.vertices, 0, attachment.vertices, 0, this.vertices.length);
|
||||
} else
|
||||
attachment.vertices = null;
|
||||
}
|
||||
|
||||
attachment.worldVerticesLength = this.worldVerticesLength;
|
||||
attachment.timelineAttahment = this.timelineAttahment;
|
||||
|
||||
@ -42,10 +42,10 @@ import { Sequence } from "./Sequence";
|
||||
* Runtimes Guide. */
|
||||
export interface AttachmentLoader {
|
||||
/** @return May be null to not load an attachment. */
|
||||
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment;
|
||||
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence | null): RegionAttachment;
|
||||
|
||||
/** @return May be null to not load an attachment. */
|
||||
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment;
|
||||
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence | null): MeshAttachment;
|
||||
|
||||
/** @return May be null to not load an attachment. */
|
||||
newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment;
|
||||
|
||||
@ -35,7 +35,7 @@ import { VertexAttachment, Attachment } from "./Attachment";
|
||||
export class ClippingAttachment extends VertexAttachment {
|
||||
/** Clipping is performed between the clipping polygon's slot and the end slot. Returns null if clipping is done until the end of
|
||||
* the skeleton's rendering. */
|
||||
endSlot: SlotData = null;
|
||||
endSlot: SlotData | null = null;
|
||||
|
||||
// Nonessential.
|
||||
/** The color of the clipping polygon as it was in Spine. Available only when nonessential data was exported. Clipping polygons
|
||||
|
||||
@ -37,7 +37,7 @@ export interface HasTextureRegion {
|
||||
|
||||
/** The region used to draw the attachment. After setting the region or if the region's properties are changed,
|
||||
* {@link #updateRegion()} must be called. */
|
||||
region: TextureRegion;
|
||||
region: TextureRegion | null;
|
||||
|
||||
/** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after setting the
|
||||
* {@link #getRegion()} or if the region's properties are changed. */
|
||||
@ -46,5 +46,5 @@ export interface HasTextureRegion {
|
||||
/** The color to tint the attachment. */
|
||||
color: Color;
|
||||
|
||||
sequence: Sequence;
|
||||
sequence: Sequence | null;
|
||||
}
|
||||
@ -40,21 +40,21 @@ import { Slot } from "../Slot";
|
||||
*
|
||||
* See [Mesh attachments](http://esotericsoftware.com/spine-meshes) in the Spine User Guide. */
|
||||
export class MeshAttachment extends VertexAttachment implements HasTextureRegion {
|
||||
region: TextureRegion = null;
|
||||
region: TextureRegion | null = null;
|
||||
|
||||
/** The name of the texture region for this attachment. */
|
||||
path: string = null;
|
||||
path: string;
|
||||
|
||||
/** The UV pair for each vertex, normalized within the texture region. */
|
||||
regionUVs: NumberArrayLike = null;
|
||||
regionUVs: NumberArrayLike = [];
|
||||
|
||||
/** The UV pair for each vertex, normalized within the entire texture.
|
||||
*
|
||||
* See {@link #updateUVs}. */
|
||||
uvs: NumberArrayLike = null;
|
||||
uvs: NumberArrayLike = [];
|
||||
|
||||
/** Triplets of vertex indices which describe the mesh's triangulation. */
|
||||
triangles: Array<number> = null;
|
||||
triangles: Array<number> = [];
|
||||
|
||||
/** The color to tint the mesh. */
|
||||
color = new Color(1, 1, 1, 1);
|
||||
@ -70,28 +70,30 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
|
||||
|
||||
/** Vertex index pairs describing edges for controling triangulation. Mesh triangles will never cross edges. Only available if
|
||||
* nonessential data was exported. Triangulation is not performed at runtime. */
|
||||
edges: Array<number> = null;
|
||||
edges: Array<number> = [];
|
||||
|
||||
private parentMesh: MeshAttachment = null;
|
||||
private parentMesh: MeshAttachment | null = null;
|
||||
|
||||
sequence: Sequence = null;
|
||||
sequence: Sequence | null = null;
|
||||
|
||||
tempColor = new Color(0, 0, 0, 0);
|
||||
|
||||
constructor (name: string) {
|
||||
constructor (name: string, path: string) {
|
||||
super(name);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/** Calculates {@link #uvs} using the {@link #regionUVs} and region. Must be called if the region, the region's properties, or
|
||||
* the {@link #regionUVs} are changed. */
|
||||
updateRegion () {
|
||||
if (!this.region) throw new Error("Region not set.");
|
||||
let regionUVs = this.regionUVs;
|
||||
if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
|
||||
let uvs = this.uvs;
|
||||
let n = this.uvs.length;
|
||||
let u = this.region.u, v = this.region.v, width = 0, height = 0;
|
||||
if (this.region instanceof TextureAtlasRegion) {
|
||||
let region = this.region, image = region.page.texture.getImage();
|
||||
let region = this.region, image = region.page!.texture!.getImage();
|
||||
let textureWidth = image.width, textureHeight = image.height;
|
||||
switch (region.degrees) {
|
||||
case 90:
|
||||
@ -167,9 +169,8 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
|
||||
copy (): Attachment {
|
||||
if (this.parentMesh) return this.newLinkedMesh();
|
||||
|
||||
let copy = new MeshAttachment(this.name);
|
||||
let copy = new MeshAttachment(this.name, this.path);
|
||||
copy.region = this.region;
|
||||
copy.path = this.path;
|
||||
copy.color.setFromColor(this.color);
|
||||
|
||||
this.copyTo(copy);
|
||||
@ -201,9 +202,8 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
|
||||
|
||||
/** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/
|
||||
newLinkedMesh (): MeshAttachment {
|
||||
let copy = new MeshAttachment(this.name);
|
||||
let copy = new MeshAttachment(this.name, this.path);
|
||||
copy.region = this.region;
|
||||
copy.path = this.path;
|
||||
copy.color.setFromColor(this.color);
|
||||
copy.timelineAttahment = this.timelineAttahment;
|
||||
copy.setParentMesh(this.parentMesh ? this.parentMesh : this);
|
||||
|
||||
@ -36,7 +36,7 @@ import { VertexAttachment, Attachment } from "./Attachment";
|
||||
export class PathAttachment extends VertexAttachment {
|
||||
|
||||
/** The lengths along the path in the setup pose from the start of the path to the end of each Bezier curve. */
|
||||
lengths: Array<number> = null;
|
||||
lengths: Array<number> = [];
|
||||
|
||||
/** If true, the start and end knots are connected. */
|
||||
closed = false;
|
||||
|
||||
@ -64,11 +64,11 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
color = new Color(1, 1, 1, 1);
|
||||
|
||||
/** The name of the texture region for this attachment. */
|
||||
path: string = null;
|
||||
path: string;
|
||||
|
||||
private rendererObject: any = null;
|
||||
region: TextureRegion = null;
|
||||
sequence: Sequence = null;
|
||||
region: TextureRegion | null = null;
|
||||
sequence: Sequence | null = null;
|
||||
|
||||
/** For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.
|
||||
*
|
||||
@ -79,12 +79,14 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
|
||||
tempColor = new Color(1, 1, 1, 1);
|
||||
|
||||
constructor (name: string) {
|
||||
constructor (name: string, path: string) {
|
||||
super(name);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
|
||||
updateRegion (): void {
|
||||
if (!this.region) throw new Error("Region not set.");
|
||||
let region = this.region;
|
||||
let regionScaleX = this.width / this.region.originalWidth * this.scaleX;
|
||||
let regionScaleY = this.height / this.region.originalHeight * this.scaleY;
|
||||
@ -179,10 +181,9 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
||||
}
|
||||
|
||||
copy (): Attachment {
|
||||
let copy = new RegionAttachment(this.name);
|
||||
let copy = new RegionAttachment(this.name, this.path);
|
||||
copy.region = this.region;
|
||||
copy.rendererObject = this.rendererObject;
|
||||
copy.path = this.path;
|
||||
copy.x = this.x;
|
||||
copy.y = this.y;
|
||||
copy.scaleX = this.scaleX;
|
||||
|
||||
@ -31,49 +31,49 @@ import { Animation, AnimationState, AnimationStateData, AtlasAttachmentLoader, B
|
||||
import { AssetManager, GLTexture, Input, LoadingScreen, ManagedWebGLRenderingContext, ResizeMode, SceneRenderer, Vector3 } from "@esotericsoftware/spine-webgl"
|
||||
|
||||
export interface SpinePlayerConfig {
|
||||
/* The URL of the skeleton JSON file (.json). */
|
||||
jsonUrl: string
|
||||
/* The URL of the skeleton JSON file (.json). Undefined if binaryUrl is given. */
|
||||
jsonUrl?: string
|
||||
|
||||
/* Optional: The name of a field in the JSON that holds the skeleton data. Default: none */
|
||||
jsonField: string
|
||||
jsonField?: string
|
||||
|
||||
/* The URL of the skeleton binary file (.skel). */
|
||||
binaryUrl: string
|
||||
/* The URL of the skeleton binary file (.skel). Undefined if jsonUrl is given. */
|
||||
binaryUrl?: string
|
||||
|
||||
/* The URL of the skeleton atlas file (.atlas). Atlas page images are automatically resolved. */
|
||||
atlasUrl: string
|
||||
atlasUrl?: string
|
||||
|
||||
/* Raw data URIs, mapping a path to base64 encoded raw data. When player's asset manager resolves the jsonUrl, binaryUrl,
|
||||
atlasUrl, or the image paths referenced in the atlas, it will first look for that path in the raw data URIs. This
|
||||
allows embedding assets directly in HTML/JS. Default: none */
|
||||
rawDataURIs: StringMap<string>
|
||||
rawDataURIs?: StringMap<string>
|
||||
|
||||
/* Optional: The name of the animation to be played. Default: empty animation */
|
||||
animation: string
|
||||
animation?: string
|
||||
|
||||
/* Optional: List of animation names from which the user can choose. Default: all animations */
|
||||
animations: string[]
|
||||
animations?: string[]
|
||||
|
||||
/* Optional: The default mix time used to switch between two animations. Default: 0.25 */
|
||||
defaultMix: number
|
||||
defaultMix?: number
|
||||
|
||||
/* Optional: The name of the skin to be set. Default: the default skin */
|
||||
skin: string
|
||||
skin?: string
|
||||
|
||||
/* Optional: List of skin names from which the user can choose. Default: all skins */
|
||||
skins: string[]
|
||||
skins?: string[]
|
||||
|
||||
/* Optional: Whether the skeleton's atlas images use premultiplied alpha. Default: true */
|
||||
premultipliedAlpha: boolean
|
||||
premultipliedAlpha?: boolean
|
||||
|
||||
/* Optional: Whether to show the player controls. When false, no external CSS file is needed. Default: true */
|
||||
showControls: boolean
|
||||
showControls?: boolean
|
||||
|
||||
/* Optional: Whether to show the loading animation. Default: true */
|
||||
showLoading: boolean
|
||||
showLoading?: boolean
|
||||
|
||||
/* Optional: Which debugging visualizations are shown. Default: none */
|
||||
debug: {
|
||||
debug?: {
|
||||
bones: boolean
|
||||
regions: boolean
|
||||
meshes: boolean
|
||||
@ -86,81 +86,81 @@ export interface SpinePlayerConfig {
|
||||
|
||||
/* Optional: The position and size of the viewport in the skeleton's world coordinates. Default: the bounding box that fits
|
||||
the current animation, 10% padding, 0.25 transition time */
|
||||
viewport: {
|
||||
viewport?: {
|
||||
/* Optional: The position and size of the viewport in the skeleton's world coordinates. Default: the bounding box that
|
||||
fits the current animation */
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
x?: number
|
||||
y?: number
|
||||
width?: number
|
||||
height?: number
|
||||
|
||||
/* Optional: Padding around the viewport size, given as a number or percentage (eg "25%"). Default: 10% */
|
||||
padLeft: string | number
|
||||
padRight: string | number
|
||||
padTop: string | number
|
||||
padBottom: string | number
|
||||
padLeft?: string | number
|
||||
padRight?: string | number
|
||||
padTop?: string | number
|
||||
padBottom?: string | number
|
||||
|
||||
/* Optional: Whether to draw lines showing the viewport bounds. Default: false */
|
||||
debugRender: boolean,
|
||||
debugRender?: boolean,
|
||||
|
||||
/* Optional: When the current viewport changes, the time to animate to the new viewport. Default: 0.25 */
|
||||
transitionTime: number
|
||||
transitionTime?: number
|
||||
|
||||
/* Optional: Viewports for specific animations. Default: none */
|
||||
animations: StringMap<Viewport>
|
||||
animations?: StringMap<Viewport>
|
||||
}
|
||||
|
||||
/* Optional: Whether the canvas is transparent, allowing the web page behind the canvas to show through when
|
||||
backgroundColor alpha is < ff. Default: false */
|
||||
alpha: boolean
|
||||
alpha?: boolean
|
||||
|
||||
/* Optional: The canvas background color, given in the format #rrggbb or #rrggbbaa. Default: #000000ff (black) or when
|
||||
alpha is true #00000000 (transparent) */
|
||||
backgroundColor: string
|
||||
backgroundColor?: string
|
||||
|
||||
/* Optional: The background color used in fullscreen mode, given in the format #rrggbb or #rrggbbaa. Default: backgroundColor */
|
||||
fullScreenBackgroundColor: string
|
||||
fullScreenBackgroundColor?: string
|
||||
|
||||
/* Optional: An image to draw behind the skeleton. Default: none */
|
||||
backgroundImage: {
|
||||
backgroundImage?: {
|
||||
url: string
|
||||
|
||||
/* Optional: The position and size of the background image in the skeleton's world coordinates. Default: fills the viewport */
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
x?: number
|
||||
y?: number
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
/* Optional: Whether mipmapping and anisotropic filtering are used for highest quality scaling when available, otherwise the
|
||||
filter settings from the texture atlas are used. Default: true */
|
||||
mipmaps: true
|
||||
mipmaps?: boolean
|
||||
|
||||
/* Optional: List of bone names that the user can drag to position. Default: none */
|
||||
controlBones: string[]
|
||||
controlBones?: string[]
|
||||
|
||||
/* Optional: Callback when the skeleton and its assets have been successfully loaded. If an animation is set on track 0,
|
||||
the player won't set its own animation. Default: none */
|
||||
success: (player: SpinePlayer) => void
|
||||
success?: (player: SpinePlayer) => void
|
||||
|
||||
/* Optional: Callback when the skeleton could not be loaded or rendered. Default: none */
|
||||
error: (player: SpinePlayer, msg: string) => void
|
||||
error?: (player: SpinePlayer, msg: string) => void
|
||||
|
||||
/* Optional: Callback at the start of each frame, before the skeleton is posed or drawn. Default: none */
|
||||
frame: (player: SpinePlayer, delta: number) => void
|
||||
frame?: (player: SpinePlayer, delta: number) => void
|
||||
|
||||
/* Optional: Callback after the skeleton is posed each frame, before it is drawn. Default: none */
|
||||
update: (player: SpinePlayer, delta: number) => void
|
||||
update?: (player: SpinePlayer, delta: number) => void
|
||||
|
||||
/* Optional: Callback after the skeleton is drawn each frame. Default: none */
|
||||
draw: (player: SpinePlayer, delta: number) => void
|
||||
draw?: (player: SpinePlayer, delta: number) => void
|
||||
|
||||
/* Optional: Callback each frame before the skeleton is loaded. Default: none */
|
||||
loading: (player: SpinePlayer, delta: number) => void
|
||||
loading?: (player: SpinePlayer, delta: number) => void
|
||||
|
||||
/* Optional: The downloader used by the player's asset manager. Passing the same downloader to multiple players using the
|
||||
same assets ensures the assets are only downloaded once. Default: new instance */
|
||||
downloader: Downloader
|
||||
downloader?: Downloader
|
||||
}
|
||||
|
||||
export interface Viewport {
|
||||
@ -181,31 +181,31 @@ export interface Viewport {
|
||||
export class SpinePlayer implements Disposable {
|
||||
public parent: HTMLElement;
|
||||
public dom: HTMLElement;
|
||||
public canvas: HTMLCanvasElement;
|
||||
public context: ManagedWebGLRenderingContext;
|
||||
public sceneRenderer: SceneRenderer;
|
||||
public loadingScreen: LoadingScreen;
|
||||
public assetManager: AssetManager;
|
||||
public canvas: HTMLCanvasElement | null = null;
|
||||
public context: ManagedWebGLRenderingContext | null = null;
|
||||
public sceneRenderer: SceneRenderer | null = null;
|
||||
public loadingScreen: LoadingScreen | null = null;
|
||||
public assetManager: AssetManager | null = null;
|
||||
public bg = new Color();
|
||||
public bgFullscreen = new Color();
|
||||
|
||||
private playerControls: HTMLElement;
|
||||
private timelineSlider: Slider;
|
||||
private playButton: HTMLElement;
|
||||
private skinButton: HTMLElement;
|
||||
private animationButton: HTMLElement;
|
||||
private playerControls: HTMLElement | null = null;
|
||||
private timelineSlider: Slider | null = null;
|
||||
private playButton: HTMLElement | null = null;
|
||||
private skinButton: HTMLElement | null = null;
|
||||
private animationButton: HTMLElement | null = null;
|
||||
|
||||
private playTime = 0;
|
||||
private selectedBones: Bone[];
|
||||
private selectedBones: (Bone | null)[] = [];
|
||||
private cancelId = 0;
|
||||
popup: Popup;
|
||||
popup: Popup | null = null;
|
||||
|
||||
/* True if the player is unable to load or render the skeleton. */
|
||||
public error: boolean;
|
||||
public error: boolean = false;
|
||||
/* The player's skeleton. Null until loading is complete (access after config.success). */
|
||||
public skeleton: Skeleton;
|
||||
public skeleton: Skeleton | null = null;
|
||||
/* The animation state controlling the skeleton. Null until loading is complete (access after config.success). */
|
||||
public animationState: AnimationState;
|
||||
public animationState: AnimationState | null = null;
|
||||
|
||||
public paused = true;
|
||||
public speed = 1;
|
||||
@ -214,14 +214,15 @@ export class SpinePlayer implements Disposable {
|
||||
private disposed = false;
|
||||
|
||||
private viewport: Viewport = {} as Viewport;
|
||||
private currentViewport: Viewport;
|
||||
private previousViewport: Viewport;
|
||||
private currentViewport: Viewport = {} as Viewport;
|
||||
private previousViewport: Viewport = {} as Viewport;
|
||||
private viewportTransitionStart = 0;
|
||||
private eventListeners: Array<{ target: any, event: any, func: any }> = [];
|
||||
|
||||
constructor (parent: HTMLElement | string, private config: SpinePlayerConfig) {
|
||||
this.parent = typeof parent === "string" ? document.getElementById(parent) : parent;
|
||||
if (!this.parent) throw new Error("SpinePlayer parent not found: " + parent);
|
||||
let parentDom = typeof parent === "string" ? document.getElementById(parent) : parent;
|
||||
if (parentDom == null) throw new Error("SpinePlayer parent not found: " + parent);
|
||||
this.parent = parentDom;
|
||||
|
||||
if (config.showControls === void 0) config.showControls = true;
|
||||
let controls = config.showControls ? /*html*/`
|
||||
@ -244,7 +245,7 @@ export class SpinePlayer implements Disposable {
|
||||
try {
|
||||
this.validateConfig(config);
|
||||
} catch (e) {
|
||||
this.showError(e.message, e);
|
||||
this.showError((e as any).message, e as any);
|
||||
}
|
||||
|
||||
this.initialize();
|
||||
@ -257,9 +258,9 @@ export class SpinePlayer implements Disposable {
|
||||
}
|
||||
|
||||
dispose (): void {
|
||||
this.sceneRenderer.dispose();
|
||||
if (this.loadingScreen) this.loadingScreen.dispose();
|
||||
this.assetManager.dispose();
|
||||
this.sceneRenderer?.dispose();
|
||||
this.loadingScreen?.dispose();
|
||||
this.assetManager?.dispose();
|
||||
for (var i = 0; i < this.eventListeners.length; i++) {
|
||||
var eventListener = this.eventListeners[i];
|
||||
eventListener.target.removeEventListener(eventListener.event, eventListener.func);
|
||||
@ -280,29 +281,38 @@ export class SpinePlayer implements Disposable {
|
||||
if (!config.atlasUrl) throw new Error("A URL must be specified for the atlas file.");
|
||||
if (!config.backgroundColor) config.backgroundColor = config.alpha ? "00000000" : "000000";
|
||||
if (!config.fullScreenBackgroundColor) config.fullScreenBackgroundColor = config.backgroundColor;
|
||||
if (config.backgroundImage && !config.backgroundImage.url) config.backgroundImage = null;
|
||||
if (config.backgroundImage && !config.backgroundImage.url) config.backgroundImage = undefined;
|
||||
if (config.premultipliedAlpha === void 0) config.premultipliedAlpha = true;
|
||||
if (config.mipmaps === void 0) config.mipmaps = true;
|
||||
if (!config.debug) config.debug = {} as any;
|
||||
if (!config.debug) config.debug = {
|
||||
bones: false,
|
||||
clipping: false,
|
||||
bounds: false,
|
||||
hulls: false,
|
||||
meshes: false,
|
||||
paths: false,
|
||||
points: false,
|
||||
regions: false
|
||||
};
|
||||
if (config.animations && config.animation && config.animations.indexOf(config.animation) < 0)
|
||||
throw new Error("Animation '" + config.animation + "' is not in the config animation list: " + toString(config.animations));
|
||||
if (config.skins && config.skin && config.skins.indexOf(config.skin) < 0)
|
||||
throw new Error("Default skin '" + config.skin + "' is not in the config skins list: " + toString(config.skins));
|
||||
if (!config.viewport) config.viewport = {} as any;
|
||||
if (!config.viewport.animations) config.viewport.animations = {};
|
||||
if (config.viewport.debugRender === void 0) config.viewport.debugRender = false;
|
||||
if (config.viewport.transitionTime === void 0) config.viewport.transitionTime = 0.25;
|
||||
if (!config.viewport!.animations) config.viewport!.animations = {};
|
||||
if (config.viewport!.debugRender === void 0) config.viewport!.debugRender = false;
|
||||
if (config.viewport!.transitionTime === void 0) config.viewport!.transitionTime = 0.25;
|
||||
if (!config.controlBones) config.controlBones = [];
|
||||
if (config.showLoading === void 0) config.showLoading = true;
|
||||
if (config.defaultMix === void 0) config.defaultMix = 0.25;
|
||||
}
|
||||
|
||||
private initialize (): HTMLElement {
|
||||
private initialize (): HTMLElement | null {
|
||||
let config = this.config;
|
||||
let dom = this.dom;
|
||||
|
||||
if (!config.alpha) { // Prevents a flash before the first frame is drawn.
|
||||
let hex = config.backgroundColor;
|
||||
let hex = config.backgroundColor!;
|
||||
this.dom.style.backgroundColor = (hex.charAt(0) == '#' ? hex : "#" + hex).substr(0, 7);
|
||||
}
|
||||
|
||||
@ -315,7 +325,8 @@ export class SpinePlayer implements Disposable {
|
||||
this.sceneRenderer = new SceneRenderer(this.canvas, this.context, true);
|
||||
if (config.showLoading) this.loadingScreen = new LoadingScreen(this.sceneRenderer);
|
||||
} catch (e) {
|
||||
this.showError("Sorry, your browser does not support \nPlease use the latest version of Firefox, Chrome, Edge, or Safari.", e);
|
||||
this.showError("Sorry, your browser does not support \nPlease use the latest version of Firefox, Chrome, Edge, or Safari.", e as any);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load the assets.
|
||||
@ -327,13 +338,13 @@ export class SpinePlayer implements Disposable {
|
||||
if (config.jsonUrl)
|
||||
this.assetManager.loadJson(config.jsonUrl);
|
||||
else
|
||||
this.assetManager.loadBinary(config.binaryUrl);
|
||||
this.assetManager.loadTextureAtlas(config.atlasUrl);
|
||||
this.assetManager.loadBinary(config.binaryUrl!);
|
||||
this.assetManager.loadTextureAtlas(config.atlasUrl!);
|
||||
if (config.backgroundImage) this.assetManager.loadTexture(config.backgroundImage.url);
|
||||
|
||||
// Setup the UI elements.
|
||||
this.bg.setFromString(config.backgroundColor);
|
||||
this.bgFullscreen.setFromString(config.fullScreenBackgroundColor);
|
||||
this.bg.setFromString(config.backgroundColor!);
|
||||
this.bgFullscreen.setFromString(config.fullScreenBackgroundColor!);
|
||||
if (config.showControls) {
|
||||
this.playerControls = dom.children[1] as HTMLElement;
|
||||
let controls = this.playerControls.children;
|
||||
@ -351,18 +362,18 @@ export class SpinePlayer implements Disposable {
|
||||
timeline.appendChild(this.timelineSlider.create());
|
||||
this.timelineSlider.change = (percentage) => {
|
||||
this.pause();
|
||||
let animationDuration = this.animationState.getCurrent(0).animation.duration;
|
||||
let animationDuration = this.animationState!.getCurrent(0)!.animation!.duration;
|
||||
let time = animationDuration * percentage;
|
||||
this.animationState.update(time - this.playTime);
|
||||
this.animationState.apply(this.skeleton);
|
||||
this.skeleton.updateWorldTransform();
|
||||
this.animationState!.update(time - this.playTime);
|
||||
this.animationState!.apply(this.skeleton!);
|
||||
this.skeleton!.updateWorldTransform();
|
||||
this.playTime = time;
|
||||
};
|
||||
|
||||
this.playButton.onclick = () => (this.paused ? this.play() : this.pause());
|
||||
speedButton.onclick = () => this.showSpeedDialog(speedButton);
|
||||
this.animationButton.onclick = () => this.showAnimationsDialog(this.animationButton);
|
||||
this.skinButton.onclick = () => this.showSkinsDialog(this.skinButton);
|
||||
this.animationButton.onclick = () => this.showAnimationsDialog(this.animationButton!);
|
||||
this.skinButton.onclick = () => this.showSkinsDialog(this.skinButton!);
|
||||
settingsButton.onclick = () => this.showSettingsDialog(settingsButton);
|
||||
|
||||
let oldWidth = this.canvas.clientWidth, oldHeight = this.canvas.clientHeight;
|
||||
@ -372,13 +383,13 @@ export class SpinePlayer implements Disposable {
|
||||
let fullscreenChanged = () => {
|
||||
isFullscreen = !isFullscreen;
|
||||
if (!isFullscreen) {
|
||||
this.canvas.style.width = oldWidth + "px";
|
||||
this.canvas.style.height = oldHeight + "px";
|
||||
this.canvas!.style.width = oldWidth + "px";
|
||||
this.canvas!.style.height = oldHeight + "px";
|
||||
this.drawFrame(false);
|
||||
// Got to reset the style to whatever the user set after the next layouting.
|
||||
requestAnimationFrame(() => {
|
||||
this.canvas.style.width = oldStyleWidth;
|
||||
this.canvas.style.height = oldStyleHeight;
|
||||
this.canvas!.style.width = oldStyleWidth;
|
||||
this.canvas!.style.height = oldStyleHeight;
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -394,10 +405,10 @@ export class SpinePlayer implements Disposable {
|
||||
else if (doc.webkitExitFullscreen) doc.webkitExitFullscreen()
|
||||
else if (doc.msExitFullscreen) doc.msExitFullscreen();
|
||||
} else {
|
||||
oldWidth = this.canvas.clientWidth;
|
||||
oldHeight = this.canvas.clientHeight;
|
||||
oldStyleWidth = this.canvas.style.width;
|
||||
oldStyleHeight = this.canvas.style.height;
|
||||
oldWidth = this.canvas!.clientWidth;
|
||||
oldHeight = this.canvas!.clientHeight;
|
||||
oldStyleWidth = this.canvas!.style.width;
|
||||
oldStyleHeight = this.canvas!.style.height;
|
||||
if (player.requestFullscreen) player.requestFullscreen();
|
||||
else if (player.webkitRequestFullScreen) player.webkitRequestFullScreen();
|
||||
else if (player.mozRequestFullScreen) player.mozRequestFullScreen();
|
||||
@ -413,18 +424,18 @@ export class SpinePlayer implements Disposable {
|
||||
private loadSkeleton () {
|
||||
if (this.error) return;
|
||||
|
||||
if (this.assetManager.hasErrors())
|
||||
this.showError("Error: Assets could not be loaded.\n" + toString(this.assetManager.getErrors()));
|
||||
if (this.assetManager!.hasErrors())
|
||||
this.showError("Error: Assets could not be loaded.\n" + toString(this.assetManager!.getErrors()));
|
||||
|
||||
let config = this.config;
|
||||
|
||||
// Configure filtering, don't use mipmaps in WebGL1 if the atlas page is non-POT
|
||||
let atlas = this.assetManager.require(config.atlasUrl) as TextureAtlas;
|
||||
let gl = this.context.gl, anisotropic = gl.getExtension("EXT_texture_filter_anisotropic");
|
||||
let atlas = this.assetManager!.require(config.atlasUrl!) as TextureAtlas;
|
||||
let gl = this.context!.gl, anisotropic = gl.getExtension("EXT_texture_filter_anisotropic");
|
||||
let isWebGL1 = gl.getParameter(gl.VERSION).indexOf("WebGL 1.0") != -1;
|
||||
for (let page of atlas.pages) {
|
||||
let minFilter = page.minFilter;
|
||||
var useMipMaps: boolean = config.mipmaps;
|
||||
var useMipMaps: boolean = config.mipmaps!;
|
||||
var isPOT = MathUtils.isPowerOfTwo(page.width) && MathUtils.isPowerOfTwo(page.height);
|
||||
if (isWebGL1 && !isPOT) useMipMaps = false;
|
||||
|
||||
@ -434,7 +445,7 @@ export class SpinePlayer implements Disposable {
|
||||
minFilter = TextureFilter.MipMapLinearLinear;
|
||||
} else
|
||||
minFilter = TextureFilter.Linear; // Don't use mipmaps without anisotropic.
|
||||
page.texture.setFilters(minFilter, TextureFilter.Nearest);
|
||||
page.texture!.setFilters(minFilter, TextureFilter.Nearest);
|
||||
}
|
||||
if (minFilter != TextureFilter.Nearest && minFilter != TextureFilter.Linear) (page.texture as GLTexture).update(true);
|
||||
}
|
||||
@ -443,7 +454,7 @@ export class SpinePlayer implements Disposable {
|
||||
let skeletonData: SkeletonData;
|
||||
if (config.jsonUrl) {
|
||||
try {
|
||||
let jsonData = this.assetManager.remove(config.jsonUrl);
|
||||
let jsonData = this.assetManager!.remove(config.jsonUrl);
|
||||
if (!jsonData) throw new Error("Empty JSON data.");
|
||||
if (config.jsonField) {
|
||||
jsonData = jsonData[config.jsonField];
|
||||
@ -452,32 +463,34 @@ export class SpinePlayer implements Disposable {
|
||||
let json = new SkeletonJson(new AtlasAttachmentLoader(atlas));
|
||||
skeletonData = json.readSkeletonData(jsonData);
|
||||
} catch (e) {
|
||||
this.showError(`Error: Could not load skeleton JSON.\n${e.message}`, e);
|
||||
this.showError(`Error: Could not load skeleton JSON.\n${(e as any).message}`, e as any);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let binaryData = this.assetManager.remove(config.binaryUrl);
|
||||
let binaryData = this.assetManager!.remove(config.binaryUrl!);
|
||||
let binary = new SkeletonBinary(new AtlasAttachmentLoader(atlas));
|
||||
try {
|
||||
skeletonData = binary.readSkeletonData(binaryData);
|
||||
} catch (e) {
|
||||
this.showError(`Error: Could not load skeleton binary.\n${e.message}`, e);
|
||||
this.showError(`Error: Could not load skeleton binary.\n${(e as any).message}`, e as any);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.skeleton = new Skeleton(skeletonData);
|
||||
let stateData = new AnimationStateData(skeletonData);
|
||||
stateData.defaultMix = config.defaultMix;
|
||||
stateData.defaultMix = config.defaultMix!;
|
||||
this.animationState = new AnimationState(stateData);
|
||||
|
||||
// Check if all control bones are in the skeleton
|
||||
config.controlBones.forEach(bone => {
|
||||
config.controlBones!.forEach(bone => {
|
||||
if (!skeletonData.findBone(bone)) this.showError(`Error: Control bone does not exist in skeleton: ${bone}`);
|
||||
})
|
||||
|
||||
// Setup skin.
|
||||
if (!config.skin && skeletonData.skins.length) config.skin = skeletonData.skins[0].name;
|
||||
if (config.skins && config.skin.length) {
|
||||
if (config.skins && config.skin!.length) {
|
||||
config.skins.forEach(skin => {
|
||||
if (!this.skeleton.data.findSkin(skin))
|
||||
if (!this.skeleton!.data.findSkin(skin))
|
||||
this.showError(`Error: Skin in config list does not exist in skeleton: ${skin}`);
|
||||
});
|
||||
}
|
||||
@ -489,7 +502,7 @@ export class SpinePlayer implements Disposable {
|
||||
}
|
||||
|
||||
// Check if all animations given a viewport exist.
|
||||
Object.getOwnPropertyNames(config.viewport.animations).forEach((animation: string) => {
|
||||
Object.getOwnPropertyNames(config.viewport!.animations).forEach((animation: string) => {
|
||||
if (!skeletonData.findAnimation(animation))
|
||||
this.showError(`Error: Animation for which a viewport was specified does not exist in skeleton: ${animation}`);
|
||||
});
|
||||
@ -497,7 +510,7 @@ export class SpinePlayer implements Disposable {
|
||||
// Setup the animations after the viewport, so default bounds don't get messed up.
|
||||
if (config.animations && config.animations.length) {
|
||||
config.animations.forEach(animation => {
|
||||
if (!this.skeleton.data.findAnimation(animation))
|
||||
if (!this.skeleton!.data.findAnimation(animation))
|
||||
this.showError(`Error: Animation in config list does not exist in skeleton: ${animation}`);
|
||||
});
|
||||
if (!config.animation) config.animation = config.animations[0];
|
||||
@ -511,8 +524,8 @@ export class SpinePlayer implements Disposable {
|
||||
|
||||
if (config.showControls) {
|
||||
// Hide skin and animation if there's only the default skin / no animation
|
||||
if (skeletonData.skins.length == 1 || (config.skins && config.skins.length == 1)) this.skinButton.classList.add("spine-player-hidden");
|
||||
if (skeletonData.animations.length == 1 || (config.animations && config.animations.length == 1)) this.animationButton.classList.add("spine-player-hidden");
|
||||
if (skeletonData.skins.length == 1 || (config.skins && config.skins.length == 1)) this.skinButton!.classList.add("spine-player-hidden");
|
||||
if (skeletonData.animations.length == 1 || (config.animations && config.animations.length == 1)) this.animationButton!.classList.add("spine-player-hidden");
|
||||
}
|
||||
|
||||
if (config.success) config.success(this);
|
||||
@ -525,37 +538,38 @@ export class SpinePlayer implements Disposable {
|
||||
} else {
|
||||
entry = this.animationState.setEmptyAnimation(0);
|
||||
entry.trackEnd = 100000000;
|
||||
this.setViewport(entry.animation);
|
||||
this.setViewport(entry.animation!);
|
||||
this.pause();
|
||||
}
|
||||
} else if (!this.currentViewport) {
|
||||
this.setViewport(entry.animation);
|
||||
this.setViewport(entry.animation!);
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
|
||||
private setupInput () {
|
||||
let config = this.config;
|
||||
let controlBones = config.controlBones;
|
||||
let controlBones = config.controlBones!;
|
||||
if (!controlBones.length && !config.showControls) return;
|
||||
let selectedBones = this.selectedBones = new Array<Bone>(controlBones.length);
|
||||
let canvas = this.canvas;
|
||||
let target: Bone = null;
|
||||
let selectedBones = this.selectedBones = new Array<Bone | null>(controlBones.length);
|
||||
let canvas = this.canvas!;
|
||||
let target: Bone | null = null;
|
||||
let offset = new Vector2();
|
||||
let coords = new Vector3();
|
||||
let mouse = new Vector3();
|
||||
let position = new Vector2();
|
||||
let skeleton = this.skeleton;
|
||||
let renderer = this.sceneRenderer;
|
||||
let skeleton = this.skeleton!;
|
||||
let renderer = this.sceneRenderer!;
|
||||
|
||||
let closest = function (x: number, y: number): Bone {
|
||||
let closest = function (x: number, y: number): Bone | null {
|
||||
mouse.set(x, canvas.clientHeight - y, 0)
|
||||
offset.x = offset.y = 0;
|
||||
let bestDistance = 24, index = 0;
|
||||
let best: Bone;
|
||||
let best: Bone | null = null;
|
||||
for (let i = 0; i < controlBones.length; i++) {
|
||||
selectedBones[i] = null;
|
||||
let bone = skeleton.findBone(controlBones[i]);
|
||||
if (!bone) continue;
|
||||
let distance = renderer.camera.worldToScreen(
|
||||
coords.set(bone.worldX, bone.worldY, 0),
|
||||
canvas.clientWidth, canvas.clientHeight).distance(mouse);
|
||||
@ -624,17 +638,17 @@ export class SpinePlayer implements Disposable {
|
||||
let mouseOverControls = true, mouseOverCanvas = false;
|
||||
let handleHover = (mouseX: number, mouseY: number) => {
|
||||
let popup = findWithClass(this.dom, "spine-player-popup");
|
||||
mouseOverControls = overlap(mouseX, mouseY, this.playerControls.getBoundingClientRect());
|
||||
mouseOverControls = overlap(mouseX, mouseY, this.playerControls!.getBoundingClientRect());
|
||||
mouseOverCanvas = overlap(mouseX, mouseY, canvas.getBoundingClientRect());
|
||||
clearTimeout(this.cancelId);
|
||||
let hide = !popup && !mouseOverControls && !mouseOverCanvas && !this.paused;
|
||||
if (hide)
|
||||
this.playerControls.classList.add("spine-player-controls-hidden");
|
||||
this.playerControls!.classList.add("spine-player-controls-hidden");
|
||||
else
|
||||
this.playerControls.classList.remove("spine-player-controls-hidden");
|
||||
this.playerControls!.classList.remove("spine-player-controls-hidden");
|
||||
if (!mouseOverControls && !popup && !this.paused) {
|
||||
this.cancelId = setTimeout(() => {
|
||||
if (!this.paused) this.playerControls.classList.add("spine-player-controls-hidden");
|
||||
if (!this.paused) this.playerControls!.classList.add("spine-player-controls-hidden");
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
@ -646,17 +660,17 @@ export class SpinePlayer implements Disposable {
|
||||
let config = this.config;
|
||||
if (config.showControls) {
|
||||
this.cancelId = setTimeout(() => {
|
||||
if (!this.paused) this.playerControls.classList.add("spine-player-controls-hidden");
|
||||
if (!this.paused) this.playerControls!.classList.add("spine-player-controls-hidden");
|
||||
}, 1000);
|
||||
this.playButton.classList.remove("spine-player-button-icon-play");
|
||||
this.playButton.classList.add("spine-player-button-icon-pause");
|
||||
this.playButton!.classList.remove("spine-player-button-icon-play");
|
||||
this.playButton!.classList.add("spine-player-button-icon-pause");
|
||||
|
||||
// If no config animation, set one when first clicked.
|
||||
if (!config.animation) {
|
||||
if (config.animations && config.animations.length)
|
||||
config.animation = config.animations[0];
|
||||
else if (this.skeleton.data.animations.length)
|
||||
config.animation = this.skeleton.data.animations[0].name;
|
||||
else if (this.skeleton!.data.animations.length)
|
||||
config.animation = this.skeleton!.data.animations[0].name;
|
||||
if (config.animation) this.setAnimation(config.animation);
|
||||
}
|
||||
}
|
||||
@ -665,33 +679,37 @@ export class SpinePlayer implements Disposable {
|
||||
pause () {
|
||||
this.paused = true;
|
||||
if (this.config.showControls) {
|
||||
this.playerControls.classList.remove("spine-player-controls-hidden");
|
||||
this.playerControls!.classList.remove("spine-player-controls-hidden");
|
||||
clearTimeout(this.cancelId);
|
||||
this.playButton.classList.remove("spine-player-button-icon-pause");
|
||||
this.playButton.classList.add("spine-player-button-icon-play");
|
||||
this.playButton!.classList.remove("spine-player-button-icon-pause");
|
||||
this.playButton!.classList.add("spine-player-button-icon-play");
|
||||
}
|
||||
}
|
||||
|
||||
/* Sets a new animation and viewport on track 0. */
|
||||
setAnimation (animation: string | Animation, loop: boolean = true): TrackEntry {
|
||||
animation = this.setViewport(animation);
|
||||
return this.animationState.setAnimationWith(0, animation, loop);
|
||||
return this.animationState!.setAnimationWith(0, animation, loop);
|
||||
}
|
||||
|
||||
/* Adds a new animation and viewport on track 0. */
|
||||
addAnimation (animation: string | Animation, loop: boolean = true, delay: number = 0): TrackEntry {
|
||||
animation = this.setViewport(animation);
|
||||
return this.animationState.addAnimationWith(0, animation, loop, delay);
|
||||
return this.animationState!.addAnimationWith(0, animation, loop, delay);
|
||||
}
|
||||
|
||||
/* Sets the viewport for the specified animation. */
|
||||
setViewport (animation: string | Animation): Animation {
|
||||
if (typeof animation == "string") animation = this.skeleton.data.findAnimation(animation);
|
||||
if (typeof animation == "string") {
|
||||
let foundAnimation = this.skeleton!.data.findAnimation(animation);
|
||||
if (!foundAnimation) throw new Error("Animation not found: " + animation);
|
||||
animation = foundAnimation;
|
||||
}
|
||||
|
||||
this.previousViewport = this.currentViewport;
|
||||
|
||||
// Determine the base viewport.
|
||||
let globalViewport = this.config.viewport;
|
||||
let globalViewport = this.config.viewport!;
|
||||
let viewport = this.currentViewport = {
|
||||
padLeft: globalViewport.padLeft !== void 0 ? globalViewport.padLeft : "10%",
|
||||
padRight: globalViewport.padRight !== void 0 ? globalViewport.padRight : "10%",
|
||||
@ -707,7 +725,7 @@ export class SpinePlayer implements Disposable {
|
||||
this.calculateAnimationViewport(animation, viewport);
|
||||
|
||||
// Override with the animation specific viewport for the final result.
|
||||
let userAnimViewport = this.config.viewport.animations[animation.name];
|
||||
let userAnimViewport = this.config.viewport!.animations![animation.name];
|
||||
if (userAnimViewport) {
|
||||
if (userAnimViewport.x !== void 0 && userAnimViewport.y !== void 0 && userAnimViewport.width && userAnimViewport.height) {
|
||||
viewport.x = userAnimViewport.x;
|
||||
@ -738,16 +756,16 @@ export class SpinePlayer implements Disposable {
|
||||
}
|
||||
|
||||
private calculateAnimationViewport (animation: Animation, viewport: Viewport) {
|
||||
this.skeleton.setToSetupPose();
|
||||
this.skeleton!.setToSetupPose();
|
||||
|
||||
let steps = 100, stepTime = animation.duration ? animation.duration / steps : 0, time = 0;
|
||||
let minX = 100000000, maxX = -100000000, minY = 100000000, maxY = -100000000;
|
||||
let offset = new Vector2(), size = new Vector2();
|
||||
|
||||
for (let i = 0; i < steps; i++, time += stepTime) {
|
||||
animation.apply(this.skeleton, time, time, false, null, 1, MixBlend.setup, MixDirection.mixIn);
|
||||
this.skeleton.updateWorldTransform();
|
||||
this.skeleton.getBounds(offset, size);
|
||||
animation.apply(this.skeleton!, time, time, false, [], 1, MixBlend.setup, MixDirection.mixIn);
|
||||
this.skeleton!.updateWorldTransform();
|
||||
this.skeleton!.getBounds(offset, size);
|
||||
|
||||
if (!isNaN(offset.x) && !isNaN(offset.y) && !isNaN(size.x) && !isNaN(size.y)) {
|
||||
minX = Math.min(offset.x, minX);
|
||||
@ -778,13 +796,13 @@ export class SpinePlayer implements Disposable {
|
||||
let delta = this.time.delta;
|
||||
|
||||
// Load the skeleton if the assets are ready.
|
||||
let loading = this.assetManager.isLoadingComplete();
|
||||
let loading = this.assetManager!.isLoadingComplete();
|
||||
if (!this.skeleton && loading) this.loadSkeleton();
|
||||
let skeleton = this.skeleton;
|
||||
let config = this.config;
|
||||
let skeleton = this.skeleton!;
|
||||
let config = this.config!;
|
||||
if (skeleton) {
|
||||
// Resize the canvas.
|
||||
let renderer = this.sceneRenderer;
|
||||
let renderer = this.sceneRenderer!;
|
||||
renderer.resize(ResizeMode.Expand);
|
||||
|
||||
let playDelta = this.paused ? 0 : delta * this.speed;
|
||||
@ -792,19 +810,19 @@ export class SpinePlayer implements Disposable {
|
||||
|
||||
// Update animation time and pose the skeleton.
|
||||
if (!this.paused) {
|
||||
this.animationState.update(playDelta);
|
||||
this.animationState.apply(skeleton);
|
||||
this.animationState!.update(playDelta);
|
||||
this.animationState!.apply(skeleton);
|
||||
skeleton.updateWorldTransform();
|
||||
|
||||
if (config.showControls) {
|
||||
this.playTime += playDelta;
|
||||
let entry = this.animationState.getCurrent(0);
|
||||
let entry = this.animationState!.getCurrent(0);
|
||||
if (entry) {
|
||||
let duration = entry.animation.duration;
|
||||
let duration = entry.animation!.duration;
|
||||
while (this.playTime >= duration && duration != 0)
|
||||
this.playTime -= duration;
|
||||
this.playTime = Math.max(0, Math.min(this.playTime, duration));
|
||||
this.timelineSlider.setValue(this.playTime / duration);
|
||||
this.timelineSlider!.setValue(this.playTime / duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -817,7 +835,7 @@ export class SpinePlayer implements Disposable {
|
||||
viewport.height = this.currentViewport.height + (this.currentViewport.padBottom as number) + (this.currentViewport.padTop as number)
|
||||
|
||||
if (this.previousViewport) {
|
||||
let transitionAlpha = (performance.now() - this.viewportTransitionStart) / 1000 / config.viewport.transitionTime;
|
||||
let transitionAlpha = (performance.now() - this.viewportTransitionStart) / 1000 / config.viewport!.transitionTime!;
|
||||
if (transitionAlpha < 1) {
|
||||
let x = this.previousViewport.x - (this.previousViewport.padLeft as number);
|
||||
let y = this.previousViewport.y - (this.previousViewport.padBottom as number);
|
||||
@ -830,13 +848,13 @@ export class SpinePlayer implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
renderer.camera.zoom = this.canvas.height / this.canvas.width > viewport.height / viewport.width
|
||||
? viewport.width / this.canvas.width : viewport.height / this.canvas.height;
|
||||
renderer.camera.zoom = this.canvas!.height / this.canvas!.width > viewport.height / viewport.width
|
||||
? viewport.width / this.canvas!.width : viewport.height / this.canvas!.height;
|
||||
renderer.camera.position.x = viewport.x + viewport.width / 2;
|
||||
renderer.camera.position.y = viewport.y + viewport.height / 2;
|
||||
|
||||
// Clear the screen.
|
||||
let gl = this.context.gl;
|
||||
let gl = this.context!.gl;
|
||||
gl.clearColor(bg.r, bg.g, bg.b, bg.a);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
@ -847,7 +865,7 @@ export class SpinePlayer implements Disposable {
|
||||
// Draw the background image.
|
||||
let bgImage = config.backgroundImage;
|
||||
if (bgImage) {
|
||||
let texture = this.assetManager.require(bgImage.url);
|
||||
let texture = this.assetManager!.require(bgImage.url);
|
||||
if (bgImage.x !== void 0 && bgImage.y !== void 0 && bgImage.width && bgImage.height)
|
||||
renderer.drawTexture(texture, bgImage.x, bgImage.y, bgImage.width, bgImage.height);
|
||||
else
|
||||
@ -856,19 +874,19 @@ export class SpinePlayer implements Disposable {
|
||||
|
||||
// Draw the skeleton and debug output.
|
||||
renderer.drawSkeleton(skeleton, config.premultipliedAlpha);
|
||||
if ((renderer.skeletonDebugRenderer.drawBones = config.debug.bones)
|
||||
|| (renderer.skeletonDebugRenderer.drawBoundingBoxes = config.debug.bounds)
|
||||
|| (renderer.skeletonDebugRenderer.drawClipping = config.debug.clipping)
|
||||
|| (renderer.skeletonDebugRenderer.drawMeshHull = config.debug.hulls)
|
||||
|| (renderer.skeletonDebugRenderer.drawPaths = config.debug.paths)
|
||||
|| (renderer.skeletonDebugRenderer.drawRegionAttachments = config.debug.regions)
|
||||
|| (renderer.skeletonDebugRenderer.drawMeshTriangles = config.debug.meshes)
|
||||
if ((renderer.skeletonDebugRenderer.drawBones = config.debug!.bones!)
|
||||
|| (renderer.skeletonDebugRenderer.drawBoundingBoxes = config.debug!.bounds!)
|
||||
|| (renderer.skeletonDebugRenderer.drawClipping = config.debug!.clipping!)
|
||||
|| (renderer.skeletonDebugRenderer.drawMeshHull = config.debug!.hulls!)
|
||||
|| (renderer.skeletonDebugRenderer.drawPaths = config.debug!.paths!)
|
||||
|| (renderer.skeletonDebugRenderer.drawRegionAttachments = config.debug!.regions!)
|
||||
|| (renderer.skeletonDebugRenderer.drawMeshTriangles = config.debug!.meshes!)
|
||||
) {
|
||||
renderer.drawSkeletonDebug(skeleton, config.premultipliedAlpha);
|
||||
}
|
||||
|
||||
// Draw the control bones.
|
||||
let controlBones = config.controlBones;
|
||||
let controlBones = config.controlBones!;
|
||||
if (controlBones.length) {
|
||||
let selectedBones = this.selectedBones;
|
||||
gl.lineWidth(2);
|
||||
@ -883,7 +901,7 @@ export class SpinePlayer implements Disposable {
|
||||
}
|
||||
|
||||
// Draw the viewport bounds.
|
||||
if (config.viewport.debugRender) {
|
||||
if (config.viewport!.debugRender) {
|
||||
gl.lineWidth(1);
|
||||
renderer.rect(false, this.currentViewport.x, this.currentViewport.y, this.currentViewport.width, this.currentViewport.height, Color.GREEN);
|
||||
renderer.rect(false, viewport.x, viewport.y, viewport.width, viewport.height, Color.RED);
|
||||
@ -896,12 +914,12 @@ export class SpinePlayer implements Disposable {
|
||||
|
||||
// Draw the loading screen.
|
||||
if (config.showLoading) {
|
||||
this.loadingScreen.backgroundColor.setFromColor(bg);
|
||||
this.loadingScreen.draw(loading);
|
||||
this.loadingScreen!.backgroundColor.setFromColor(bg);
|
||||
this.loadingScreen!.draw(loading);
|
||||
}
|
||||
if (loading && config.loading) config.loading(this, delta);
|
||||
} catch (e) {
|
||||
this.showError(`Error: Unable to render skeleton.\n${e.message}`, e);
|
||||
this.showError(`Error: Unable to render skeleton.\n${(e as any).message}`, e as any);
|
||||
}
|
||||
}
|
||||
|
||||
@ -910,14 +928,14 @@ export class SpinePlayer implements Disposable {
|
||||
}
|
||||
|
||||
private hidePopup (id: string): boolean {
|
||||
return this.popup && this.popup.hide(id);
|
||||
return this.popup != null && this.popup.hide(id);
|
||||
}
|
||||
|
||||
private showSpeedDialog (speedButton: HTMLElement) {
|
||||
let id = "speed";
|
||||
if (this.hidePopup(id)) return;
|
||||
|
||||
let popup = new Popup(id, speedButton, this, this.playerControls, /*html*/`
|
||||
let popup = new Popup(id, speedButton, this, this.playerControls!, /*html*/`
|
||||
<div class="spine-player-popup-title">Speed</div>
|
||||
<hr>
|
||||
<div class="spine-player-row" style="align-items:center;padding:8px">
|
||||
@ -938,7 +956,7 @@ export class SpinePlayer implements Disposable {
|
||||
if (this.hidePopup(id)) return;
|
||||
if (!this.skeleton || !this.skeleton.data.animations.length) return;
|
||||
|
||||
let popup = new Popup(id, animationsButton, this, this.playerControls,
|
||||
let popup = new Popup(id, animationsButton, this, this.playerControls!,
|
||||
/*html*/`<div class="spine-player-popup-title">Animations</div><hr><ul class="spine-player-list"></ul>`);
|
||||
|
||||
let rows = findWithClass(popup.dom, "spine-player-list");
|
||||
@ -968,7 +986,7 @@ export class SpinePlayer implements Disposable {
|
||||
if (this.hidePopup(id)) return;
|
||||
if (!this.skeleton || !this.skeleton.data.animations.length) return;
|
||||
|
||||
let popup = new Popup(id, skinButton, this, this.playerControls,
|
||||
let popup = new Popup(id, skinButton, this, this.playerControls!,
|
||||
/*html*/`<div class="spine-player-popup-title">Skins</div><hr><ul class="spine-player-list"></ul>`);
|
||||
|
||||
let rows = findWithClass(popup.dom, "spine-player-list");
|
||||
@ -984,8 +1002,8 @@ export class SpinePlayer implements Disposable {
|
||||
removeClass(rows.children, "selected");
|
||||
row.classList.add("selected");
|
||||
this.config.skin = skin.name;
|
||||
this.skeleton.setSkinByName(this.config.skin);
|
||||
this.skeleton.setSlotsToSetupPose();
|
||||
this.skeleton!.setSkinByName(this.config.skin);
|
||||
this.skeleton!.setSlotsToSetupPose();
|
||||
}
|
||||
});
|
||||
popup.show();
|
||||
@ -996,7 +1014,7 @@ export class SpinePlayer implements Disposable {
|
||||
if (this.hidePopup(id)) return;
|
||||
if (!this.skeleton || !this.skeleton.data.animations.length) return;
|
||||
|
||||
let popup = new Popup(id, settingsButton, this, this.playerControls, /*html*/`<div class="spine-player-popup-title">Debug</div><hr><ul class="spine-player-list"></li>`);
|
||||
let popup = new Popup(id, settingsButton, this, this.playerControls!, /*html*/`<div class="spine-player-popup-title">Debug</div><hr><ul class="spine-player-list"></li>`);
|
||||
|
||||
let rows = findWithClass(popup.dom, "spine-player-list");
|
||||
let makeItem = (label: string, name: string) => {
|
||||
@ -1019,7 +1037,7 @@ export class SpinePlayer implements Disposable {
|
||||
popup.show();
|
||||
}
|
||||
|
||||
private showError (message: string, error: Error = null) {
|
||||
private showError (message: string, error?: Error) {
|
||||
if (this.error) {
|
||||
if (error) throw error; // Don't lose error if showError throws, is caught, and showError is called again.
|
||||
} else {
|
||||
@ -1056,6 +1074,7 @@ class Popup {
|
||||
this.player.popup = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
show () {
|
||||
@ -1094,9 +1113,9 @@ class Popup {
|
||||
}
|
||||
|
||||
class Switch {
|
||||
private switch: HTMLElement;
|
||||
private switch: HTMLElement | null = null;
|
||||
private enabled = false;
|
||||
public change: (value: boolean) => void;
|
||||
public change: (value: boolean) => void = () => { };
|
||||
|
||||
constructor (private text: string) { }
|
||||
|
||||
@ -1117,10 +1136,9 @@ class Switch {
|
||||
}
|
||||
|
||||
setEnabled (enabled: boolean) {
|
||||
if (enabled) this.switch.classList.add("active");
|
||||
else this.switch.classList.remove("active");
|
||||
this.enabled = enabled
|
||||
;
|
||||
if (enabled) this.switch?.classList.add("active");
|
||||
else this.switch?.classList.remove("active");
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
isEnabled (): boolean {
|
||||
@ -1129,10 +1147,10 @@ class Switch {
|
||||
}
|
||||
|
||||
class Slider {
|
||||
private slider: HTMLElement;
|
||||
private value: HTMLElement;
|
||||
private knob: HTMLElement;
|
||||
public change: (percentage: number) => void;
|
||||
private slider: HTMLElement | null = null;
|
||||
private value: HTMLElement | null = null;
|
||||
private knob: HTMLElement | null = null;
|
||||
public change: (percentage: number) => void = () => { };
|
||||
|
||||
constructor (public snaps = 0, public snapPercentage = 0.1, public big = false) { }
|
||||
|
||||
@ -1150,18 +1168,18 @@ class Slider {
|
||||
new Input(this.slider).addListener({
|
||||
down: (x, y) => {
|
||||
dragging = true;
|
||||
this.value.classList.add("hovering");
|
||||
this.value?.classList.add("hovering");
|
||||
},
|
||||
up: (x, y) => {
|
||||
dragging = false;
|
||||
if (this.change) this.change(this.setValue(x / this.slider.clientWidth));
|
||||
this.value.classList.remove("hovering");
|
||||
if (this.change) this.change(this.setValue(x / this.slider!.clientWidth));
|
||||
this.value?.classList.remove("hovering");
|
||||
},
|
||||
moved: (x, y) => {
|
||||
if (dragging && this.change) this.change(this.setValue(x / this.slider.clientWidth));
|
||||
if (dragging && this.change) this.change(this.setValue(x / this.slider!.clientWidth));
|
||||
},
|
||||
dragged: (x, y) => {
|
||||
if (this.change) this.change(this.setValue(x / this.slider.clientWidth));
|
||||
if (this.change) this.change(this.setValue(x / this.slider!.clientWidth));
|
||||
}
|
||||
});
|
||||
|
||||
@ -1180,7 +1198,7 @@ class Slider {
|
||||
percentage = percentage - modulo + snap;
|
||||
percentage = Math.max(0, Math.min(1, percentage));
|
||||
}
|
||||
this.value.style.width = "" + (percentage * 100) + "%";
|
||||
this.value!.style.width = "" + (percentage * 100) + "%";
|
||||
// this.knob.style.left = "" + (-8 + percentage * this.slider.clientWidth) + "px";
|
||||
return percentage;
|
||||
}
|
||||
|
||||
@ -31,8 +31,8 @@ import { AssetManagerBase, Downloader } from "@esotericsoftware/spine-core"
|
||||
import { ThreeJsTexture } from "./ThreeJsTexture";
|
||||
|
||||
export class AssetManager extends AssetManagerBase {
|
||||
constructor (pathPrefix: string = "", downloader: Downloader = null) {
|
||||
super((image: HTMLImageElement) => {
|
||||
constructor (pathPrefix: string = "", downloader: Downloader = new Downloader()) {
|
||||
super((image: HTMLImageElement | ImageBitmap) => {
|
||||
return new ThreeJsTexture(image);
|
||||
}, pathPrefix, downloader);
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ export class MeshBatcher extends THREE.Mesh {
|
||||
geo.setAttribute("color", new THREE.InterleavedBufferAttribute(vertexBuffer, 4, 3, false));
|
||||
geo.setAttribute("uv", new THREE.InterleavedBufferAttribute(vertexBuffer, 2, 7, false));
|
||||
geo.setIndex(new THREE.BufferAttribute(indices, 1));
|
||||
geo.getIndex().usage = WebGLRenderingContext.DYNAMIC_DRAW;
|
||||
geo.getIndex()!.usage = WebGLRenderingContext.DYNAMIC_DRAW;
|
||||
geo.drawRange.start = 0;
|
||||
geo.drawRange.count = 0;
|
||||
this.geometry = geo;
|
||||
@ -134,9 +134,11 @@ export class MeshBatcher extends THREE.Mesh {
|
||||
this.vertexBuffer.updateRange.count = this.verticesLength;
|
||||
let geo = (<THREE.BufferGeometry>this.geometry);
|
||||
this.closeMaterialGroups();
|
||||
geo.getIndex().needsUpdate = this.indicesLength > 0;
|
||||
geo.getIndex().updateRange.offset = 0;
|
||||
geo.getIndex().updateRange.count = this.indicesLength;
|
||||
let index = geo.getIndex();
|
||||
if (!index) throw new Error("BufferAttribute must not be null.");
|
||||
index.needsUpdate = this.indicesLength > 0;
|
||||
index.updateRange.offset = 0;
|
||||
index.updateRange.count = this.indicesLength;
|
||||
geo.drawRange.start = 0;
|
||||
geo.drawRange.count = this.indicesLength;
|
||||
}
|
||||
@ -168,7 +170,7 @@ export class MeshBatcher extends THREE.Mesh {
|
||||
for (let i = 0; i < this.material.length; i++) {
|
||||
const meshMaterial = this.material[i] as SkeletonMeshMaterial;
|
||||
|
||||
if (meshMaterial.uniforms.map.value === null) {
|
||||
if (!meshMaterial.uniforms.map.value) {
|
||||
updateMeshMaterial(meshMaterial, slotTexture, blending);
|
||||
return i;
|
||||
}
|
||||
|
||||
@ -73,8 +73,9 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
|
||||
alphaTest: 0.0
|
||||
};
|
||||
customizer(parameters);
|
||||
if (parameters.alphaTest > 0) {
|
||||
if (parameters.alphaTest && parameters.alphaTest > 0) {
|
||||
parameters.defines = { "USE_SPINE_ALPHATEST": 1 };
|
||||
if (!parameters.uniforms) parameters.uniforms = {};
|
||||
parameters.uniforms["alphaTest"] = { value: parameters.alphaTest };
|
||||
}
|
||||
super(parameters);
|
||||
@ -89,7 +90,7 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
skeleton: Skeleton;
|
||||
state: AnimationState;
|
||||
zOffset: number = 0.1;
|
||||
vertexEffect: VertexEffect;
|
||||
vertexEffect: VertexEffect | null = null;
|
||||
|
||||
private batches = new Array<MeshBatcher>();
|
||||
private nextBatchIndex = 0;
|
||||
@ -152,17 +153,11 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
let tempUv = this.tempUv;
|
||||
let tempLight = this.tempLight;
|
||||
let tempDark = this.tempDark;
|
||||
|
||||
var numVertices = 0;
|
||||
var verticesLength = 0;
|
||||
var indicesLength = 0;
|
||||
|
||||
let blendMode: BlendMode = null;
|
||||
let clipper = this.clipper;
|
||||
|
||||
let vertices: NumberArrayLike = this.vertices;
|
||||
let triangles: Array<number> = null;
|
||||
let uvs: NumberArrayLike = null;
|
||||
let triangles: Array<number> | null = null;
|
||||
let uvs: NumberArrayLike | null = null;
|
||||
let drawOrder = this.skeleton.drawOrder;
|
||||
let batch = this.nextBatch();
|
||||
batch.begin();
|
||||
@ -176,8 +171,8 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
continue;
|
||||
}
|
||||
let attachment = slot.getAttachment();
|
||||
let attachmentColor: Color = null;
|
||||
let texture: ThreeJsTexture = null;
|
||||
let attachmentColor: Color | null;
|
||||
let texture: ThreeJsTexture | null;
|
||||
let numFloats = 0;
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
let region = <RegionAttachment>attachment;
|
||||
@ -187,7 +182,7 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
region.computeWorldVertices(slot, vertices, 0, vertexSize);
|
||||
triangles = SkeletonMesh.QUAD_TRIANGLES;
|
||||
uvs = region.uvs;
|
||||
texture = <ThreeJsTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
|
||||
texture = <ThreeJsTexture>(<TextureAtlasRegion>region.region!.renderObject).page.texture;
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
let mesh = <MeshAttachment>attachment;
|
||||
attachmentColor = mesh.color;
|
||||
@ -199,7 +194,7 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, vertices, 0, vertexSize);
|
||||
triangles = mesh.triangles;
|
||||
uvs = mesh.uvs;
|
||||
texture = <ThreeJsTexture>(<TextureAtlasRegion>mesh.region.renderObject).page.texture;
|
||||
texture = <ThreeJsTexture>(<TextureAtlasRegion>mesh.region!.renderObject).page.texture;
|
||||
} else if (attachment instanceof ClippingAttachment) {
|
||||
let clip = <ClippingAttachment>(attachment);
|
||||
clipper.clipStart(slot, clip);
|
||||
@ -226,7 +221,7 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
let finalIndicesLength: number;
|
||||
|
||||
if (clipper.isClipping()) {
|
||||
clipper.clipTriangles(vertices, numFloats, triangles, triangles.length, uvs, color, null, false);
|
||||
clipper.clipTriangles(vertices, numFloats, triangles, triangles.length, uvs, color, tempLight, false);
|
||||
let clippedVertices = clipper.clippedVertices;
|
||||
let clippedTriangles = clipper.clippedTriangles;
|
||||
if (this.vertexEffect != null) {
|
||||
|
||||
@ -33,8 +33,9 @@ import * as THREE from "three";
|
||||
export class ThreeJsTexture extends Texture {
|
||||
texture: THREE.Texture;
|
||||
|
||||
constructor (image: HTMLImageElement) {
|
||||
constructor (image: HTMLImageElement | ImageBitmap) {
|
||||
super(image);
|
||||
if (image instanceof ImageBitmap) throw new Error("ImageBitmap not supported.");
|
||||
this.texture = new THREE.Texture(image);
|
||||
this.texture.flipY = false;
|
||||
this.texture.needsUpdate = true;
|
||||
|
||||
@ -234,7 +234,7 @@
|
||||
function setupUI() {
|
||||
let formatList = $("#formatList");
|
||||
formatList.append($("<option>Binary</option>"));
|
||||
formatList.append($("<option>JSON</option>"));
|
||||
formatList.append($("<option selected>JSON</option>"));
|
||||
let skeletonList = $("#skeletonList");
|
||||
for (let skeletonName in skeletons) {
|
||||
let option = $("<option></option>");
|
||||
|
||||
@ -33,7 +33,7 @@ import { GLTexture } from "./GLTexture";
|
||||
|
||||
|
||||
export class AssetManager extends AssetManagerBase {
|
||||
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = null) {
|
||||
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = new Downloader()) {
|
||||
super((image: HTMLImageElement | ImageBitmap) => {
|
||||
return new GLTexture(context, image);
|
||||
}, pathPrefix, downloader);
|
||||
|
||||
@ -32,7 +32,7 @@ import { ManagedWebGLRenderingContext } from "./WebGL";
|
||||
|
||||
export class GLTexture extends Texture implements Disposable, Restorable {
|
||||
context: ManagedWebGLRenderingContext;
|
||||
private texture: WebGLTexture = null;
|
||||
private texture: WebGLTexture | null = null;
|
||||
private boundUnit = 0;
|
||||
private useMipMaps = false;
|
||||
|
||||
|
||||
@ -32,8 +32,8 @@ export class Input {
|
||||
mouseX = 0;
|
||||
mouseY = 0;
|
||||
buttonDown = false;
|
||||
touch0: Touch = null;
|
||||
touch1: Touch = null;
|
||||
touch0: Touch | null = null;
|
||||
touch1: Touch | null = null;
|
||||
initialPinchDistance = 0;
|
||||
private listeners = new Array<InputListener>();
|
||||
private eventListeners: Array<{ target: any, event: any, func: any }> = [];
|
||||
@ -104,6 +104,7 @@ export class Input {
|
||||
if (!this.touch0 || !this.touch1) {
|
||||
var touches = ev.changedTouches;
|
||||
let nativeTouch = touches.item(0);
|
||||
if (!nativeTouch) return;
|
||||
let rect = element.getBoundingClientRect();
|
||||
let x = nativeTouch.clientX - rect.left;
|
||||
let y = nativeTouch.clientY - rect.top;
|
||||
@ -180,7 +181,7 @@ export class Input {
|
||||
this.mouseX = this.touch0.x;
|
||||
this.mouseX = this.touch0.x;
|
||||
this.buttonDown = true;
|
||||
this.listeners.map((listener) => { if (listener.down) listener.down(this.touch0.x, this.touch0.y) });
|
||||
this.listeners.map((listener) => { if (listener.down) listener.down(this.touch0!.x, this.touch0!.y) });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -40,8 +40,8 @@ const logoWidth = 165, logoHeight = 108, spinnerSize = 163;
|
||||
|
||||
export class LoadingScreen implements Disposable {
|
||||
private renderer: SceneRenderer;
|
||||
private logo: GLTexture = null;
|
||||
private spinner: GLTexture = null;
|
||||
private logo: GLTexture | null = null;
|
||||
private spinner: GLTexture | null = null;
|
||||
private angle = 0;
|
||||
private fadeOut = 0;
|
||||
private fadeIn = 0;
|
||||
@ -70,8 +70,8 @@ export class LoadingScreen implements Disposable {
|
||||
}
|
||||
}
|
||||
dispose (): void {
|
||||
this.logo.dispose();
|
||||
this.spinner.dispose();
|
||||
this.logo?.dispose();
|
||||
this.spinner?.dispose();
|
||||
}
|
||||
|
||||
draw (complete = false) {
|
||||
@ -122,7 +122,7 @@ export class LoadingScreen implements Disposable {
|
||||
renderer.camera.zoom = Math.max(1, spinnerSize / canvas.height);
|
||||
renderer.begin();
|
||||
renderer.drawTexture(this.logo, (canvas.width - logoWidth) / 2, (canvas.height - logoHeight) / 2, logoWidth, logoHeight, tempColor);
|
||||
renderer.drawTextureRotated(this.spinner, (canvas.width - spinnerSize) / 2, (canvas.height - spinnerSize) / 2, spinnerSize, spinnerSize, spinnerSize / 2, spinnerSize / 2, this.angle, tempColor);
|
||||
if (this.spinner) renderer.drawTextureRotated(this.spinner, (canvas.width - spinnerSize) / 2, (canvas.height - spinnerSize) / 2, spinnerSize, spinnerSize, spinnerSize / 2, spinnerSize / 2, this.angle, tempColor);
|
||||
renderer.end();
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
import { Vector2 } from "@esotericsoftware/spine-core";
|
||||
import { Vector3 } from "./Vector3";
|
||||
|
||||
export const M00 = 0;
|
||||
@ -50,9 +51,9 @@ export class Matrix4 {
|
||||
temp: Float32Array = new Float32Array(16);
|
||||
values: Float32Array = new Float32Array(16);
|
||||
|
||||
private static xAxis: Vector3 = null;
|
||||
private static yAxis: Vector3 = null;
|
||||
private static zAxis: Vector3 = null;
|
||||
private static xAxis = new Vector3();
|
||||
private static yAxis = new Vector3();
|
||||
private static zAxis = new Vector3();
|
||||
private static tmpMatrix = new Matrix4();
|
||||
|
||||
constructor () {
|
||||
@ -305,7 +306,6 @@ export class Matrix4 {
|
||||
}
|
||||
|
||||
lookAt (position: Vector3, direction: Vector3, up: Vector3) {
|
||||
Matrix4.initTemps();
|
||||
let xAxis = Matrix4.xAxis, yAxis = Matrix4.yAxis, zAxis = Matrix4.zAxis;
|
||||
zAxis.setFrom(direction).normalize();
|
||||
xAxis.setFrom(direction).normalize();
|
||||
@ -331,10 +331,4 @@ export class Matrix4 {
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
static initTemps () {
|
||||
if (Matrix4.xAxis === null) Matrix4.xAxis = new Vector3();
|
||||
if (Matrix4.yAxis === null) Matrix4.yAxis = new Vector3();
|
||||
if (Matrix4.zAxis === null) Matrix4.zAxis = new Vector3();
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,11 +35,11 @@ import { ManagedWebGLRenderingContext } from "./WebGL";
|
||||
export class Mesh implements Disposable, Restorable {
|
||||
private context: ManagedWebGLRenderingContext;
|
||||
private vertices: Float32Array;
|
||||
private verticesBuffer: WebGLBuffer;
|
||||
private verticesBuffer: WebGLBuffer | null = null;
|
||||
private verticesLength = 0;
|
||||
private dirtyVertices = false;
|
||||
private indices: Uint16Array;
|
||||
private indicesBuffer: WebGLBuffer;
|
||||
private indicesBuffer: WebGLBuffer | null = null;
|
||||
private indicesLength = 0;
|
||||
private dirtyIndices = false;
|
||||
private elementsPerVertex = 0;
|
||||
|
||||
@ -35,17 +35,17 @@ import { ManagedWebGLRenderingContext } from "./WebGL";
|
||||
|
||||
export class PolygonBatcher implements Disposable {
|
||||
private context: ManagedWebGLRenderingContext;
|
||||
private drawCalls: number;
|
||||
private drawCalls = 0;
|
||||
private isDrawing = false;
|
||||
private mesh: Mesh;
|
||||
private shader: Shader = null;
|
||||
private lastTexture: GLTexture = null;
|
||||
private shader: Shader | null = null;
|
||||
private lastTexture: GLTexture | null = null;
|
||||
private verticesLength = 0;
|
||||
private indicesLength = 0;
|
||||
private srcColorBlend: number;
|
||||
private srcAlphaBlend: number;
|
||||
private dstBlend: number;
|
||||
private cullWasEnabled: boolean;
|
||||
private cullWasEnabled = false;
|
||||
|
||||
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, twoColorTint: boolean = true, maxVertices: number = 10920) {
|
||||
if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
|
||||
@ -110,7 +110,8 @@ export class PolygonBatcher implements Disposable {
|
||||
|
||||
flush () {
|
||||
if (this.verticesLength == 0) return;
|
||||
|
||||
if (!this.lastTexture) throw new Error("No texture set.");
|
||||
if (!this.shader) throw new Error("No shader set.");
|
||||
this.lastTexture.bind();
|
||||
this.mesh.draw(this.shader, this.context.gl.TRIANGLES);
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ export class SceneRenderer implements Disposable {
|
||||
private batcherShader: Shader;
|
||||
private shapes: ShapeRenderer;
|
||||
private shapesShader: Shader;
|
||||
private activeRenderer: PolygonBatcher | ShapeRenderer | SkeletonDebugRenderer;
|
||||
private activeRenderer: PolygonBatcher | ShapeRenderer | SkeletonDebugRenderer | null = null;
|
||||
skeletonRenderer: SkeletonRenderer;
|
||||
skeletonDebugRenderer: SkeletonDebugRenderer;
|
||||
|
||||
@ -92,15 +92,15 @@ export class SceneRenderer implements Disposable {
|
||||
this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd);
|
||||
}
|
||||
|
||||
drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones: Array<string> = null) {
|
||||
drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones?: Array<string>) {
|
||||
this.enableRenderer(this.shapes);
|
||||
this.skeletonDebugRenderer.premultipliedAlpha = premultipliedAlpha;
|
||||
this.skeletonDebugRenderer.draw(this.shapes, skeleton, ignoredBones);
|
||||
}
|
||||
|
||||
drawTexture (texture: GLTexture, x: number, y: number, width: number, height: number, color: Color = null) {
|
||||
drawTexture (texture: GLTexture, x: number, y: number, width: number, height: number, color?: Color) {
|
||||
this.enableRenderer(this.batcher);
|
||||
if (color === null) color = WHITE;
|
||||
if (!color) color = WHITE;
|
||||
var i = 0;
|
||||
quad[i++] = x;
|
||||
quad[i++] = y;
|
||||
@ -161,9 +161,9 @@ export class SceneRenderer implements Disposable {
|
||||
this.batcher.draw(texture, quad, QUAD_TRIANGLES);
|
||||
}
|
||||
|
||||
drawTextureUV (texture: GLTexture, x: number, y: number, width: number, height: number, u: number, v: number, u2: number, v2: number, color: Color = null) {
|
||||
drawTextureUV (texture: GLTexture, x: number, y: number, width: number, height: number, u: number, v: number, u2: number, v2: number, color?: Color) {
|
||||
this.enableRenderer(this.batcher);
|
||||
if (color === null) color = WHITE;
|
||||
if (!color) color = WHITE;
|
||||
var i = 0;
|
||||
quad[i++] = x;
|
||||
quad[i++] = y;
|
||||
@ -224,9 +224,9 @@ export class SceneRenderer implements Disposable {
|
||||
this.batcher.draw(texture, quad, QUAD_TRIANGLES);
|
||||
}
|
||||
|
||||
drawTextureRotated (texture: GLTexture, x: number, y: number, width: number, height: number, pivotX: number, pivotY: number, angle: number, color: Color = null) {
|
||||
drawTextureRotated (texture: GLTexture, x: number, y: number, width: number, height: number, pivotX: number, pivotY: number, angle: number, color?: Color) {
|
||||
this.enableRenderer(this.batcher);
|
||||
if (color === null) color = WHITE;
|
||||
if (!color) color = WHITE;
|
||||
|
||||
// bottom left and top right corner points relative to origin
|
||||
let worldOriginX = x + pivotX;
|
||||
@ -354,9 +354,9 @@ export class SceneRenderer implements Disposable {
|
||||
this.batcher.draw(texture, quad, QUAD_TRIANGLES);
|
||||
}
|
||||
|
||||
drawRegion (region: TextureAtlasRegion, x: number, y: number, width: number, height: number, color: Color = null) {
|
||||
drawRegion (region: TextureAtlasRegion, x: number, y: number, width: number, height: number, color?: Color) {
|
||||
this.enableRenderer(this.batcher);
|
||||
if (color === null) color = WHITE;
|
||||
if (!color) color = WHITE;
|
||||
var i = 0;
|
||||
quad[i++] = x;
|
||||
quad[i++] = y;
|
||||
@ -417,42 +417,42 @@ export class SceneRenderer implements Disposable {
|
||||
this.batcher.draw(<GLTexture>region.page.texture, quad, QUAD_TRIANGLES);
|
||||
}
|
||||
|
||||
line (x: number, y: number, x2: number, y2: number, color: Color = null, color2: Color = null) {
|
||||
line (x: number, y: number, x2: number, y2: number, color?: Color, color2?: Color) {
|
||||
this.enableRenderer(this.shapes);
|
||||
this.shapes.line(x, y, x2, y2, color);
|
||||
}
|
||||
|
||||
triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color: Color = null, color2: Color = null, color3: Color = null) {
|
||||
triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color?: Color, color2?: Color, color3?: Color) {
|
||||
this.enableRenderer(this.shapes);
|
||||
this.shapes.triangle(filled, x, y, x2, y2, x3, y3, color, color2, color3);
|
||||
}
|
||||
|
||||
quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: Color = null, color2: Color = null, color3: Color = null, color4: Color = null) {
|
||||
quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color?: Color, color2?: Color, color3?: Color, color4?: Color) {
|
||||
this.enableRenderer(this.shapes);
|
||||
this.shapes.quad(filled, x, y, x2, y2, x3, y3, x4, y4, color, color2, color3, color4);
|
||||
}
|
||||
|
||||
rect (filled: boolean, x: number, y: number, width: number, height: number, color: Color = null) {
|
||||
rect (filled: boolean, x: number, y: number, width: number, height: number, color?: Color) {
|
||||
this.enableRenderer(this.shapes);
|
||||
this.shapes.rect(filled, x, y, width, height, color);
|
||||
}
|
||||
|
||||
rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color: Color = null) {
|
||||
rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color?: Color) {
|
||||
this.enableRenderer(this.shapes);
|
||||
this.shapes.rectLine(filled, x1, y1, x2, y2, width, color);
|
||||
}
|
||||
|
||||
polygon (polygonVertices: ArrayLike<number>, offset: number, count: number, color: Color = null) {
|
||||
polygon (polygonVertices: ArrayLike<number>, offset: number, count: number, color?: Color) {
|
||||
this.enableRenderer(this.shapes);
|
||||
this.shapes.polygon(polygonVertices, offset, count, color);
|
||||
}
|
||||
|
||||
circle (filled: boolean, x: number, y: number, radius: number, color: Color = null, segments: number = 0) {
|
||||
circle (filled: boolean, x: number, y: number, radius: number, color?: Color, segments: number = 0) {
|
||||
this.enableRenderer(this.shapes);
|
||||
this.shapes.circle(filled, x, y, radius, color, segments);
|
||||
}
|
||||
|
||||
curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color: Color = null) {
|
||||
curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color?: Color) {
|
||||
this.enableRenderer(this.shapes);
|
||||
this.shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments, color);
|
||||
}
|
||||
|
||||
@ -39,11 +39,11 @@ export class Shader implements Disposable, Restorable {
|
||||
public static SAMPLER = "u_texture";
|
||||
|
||||
private context: ManagedWebGLRenderingContext;
|
||||
private vs: WebGLShader = null;
|
||||
private vs: WebGLShader | null = null;
|
||||
private vsSource: string;
|
||||
private fs: WebGLShader = null;
|
||||
private fs: WebGLShader | null = null;
|
||||
private fsSource: string;
|
||||
private program: WebGLProgram = null;
|
||||
private program: WebGLProgram | null = null;
|
||||
private tmp2x2: Float32Array = new Float32Array(2 * 2);
|
||||
private tmp3x3: Float32Array = new Float32Array(3 * 3);
|
||||
private tmp4x4: Float32Array = new Float32Array(4 * 4);
|
||||
@ -66,7 +66,9 @@ export class Shader implements Disposable, Restorable {
|
||||
let gl = this.context.gl;
|
||||
try {
|
||||
this.vs = this.compileShader(gl.VERTEX_SHADER, this.vertexShader);
|
||||
if (!this.vs) throw new Error("Couldn't compile vertex shader.");
|
||||
this.fs = this.compileShader(gl.FRAGMENT_SHADER, this.fragmentShader);
|
||||
if (!this.fs) throw new Error("Couldn#t compile fragment shader.");
|
||||
this.program = this.compileProgram(this.vs, this.fs);
|
||||
} catch (e) {
|
||||
this.dispose();
|
||||
@ -77,6 +79,7 @@ export class Shader implements Disposable, Restorable {
|
||||
private compileShader (type: number, source: string) {
|
||||
let gl = this.context.gl;
|
||||
let shader = gl.createShader(type);
|
||||
if (!shader) throw new Error("Couldn't create shader.");
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
@ -90,6 +93,7 @@ export class Shader implements Disposable, Restorable {
|
||||
private compileProgram (vs: WebGLShader, fs: WebGLShader) {
|
||||
let gl = this.context.gl;
|
||||
let program = gl.createProgram();
|
||||
if (!program) throw new Error("Couldn't compile program.");
|
||||
gl.attachShader(program, vs);
|
||||
gl.attachShader(program, fs);
|
||||
gl.linkProgram(program);
|
||||
@ -152,8 +156,9 @@ export class Shader implements Disposable, Restorable {
|
||||
gl.uniformMatrix4fv(this.getUniformLocation(uniform), false, this.tmp4x4);
|
||||
}
|
||||
|
||||
public getUniformLocation (uniform: string): WebGLUniformLocation {
|
||||
public getUniformLocation (uniform: string): WebGLUniformLocation | null {
|
||||
let gl = this.context.gl;
|
||||
if (!this.program) throw new Error("Shader not compiled.");
|
||||
let location = gl.getUniformLocation(this.program, uniform);
|
||||
if (!location && !gl.isContextLost()) throw new Error(`Couldn't find location for uniform ${uniform}`);
|
||||
return location;
|
||||
@ -161,6 +166,7 @@ export class Shader implements Disposable, Restorable {
|
||||
|
||||
public getAttributeLocation (attribute: string): number {
|
||||
let gl = this.context.gl;
|
||||
if (!this.program) throw new Error("Shader not compiled.");
|
||||
let location = gl.getAttribLocation(this.program, attribute);
|
||||
if (location == -1 && !gl.isContextLost()) throw new Error(`Couldn't find location for attribute ${attribute}`);
|
||||
return location;
|
||||
|
||||
@ -38,7 +38,7 @@ export class ShapeRenderer implements Disposable {
|
||||
private mesh: Mesh;
|
||||
private shapeType = ShapeType.Filled;
|
||||
private color = new Color(1, 1, 1, 1);
|
||||
private shader: Shader;
|
||||
private shader: Shader | null = null;
|
||||
private vertexIndex = 0;
|
||||
private tmp = new Vector2();
|
||||
private srcColorBlend: number;
|
||||
@ -85,28 +85,28 @@ export class ShapeRenderer implements Disposable {
|
||||
this.color.set(r, g, b, a);
|
||||
}
|
||||
|
||||
point (x: number, y: number, color: Color = null) {
|
||||
point (x: number, y: number, color?: Color) {
|
||||
this.check(ShapeType.Point, 1);
|
||||
if (color === null) color = this.color;
|
||||
if (!color) color = this.color;
|
||||
this.vertex(x, y, color);
|
||||
}
|
||||
|
||||
line (x: number, y: number, x2: number, y2: number, color: Color = null) {
|
||||
line (x: number, y: number, x2: number, y2: number, color?: Color) {
|
||||
this.check(ShapeType.Line, 2);
|
||||
let vertices = this.mesh.getVertices();
|
||||
let idx = this.vertexIndex;
|
||||
if (color === null) color = this.color;
|
||||
if (!color) color = this.color;
|
||||
this.vertex(x, y, color);
|
||||
this.vertex(x2, y2, color);
|
||||
}
|
||||
|
||||
triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color: Color = null, color2: Color = null, color3: Color = null) {
|
||||
triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color?: Color, color2?: Color, color3?: Color) {
|
||||
this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
|
||||
let vertices = this.mesh.getVertices();
|
||||
let idx = this.vertexIndex;
|
||||
if (color === null) color = this.color;
|
||||
if (color2 === null) color2 = this.color;
|
||||
if (color3 === null) color3 = this.color;
|
||||
if (!color) color = this.color;
|
||||
if (!color2) color2 = this.color;
|
||||
if (!color3) color3 = this.color;
|
||||
if (filled) {
|
||||
this.vertex(x, y, color);
|
||||
this.vertex(x2, y2, color2);
|
||||
@ -123,14 +123,14 @@ export class ShapeRenderer implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: Color = null, color2: Color = null, color3: Color = null, color4: Color = null) {
|
||||
quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color?: Color, color2?: Color, color3?: Color, color4?: Color) {
|
||||
this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
|
||||
let vertices = this.mesh.getVertices();
|
||||
let idx = this.vertexIndex;
|
||||
if (color === null) color = this.color;
|
||||
if (color2 === null) color2 = this.color;
|
||||
if (color3 === null) color3 = this.color;
|
||||
if (color4 === null) color4 = this.color;
|
||||
if (!color) color = this.color;
|
||||
if (!color2) color2 = this.color;
|
||||
if (!color3) color3 = this.color;
|
||||
if (!color4) color4 = this.color;
|
||||
if (filled) {
|
||||
this.vertex(x, y, color); this.vertex(x2, y2, color2); this.vertex(x3, y3, color3);
|
||||
this.vertex(x3, y3, color3); this.vertex(x4, y4, color4); this.vertex(x, y, color);
|
||||
@ -142,13 +142,13 @@ export class ShapeRenderer implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
rect (filled: boolean, x: number, y: number, width: number, height: number, color: Color = null) {
|
||||
rect (filled: boolean, x: number, y: number, width: number, height: number, color?: Color) {
|
||||
this.quad(filled, x, y, x + width, y, x + width, y + height, x, y + height, color, color, color, color);
|
||||
}
|
||||
|
||||
rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color: Color = null) {
|
||||
rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color?: Color) {
|
||||
this.check(filled ? ShapeType.Filled : ShapeType.Line, 8);
|
||||
if (color === null) color = this.color;
|
||||
if (!color) color = this.color;
|
||||
let t = this.tmp.set(y2 - y1, x1 - x2);
|
||||
t.normalize();
|
||||
width *= 0.5;
|
||||
@ -181,10 +181,10 @@ export class ShapeRenderer implements Disposable {
|
||||
this.line(x - size, y + size, x + size, y - size);
|
||||
}
|
||||
|
||||
polygon (polygonVertices: ArrayLike<number>, offset: number, count: number, color: Color = null) {
|
||||
polygon (polygonVertices: ArrayLike<number>, offset: number, count: number, color?: Color) {
|
||||
if (count < 3) throw new Error("Polygon must contain at least 3 vertices");
|
||||
this.check(ShapeType.Line, count * 2);
|
||||
if (color === null) color = this.color;
|
||||
if (color) color = this.color;
|
||||
let vertices = this.mesh.getVertices();
|
||||
let idx = this.vertexIndex;
|
||||
|
||||
@ -210,15 +210,15 @@ export class ShapeRenderer implements Disposable {
|
||||
y2 = polygonVertices[i + 3];
|
||||
}
|
||||
|
||||
this.vertex(x1, y1, color);
|
||||
this.vertex(x2, y2, color);
|
||||
this.vertex(x1, y1, color!);
|
||||
this.vertex(x2, y2, color!);
|
||||
}
|
||||
}
|
||||
|
||||
circle (filled: boolean, x: number, y: number, radius: number, color: Color = null, segments: number = 0) {
|
||||
if (segments === 0) segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0);
|
||||
circle (filled: boolean, x: number, y: number, radius: number, color?: Color, segments: number = 0) {
|
||||
if (segments == 0) segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0);
|
||||
if (segments <= 0) throw new Error("segments must be > 0.");
|
||||
if (color === null) color = this.color;
|
||||
if (!color) color = this.color;
|
||||
let angle = 2 * MathUtils.PI / segments;
|
||||
let cos = Math.cos(angle);
|
||||
let sin = Math.sin(angle);
|
||||
@ -256,9 +256,9 @@ export class ShapeRenderer implements Disposable {
|
||||
this.vertex(x + cx, y + cy, color);
|
||||
}
|
||||
|
||||
curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color: Color = null) {
|
||||
curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color?: Color) {
|
||||
this.check(ShapeType.Line, segments * 2 + 2);
|
||||
if (color === null) color = this.color;
|
||||
if (color) color = this.color;
|
||||
|
||||
// Algorithm from: http://www.antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION
|
||||
let subdiv_step = 1 / segments;
|
||||
@ -289,17 +289,17 @@ export class ShapeRenderer implements Disposable {
|
||||
let dddfy = tmp2y * pre5;
|
||||
|
||||
while (segments-- > 0) {
|
||||
this.vertex(fx, fy, color);
|
||||
this.vertex(fx, fy, color!);
|
||||
fx += dfx;
|
||||
fy += dfy;
|
||||
dfx += ddfx;
|
||||
dfy += ddfy;
|
||||
ddfx += dddfx;
|
||||
ddfy += dddfy;
|
||||
this.vertex(fx, fy, color);
|
||||
this.vertex(fx, fy, color!);
|
||||
}
|
||||
this.vertex(fx, fy, color);
|
||||
this.vertex(x2, y2, color);
|
||||
this.vertex(fx, fy, color!);
|
||||
this.vertex(x2, y2, color!);
|
||||
}
|
||||
|
||||
private vertex (x: number, y: number, color: Color) {
|
||||
@ -324,6 +324,7 @@ export class ShapeRenderer implements Disposable {
|
||||
|
||||
private flush () {
|
||||
if (this.vertexIndex == 0) return;
|
||||
if (!this.shader) throw new Error("No shader set.");
|
||||
this.mesh.setVerticesLength(this.vertexIndex);
|
||||
this.mesh.draw(this.shader, this.shapeType);
|
||||
this.vertexIndex = 0;
|
||||
|
||||
@ -62,7 +62,7 @@ export class SkeletonDebugRenderer implements Disposable {
|
||||
this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
|
||||
}
|
||||
|
||||
draw (shapes: ShapeRenderer, skeleton: Skeleton, ignoredBones: Array<string> = null) {
|
||||
draw (shapes: ShapeRenderer, skeleton: Skeleton, ignoredBones?: Array<string>) {
|
||||
let skeletonX = skeleton.x;
|
||||
let skeletonY = skeleton.y;
|
||||
let gl = this.context.gl;
|
||||
|
||||
@ -41,13 +41,13 @@ export class SkeletonRenderer {
|
||||
static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
|
||||
|
||||
premultipliedAlpha = false;
|
||||
vertexEffect: VertexEffect = null;
|
||||
vertexEffect: VertexEffect | null = null;
|
||||
private tempColor = new Color();
|
||||
private tempColor2 = new Color();
|
||||
private vertices: NumberArrayLike;
|
||||
private vertexSize = 2 + 2 + 4;
|
||||
private twoColorTint = false;
|
||||
private renderable: Renderable = new Renderable(null, 0, 0);
|
||||
private renderable: Renderable = new Renderable([], 0, 0);
|
||||
private clipper: SkeletonClipping = new SkeletonClipping();
|
||||
private temp = new Vector2();
|
||||
private temp2 = new Vector2();
|
||||
@ -65,7 +65,7 @@ export class SkeletonRenderer {
|
||||
let clipper = this.clipper;
|
||||
let premultipliedAlpha = this.premultipliedAlpha;
|
||||
let twoColorTint = this.twoColorTint;
|
||||
let blendMode: BlendMode = null;
|
||||
let blendMode: BlendMode | null = null;
|
||||
|
||||
let tempPos = this.temp;
|
||||
let tempUv = this.temp2;
|
||||
@ -73,10 +73,10 @@ export class SkeletonRenderer {
|
||||
let tempDark = this.temp4;
|
||||
|
||||
let renderable: Renderable = this.renderable;
|
||||
let uvs: NumberArrayLike = null;
|
||||
let triangles: Array<number> = null;
|
||||
let uvs: NumberArrayLike;
|
||||
let triangles: Array<number>;
|
||||
let drawOrder = skeleton.drawOrder;
|
||||
let attachmentColor: Color = null;
|
||||
let attachmentColor: Color;
|
||||
let skeletonColor = skeleton.color;
|
||||
let vertexSize = twoColorTint ? 12 : 8;
|
||||
let inRange = false;
|
||||
@ -103,7 +103,7 @@ export class SkeletonRenderer {
|
||||
}
|
||||
|
||||
let attachment = slot.getAttachment();
|
||||
let texture: GLTexture = null;
|
||||
let texture: GLTexture;
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
let region = <RegionAttachment>attachment;
|
||||
renderable.vertices = this.vertices;
|
||||
@ -112,7 +112,7 @@ export class SkeletonRenderer {
|
||||
region.computeWorldVertices(slot, renderable.vertices, 0, clippedVertexSize);
|
||||
triangles = SkeletonRenderer.QUAD_TRIANGLES;
|
||||
uvs = region.uvs;
|
||||
texture = <GLTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;
|
||||
texture = <GLTexture>(<TextureAtlasRegion>region.region!.renderObject).page.texture;
|
||||
attachmentColor = region.color;
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
let mesh = <MeshAttachment>attachment;
|
||||
@ -124,7 +124,7 @@ export class SkeletonRenderer {
|
||||
}
|
||||
mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, renderable.vertices, 0, clippedVertexSize);
|
||||
triangles = mesh.triangles;
|
||||
texture = <GLTexture>(<TextureAtlasRegion>mesh.region.renderObject).page.texture;
|
||||
texture = <GLTexture>(<TextureAtlasRegion>mesh.region!.renderObject).page.texture;
|
||||
uvs = mesh.uvs;
|
||||
attachmentColor = mesh.color;
|
||||
} else if (attachment instanceof ClippingAttachment) {
|
||||
|
||||
@ -77,15 +77,15 @@ export class SpineCanvas {
|
||||
|
||||
/** Constructs a new spine canvas, rendering to the provided HTML canvas. */
|
||||
constructor (canvas: HTMLCanvasElement, config: SpineCanvasConfig) {
|
||||
if (config.pathPrefix === undefined) config.pathPrefix = "";
|
||||
if (config.app === undefined) config.app = {
|
||||
if (!config.pathPrefix) config.pathPrefix = "";
|
||||
if (!config.app) config.app = {
|
||||
loadAssets: () => { },
|
||||
initialize: () => { },
|
||||
update: () => { },
|
||||
render: () => { },
|
||||
error: () => { },
|
||||
}
|
||||
if (config.webglConfig === undefined) config.webglConfig = { alpha: true };
|
||||
if (config.webglConfig) config.webglConfig = { alpha: true };
|
||||
|
||||
this.htmlCanvas = canvas;
|
||||
this.context = new ManagedWebGLRenderingContext(canvas, config.webglConfig);
|
||||
@ -94,21 +94,21 @@ export class SpineCanvas {
|
||||
this.assetManager = new AssetManager(this.context, config.pathPrefix);
|
||||
this.input = new Input(canvas);
|
||||
|
||||
config.app.loadAssets(this);
|
||||
if (config.app.loadAssets) config.app.loadAssets(this);
|
||||
|
||||
let loop = () => {
|
||||
requestAnimationFrame(loop);
|
||||
this.time.update();
|
||||
config.app.update(this, this.time.delta);
|
||||
config.app.render(this);
|
||||
if (config.app.update) config.app.update(this, this.time.delta);
|
||||
if (config.app.render) config.app.render(this);
|
||||
}
|
||||
|
||||
let waitForAssets = () => {
|
||||
if (this.assetManager.isLoadingComplete()) {
|
||||
if (this.assetManager.hasErrors()) {
|
||||
config.app.error(this, this.assetManager.getErrors());
|
||||
if (config.app.error) config.app.error(this, this.assetManager.getErrors());
|
||||
} else {
|
||||
config.app.initialize(this);
|
||||
if (config.app.initialize) config.app.initialize(this);
|
||||
loop();
|
||||
}
|
||||
return;
|
||||
|
||||
@ -34,27 +34,23 @@ export class ManagedWebGLRenderingContext {
|
||||
public gl: WebGLRenderingContext;
|
||||
private restorables = new Array<Restorable>();
|
||||
|
||||
constructor (canvasOrContext: HTMLCanvasElement | WebGLRenderingContext | EventTarget, contextConfig: any = { alpha: "true" }) {
|
||||
if (!((canvasOrContext instanceof WebGLRenderingContext) || (typeof WebGL2RenderingContext !== 'undefined' && canvasOrContext instanceof WebGL2RenderingContext)))
|
||||
this.setupCanvas(canvasOrContext, contextConfig);
|
||||
else {
|
||||
this.gl = canvasOrContext;
|
||||
this.canvas = this.gl.canvas;
|
||||
}
|
||||
}
|
||||
|
||||
private setupCanvas (canvas: any, contextConfig: any) {
|
||||
constructor (canvasOrContext: HTMLCanvasElement | WebGLRenderingContext, contextConfig: any = { alpha: "true" }) {
|
||||
if (!((canvasOrContext instanceof WebGLRenderingContext) || (typeof WebGL2RenderingContext !== 'undefined' && canvasOrContext instanceof WebGL2RenderingContext))) {
|
||||
let canvas: HTMLCanvasElement = canvasOrContext;
|
||||
this.gl = <WebGLRenderingContext>(canvas.getContext("webgl2", contextConfig) || canvas.getContext("webgl", contextConfig));
|
||||
this.canvas = canvas;
|
||||
canvas.addEventListener("webglcontextlost", (e: any) => {
|
||||
let event = <WebGLContextEvent>e;
|
||||
if (e) e.preventDefault();
|
||||
});
|
||||
|
||||
canvas.addEventListener("webglcontextrestored", (e: any) => {
|
||||
for (let i = 0, n = this.restorables.length; i < n; i++)
|
||||
this.restorables[i].restore();
|
||||
});
|
||||
} else {
|
||||
this.gl = canvasOrContext;
|
||||
this.canvas = this.gl.canvas;
|
||||
}
|
||||
}
|
||||
|
||||
addRestorable (restorable: Restorable) {
|
||||
|
||||
@ -12,8 +12,7 @@
|
||||
],
|
||||
"declaration": true,
|
||||
"composite": true,
|
||||
"moduleResolution": "node"
|
||||
/*"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true*/
|
||||
"moduleResolution": "node",
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
@ -65,7 +65,12 @@ void SSpineWidget::SetData(USpineWidget *Widget) {
|
||||
skeleton->setToSetupPose();
|
||||
skeleton->updateWorldTransform();
|
||||
Vector<float> scratchBuffer;
|
||||
skeleton->getBounds(this->boundsMin.X, this->boundsMin.Y, this->boundsSize.X, this->boundsSize.Y, scratchBuffer);
|
||||
float x, y, w, h;
|
||||
skeleton->getBounds(x, y, w, h, scratchBuffer);
|
||||
boundsMin.X = x;
|
||||
boundsMin.Y = y;
|
||||
boundsSize.X = w;
|
||||
boundsSize.Y = h;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -154,7 +154,7 @@ namespace Spine.Unity.Editor {
|
||||
|
||||
void Clear () {
|
||||
preview.Clear();
|
||||
targetSkeletonDataAsset.Clear();
|
||||
SpineEditorUtilities.ClearSkeletonDataAsset(targetSkeletonDataAsset);
|
||||
targetSkeletonData = null;
|
||||
}
|
||||
|
||||
|
||||
@ -31,6 +31,10 @@
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
#define HAS_CULL_TRANSPARENT_MESH
|
||||
#endif
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
@ -469,6 +473,11 @@ namespace Spine.Unity.Editor {
|
||||
graphic.additiveMaterial = SkeletonGraphicInspector.DefaultSkeletonGraphicAdditiveMaterial;
|
||||
graphic.multiplyMaterial = SkeletonGraphicInspector.DefaultSkeletonGraphicMultiplyMaterial;
|
||||
graphic.screenMaterial = SkeletonGraphicInspector.DefaultSkeletonGraphicScreenMaterial;
|
||||
|
||||
#if HAS_CULL_TRANSPARENT_MESH
|
||||
var canvasRenderer = go.GetComponent<CanvasRenderer>();
|
||||
canvasRenderer.cullTransparentMesh = false;
|
||||
#endif
|
||||
return go;
|
||||
}
|
||||
|
||||
|
||||
@ -493,7 +493,7 @@ namespace Spine.Unity.Editor {
|
||||
}
|
||||
|
||||
Debug.LogFormat("Changes to '{0}' or atlas detected. Clearing SkeletonDataAsset: {1}", skeletonJSONPath, localPath);
|
||||
skeletonDataAsset.Clear();
|
||||
SpineEditorUtilities.ClearSkeletonDataAsset(skeletonDataAsset);
|
||||
|
||||
string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(skeletonDataAsset));
|
||||
string lastHash = EditorPrefs.GetString(guid + "_hash");
|
||||
@ -978,7 +978,7 @@ namespace Spine.Unity.Editor {
|
||||
AssetDatabase.CreateAsset(skeletonDataAsset, filePath);
|
||||
} else {
|
||||
skeletonDataAsset.atlasAssets = atlasAssets;
|
||||
skeletonDataAsset.Clear();
|
||||
SpineEditorUtilities.ClearSkeletonDataAsset(skeletonDataAsset);
|
||||
}
|
||||
var skeletonData = skeletonDataAsset.GetSkeletonData(true);
|
||||
if (skeletonData != null)
|
||||
|
||||
@ -96,7 +96,7 @@ namespace Spine.Unity.Editor {
|
||||
}
|
||||
}
|
||||
|
||||
skeletonDataAsset.Clear();
|
||||
SpineEditorUtilities.ClearSkeletonDataAsset(skeletonDataAsset);
|
||||
skeletonData = skeletonDataAsset.GetSkeletonData(true);
|
||||
if (anyMaterialsChanged)
|
||||
ReloadSceneSkeletons(skeletonDataAsset);
|
||||
@ -164,7 +164,7 @@ namespace Spine.Unity.Editor {
|
||||
|
||||
var skinEntries = new List<Skin.SkinEntry>();
|
||||
|
||||
skeletonDataAsset.Clear();
|
||||
SpineEditorUtilities.ClearSkeletonDataAsset(skeletonDataAsset);
|
||||
skeletonDataAsset.isUpgradingBlendModeMaterials = true;
|
||||
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
|
||||
|
||||
|
||||
@ -80,36 +80,24 @@ namespace Spine.Unity.Editor {
|
||||
// Here we save the skeletonGraphic.skeletonDataAsset asset path in order
|
||||
// to restore it later.
|
||||
var activeSkeletonGraphics = GameObject.FindObjectsOfType<SkeletonGraphic>();
|
||||
foreach (var sg in activeSkeletonGraphics) {
|
||||
var skeletonDataAsset = sg.skeletonDataAsset;
|
||||
foreach (var skeletonGraphic in activeSkeletonGraphics) {
|
||||
var skeletonDataAsset = skeletonGraphic.skeletonDataAsset;
|
||||
if (skeletonDataAsset != null) {
|
||||
var assetPath = AssetDatabase.GetAssetPath(skeletonDataAsset);
|
||||
var sgID = sg.GetInstanceID();
|
||||
var sgID = skeletonGraphic.GetInstanceID();
|
||||
savedSkeletonDataAssetAtSKeletonGraphicID[sgID] = assetPath;
|
||||
skeletonDataAssetsToReload.Add(skeletonDataAsset);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sda in skeletonDataAssetsToReload) {
|
||||
sda.Clear();
|
||||
sda.GetSkeletonData(true);
|
||||
foreach (var skeletonDataAsset in skeletonDataAssetsToReload) {
|
||||
ReloadSkeletonDataAsset(skeletonDataAsset, false);
|
||||
}
|
||||
|
||||
foreach (var sr in activeSkeletonRenderers) {
|
||||
var meshRenderer = sr.GetComponent<MeshRenderer>();
|
||||
var sharedMaterials = meshRenderer.sharedMaterials;
|
||||
foreach (var m in sharedMaterials) {
|
||||
if (m == null) {
|
||||
sr.Initialize(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sg in activeSkeletonGraphics) {
|
||||
if (sg.mainTexture == null)
|
||||
sg.Initialize(true);
|
||||
}
|
||||
foreach (var skeletonRenderer in activeSkeletonRenderers)
|
||||
skeletonRenderer.Initialize(true);
|
||||
foreach (var skeletonGraphic in activeSkeletonGraphics)
|
||||
skeletonGraphic.Initialize(true);
|
||||
}
|
||||
|
||||
public static void ReloadSceneSkeletonComponents (SkeletonDataAsset skeletonDataAsset) {
|
||||
@ -119,24 +107,35 @@ namespace Spine.Unity.Editor {
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
|
||||
|
||||
var activeSkeletonRenderers = GameObject.FindObjectsOfType<SkeletonRenderer>();
|
||||
foreach (var sr in activeSkeletonRenderers) {
|
||||
if (sr.isActiveAndEnabled && sr.skeletonDataAsset == skeletonDataAsset) sr.Initialize(true);
|
||||
foreach (var renderer in activeSkeletonRenderers) {
|
||||
if (renderer.isActiveAndEnabled && renderer.skeletonDataAsset == skeletonDataAsset) renderer.Initialize(true);
|
||||
}
|
||||
|
||||
var activeSkeletonGraphics = GameObject.FindObjectsOfType<SkeletonGraphic>();
|
||||
foreach (var sg in activeSkeletonGraphics) {
|
||||
if (sg.isActiveAndEnabled && sg.skeletonDataAsset == skeletonDataAsset) sg.Initialize(true);
|
||||
foreach (var graphic in activeSkeletonGraphics) {
|
||||
if (graphic.isActiveAndEnabled && graphic.skeletonDataAsset == skeletonDataAsset)
|
||||
graphic.Initialize(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearAnimationReferenceAssets (SkeletonDataAsset skeletonDataAsset) {
|
||||
ForEachAnimationReferenceAsset(skeletonDataAsset, (referenceAsset) => referenceAsset.Clear());
|
||||
}
|
||||
|
||||
public static void ReloadAnimationReferenceAssets (SkeletonDataAsset skeletonDataAsset) {
|
||||
ForEachAnimationReferenceAsset(skeletonDataAsset, (referenceAsset) => referenceAsset.Initialize());
|
||||
}
|
||||
|
||||
private static void ForEachAnimationReferenceAsset (SkeletonDataAsset skeletonDataAsset,
|
||||
System.Action<AnimationReferenceAsset> func) {
|
||||
|
||||
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:AnimationReferenceAsset");
|
||||
foreach (string guid in guids) {
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!string.IsNullOrEmpty(path)) {
|
||||
var referenceAsset = UnityEditor.AssetDatabase.LoadAssetAtPath<AnimationReferenceAsset>(path);
|
||||
if (referenceAsset.SkeletonDataAsset == skeletonDataAsset)
|
||||
referenceAsset.Initialize();
|
||||
func(referenceAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,14 +266,23 @@ namespace Spine.Unity.Editor {
|
||||
ReinitializeComponent(component);
|
||||
}
|
||||
|
||||
public static void ReloadSkeletonDataAsset (SkeletonDataAsset skeletonDataAsset) {
|
||||
if (skeletonDataAsset != null) {
|
||||
public static void ClearSkeletonDataAsset (SkeletonDataAsset skeletonDataAsset) {
|
||||
skeletonDataAsset.Clear();
|
||||
DataReloadHandler.ClearAnimationReferenceAssets(skeletonDataAsset);
|
||||
}
|
||||
|
||||
public static void ReloadSkeletonDataAsset (SkeletonDataAsset skeletonDataAsset, bool clearAtlasAssets = true) {
|
||||
if (skeletonDataAsset == null)
|
||||
return;
|
||||
|
||||
if (clearAtlasAssets) {
|
||||
foreach (AtlasAssetBase aa in skeletonDataAsset.atlasAssets) {
|
||||
if (aa != null) aa.Clear();
|
||||
}
|
||||
skeletonDataAsset.Clear();
|
||||
}
|
||||
ClearSkeletonDataAsset(skeletonDataAsset);
|
||||
skeletonDataAsset.GetSkeletonData(true);
|
||||
DataReloadHandler.ReloadAnimationReferenceAssets(skeletonDataAsset);
|
||||
}
|
||||
|
||||
public static void ReinitializeComponent (SkeletonRenderer component) {
|
||||
|
||||
@ -48,11 +48,16 @@ namespace Spine.Unity {
|
||||
if (animation == null)
|
||||
Initialize();
|
||||
#endif
|
||||
|
||||
return animation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Clears the cached animation corresponding to a loaded SkeletonData object.
|
||||
/// Use this to force a reload for the next time Animation is called.</summary>
|
||||
public void Clear () {
|
||||
animation = null;
|
||||
}
|
||||
|
||||
public void Initialize () {
|
||||
if (skeletonDataAsset == null) return;
|
||||
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(AnimationReferenceAsset.QuietSkeletonData);
|
||||
|
||||
@ -169,13 +169,17 @@ namespace Spine.Unity {
|
||||
/// <summary>Add a SkeletonGraphic component to a GameObject.</summary>
|
||||
/// <param name="material">Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work.</param>
|
||||
public static SkeletonGraphic AddSkeletonGraphicComponent (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, Material material) {
|
||||
var c = gameObject.AddComponent<SkeletonGraphic>();
|
||||
var skeletonGraphic = gameObject.AddComponent<SkeletonGraphic>();
|
||||
if (skeletonDataAsset != null) {
|
||||
c.material = material;
|
||||
c.skeletonDataAsset = skeletonDataAsset;
|
||||
c.Initialize(false);
|
||||
skeletonGraphic.material = material;
|
||||
skeletonGraphic.skeletonDataAsset = skeletonDataAsset;
|
||||
skeletonGraphic.Initialize(false);
|
||||
}
|
||||
return c;
|
||||
#if HAS_CULL_TRANSPARENT_MESH
|
||||
var canvasRenderer = gameObject.GetComponent<CanvasRenderer>();
|
||||
if (canvasRenderer) canvasRenderer.cullTransparentMesh = false;
|
||||
#endif
|
||||
return skeletonGraphic;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user