[ts] Port of sequence attachments, see #1956

SkeletonJson parsing of sequence timelines incomplete. Untested.
This commit is contained in:
Mario Zechner 2021-10-13 01:14:58 +02:00
parent 7876897c59
commit 81927051ff
18 changed files with 516 additions and 196 deletions

5
.gitignore vendored
View File

@ -151,4 +151,7 @@ spine-ts/spine-core/dist
spine-ts/spine-canvas/dist
spine-ts/spine-webgl/dist
spine-ts/spine-player/dist
spine-ts/spine-threejs/dist
spine-ts/spine-threejs/dist
spine-libgdx/gradle
spine-libgdx/gradlew
spine-libgdx/gradlew.bat

View File

@ -241,7 +241,7 @@ export class SkeletonRenderer {
skeletonColor.b * slotColor.b * regionColor.b * multiplier,
alpha);
region.computeWorldVertices(slot.bone, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE);
region.computeWorldVertices(slot, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE);
let vertices = this.vertices;
let uvs = region.uvs;

View File

@ -35,6 +35,8 @@ import { Slot } from "./Slot";
import { TransformConstraint } from "./TransformConstraint";
import { StringSet, Utils, MathUtils, NumberArrayLike } from "./Utils";
import { Event } from "./Event";
import { HasTextureRegion } from "./attachments/HasTextureRegion";
import { SequenceMode, SequenceModeValues } from "./attachments/Sequence";
/** A simple container for a list of timelines and a name. */
export class Animation {
@ -146,7 +148,9 @@ const Property = {
pathConstraintPosition: 16,
pathConstraintSpacing: 17,
pathConstraintMix: 18
pathConstraintMix: 18,
sequence: 19
}
/** The interface for all timelines. */
@ -1505,7 +1509,7 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
let slot: Slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active) return;
let slotAttachment: Attachment = slot.getAttachment();
if (!(slotAttachment instanceof VertexAttachment) || (<VertexAttachment>slotAttachment).deformAttachment != this.attachment) return;
if (!(slotAttachment instanceof VertexAttachment) || (<VertexAttachment>slotAttachment).timelineAttahment != this.attachment) return;
let deform: Array<number> = slot.deform;
if (deform.length == 0) blend = MixBlend.setup;
@ -1515,7 +1519,6 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
let frames = this.frames;
if (time < frames[0]) {
let vertexAttachment = <VertexAttachment>slotAttachment;
switch (blend) {
case MixBlend.setup:
deform.length = 0;
@ -1526,6 +1529,7 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
return;
}
deform.length = vertexCount;
let vertexAttachment = <VertexAttachment>slotAttachment;
if (!vertexAttachment.bones) {
// Unweighted vertex positions.
let setupVertices = vertexAttachment.vertices;
@ -2141,3 +2145,91 @@ export class PathConstraintMixTimeline extends CurveTimeline {
}
}
}
/** Changes a slot's {@link Slot#getSequenceIndex()} for an attachment's {@link Sequence}. */
export class SequenceTimeline extends Timeline implements SlotTimeline {
static ENTRIES = 3;
static MODE = 1;
static DELAY = 2;
slotIndex: number;
attachment: HasTextureRegion;
constructor (frameCount: number, slotIndex: number, attachment: HasTextureRegion) {
super(frameCount, [
Property.sequence + "|" + slotIndex + "|" + attachment.sequence.id
]);
this.slotIndex = slotIndex;
this.attachment = attachment;
}
getFrameEntries () {
return SequenceTimeline.ENTRIES;
}
getSlotIndex () {
return this.slotIndex;
}
getAttachment () {
return this.attachment as unknown as Attachment;
}
/** Sets the time, mode, index, and frame time for the specified frame.
* @param frame Between 0 and <code>frameCount</code>, inclusive.
* @param time Seconds between frames. */
setFrame (frame: number, time: number, mode: SequenceMode, index: number, delay: number) {
let frames = this.frames;
frame *= SequenceTimeline.ENTRIES;
frames[frame] = time;
frames[frame + SequenceTimeline.MODE] = mode | (index << 4);
frames[frame + SequenceTimeline.DELAY] = delay;
}
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
let slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active) return;
let slotAttachment = slot.attachment;
let attachment = this.attachment as unknown as Attachment;
if (slotAttachment != attachment) {
if (!(slotAttachment instanceof VertexAttachment)
|| (slotAttachment as VertexAttachment).timelineAttahment != attachment) return;
}
let frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
if (blend == MixBlend.setup || blend == MixBlend.first) slot.sequenceIndex = -1;
return;
}
let i = Timeline.search(frames, time, SequenceTimeline.ENTRIES);
let before = frames[i];
let modeAndIndex = frames[i + SequenceTimeline.MODE];
let delay = frames[i + SequenceTimeline.DELAY];
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;
switch (mode) {
case SequenceMode.once:
index = Math.min(count - 1, index);
break;
case SequenceMode.loop:
index %= count;
break;
case SequenceMode.pingpong:
let n = (count << 1) - 2;
index %= n;
if (index >= count) index = n - index;
break;
case SequenceMode.onceReverse:
index = Math.max(count - 1 - index, 0);
break;
case SequenceMode.loopReverse:
index = count - 1 - (index % count);
}
}
slot.sequenceIndex = index;
}
}

