[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

3
.gitignore vendored
View File

@ -152,3 +152,6 @@ spine-ts/spine-canvas/dist
spine-ts/spine-webgl/dist spine-ts/spine-webgl/dist
spine-ts/spine-player/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, skeletonColor.b * slotColor.b * regionColor.b * multiplier,
alpha); 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 vertices = this.vertices;
let uvs = region.uvs; let uvs = region.uvs;

View File

@ -35,6 +35,8 @@ import { Slot } from "./Slot";
import { TransformConstraint } from "./TransformConstraint"; import { TransformConstraint } from "./TransformConstraint";
import { StringSet, Utils, MathUtils, NumberArrayLike } from "./Utils"; import { StringSet, Utils, MathUtils, NumberArrayLike } from "./Utils";
import { Event } from "./Event"; 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. */ /** A simple container for a list of timelines and a name. */
export class Animation { export class Animation {
@ -146,7 +148,9 @@ const Property = {
pathConstraintPosition: 16, pathConstraintPosition: 16,
pathConstraintSpacing: 17, pathConstraintSpacing: 17,
pathConstraintMix: 18 pathConstraintMix: 18,
sequence: 19
} }
/** The interface for all timelines. */ /** The interface for all timelines. */
@ -1505,7 +1509,7 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
let slot: Slot = skeleton.slots[this.slotIndex]; let slot: Slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active) return; if (!slot.bone.active) return;
let slotAttachment: Attachment = slot.getAttachment(); 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; let deform: Array<number> = slot.deform;
if (deform.length == 0) blend = MixBlend.setup; if (deform.length == 0) blend = MixBlend.setup;
@ -1515,7 +1519,6 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
let frames = this.frames; let frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) {
let vertexAttachment = <VertexAttachment>slotAttachment;
switch (blend) { switch (blend) {
case MixBlend.setup: case MixBlend.setup:
deform.length = 0; deform.length = 0;
@ -1526,6 +1529,7 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
return; return;
} }
deform.length = vertexCount; deform.length = vertexCount;
let vertexAttachment = <VertexAttachment>slotAttachment;
if (!vertexAttachment.bones) { if (!vertexAttachment.bones) {
// Unweighted vertex positions. // Unweighted vertex positions.
let setupVertices = vertexAttachment.vertices; 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 { RegionAttachment } from "./attachments/RegionAttachment";
import { Skin } from "./Skin"; import { Skin } from "./Skin";
import { TextureAtlas } from "./TextureAtlas"; import { TextureAtlas } from "./TextureAtlas";
import { Sequence } from "./attachments/Sequence"
/** An {@link AttachmentLoader} that configures attachments using texture regions from an {@link TextureAtlas}. /** 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; this.atlas = atlas;
} }
newRegionAttachment (skin: Skin, name: string, path: string): RegionAttachment { loadSequence (name: string, basePath: string, sequence: Sequence) {
let region = this.atlas.findRegion(path); let regions = sequence.regions;
if (!region) throw new Error("Region not found in atlas: " + path + " (region attachment: " + name + ")"); for (let i = 0, n = regions.length; i < n; i++) {
region.renderObject = region; 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); 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; return attachment;
} }
newMeshAttachment (skin: Skin, name: string, path: string): MeshAttachment { newMeshAttachment (skin: Skin, name: string, path: string, sequence: Sequence): MeshAttachment {
let region = this.atlas.findRegion(path);
if (!region) throw new Error("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
region.renderObject = region;
let attachment = new MeshAttachment(name); 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; return attachment;
} }

View File

@ -75,11 +75,6 @@ export class Skeleton {
/** The color to tint all the skeleton's attachments. */ /** The color to tint all the skeleton's attachments. */
color: Color; 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 /** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale
* inheritance. */ * inheritance. */
scaleX = 1; scaleX = 1;
@ -603,7 +598,7 @@ export class Skeleton {
if (attachment instanceof RegionAttachment) { if (attachment instanceof RegionAttachment) {
verticesLength = 8; verticesLength = 8;
vertices = Utils.setArraySize(temp, verticesLength, 0); 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) { } else if (attachment instanceof MeshAttachment) {
let mesh = (<MeshAttachment>attachment); let mesh = (<MeshAttachment>attachment);
verticesLength = mesh.worldVerticesLength; verticesLength = mesh.worldVerticesLength;
@ -623,9 +618,4 @@ export class Skeleton {
offset.set(minX, minY); offset.set(minX, minY);
size.set(maxX - minX, maxY - 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. * 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 { VertexAttachment, Attachment } from "./attachments/Attachment";
import { AttachmentLoader } from "./attachments/AttachmentLoader"; import { AttachmentLoader } from "./attachments/AttachmentLoader";
import { HasTextureRegion } from "./attachments/HasTextureRegion";
import { MeshAttachment } from "./attachments/MeshAttachment"; import { MeshAttachment } from "./attachments/MeshAttachment";
import { Sequence, SequenceModeValues } from "./attachments/Sequence";
import { BoneData } from "./BoneData"; import { BoneData } from "./BoneData";
import { Event } from "./Event"; import { Event } from "./Event";
import { EventData } from "./EventData"; import { EventData } from "./EventData";
@ -219,9 +221,9 @@ export class SkeletonBinary {
let linkedMesh = this.linkedMeshes[i]; let linkedMesh = this.linkedMeshes[i];
let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin); let skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin);
let parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); 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.setParentMesh(parent as MeshAttachment);
linkedMesh.mesh.updateUVs(); if (linkedMesh.mesh.region != null) linkedMesh.mesh.updateRegion();
} }
this.linkedMeshes.length = 0; this.linkedMeshes.length = 0;
@ -299,9 +301,10 @@ export class SkeletonBinary {
let width = input.readFloat(); let width = input.readFloat();
let height = input.readFloat(); let height = input.readFloat();
let color = input.readInt32(); let color = input.readInt32();
let sequence = this.readSequence(input);
if (!path) path = name; 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; if (!region) return null;
region.path = path; region.path = path;
region.x = x * scale; region.x = x * scale;
@ -312,7 +315,8 @@ export class SkeletonBinary {
region.width = width * scale; region.width = width * scale;
region.height = height * scale; region.height = height * scale;
Color.rgba8888ToColor(region.color, color); Color.rgba8888ToColor(region.color, color);
region.updateOffset(); region.sequence = sequence;
if (sequence == null) region.updateRegion();
return region; return region;
} }
case AttachmentType.BoundingBox: { case AttachmentType.BoundingBox: {
@ -336,6 +340,7 @@ export class SkeletonBinary {
let triangles = this.readShortArray(input); let triangles = this.readShortArray(input);
let vertices = this.readVertices(input, vertexCount); let vertices = this.readVertices(input, vertexCount);
let hullLength = input.readInt(true); let hullLength = input.readInt(true);
let sequence = this.readSequence(input);
let edges = null; let edges = null;
let width = 0, height = 0; let width = 0, height = 0;
if (nonessential) { if (nonessential) {
@ -345,7 +350,7 @@ export class SkeletonBinary {
} }
if (!path) path = name; 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; if (!mesh) return null;
mesh.path = path; mesh.path = path;
Color.rgba8888ToColor(mesh.color, color); Color.rgba8888ToColor(mesh.color, color);
@ -354,8 +359,9 @@ export class SkeletonBinary {
mesh.worldVerticesLength = vertexCount << 1; mesh.worldVerticesLength = vertexCount << 1;
mesh.triangles = triangles; mesh.triangles = triangles;
mesh.regionUVs = uvs; mesh.regionUVs = uvs;
mesh.updateUVs(); if (sequence == null) mesh.updateRegion();
mesh.hullLength = hullLength << 1; mesh.hullLength = hullLength << 1;
mesh.sequence = sequence;
if (nonessential) { if (nonessential) {
mesh.edges = edges; mesh.edges = edges;
mesh.width = width * scale; mesh.width = width * scale;
@ -368,7 +374,8 @@ export class SkeletonBinary {
let color = input.readInt32(); let color = input.readInt32();
let skinName = input.readStringRef(); let skinName = input.readStringRef();
let parent = input.readStringRef(); let parent = input.readStringRef();
let inheritDeform = input.readBoolean(); let inheritTimelines = input.readBoolean();
let sequence = this.readSequence(input);
let width = 0, height = 0; let width = 0, height = 0;
if (nonessential) { if (nonessential) {
width = input.readFloat(); width = input.readFloat();
@ -376,15 +383,16 @@ export class SkeletonBinary {
} }
if (!path) path = name; 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; if (!mesh) return null;
mesh.path = path; mesh.path = path;
Color.rgba8888ToColor(mesh.color, color); Color.rgba8888ToColor(mesh.color, color);
mesh.sequence = sequence;
if (nonessential) { if (nonessential) {
mesh.width = width * scale; mesh.width = width * scale;
mesh.height = height * 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; return mesh;
} }
case AttachmentType.Path: { case AttachmentType.Path: {
@ -441,6 +449,15 @@ export class SkeletonBinary {
return null; 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 { private readVertices (input: BinaryInput, vertexCount: number): Vertices {
let scale = this.scale; let scale = this.scale;
let verticesLength = vertexCount << 1; let verticesLength = vertexCount << 1;
@ -697,7 +714,6 @@ export class SkeletonBinary {
a = a2; a = a2;
} }
timelines.push(timeline); timelines.push(timeline);
break;
} }
} }
} }
@ -850,112 +866,130 @@ export class SkeletonBinary {
let slotIndex = input.readInt(true); let slotIndex = input.readInt(true);
for (let iii = 0, nnn = input.readInt(true); iii < nnn; iii++) { for (let iii = 0, nnn = input.readInt(true); iii < nnn; iii++) {
let attachmentName = input.readStringRef(); let attachmentName = input.readStringRef();
let attachment = skin.getAttachment(slotIndex, attachmentName) as VertexAttachment; let attachment = skin.getAttachment(slotIndex, attachmentName);
let weighted = attachment.bones; let timelineType = input.readByte();
let vertices = attachment.vertices;
let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
let frameCount = input.readInt(true); let frameCount = input.readInt(true);
let frameLast = frameCount - 1; let frameLast = frameCount - 1;
let bezierCount = input.readInt(true);
let timeline = new DeformTimeline(frameCount, bezierCount, slotIndex, attachment);
let time = input.readFloat(); switch (timelineType) {
for (let frame = 0, bezier = 0; ; frame++) { case ATTACHMENT_DEFORM: {
let deform; let vertexAttachment = attachment as VertexAttachment;
let end = input.readInt(true); let weighted = vertexAttachment.bones;
if (end == 0) let vertices = vertexAttachment.vertices;
deform = weighted ? Utils.newFloatArray(deformLength) : vertices; let deformLength = weighted ? vertices.length / 3 * 2 : vertices.length;
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 bezierCount = input.readInt(true);
let time2 = input.readFloat(); let timeline = new DeformTimeline(frameCount, bezierCount, slotIndex, vertexAttachment);
switch (input.readByte()) {
case CURVE_STEPPED: let time = input.readFloat();
timeline.setStepped(frame); for (let frame = 0, bezier = 0; ; frame++) {
break; let deform;
case CURVE_BEZIER: let end = input.readInt(true);
setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); 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. // Draw order timeline.
let drawOrderCount = input.readInt(true); let drawOrderCount = input.readInt(true);
if (drawOrderCount > 0) { if (drawOrderCount > 0) {
let timeline = new DrawOrderTimeline(drawOrderCount); let timeline = new DrawOrderTimeline(drawOrderCount);
let slotCount = skeletonData.slots.length; let slotCount = skeletonData.slots.length;
for (let i = 0; i < drawOrderCount; i++) { for (let i = 0; i < drawOrderCount; i++) {
let time = input.readFloat(); let time = input.readFloat();
let offsetCount = input.readInt(true); let offsetCount = input.readInt(true);
let drawOrder = Utils.newArray(slotCount, 0); let drawOrder = Utils.newArray(slotCount, 0);
for (let ii = slotCount - 1; ii >= 0; ii--) for (let ii = slotCount - 1; ii >= 0; ii--)
drawOrder[ii] = -1; drawOrder[ii] = -1;
let unchanged = Utils.newArray(slotCount - offsetCount, 0); let unchanged = Utils.newArray(slotCount - offsetCount, 0);
let originalIndex = 0, unchangedIndex = 0; let originalIndex = 0, unchangedIndex = 0;
for (let ii = 0; ii < offsetCount; ii++) { for (let ii = 0; ii < offsetCount; ii++) {
let slotIndex = input.readInt(true); let slotIndex = input.readInt(true);
// Collect unchanged items. // Collect unchanged items.
while (originalIndex != slotIndex) 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++; unchanged[unchangedIndex++] = originalIndex++;
// Set changed items. // Fill in unchanged items.
drawOrder[originalIndex + input.readInt(true)] = originalIndex++; 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. timelines.push(timeline);
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);
}
// Event timeline. // Event timeline.
let eventCount = input.readInt(true); let eventCount = input.readInt(true);
if (eventCount > 0) { if (eventCount > 0) {
let timeline = new EventTimeline(eventCount); let timeline = new EventTimeline(eventCount);
for (let i = 0; i < eventCount; i++) { for (let i = 0; i < eventCount; i++) {
let time = input.readFloat(); let time = input.readFloat();
let eventData = skeletonData.events[input.readInt(true)]; let eventData = skeletonData.events[input.readInt(true)];
let event = new Event(time, eventData); let event = new Event(time, eventData);
event.intValue = input.readInt(false); event.intValue = input.readInt(false);
event.floatValue = input.readFloat(); event.floatValue = input.readFloat();
event.stringValue = input.readBoolean() ? input.readString() : eventData.stringValue; event.stringValue = input.readBoolean() ? input.readString() : eventData.stringValue;
if (event.data.audioPath) { if (event.data.audioPath) {
event.volume = input.readFloat(); event.volume = input.readFloat();
event.balance = input.readFloat(); event.balance = input.readFloat();
}
timeline.setFrame(i, event);
} }
timeline.setFrame(i, event); timelines.push(timeline);
} }
timelines.push(timeline);
}
let duration = 0; let duration = 0;
for (let i = 0, n = timelines.length; i < n; i++) for (let i = 0, n = timelines.length; i < n; i++)
duration = Math.max(duration, timelines[i].getDuration()); duration = Math.max(duration, timelines[i].getDuration());
return new Animation(name, timelines, duration); return new Animation(name, timelines, duration);
}
} }
} }
@ -1056,14 +1090,14 @@ class LinkedMesh {
parent: string; skin: string; parent: string; skin: string;
slotIndex: number; slotIndex: number;
mesh: MeshAttachment; mesh: MeshAttachment;
inheritDeform: boolean; inheritTimeline: boolean;
constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) { constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) {
this.mesh = mesh; this.mesh = mesh;
this.skin = skin; this.skin = skin;
this.slotIndex = slotIndex; this.slotIndex = slotIndex;
this.parent = parent; this.parent = parent;
this.inheritDeform = inheritDeform; this.inheritTimeline = inheritDeform;
} }
} }
@ -1136,6 +1170,9 @@ const SLOT_RGBA2 = 3;
const SLOT_RGB2 = 4; const SLOT_RGB2 = 4;
const SLOT_ALPHA = 5; const SLOT_ALPHA = 5;
const ATTACHMENT_DEFORM = 0;
const ATTACHMENT_SEQUENCE = 1;
const PATH_POSITION = 0; const PATH_POSITION = 0;
const PATH_SPACING = 1; const PATH_SPACING = 1;
const PATH_MIX = 2; const PATH_MIX = 2;

View File

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

View File

@ -53,10 +53,12 @@ export class Slot {
attachment: Attachment; attachment: Attachment;
private attachmentTime: number;
attachmentState: 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 /** 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. * 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; return this.attachment;
} }
/** Sets the slot's attachment and, if the attachment changed, resets {@link #attachmentTime} and clears the {@link #deform}. /** 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#getDeformAttachment()} as the specified * The deform is not cleared if the old attachment has the same {@link VertexAttachment#getTimelineAttachment()} as the
* attachment. * specified attachment. */
* @param attachment May be null. */
setAttachment (attachment: Attachment) { setAttachment (attachment: Attachment) {
if (this.attachment == attachment) return; if (this.attachment == attachment) return;
if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment) 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.deform.length = 0;
} }
this.attachment = attachment; this.attachment = attachment;
this.attachmentTime = this.bone.skeleton.time; this.sequenceIndex = -1;
}
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;
} }
/** Sets this slot to the setup pose. */ /** 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. */ * {@link #computeWorldVertices()} using the `count` parameter. */
worldVerticesLength = 0; worldVerticesLength = 0;
/** Deform keys for the deform attachment are also applied to this attachment. May be null if no deform keys should be applied. */ /** Timelines for the timeline attachment are also applied to this attachment.
deformAttachment: VertexAttachment = this; * May be null if no attachment-specific timelines should be applied. */
timelineAttahment: Attachment = this;
constructor (name: string) { constructor (name: string) {
super(name); super(name);
@ -155,6 +156,6 @@ export abstract class VertexAttachment extends Attachment {
attachment.vertices = null; attachment.vertices = null;
attachment.worldVerticesLength = this.worldVerticesLength; 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 { PathAttachment } from "./PathAttachment";
import { PointAttachment } from "./PointAttachment"; import { PointAttachment } from "./PointAttachment";
import { RegionAttachment } from "./RegionAttachment"; import { RegionAttachment } from "./RegionAttachment";
import { Sequence } from "./Sequence";
/** The interface which can be implemented to customize creating and populating attachments. /** The interface which can be implemented to customize creating and populating attachments.
* *
@ -41,10 +42,10 @@ import { RegionAttachment } from "./RegionAttachment";
* Runtimes Guide. */ * Runtimes Guide. */
export interface AttachmentLoader { export interface AttachmentLoader {
/** @return May be null to not load an attachment. */ /** @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. */ /** @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. */ /** @return May be null to not load an attachment. */
newBoundingBoxAttachment (skin: Skin, name: string): BoundingBoxAttachment; 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 { TextureAtlasRegion } from "../TextureAtlas";
import { Color, NumberArrayLike, Utils } from "../Utils"; import { Color, NumberArrayLike, Utils } from "../Utils";
import { VertexAttachment, Attachment } from "./Attachment"; 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 /** 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. * 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. */ * 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; region: TextureRegion;
/** The name of the texture region for this attachment. */ /** The name of the texture region for this attachment. */
@ -70,15 +73,18 @@ export class MeshAttachment extends VertexAttachment {
edges: Array<number>; edges: Array<number>;
private parentMesh: MeshAttachment; private parentMesh: MeshAttachment;
sequence: Sequence;
tempColor = new Color(0, 0, 0, 0); tempColor = new Color(0, 0, 0, 0);
constructor (name: string) { constructor (name: string) {
super(name); super(name);
} }
/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region UVs or /** Calculates {@link #uvs} using the {@link #regionUVs} and region. Must be called if the region, the region's properties, or
* region. */ * the {@link #regionUVs} are changed. */
updateUVs () { updateRegion () {
let regionUVs = this.regionUVs; let regionUVs = this.regionUVs;
if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length); if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
let uvs = this.uvs; let uvs = this.uvs;
@ -175,6 +181,8 @@ export class MeshAttachment extends VertexAttachment {
Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length); Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length);
copy.hullLength = this.hullLength; copy.hullLength = this.hullLength;
copy.sequence = this.sequence.copy();
// Nonessential. // Nonessential.
if (this.edges) { if (this.edges) {
copy.edges = new Array<number>(this.edges.length); copy.edges = new Array<number>(this.edges.length);
@ -186,15 +194,20 @@ export class MeshAttachment extends VertexAttachment {
return copy; 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. **/ /** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/
newLinkedMesh (): MeshAttachment { newLinkedMesh (): MeshAttachment {
let copy = new MeshAttachment(this.name); let copy = new MeshAttachment(this.name);
copy.region = this.region; copy.region = this.region;
copy.path = this.path; copy.path = this.path;
copy.color.setFromColor(this.color); copy.color.setFromColor(this.color);
copy.deformAttachment = this.deformAttachment; copy.timelineAttahment = this.timelineAttahment;
copy.setParentMesh(this.parentMesh ? this.parentMesh : this); copy.setParentMesh(this.parentMesh ? this.parentMesh : this);
copy.updateUVs(); if (copy.region != null) copy.updateRegion();
return copy; return copy;
} }
} }

View File

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

View File

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

View File

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

View File

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