diff --git a/.gitignore b/.gitignore index 5baaffea6..0360782d4 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file +spine-ts/spine-threejs/dist +spine-libgdx/gradle +spine-libgdx/gradlew +spine-libgdx/gradlew.bat diff --git a/spine-ts/spine-canvas/src/SkeletonRenderer.ts b/spine-ts/spine-canvas/src/SkeletonRenderer.ts index c0282e7d9..c66eb2e40 100644 --- a/spine-ts/spine-canvas/src/SkeletonRenderer.ts +++ b/spine-ts/spine-canvas/src/SkeletonRenderer.ts @@ -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; diff --git a/spine-ts/spine-core/src/Animation.ts b/spine-ts/spine-core/src/Animation.ts index 5f52bd698..601c4b869 100644 --- a/spine-ts/spine-core/src/Animation.ts +++ b/spine-ts/spine-core/src/Animation.ts @@ -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) || (slotAttachment).deformAttachment != this.attachment) return; + if (!(slotAttachment instanceof VertexAttachment) || (slotAttachment).timelineAttahment != this.attachment) return; let deform: Array = 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 = 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 = 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 frameCount, 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, 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; + } +} \ No newline at end of file diff --git a/spine-ts/spine-core/src/AtlasAttachmentLoader.ts b/spine-ts/spine-core/src/AtlasAttachmentLoader.ts index 5c1018438..30973056a 100644 --- a/spine-ts/spine-core/src/AtlasAttachmentLoader.ts +++ b/spine-ts/spine-core/src/AtlasAttachmentLoader.ts @@ -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; } diff --git a/spine-ts/spine-core/src/Skeleton.ts b/spine-ts/spine-core/src/Skeleton.ts index 442063b8a..35ff4589c 100644 --- a/spine-ts/spine-core/src/Skeleton.ts +++ b/spine-ts/spine-core/src/Skeleton.ts @@ -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}. - *

- * 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); - (attachment).computeWorldVertices(slot.bone, vertices, 0, 2); + (attachment).computeWorldVertices(slot, vertices, 0, 2); } else if (attachment instanceof MeshAttachment) { let mesh = (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; - } } diff --git a/spine-ts/spine-core/src/SkeletonBinary.ts b/spine-ts/spine-core/src/SkeletonBinary.ts index 392068990..e81c88b8b 100644 --- a/spine-ts/spine-core/src/SkeletonBinary.ts +++ b/spine-ts/spine-core/src/SkeletonBinary.ts @@ -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; diff --git a/spine-ts/spine-core/src/SkeletonJson.ts b/spine-ts/spine-core/src/SkeletonJson.ts index 33c4943ba..ed05e8f97 100644 --- a/spine-ts/spine-core/src/SkeletonJson.ts +++ b/spine-ts/spine-core/src/SkeletonJson.ts @@ -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 ? parent : linkedMesh.mesh; + linkedMesh.mesh.timelineAttahment = linkedMesh.inheritTimeline ? parent : linkedMesh.mesh; linkedMesh.mesh.setParentMesh(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, getValue(map, "skin", null), slotIndex, parent, getValue(map, "deform", true))); + this.linkedMeshes.push(new LinkedMesh(mesh, 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 = 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; } } diff --git a/spine-ts/spine-core/src/Slot.ts b/spine-ts/spine-core/src/Slot.ts index 32391728c..f36e95d39 100644 --- a/spine-ts/spine-core/src/Slot.ts +++ b/spine-ts/spine-core/src/Slot.ts @@ -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) - || (attachment).deformAttachment != (this.attachment).deformAttachment) { + || (attachment).timelineAttahment != (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. */ diff --git a/spine-ts/spine-core/src/attachments/Attachment.ts b/spine-ts/spine-core/src/attachments/Attachment.ts index 23ff93ce4..eb69c09b5 100644 --- a/spine-ts/spine-core/src/attachments/Attachment.ts +++ b/spine-ts/spine-core/src/attachments/Attachment.ts @@ -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; } } diff --git a/spine-ts/spine-core/src/attachments/AttachmentLoader.ts b/spine-ts/spine-core/src/attachments/AttachmentLoader.ts index b92bc82e2..8166d807f 100644 --- a/spine-ts/spine-core/src/attachments/AttachmentLoader.ts +++ b/spine-ts/spine-core/src/attachments/AttachmentLoader.ts @@ -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; diff --git a/spine-ts/spine-core/src/attachments/HasTextureRegion.ts b/spine-ts/spine-core/src/attachments/HasTextureRegion.ts new file mode 100644 index 000000000..b8ca7d42a --- /dev/null +++ b/spine-ts/spine-core/src/attachments/HasTextureRegion.ts @@ -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; +} \ No newline at end of file diff --git a/spine-ts/spine-core/src/attachments/MeshAttachment.ts b/spine-ts/spine-core/src/attachments/MeshAttachment.ts index b67325766..0da79f8f5 100644 --- a/spine-ts/spine-core/src/attachments/MeshAttachment.ts +++ b/spine-ts/spine-core/src/attachments/MeshAttachment.ts @@ -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; 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(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; } } diff --git a/spine-ts/spine-core/src/attachments/RegionAttachment.ts b/spine-ts/spine-core/src/attachments/RegionAttachment.ts index 715f5f038..a447a30db 100644 --- a/spine-ts/spine-core/src/attachments/RegionAttachment.ts +++ b/spine-ts/spine-core/src/attachments/RegionAttachment.ts @@ -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 x,y 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. + *

+ * See World transforms 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 >= offset + 8. + * @param offset The worldVertices index to begin writing values. + * @param stride The number of worldVertices 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; } diff --git a/spine-ts/spine-core/src/attachments/Sequence.ts b/spine-ts/spine-core/src/attachments/Sequence.ts new file mode 100644 index 000000000..33e359b9b --- /dev/null +++ b/spine-ts/spine-core/src/attachments/Sequence.ts @@ -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(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 +]; \ No newline at end of file diff --git a/spine-ts/spine-threejs/src/SkeletonMesh.ts b/spine-ts/spine-threejs/src/SkeletonMesh.ts index 3135001f4..1716e83cf 100644 --- a/spine-ts/spine-threejs/src/SkeletonMesh.ts +++ b/spine-ts/spine-threejs/src/SkeletonMesh.ts @@ -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 = (region.region.renderObject).page.texture; diff --git a/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts b/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts index e1b20bcec..bcce09637 100644 --- a/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts +++ b/spine-ts/spine-webgl/src/SkeletonDebugRenderer.ts @@ -92,7 +92,7 @@ export class SkeletonDebugRenderer implements Disposable { if (attachment instanceof RegionAttachment) { let 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]); diff --git a/spine-ts/spine-webgl/src/SkeletonRenderer.ts b/spine-ts/spine-webgl/src/SkeletonRenderer.ts index 0065f8763..d8c2be925 100644 --- a/spine-ts/spine-webgl/src/SkeletonRenderer.ts +++ b/spine-ts/spine-webgl/src/SkeletonRenderer.ts @@ -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 = (region.region.renderObject).page.texture; diff --git a/spine-ts/spine-webgl/tests/test-drawcalls.html b/spine-ts/spine-webgl/tests/test-drawcalls.html index 5b66ee5a8..d51dc4289 100644 --- a/spine-ts/spine-webgl/tests/test-drawcalls.html +++ b/spine-ts/spine-webgl/tests/test-drawcalls.html @@ -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() });