View File

@ -36,6 +36,7 @@ import { PointAttachment } from "./attachments/PointAttachment";
import { RegionAttachment } from "./attachments/RegionAttachment";
import { Skin } from "./Skin";
import { TextureAtlas } from "./TextureAtlas";
import { Sequence } from "./attachments/Sequence"
/** An {@link AttachmentLoader} that configures attachments using texture regions from an {@link TextureAtlas}.
*
@ -48,21 +49,39 @@ export class AtlasAttachmentLoader implements AttachmentLoader {
this.atlas = atlas;
}
newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment {
let region = this.atlas.findRegion(path);
if (!region) throw new Error("Region not found in atlas: " + path + " (region attachment: " + name + ")");
region.renderObject = region;
loadSequence (name: string, basePath: string, sequence: Sequence) {
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);
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);
attachment.setRegion(region);
if (sequence != null) {
this.loadSequence(name, path, sequence);
} else {
let region = this.atlas.findRegion(path);
if (!region) throw new Error("Region not found in atlas: " + path + " (region attachment: " + name + ")");
region.renderObject = region;
attachment.region = region;
}
return attachment;
}
newMeshAttachment (skin: Skin, name: string, path: string): MeshAttachment {
let region = this.atlas.findRegion(path);
if (!region) throw new Error("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
region.renderObject = region;
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment {
let attachment = new MeshAttachment(name);
attachment.region = region;
if (sequence != null) {
this.loadSequence(name, path, sequence);
} else {
let region = this.atlas.findRegion(path);
if (!region) throw new Error("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
region.renderObject = region;
attachment.region = region;
}
return attachment;
}

View File

@ -75,11 +75,6 @@ export class Skeleton {
/** The color to tint all the skeleton's attachments. */
color: Color;
/** Returns the skeleton's time. This can be used for tracking, such as with Slot {@link Slot#attachmentTime}.
* <p>
* See {@link #update()}. */
time = 0;
/** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale
* inheritance. */
scaleX = 1;
@ -603,7 +598,7 @@ export class Skeleton {
if (attachment instanceof RegionAttachment) {
verticesLength = 8;
vertices = Utils.setArraySize(temp, verticesLength, 0);
(<RegionAttachment>attachment).computeWorldVertices(slot.bone, vertices, 0, 2);
(<RegionAttachment>attachment).computeWorldVertices(slot, vertices, 0, 2);
} else if (attachment instanceof MeshAttachment) {
let mesh = (<MeshAttachment>attachment);
verticesLength = mesh.worldVerticesLength;
@ -623,9 +618,4 @@ export class Skeleton {
offset.set(minX, minY);
size.set(maxX - minX, maxY - minY);
}
/** Increments the skeleton's {@link #time}. */
update (delta: number) {
this.time += delta;
}
}

View File

@ -27,10 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { Animation, Timeline, AttachmentTimeline, RGBATimeline, RGBTimeline, RGBA2Timeline, RGB2Timeline, AlphaTimeline, RotateTimeline, TranslateTimeline, TranslateXTimeline, TranslateYTimeline, ScaleTimeline, ScaleXTimeline, ScaleYTimeline, ShearTimeline, ShearXTimeline, ShearYTimeline, IkConstraintTimeline, TransformConstraintTimeline, PathConstraintPositionTimeline, PathConstraintSpacingTimeline, PathConstraintMixTimeline, DeformTimeline, DrawOrderTimeline, EventTimeline, CurveTimeline1, CurveTimeline2, CurveTimeline } from "./Animation";
import { Animation, Timeline, AttachmentTimeline, RGBATimeline, RGBTimeline, RGBA2Timeline, RGB2Timeline, AlphaTimeline, RotateTimeline, TranslateTimeline, TranslateXTimeline, TranslateYTimeline, ScaleTimeline, ScaleXTimeline, ScaleYTimeline, ShearTimeline, ShearXTimeline, ShearYTimeline, IkConstraintTimeline, TransformConstraintTimeline, PathConstraintPositionTimeline, PathConstraintSpacingTimeline, PathConstraintMixTimeline, DeformTimeline, DrawOrderTimeline, EventTimeline, CurveTimeline1, CurveTimeline2, CurveTimeline, SequenceTimeline } from "./Animation";
import { VertexAttachment, Attachment } from "./attachments/Attachment";
import { AttachmentLoader } from "./attachments/AttachmentLoader";
import { HasTextureRegion } from "./attachments/HasTextureRegion";
import { MeshAttachment } from "./attachments/MeshAttachment";
import { Sequence, SequenceModeValues } from "./attachments/Sequence";
import { BoneData } from "./BoneData";
import { Event } from "./Event";
import { EventData } from "./EventData";
@ -219,9 +221,9 @@ export class SkeletonBinary {
let linkedMesh = this.linkedMeshes[i];
let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? parent as VertexAttachment : linkedMesh.mesh;
linkedMesh.mesh.timelineAttahment = linkedMesh.inheritTimeline ? parent as VertexAttachment : linkedMesh.mesh;
linkedMesh.mesh.setParentMesh(parent as MeshAttachment);
linkedMesh.mesh.updateUVs();
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
}
this.linkedMeshes.length = 0;
@ -299,9 +301,10 @@ export class SkeletonBinary {
let width = input.readFloat();
let height = input.readFloat();
let color = input.readInt32();
let sequence = this.readSequence(input);
if (!path) path = name;
let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
let region = this.attachmentLoader.newRegionAttachment(skin, name, path, sequence);
if (!region) return null;
region.path = path;
region.x = x * scale;
@ -312,7 +315,8 @@ export class SkeletonBinary {
region.width = width * scale;
region.height = height * scale;
Color.rgba8888ToColor(region.color, color);
region.updateOffset();
region.sequence = sequence;
if (sequence == null) region.updateRegion();
return region;
}
case AttachmentType.BoundingBox: {
@ -336,6 +340,7 @@ export class SkeletonBinary {
let triangles = this.readShortArray(input);
let vertices = this.readVertices(input, vertexCount);
let hullLength = input.readInt(true);
let sequence = this.readSequence(input);
let edges = null;
let width = 0, height = 0;
if (nonessential) {
@ -345,7 +350,7 @@ export class SkeletonBinary {
}
if (!path) path = name;
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path, sequence);
if (!mesh) return null;
mesh.path = path;
Color.rgba8888ToColor(mesh.color, color);
@ -354,8 +359,9 @@ export class SkeletonBinary {
mesh.worldVerticesLength = vertexCount << 1;
mesh.triangles = triangles;
mesh.regionUVs = uvs;
mesh.updateUVs();
if (sequence == null) mesh.updateRegion();
mesh.hullLength = hullLength << 1;
mesh.sequence = sequence;
if (nonessential) {
mesh.edges = edges;
mesh.width = width * scale;
@ -368,7 +374,8 @@ export class SkeletonBinary {
let color = input.readInt32();
let skinName = input.readStringRef();
let parent = input.readStringRef();
let inheritDeform = input.readBoolean();
let inheritTimelines = input.readBoolean();
let sequence = this.readSequence(input);
let width = 0, height = 0;
if (nonessential) {
width = input.readFloat();
@ -376,15 +383,16 @@ export class SkeletonBinary {
}
if (!path) path = name;
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path, sequence);
if (!mesh) return null;
mesh.path = path;
Color.rgba8888ToColor(mesh.color, color);
mesh.sequence = sequence;
if (nonessential) {
mesh.width = width * scale;
mesh.height = height * scale;
}
this.linkedMeshes.push(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform));
this.linkedMeshes.push(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritTimelines));
return mesh;
}
case AttachmentType.Path: {
@ -441,6 +449,15 @@ export class SkeletonBinary {
return null;
}
private readSequence (input: BinaryInput) {
if (!input.readBoolean()) return null;
let sequence = new Sequence(input.readInt(true));
sequence.start = input.readInt(true);
sequence.digits = input.readInt(true);
sequence.setupIndex = input.readInt(true);
return sequence;
}
private readVertices (input: BinaryInput, vertexCount: number): Vertices {
let scale = this.scale;
let verticesLength = vertexCount << 1;
@ -697,7 +714,6 @@ export class SkeletonBinary {
a = a2;
}
timelines.push(timeline);
break;
}
}
}
@ -850,112 +866,130 @@ export class SkeletonBinary {
let slotIndex = input.readInt(true);
for (let iii = 0, nnn = input.readInt(true); iii < nnn; iii++) {
let attachmentName = input.readStringRef();
let attachment = skin.getAttachment(slotIndex, attachmentName) as VertexAttachment;
let weighted = attachment.bones;
let vertices = attachment.vertices;
let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
let attachment = skin.getAttachment(slotIndex, attachmentName);
let timelineType = input.readByte();
let frameCount = input.readInt(true);
let frameLast = frameCount - 1;
let bezierCount = input.readInt(true);
let timeline = new DeformTimeline(frameCount, bezierCount, slotIndex, attachment);
let time = input.readFloat();
for (let frame = 0, bezier = 0; ; frame++) {
let deform;
let end = input.readInt(true);
if (end == 0)
deform = weighted ? Utils.newFloatArray(deformLength) : vertices;
else {
deform = Utils.newFloatArray(deformLength);
let start = input.readInt(true);
end += start;
if (scale == 1) {
for (let v = start; v < end; v++)
deform[v] = input.readFloat();
} else {
for (let v = start; v < end; v++)
deform[v] = input.readFloat() * scale;
}
if (!weighted) {
for (let v = 0, vn = deform.length; v < vn; v++)
deform[v] += vertices[v];
}
}
switch (timelineType) {
case ATTACHMENT_DEFORM: {
let vertexAttachment = attachment as VertexAttachment;
let weighted = vertexAttachment.bones;
let vertices = vertexAttachment.vertices;
let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
timeline.setFrame(frame, time, deform);
if (frame == frameLast) break;
let time2 = input.readFloat();
switch (input.readByte()) {
case CURVE_STEPPED:
timeline.setStepped(frame);
break;
case CURVE_BEZIER:
setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1);
let bezierCount = input.readInt(true);
let timeline = new DeformTimeline(frameCount, bezierCount, slotIndex, vertexAttachment);
let time = input.readFloat();
for (let frame = 0, bezier = 0; ; frame++) {
let deform;
let end = input.readInt(true);
if (end == 0)
deform = weighted ? Utils.newFloatArray(deformLength) : vertices;
else {
deform = Utils.newFloatArray(deformLength);
let start = input.readInt(true);
end += start;
if (scale == 1) {
for (let v = start; v < end; v++)
deform[v] = input.readFloat();
} else {
for (let v = start; v < end; v++)
deform[v] = input.readFloat() * scale;
}
if (!weighted) {
for (let v = 0, vn = deform.length; v < vn; v++)
deform[v] += vertices[v];
}
}
timeline.setFrame(frame, time, deform);
if (frame == frameLast) break;
let time2 = input.readFloat();
switch (input.readByte()) {
case CURVE_STEPPED:
timeline.setStepped(frame);
break;
case CURVE_BEZIER:
setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1);
}
time = time2;
}
timelines.push(timeline);
}
case ATTACHMENT_SEQUENCE: {
let timeline = new SequenceTimeline(frameCount, slotIndex, attachment as unknown as HasTextureRegion);
for (let frame = 0; frame < frameCount; frame++) {
let time = input.readFloat();
let modeAndIndex = input.readInt32();
timeline.setFrame(frame, time, SequenceModeValues[modeAndIndex & 0xf], modeAndIndex >> 4,
input.readFloat());
}
timelines.push(timeline);
}
time = time2;
}
timelines.push(timeline);
}
}
}
// Draw order timeline.
let drawOrderCount = input.readInt(true);
if (drawOrderCount > 0) {
let timeline = new DrawOrderTimeline(drawOrderCount);
let slotCount = skeletonData.slots.length;
for (let i = 0; i < drawOrderCount; i++) {
let time = input.readFloat();
let offsetCount = input.readInt(true);
let drawOrder = Utils.newArray(slotCount, 0);
for (let ii = slotCount - 1; ii >= 0; ii--)
drawOrder[ii] = -1;
let unchanged = Utils.newArray(slotCount - offsetCount, 0);
let originalIndex = 0, unchangedIndex = 0;
for (let ii = 0; ii < offsetCount; ii++) {
let slotIndex = input.readInt(true);
// Collect unchanged items.
while (originalIndex != slotIndex)
// Draw order timeline.
let drawOrderCount = input.readInt(true);
if (drawOrderCount > 0) {
let timeline = new DrawOrderTimeline(drawOrderCount);
let slotCount = skeletonData.slots.length;
for (let i = 0; i < drawOrderCount; i++) {
let time = input.readFloat();
let offsetCount = input.readInt(true);
let drawOrder = Utils.newArray(slotCount, 0);
for (let ii = slotCount - 1; ii >= 0; ii--)
drawOrder[ii] = -1;
let unchanged = Utils.newArray(slotCount - offsetCount, 0);
let originalIndex = 0, unchangedIndex = 0;
for (let ii = 0; ii < offsetCount; ii++) {
let slotIndex = input.readInt(true);
// Collect unchanged items.
while (originalIndex != slotIndex)
unchanged[unchangedIndex++] = originalIndex++;
// Set changed items.
drawOrder[originalIndex + input.readInt(true)] = originalIndex++;
}
// Collect remaining unchanged items.
while (originalIndex < slotCount)
unchanged[unchangedIndex++] = originalIndex++;
// Set changed items.
drawOrder[originalIndex + input.readInt(true)] = originalIndex++;
// Fill in unchanged items.
for (let ii = slotCount - 1; ii >= 0; ii--)
if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
timeline.setFrame(i, time, drawOrder);
}
// Collect remaining unchanged items.
while (originalIndex < slotCount)
unchanged[unchangedIndex++] = originalIndex++;
// Fill in unchanged items.
for (let ii = slotCount - 1; ii >= 0; ii--)
if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
timeline.setFrame(i, time, drawOrder);
timelines.push(timeline);
}
timelines.push(timeline);
}
// Event timeline.
let eventCount = input.readInt(true);
if (eventCount > 0) {
let timeline = new EventTimeline(eventCount);
for (let i = 0; i < eventCount; i++) {
let time = input.readFloat();
let eventData = skeletonData.events[input.readInt(true)];
let event = new Event(time, eventData);
event.intValue = input.readInt(false);
event.floatValue = input.readFloat();
event.stringValue = input.readBoolean() ? input.readString() : eventData.stringValue;
if (event.data.audioPath) {
event.volume = input.readFloat();
event.balance = input.readFloat();
// Event timeline.
let eventCount = input.readInt(true);
if (eventCount > 0) {
let timeline = new EventTimeline(eventCount);
for (let i = 0; i < eventCount; i++) {
let time = input.readFloat();
let eventData = skeletonData.events[input.readInt(true)];
let event = new Event(time, eventData);
event.intValue = input.readInt(false);
event.floatValue = input.readFloat();
event.stringValue = input.readBoolean() ? input.readString() : eventData.stringValue;
if (event.data.audioPath) {
event.volume = input.readFloat();
event.balance = input.readFloat();
}
timeline.setFrame(i, event);
}
timeline.setFrame(i, event);
timelines.push(timeline);
}
timelines.push(timeline);
}
let duration = 0;
for (let i = 0, n = timelines.length; i < n; i++)
duration = Math.max(duration, timelines[i].getDuration());
return new Animation(name, timelines, duration);
let duration = 0;
for (let i = 0, n = timelines.length; i < n; i++)
duration = Math.max(duration, timelines[i].getDuration());
return new Animation(name, timelines, duration);
}
}
}
@ -1056,14 +1090,14 @@ class LinkedMesh {
parent: string; skin: string;
slotIndex: number;
mesh: MeshAttachment;
inheritDeform: boolean;
inheritTimeline: boolean;
constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) {
this.mesh = mesh;
this.skin = skin;
this.slotIndex = slotIndex;
this.parent = parent;
this.inheritDeform = inheritDeform;
this.inheritTimeline = inheritDeform;
}
}
@ -1136,6 +1170,9 @@ const SLOT_RGBA2 = 3;
const SLOT_RGB2 = 4;
const SLOT_ALPHA = 5;
const ATTACHMENT_DEFORM = 0;
const ATTACHMENT_SEQUENCE = 1;
const PATH_POSITION = 0;
const PATH_SPACING = 1;
const PATH_MIX = 2;

View File

@ -41,6 +41,7 @@ import { Skin } from "./Skin";
import { SlotData, BlendMode } from "./SlotData";
import { TransformConstraintData } from "./TransformConstraintData";
import { Utils, Color, NumberArrayLike } from "./Utils";
import { Sequence } from "./attachments/Sequence";
/** Loads skeleton data in the Spine JSON format.
*
@ -257,9 +258,9 @@ export class SkeletonJson {
let linkedMesh = this.linkedMeshes[i];
let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
linkedMesh.mesh.timelineAttahment = linkedMesh.inheritTimeline ? <VertexAttachment>parent : <VertexAttachment>linkedMesh.mesh;
linkedMesh.mesh.setParentMesh(<MeshAttachment>parent);
linkedMesh.mesh.updateUVs();
if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
}
this.linkedMeshes.length = 0;
@ -298,7 +299,8 @@ export class SkeletonJson {
switch (getValue(map, "type", "region")) {
case "region": {
let path = getValue(map, "path", name);
let region = this.attachmentLoader.newRegionAttachment(skin, name, path);
let sequence = this.readSequence(getValue(map, "sequence", null));
let region = this.attachmentLoader.newRegionAttachment(skin, name, path, sequence);
if (!region) return null;
region.path = path;
region.x = getValue(map, "x", 0) * scale;
@ -308,11 +310,12 @@ export class SkeletonJson {
region.rotation = getValue(map, "rotation", 0);
region.width = map.width * scale;
region.height = map.height * scale;
region.sequence = sequence;
let color: string = getValue(map, "color", null);
if (color) region.color.setFromString(color);
region.updateOffset();
if (region.region != null) region.updateRegion();
return region;
}
case "boundingbox": {
@ -326,7 +329,8 @@ export class SkeletonJson {
case "mesh":
case "linkedmesh": {
let path = getValue(map, "path", name);
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path);
let sequence = this.readSequence(getValue(map, "sequence", null));
let mesh = this.attachmentLoader.newMeshAttachment(skin, name, path, sequence);
if (!mesh) return null;
mesh.path = path;
@ -335,10 +339,11 @@ export class SkeletonJson {
mesh.width = getValue(map, "width", 0) * scale;
mesh.height = getValue(map, "height", 0) * scale;
mesh.sequence = sequence;
let parent: string = getValue(map, "parent", null);
if (parent) {
this.linkedMeshes.push(new LinkedMesh(mesh, <string>getValue(map, "skin", null), slotIndex, parent, getValue(map, "deform", true)));
this.linkedMeshes.push(new LinkedMesh(mesh, <string>getValue(map, "skin", null), slotIndex, parent, getValue(map, "timelines", true)));
return mesh;
}
@ -346,7 +351,7 @@ export class SkeletonJson {
this.readVertices(map, mesh, uvs.length);
mesh.triangles = map.triangles;
mesh.regionUVs = uvs;
mesh.updateUVs();
if (mesh.region != null) mesh.updateRegion();
mesh.edges = getValue(map, "edges", null);
mesh.hullLength = getValue(map, "hull", 0) * 2;
@ -399,6 +404,15 @@ export class SkeletonJson {
return null;
}
readSequence (map: any) {
if (map == null) return null;
let sequence = new Sequence(getValue(map, "count", 0));
sequence.start = getValue(map, "start", 1);
sequence.digits = getValue(map, "digits", 0);
sequence.setupIndex = getValue(map, "setup", 0);
return sequence;
}
readVertices (map: any, attachment: VertexAttachment, verticesLength: number) {
let scale = this.scale;
attachment.worldVerticesLength = verticesLength;
@ -445,7 +459,7 @@ export class SkeletonJson {
let timeline = new AttachmentTimeline(frames, slotIndex);
for (let frame = 0; frame < frames; frame++) {
let keyMap = timelineMap[frame];
timeline.setFrame(frame, getValue(keyMap, "time", 0), keyMap.name);
timeline.setFrame(frame, getValue(keyMap, "time", 0), getValue(keyMap, "name", null));
}
timelines.push(timeline);
@ -778,17 +792,18 @@ export class SkeletonJson {
}
}
// Deform timelines.
if (map.deform) {
for (let deformName in map.deform) {
let deformMap = map.deform[deformName];
let skin = skeletonData.findSkin(deformName);
for (let slotName in deformMap) {
let slotMap = deformMap[slotName];
// Attachment timelines.
if (map.attachments) {
for (let attachmentsName in map.attachments) {
let attachmentsMap = map.attachments[attachmentsName];
let skin = skeletonData.findSkin(attachmentsName);
for (let slotName in attachmentsMap) {
let slotMap = attachmentsMap[slotName];
let slotIndex = skeletonData.findSlot(slotName).index;
for (let timelineName in slotMap) {
let timelineMap = slotMap[timelineName];
let keyMap = timelineMap[0];
let attachmentMap = slotMap[timelineName];
let attachmentMapName = timelineName;
let keyMap = attachmentMap[0];
if (!keyMap) continue;
let attachment = <VertexAttachment>skin.getAttachment(slotIndex, timelineName);
@ -796,7 +811,7 @@ export class SkeletonJson {
let vertices = attachment.vertices;
let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
let timeline = new DeformTimeline(timelineMap.length, timelineMap.length, slotIndex, attachment);
let timeline = new DeformTimeline(attachmentMap.length, attachmentMap.length, slotIndex, attachment);
let time = getValue(keyMap, "time", 0);
for (let frame = 0, bezier = 0; ; frame++) {
let deform: NumberArrayLike;
@ -818,7 +833,7 @@ export class SkeletonJson {
}
timeline.setFrame(frame, time, deform);
let nextMap = timelineMap[frame + 1];
let nextMap = attachmentMap[frame + 1];
if (!nextMap) {
timeline.shrink(bezier);
break;
@ -900,14 +915,14 @@ class LinkedMesh {
parent: string; skin: string;
slotIndex: number;
mesh: MeshAttachment;
inheritDeform: boolean;
inheritTimeline: boolean;
constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) {
this.mesh = mesh;
this.skin = skin;
this.slotIndex = slotIndex;
this.parent = parent;
this.inheritDeform = inheritDeform;
this.inheritTimeline = inheritDeform;
}
}

View File

@ -53,10 +53,12 @@ export class Slot {
attachment: Attachment;
private attachmentTime: number;
attachmentState: number;
/** 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;
/** 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.
*
@ -83,28 +85,17 @@ export class Slot {
return this.attachment;
}
/** Sets the slot's attachment and, if the attachment changed, resets {@link #attachmentTime} and clears the {@link #deform}.
* The deform is not cleared if the old attachment has the same {@link VertexAttachment#getDeformAttachment()} as the specified
* attachment.
* @param attachment May be null. */
/** 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) {
if (this.attachment == attachment) return;
if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment)
|| (<VertexAttachment>attachment).deformAttachment != (<VertexAttachment>this.attachment).deformAttachment) {
|| (<VertexAttachment>attachment).timelineAttahment != (<VertexAttachment>this.attachment).timelineAttahment) {
this.deform.length = 0;
}
this.attachment = attachment;
this.attachmentTime = this.bone.skeleton.time;
}
setAttachmentTime (time: number) {
this.attachmentTime = this.bone.skeleton.time - time;
}
/** The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton
* {@link Skeleton#time}. */
getAttachmentTime (): number {
return this.bone.skeleton.time - this.attachmentTime;
this.sequenceIndex = -1;
}
/** Sets this slot to the setup pose. */

View File

@ -64,8 +64,9 @@ export abstract class VertexAttachment extends Attachment {
* {@link #computeWorldVertices()} using the `count` parameter. */
worldVerticesLength = 0;
/** Deform keys for the deform attachment are also applied to this attachment. May be null if no deform keys should be applied. */
deformAttachment: VertexAttachment = this;
/** Timelines for the timeline attachment are also applied to this attachment.
* May be null if no attachment-specific timelines should be applied. */
timelineAttahment: Attachment = this;
constructor (name: string) {
super(name);
@ -155,6 +156,6 @@ export abstract class VertexAttachment extends Attachment {
attachment.vertices = null;
attachment.worldVerticesLength = this.worldVerticesLength;
attachment.deformAttachment = this.deformAttachment;
attachment.timelineAttahment = this.timelineAttahment;
}
}

View File

@ -34,6 +34,7 @@ import { MeshAttachment } from "./MeshAttachment";
import { PathAttachment } from "./PathAttachment";
import { PointAttachment } from "./PointAttachment";
import { RegionAttachment } from "./RegionAttachment";
import { Sequence } from "./Sequence";
/** The interface which can be implemented to customize creating and populating attachments.
*
@ -41,10 +42,10 @@ import { RegionAttachment } from "./RegionAttachment";
* Runtimes Guide. */
export interface AttachmentLoader {
/** @return May be null to not load an attachment. */
newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment;
newRegionAttachment (skin: Skin, name: string, path: string, sequence: Sequence): RegionAttachment;
/** @return May be null to not load an attachment. */
newMeshAttachment (skin: Skin, name: string, path: string): MeshAttachment;
newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment;
/** @return May be null to not load an attachment. */
newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment;

View File

@ -0,0 +1,50 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated September 24, 2021. Replaces all prior versions.
*
* Copyright (c) 2013-2021, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { TextureRegion } from "../Texture"
import { Color } from "../Utils"
import { Sequence } from "./Sequence"
export interface HasTextureRegion {
/** The name used to find the {@link #region()}. */
path: string;
/** 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;
/** 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. */
updateRegion (): void;
/** The color to tint the attachment. */
color: Color;
sequence: Sequence;
}

View File

@ -31,12 +31,15 @@ import { TextureRegion } from "../Texture";
import { TextureAtlasRegion } from "../TextureAtlas";
import { Color, NumberArrayLike, Utils } from "../Utils";
import { VertexAttachment, Attachment } from "./Attachment";
import { HasTextureRegion } from "./HasTextureRegion";
import { Sequence } from "./Sequence";
import { Slot } from "../Slot";
/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not
* supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
*
* See [Mesh attachments](http://esotericsoftware.com/spine-meshes) in the Spine User Guide. */
export class MeshAttachment extends VertexAttachment {
export class MeshAttachment extends VertexAttachment implements HasTextureRegion {
region: TextureRegion;
/** The name of the texture region for this attachment. */
@ -70,15 +73,18 @@ export class MeshAttachment extends VertexAttachment {
edges: Array<number>;
private parentMesh: MeshAttachment;
sequence: Sequence;
tempColor = new Color(0, 0, 0, 0);
constructor (name: string) {
super(name);
}
/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region UVs or
* region. */
updateUVs () {
/** 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 () {
let regionUVs = this.regionUVs;
if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
let uvs = this.uvs;
@ -175,6 +181,8 @@ export class MeshAttachment extends VertexAttachment {
Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length);
copy.hullLength = this.hullLength;
copy.sequence = this.sequence.copy();
// Nonessential.
if (this.edges) {
copy.edges = new Array<number>(this.edges.length);
@ -186,15 +194,20 @@ export class MeshAttachment extends VertexAttachment {
return copy;
}
computeWorldVertices (slot: Slot, start: number, count: number, worldVertices: NumberArrayLike, offset: number, stride: number) {
if (this.sequence != null) this.sequence.apply(slot, this);
super.computeWorldVertices(slot, start, count, worldVertices, offset, stride);
}
/** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/
newLinkedMesh (): MeshAttachment {
let copy = new MeshAttachment(this.name);
copy.region = this.region;
copy.path = this.path;
copy.color.setFromColor(this.color);
copy.deformAttachment = this.deformAttachment;
copy.timelineAttahment = this.timelineAttahment;
copy.setParentMesh(this.parentMesh ? this.parentMesh : this);
copy.updateUVs();
if (copy.region != null) copy.updateRegion();
return copy;
}
}

View File

@ -31,11 +31,14 @@ import { Bone } from "../Bone";
import { TextureRegion } from "../Texture";
import { Color, NumberArrayLike, Utils } from "../Utils";
import { Attachment } from "./Attachment";
import { HasTextureRegion } from "./HasTextureRegion";
import { Sequence } from "./Sequence";
import { Slot } from "../Slot";
/** An attachment that displays a textured quadrilateral.
*
* See [Region attachments](http://esotericsoftware.com/spine-regions) in the Spine User Guide. */
export class RegionAttachment extends Attachment {
export class RegionAttachment extends Attachment implements HasTextureRegion {
/** The local x translation. */
x = 0;
@ -63,8 +66,9 @@ export class RegionAttachment extends Attachment {
/** The name of the texture region for this attachment. */
path: string;
rendererObject: any;
private rendererObject: any;
region: TextureRegion;
sequence: Sequence;
/** For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.
*
@ -80,7 +84,7 @@ export class RegionAttachment extends Attachment {
}
/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
updateOffset (): void {
updateRegion (): void {
let region = this.region;
let regionScaleX = this.width / this.region.originalWidth * this.scaleX;
let regionScaleY = this.height / this.region.originalHeight * this.scaleY;
@ -109,10 +113,7 @@ export class RegionAttachment extends Attachment {
offset[5] = localY2Cos + localX2Sin;
offset[6] = localX2Cos - localYSin;
offset[7] = localYCos + localX2Sin;
}
setRegion (region: TextureRegion): void {
this.region = region;
let uvs = this.uvs;
if (region.degrees == 90) {
uvs[2] = region.u;
@ -135,14 +136,18 @@ export class RegionAttachment extends Attachment {
}
}
/** Transforms the attachment's four vertices to world coordinates.
*
* See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
/** Transforms the attachment's four vertices to world coordinates. If the attachment has a {@link #sequence}, the region may
* be changed.
* <p>
* See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide.
* @param worldVertices The output world vertices. Must have a length >= `offset` + 8.
* @param offset The `worldVertices` index to begin writing values.
* @param stride The number of `worldVertices` entries between the value pairs written. */
computeWorldVertices (bone: Bone, worldVertices: NumberArrayLike, offset: number, stride: number) {
* @param worldVertices The output world vertices. Must have a length >= <code>offset</code> + 8.
* @param offset The <code>worldVertices</code> index to begin writing values.
* @param stride The number of <code>worldVertices</code> entries between the value pairs written. */
computeWorldVertices (slot: Slot, worldVertices: NumberArrayLike, offset: number, stride: number) {
if (this.sequence != null) this.sequence.apply(slot, this);
let bone = slot.bone;
let vertexOffset = this.offset;
let x = bone.worldX, y = bone.worldY;
let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
@ -187,6 +192,7 @@ export class RegionAttachment extends Attachment {
Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, 8);
Utils.arrayCopy(this.offset, 0, copy.offset, 0, 8);
copy.color.setFromColor(this.color);
copy.sequence = this.sequence.copy();
return copy;
}

View File

@ -0,0 +1,102 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated September 24, 2021. Replaces all prior versions.
*
* Copyright (c) 2013-2021, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { TextureRegion } from "../Texture";
import { Slot } from "../Slot";
import { HasTextureRegion } from "./HasTextureRegion";
import { Utils } from "src";
export class Sequence {
private static _nextID = 0;
id = Sequence.nextID();
regions: TextureRegion[];
start = 0;
digits = 0;
/** The index of the region to show for the setup pose. */
setupIndex = 0;
constructor (count: number) {
this.regions = new Array<TextureRegion>(count);
}
copy (): Sequence {
let copy = new Sequence(this.regions.length);
Utils.arrayCopy(this.regions, 0, copy.regions, 0, this.regions.length);
copy.start = this.start;
copy.digits = this.digits;
copy.setupIndex = this.setupIndex;
return copy;
}
apply (slot: Slot, attachment: HasTextureRegion) {
let index = slot.sequenceIndex;
if (index == -1) index = this.setupIndex;
if (index >= this.regions.length) index = this.regions.length - 1;
let region = this.regions[index];
if (attachment.region != region) {
attachment.region = region;
attachment.updateRegion();
}
}
getPath (basePath: string, index: number): string {
let result = basePath;
let frame = (this.start + index).toString();
for (let i = this.digits - frame.length; i > 0; i--)
result += "0";
result += frame;
return result;
}
private static nextID (): number {
return Sequence._nextID++;
}
}
export enum SequenceMode {
hold = 0,
once = 1,
loop = 2,
pingpong = 3,
onceReverse = 4,
loopReverse = 5,
pingpongReverse = 6
}
export const SequenceModeValues = [
SequenceMode.hold,
SequenceMode.once,
SequenceMode.loop,
SequenceMode.pingpong,
SequenceMode.onceReverse,
SequenceMode.loopReverse,
SequenceMode.pingpongReverse
];

View File

@ -176,7 +176,7 @@ export class SkeletonMesh extends THREE.Object3D {
attachmentColor = region.color;
vertices = this.vertices;
numFloats = vertexSize * 4;
region.computeWorldVertices(slot.bone, vertices, 0, vertexSize);
region.computeWorldVertices(slot, vertices, 0, vertexSize);
triangles = SkeletonMesh.QUAD_TRIANGLES;
uvs = region.uvs;
texture = <ThreeJsTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;

View File

@ -92,7 +92,7 @@ export class SkeletonDebugRenderer implements Disposable {
if (attachment instanceof RegionAttachment) {
let regionAttachment = <RegionAttachment>attachment;
let vertices = this.vertices;
regionAttachment.computeWorldVertices(slot.bone, vertices, 0, 2);
regionAttachment.computeWorldVertices(slot, vertices, 0, 2);
shapes.line(vertices[0], vertices[1], vertices[2], vertices[3]);
shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
shapes.line(vertices[4], vertices[5], vertices[6], vertices[7]);

View File

@ -109,7 +109,7 @@ export class SkeletonRenderer {
renderable.vertices = this.vertices;
renderable.numVertices = 4;
renderable.numFloats = clippedVertexSize << 2;
region.computeWorldVertices(slot.bone, renderable.vertices, 0, clippedVertexSize);
region.computeWorldVertices(slot, renderable.vertices, 0, clippedVertexSize);
triangles = SkeletonRenderer.QUAD_TRIANGLES;
uvs = region.uvs;
texture = <GLTexture>(<TextureAtlasRegion>region.region.renderObject).page.texture;

View File

@ -98,7 +98,7 @@
// Create the Spine canvas which runs the app
new spine.SpineCanvas(document.getElementById("canvas"), {
pathPrefix: "assets/",
pathPrefix: "../example/assets/",
app: new App()
});
</script>