From 6e938b32ea5f2c9ac440bf6f818d75104593f931 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sun, 26 Sep 2021 21:35:07 -1000 Subject: [PATCH 01/10] Rewrote sequence attachment as a sequence property on region and mesh attachments. --- .../spine/AnimationStateTests.java | 12 +- .../esotericsoftware/spine/BonePlotting.java | 11 +- .../esotericsoftware/spine/Box2DExample.java | 4 +- .../com/esotericsoftware/spine/MixTest.java | 1 - .../esotericsoftware/spine/NormalMapTest.java | 1 - .../spine/VertexEffectTest.java | 1 - .../com/esotericsoftware/spine/Animation.java | 99 +++++++++++- .../com/esotericsoftware/spine/Skeleton.java | 23 +-- .../spine/SkeletonBinary.java | 42 ++--- .../esotericsoftware/spine/SkeletonJson.java | 35 ++-- .../spine/SkeletonRenderer.java | 6 +- .../spine/SkeletonRendererDebug.java | 2 +- .../src/com/esotericsoftware/spine/Slot.java | 30 ++-- .../attachments/AtlasAttachmentLoader.java | 59 ++++--- .../spine/attachments/Attachment.java | 7 +- .../spine/attachments/AttachmentLoader.java | 7 +- .../attachments/BoundingBoxAttachment.java | 13 +- .../spine/attachments/ClippingAttachment.java | 15 +- .../spine/attachments/HasTextureRegion.java | 17 +- .../spine/attachments/MeshAttachment.java | 84 ++++++---- .../spine/attachments/PathAttachment.java | 23 +-- .../spine/attachments/PointAttachment.java | 18 ++- .../spine/attachments/RegionAttachment.java | 82 ++++++---- .../spine/attachments/Sequence.java | 124 ++++++++++++++ .../spine/attachments/SequenceAttachment.java | 151 ------------------ .../spine/attachments/SkeletonAttachment.java | 15 +- .../spine/attachments/VertexAttachment.java | 62 +++---- .../spine/SkeletonViewer.java | 1 - 28 files changed, 514 insertions(+), 431 deletions(-) create mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java delete mode 100644 spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SequenceAttachment.java diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java index e74745f43..4df486d51 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTests.java @@ -36,6 +36,7 @@ import com.badlogic.gdx.Files.FileType; import com.badlogic.gdx.backends.lwjgl.LwjglFileHandle; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Pool; import com.esotericsoftware.spine.AnimationState.AnimationStateListener; @@ -47,19 +48,15 @@ import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PointAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.SequenceAttachment; +import com.esotericsoftware.spine.attachments.Sequence; public class AnimationStateTests { final SkeletonJson json = new SkeletonJson(new AttachmentLoader() { - public RegionAttachment newRegionAttachment (Skin skin, String name, String path) { + public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) { return null; } - public MeshAttachment newMeshAttachment (Skin skin, String name, String path) { - return null; - } - - public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) { + public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) { return null; } @@ -893,7 +890,6 @@ public class AnimationStateTests { state.apply(skeleton); while (time < endTime) { time += incr; - skeleton.update(incr); state.update(incr); // Reduce float discrepancies for tests. diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java index c5a1e93f7..e492bbf94 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java @@ -30,6 +30,7 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.Animation.MixDirection; @@ -40,21 +41,17 @@ import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PointAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.SequenceAttachment; +import com.esotericsoftware.spine.attachments.Sequence; public class BonePlotting { static public void main (String[] args) throws Exception { // This example shows how to load skeleton data and plot a bone transform for each animation. SkeletonJson json = new SkeletonJson(new AttachmentLoader() { - public RegionAttachment newRegionAttachment (Skin skin, String name, String path) { + public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) { return null; } - public MeshAttachment newMeshAttachment (Skin skin, String name, String path) { - return null; - } - - public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) { + public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) { return null; } diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java index a5d81f1dc..a32408a2a 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java @@ -49,12 +49,14 @@ import com.badlogic.gdx.physics.box2d.FixtureDef; import com.badlogic.gdx.physics.box2d.PolygonShape; import com.badlogic.gdx.physics.box2d.World; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.ScreenUtils; import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.RegionAttachment; +import com.esotericsoftware.spine.attachments.Sequence; public class Box2DExample extends ApplicationAdapter { SpriteBatch batch; @@ -85,7 +87,7 @@ public class Box2DExample extends ApplicationAdapter { // This loader creates Box2dAttachments instead of RegionAttachments for an easy way to keep // track of the Box2D body for each attachment. AtlasAttachmentLoader atlasLoader = new AtlasAttachmentLoader(atlas) { - public RegionAttachment newRegionAttachment (Skin skin, String name, String path) { + public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) { Box2dAttachment attachment = new Box2dAttachment(name); AtlasRegion region = atlas.findRegion(attachment.getName()); if (region == null) throw new RuntimeException("Region not found in atlas: " + attachment); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/MixTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/MixTest.java index 6f39a6eae..cee706c5a 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/MixTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/MixTest.java @@ -125,7 +125,6 @@ public class MixTest extends ApplicationAdapter { } skeleton.updateWorldTransform(); - skeleton.update(Gdx.graphics.getDeltaTime()); batch.begin(); renderer.draw(batch, skeleton); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java index 999cb130f..900018b67 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java @@ -134,7 +134,6 @@ public class NormalMapTest extends ApplicationAdapter { time += Gdx.graphics.getDeltaTime(); if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, MixBlend.first, MixDirection.in); skeleton.updateWorldTransform(); - skeleton.update(Gdx.graphics.getDeltaTime()); lightPosition.x = Gdx.input.getX(); lightPosition.y = (Gdx.graphics.getHeight() - 1 - Gdx.input.getY()); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/VertexEffectTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/VertexEffectTest.java index e759a5ae0..4fc136f75 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/VertexEffectTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/VertexEffectTest.java @@ -85,7 +85,6 @@ public class VertexEffectTest extends ApplicationAdapter { public void render () { // Update the skeleton and animation time. float delta = Gdx.graphics.getDeltaTime(); - skeleton.update(delta); state.update(delta); swirlTime += delta; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index 3ba5122b9..028debe4f 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -40,6 +40,9 @@ import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.ObjectSet; import com.esotericsoftware.spine.attachments.Attachment; +import com.esotericsoftware.spine.attachments.HasTextureRegion; +import com.esotericsoftware.spine.attachments.Sequence; +import com.esotericsoftware.spine.attachments.Sequence.SequenceMode; import com.esotericsoftware.spine.attachments.VertexAttachment; /** Stores a list of timelines to animate a skeleton's pose over time. */ @@ -175,7 +178,8 @@ public class Animation { attachment, deform, // event, drawOrder, // ikConstraint, transformConstraint, // - pathConstraintPosition, pathConstraintSpacing, pathConstraintMix + pathConstraintPosition, pathConstraintSpacing, pathConstraintMix, // + sequence } /** The base class for all timelines. */ @@ -1646,7 +1650,7 @@ public class Animation { /** The attachment that will be deformed. *

- * See {@link VertexAttachment#getDeformAttachment()}. */ + * See {@link VertexAttachment#getTimelineAttachment()}. */ public VertexAttachment getAttachment () { return attachment; } @@ -1724,9 +1728,9 @@ public class Animation { if (!slot.bone.active) return; Attachment slotAttachment = slot.attachment; if (!(slotAttachment instanceof VertexAttachment) - || ((VertexAttachment)slotAttachment).getDeformAttachment() != attachment) return; + || ((VertexAttachment)slotAttachment).getTimelineAttachment() != attachment) return; - FloatArray deformArray = slot.getDeform(); + FloatArray deformArray = slot.deform; if (deformArray.size == 0) blend = setup; float[][] vertices = this.vertices; @@ -1734,7 +1738,6 @@ public class Animation { float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; switch (blend) { case setup: deformArray.clear(); @@ -1745,6 +1748,7 @@ public class Animation { return; } float[] deform = deformArray.setSize(vertexCount); + VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; if (vertexAttachment.getBones() == null) { // Unweighted vertex positions. float[] setupVertices = vertexAttachment.getVertices(); @@ -2419,4 +2423,89 @@ public class Animation { } } } + + /** Changes a slot's {@link Slot#getSequenceIndex()} for an attachment's {@link Sequence}. */ + static public class SequenceTimeline extends Timeline implements SlotTimeline { + static public final int ENTRIES = 3; + static private final int MODE = 1, FRAME_TIME = 2; + + final int slotIndex; + final T attachment; + + public SequenceTimeline (int frameCount, int slotIndex, T attachment) { + super(frameCount, Property.sequence.ordinal() + "|" + slotIndex + "|" + attachment.getSequence().getId()); + this.slotIndex = slotIndex; + this.attachment = attachment; + } + + public int getFrameEntries () { + return ENTRIES; + } + + public int getSlotIndex () { + return slotIndex; + } + + public T getAttachment () { + return attachment; + } + + /** Sets the time, mode, index, and frame time for the specified frame. + * @param frame Between 0 and frameCount, inclusive. + * @param time The frame time in seconds. */ + public void setFrame (int frame, float time, SequenceMode mode, int index, float frameTime) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + MODE] = mode.ordinal() | (index << 4); + frames[frame + FRAME_TIME] = frameTime; + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, + MixDirection direction) { + + Slot slot = skeleton.slots.get(slotIndex); + if (!slot.bone.active) return; + Attachment slotAttachment = slot.attachment; + if (slotAttachment != attachment) { + if (!(slotAttachment instanceof VertexAttachment) + || ((VertexAttachment)slotAttachment).getTimelineAttachment() != attachment) return; + } + + float[] frames = this.frames; + if (time < frames[0]) { // Time is before first frame. + if (blend == setup || blend == first) slot.setSequenceIndex(-1); + return; + } + + int i = search(frames, time, ENTRIES); + float before = frames[i]; + int modeAndIndex = (int)frames[i + MODE]; + float frameTime = frames[i + FRAME_TIME]; + + int index = modeAndIndex >> 4, count = attachment.getSequence().getRegions().length; + SequenceMode mode = SequenceMode.values[modeAndIndex & 0xf]; + if (mode != SequenceMode.stop) { + index += (time - before) / frameTime; + switch (mode) { + case play: + index = Math.min(count - 1, index); + break; + case loop: + index %= count; + break; + case pingpong: + int n = (count << 1) - 2; + index %= n; + if (index >= count) index = n - index; + break; + case playReverse: + index = Math.max(count - 1 - index, 0); + break; + case loopReverse: + index = count - 1 - (index % count); + } + } + slot.setSequenceIndex(index); + } + } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java index b743170ee..8d2154229 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -42,7 +42,6 @@ import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.SequenceAttachment; /** Stores the current pose for a skeleton. *

@@ -60,7 +59,6 @@ public class Skeleton { final Array updateCache = new Array(); @Null Skin skin; final Color color; - float time; float scaleX = 1, scaleY = 1; float x, y; @@ -158,7 +156,6 @@ public class Skeleton { skin = skeleton.skin; color = new Color(skeleton.color); - time = skeleton.time; scaleX = skeleton.scaleX; scaleY = skeleton.scaleY; @@ -722,11 +719,11 @@ public class Skeleton { int verticesLength = 0; float[] vertices = null; Attachment attachment = slot.attachment; - if (attachment instanceof SequenceAttachment) attachment = ((SequenceAttachment)attachment).updateAttachment(slot); if (attachment instanceof RegionAttachment) { + RegionAttachment region = (RegionAttachment)attachment; verticesLength = 8; vertices = temp.setSize(8); - ((RegionAttachment)attachment).computeWorldVertices(slot.getBone(), vertices, 0, 2); + region.computeWorldVertices(slot, vertices, 0, 2); } else if (attachment instanceof MeshAttachment) { MeshAttachment mesh = (MeshAttachment)attachment; verticesLength = mesh.getWorldVerticesLength(); @@ -812,22 +809,6 @@ public class Skeleton { this.y = y; } - /** Returns the skeleton's time. This can be used for tracking, such as with Slot {@link Slot#getAttachmentTime()}. - *

- * See {@link #update(float)}. */ - public float getTime () { - return time; - } - - public void setTime (float time) { - this.time = time; - } - - /** Increments the skeleton's {@link #time}. */ - public void update (float delta) { - time += delta; - } - public String toString () { return data.name != null ? data.name : super.toString(); } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index 050805e1f..9d12696e7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -85,9 +85,6 @@ import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PointAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.SequenceAttachment; -import com.esotericsoftware.spine.attachments.SequenceAttachment.SequenceMode; -import com.esotericsoftware.spine.attachments.HasTextureRegion; import com.esotericsoftware.spine.attachments.VertexAttachment; /** Loads skeleton data in the Spine binary format. @@ -303,7 +300,7 @@ public class SkeletonBinary extends SkeletonLoader { if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin); Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh); + linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh); linkedMesh.mesh.setParentMesh((MeshAttachment)parent); linkedMesh.mesh.updateRegion(); } @@ -399,8 +396,10 @@ public class SkeletonBinary extends SkeletonLoader { float height = input.readFloat(); int color = input.readInt(); + // BOZO! - Read sequence. + if (path == null) path = name; - RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path); + RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, null); if (region == null) return null; region.setPath(path); region.setX(x * scale); @@ -443,8 +442,10 @@ public class SkeletonBinary extends SkeletonLoader { height = input.readFloat(); } + // BOZO! - Read sequence. + if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path); + MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, null); if (mesh == null) return null; mesh.setPath(path); Color.rgba8888ToColor(mesh.getColor(), color); @@ -467,15 +468,17 @@ public class SkeletonBinary extends SkeletonLoader { int color = input.readInt(); String skinName = input.readStringRef(); String parent = input.readStringRef(); - boolean inheritDeform = input.readBoolean(); + boolean inheritTimelines = input.readBoolean(); float width = 0, height = 0; if (nonessential) { width = input.readFloat(); height = input.readFloat(); } + // BOZO! - Read sequence. + if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path); + MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, null); if (mesh == null) return null; mesh.setPath(path); Color.rgba8888ToColor(mesh.getColor(), color); @@ -483,7 +486,7 @@ public class SkeletonBinary extends SkeletonLoader { mesh.setWidth(width * scale); mesh.setHeight(height * scale); } - linkedMeshes.add(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform)); + linkedMeshes.add(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritTimelines)); return mesh; } case path: { @@ -521,7 +524,7 @@ public class SkeletonBinary extends SkeletonLoader { if (nonessential) Color.rgba8888ToColor(point.getColor(), color); return point; } - case clipping: { + case clipping: int endSlotIndex = input.readInt(true); int vertexCount = input.readInt(true); Vertices vertices = readVertices(input, vertexCount); @@ -536,25 +539,6 @@ public class SkeletonBinary extends SkeletonLoader { if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); return clip; } - case sequence: - Attachment attachment = readAttachment(input, skeletonData, skin, slotIndex, attachmentName, nonessential); - int frameCount = input.readInt(true); - float frameTime = input.readFloat(); - SequenceMode mode = SequenceMode.values[input.readInt(true)]; - - if (attachment == null) return null; - String path = ((HasTextureRegion)attachment).getPath(); - - SequenceAttachment sequence = attachmentLoader.newSequenceAttachment(skin, name, path, frameCount); - if (sequence == null) return null; - - sequence.setAttachment(attachment); - sequence.setPath(path); - sequence.setFrameCount(frameCount); - sequence.setFrameTime(frameTime); - sequence.setMode(mode); - return sequence; - } return null; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index d32534b13..66ce3bbfe 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -84,9 +84,6 @@ import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PointAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; -import com.esotericsoftware.spine.attachments.SequenceAttachment; -import com.esotericsoftware.spine.attachments.SequenceAttachment.SequenceMode; -import com.esotericsoftware.spine.attachments.HasTextureRegion; import com.esotericsoftware.spine.attachments.VertexAttachment; /** Loads skeleton data in the Spine JSON format. @@ -324,7 +321,7 @@ public class SkeletonJson extends SkeletonLoader { if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin); Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh); + linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh); linkedMesh.mesh.setParentMesh((MeshAttachment)parent); linkedMesh.mesh.updateRegion(); } @@ -369,7 +366,8 @@ public class SkeletonJson extends SkeletonLoader { switch (AttachmentType.valueOf(map.getString("type", AttachmentType.region.name()))) { case region: { String path = map.getString("path", name); - RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path); + // BOZO! - Read sequence. + RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, null); if (region == null) return null; region.setPath(path); region.setX(map.getFloat("x", 0) * scale); @@ -398,7 +396,8 @@ public class SkeletonJson extends SkeletonLoader { case mesh: case linkedmesh: { String path = map.getString("path", name); - MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path); + // BOZO! - Read sequence. + MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, null); if (mesh == null) return null; mesh.setPath(path); @@ -411,7 +410,7 @@ public class SkeletonJson extends SkeletonLoader { String parent = map.getString("parent", null); if (parent != null) { linkedMeshes - .add(new LinkedMesh(mesh, map.getString("skin", null), slotIndex, parent, map.getBoolean("deform", true))); + .add(new LinkedMesh(mesh, map.getString("skin", null), slotIndex, parent, map.getBoolean("timelines", true))); return mesh; } @@ -455,7 +454,7 @@ public class SkeletonJson extends SkeletonLoader { if (color != null) Color.valueOf(color, point.getColor()); return point; } - case clipping: { + case clipping: ClippingAttachment clip = attachmentLoader.newClippingAttachment(skin, name); if (clip == null) return null; @@ -472,20 +471,6 @@ public class SkeletonJson extends SkeletonLoader { if (color != null) Color.valueOf(color, clip.getColor()); return clip; } - case sequence: - Attachment attachment = readAttachment(map.getChild("attachment"), skin, slotIndex, name, skeletonData); - if (attachment == null) return null; - String path = ((HasTextureRegion)attachment).getPath(); - int frameCount = map.getInt("count"); - SequenceAttachment sequence = attachmentLoader.newSequenceAttachment(skin, name, path, frameCount); - if (sequence == null) return null; - sequence.setAttachment(attachment); - sequence.setPath(path); - sequence.setFrameCount(frameCount); - sequence.setFrameTime(map.getInt("time")); - sequence.setMode(SequenceMode.valueOf(map.getString("mode", SequenceMode.forward.name()))); - return sequence; - } return null; } @@ -1068,14 +1053,14 @@ public class SkeletonJson extends SkeletonLoader { String parent, skin; int slotIndex; MeshAttachment mesh; - boolean inheritDeform; + boolean inheritTimelines; - public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent, boolean inheritDeform) { + public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent, boolean inheritTimelines) { this.mesh = mesh; this.skin = skin; this.slotIndex = slotIndex; this.parent = parent; - this.inheritDeform = inheritDeform; + this.inheritTimelines = inheritTimelines; } } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index a93f7e333..4f66bef09 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -98,7 +98,7 @@ public class SkeletonRenderer { Attachment attachment = slot.attachment; if (attachment instanceof RegionAttachment) { RegionAttachment region = (RegionAttachment)attachment; - region.computeWorldVertices(slot.getBone(), vertices, 0, 5); + region.computeWorldVertices(slot, vertices, 0, 5); Color color = region.getColor(), slotColor = slot.getColor(); float alpha = a * slotColor.a * color.a * 255; float multiplier = pmaColors ? alpha : 255; @@ -183,7 +183,7 @@ public class SkeletonRenderer { RegionAttachment region = (RegionAttachment)attachment; verticesLength = vertexSize << 2; vertices = this.vertices.items; - region.computeWorldVertices(slot.getBone(), vertices, 0, vertexSize); + region.computeWorldVertices(slot, vertices, 0, vertexSize); triangles = quadTriangles; texture = region.getRegion().getTexture(); uvs = region.getUVs(); @@ -309,7 +309,7 @@ public class SkeletonRenderer { RegionAttachment region = (RegionAttachment)attachment; verticesLength = vertexSize << 2; vertices = this.vertices.items; - region.computeWorldVertices(slot.getBone(), vertices, 0, vertexSize); + region.computeWorldVertices(slot, vertices, 0, vertexSize); triangles = quadTriangles; texture = region.getRegion().getTexture(); uvs = region.getUVs(); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java index be2a67da5..f50e89c02 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java @@ -127,7 +127,7 @@ public class SkeletonRendererDebug { if (attachment instanceof RegionAttachment) { RegionAttachment region = (RegionAttachment)attachment; float[] vertices = this.vertices.items; - region.computeWorldVertices(slot.getBone(), vertices, 0, 2); + region.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-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java index 2dd1fb348..13c75c2fd 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java @@ -35,6 +35,7 @@ import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.Animation.DeformTimeline; import com.esotericsoftware.spine.attachments.Attachment; +import com.esotericsoftware.spine.attachments.Sequence; import com.esotericsoftware.spine.attachments.VertexAttachment; /** Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store @@ -46,8 +47,8 @@ public class Slot { final Color color = new Color(); @Null final Color darkColor; @Null Attachment attachment; - private float attachmentTime; - private FloatArray deform = new FloatArray(); + int sequenceIndex; + FloatArray deform = new FloatArray(); int attachmentState; @@ -69,7 +70,7 @@ public class Slot { color.set(slot.color); darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); attachment = slot.attachment; - attachmentTime = slot.attachmentTime; + sequenceIndex = slot.sequenceIndex; deform.addAll(slot.deform); } @@ -105,27 +106,28 @@ public class Slot { return 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. */ + /** 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. */ public void setAttachment (@Null Attachment attachment) { if (this.attachment == attachment) return; if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment) - || ((VertexAttachment)attachment).getDeformAttachment() != ((VertexAttachment)this.attachment).getDeformAttachment()) { + || ((VertexAttachment)attachment).getTimelineAttachment() != ((VertexAttachment)this.attachment) + .getTimelineAttachment()) { deform.clear(); } this.attachment = attachment; - attachmentTime = bone.skeleton.time; + sequenceIndex = -1; } - /** The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton - * {@link Skeleton#time}. */ - public float getAttachmentTime () { - return bone.skeleton.time - attachmentTime; + /** The index of the texture region to display when the slot's attachment has a {@link Sequence}. -1 represents the + * {@link Sequence#getSetupIndex()}. */ + public int getSequenceIndex () { + return sequenceIndex; } - public void setAttachmentTime (float time) { - attachmentTime = bone.skeleton.time - time; + public void setSequenceIndex (int sequenceIndex) { + this.sequenceIndex = sequenceIndex; } /** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java index e5ba26041..384baabd4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java @@ -31,6 +31,8 @@ package com.esotericsoftware.spine.attachments; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.Skin; @@ -47,32 +49,39 @@ public class AtlasAttachmentLoader implements AttachmentLoader { this.atlas = atlas; } - public RegionAttachment newRegionAttachment (Skin skin, String name, String path) { - AtlasRegion region = atlas.findRegion(path); - if (region == null) throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")"); - RegionAttachment attachment = new RegionAttachment(name); - attachment.setRegion(region); - return attachment; - } - - public MeshAttachment newMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = atlas.findRegion(path); - if (region == null) throw new RuntimeException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); - MeshAttachment attachment = new MeshAttachment(name); - attachment.setRegion(region); - return attachment; - } - - public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) { - AtlasRegion[] regions = new AtlasRegion[frameCount]; - for (int i = 0; i < frameCount; i++) { - AtlasRegion region = atlas.findRegion(path + frameCount); // BOZO - Zero pad? - if (region == null) - throw new RuntimeException("Region not found in atlas: " + path + frameCount + " (sequence: " + name + ")"); + private void loadSequence (String name, String basePath, Sequence sequence) { + TextureRegion[] regions = sequence.getRegions(); + for (int i = 0, n = regions.length; i < n; i++) { + String path = sequence.getPath(basePath, i); + regions[i] = atlas.findRegion(path); + if (regions[i] == null) throw new RuntimeException("Region not found in atlas: " + path + " (sequence: " + name + ")"); } - SequenceAttachment sequence = new SequenceAttachment(name); - sequence.setRegions(regions); - return sequence; + } + + public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) { + RegionAttachment attachment = new RegionAttachment(name); + if (sequence != null) + loadSequence(name, path, sequence); + else { + AtlasRegion region = atlas.findRegion(path); + if (region == null) + throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")"); + attachment.setRegion(region); + } + return attachment; + } + + public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) { + MeshAttachment attachment = new MeshAttachment(name); + if (sequence != null) + loadSequence(name, path, sequence); + else { + AtlasRegion region = atlas.findRegion(path); + if (region == null) + throw new RuntimeException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); + attachment.setRegion(region); + } + return attachment; } public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Attachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Attachment.java index 0ecc20f47..b76fc6bbc 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Attachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Attachment.java @@ -31,13 +31,18 @@ package com.esotericsoftware.spine.attachments; /** The base class for all attachments. */ abstract public class Attachment { - String name; + final String name; public Attachment (String name) { if (name == null) throw new IllegalArgumentException("name cannot be null."); this.name = name; } + /** Copy constructor. */ + protected Attachment (Attachment other) { + name = other.name; + } + /** The attachment's name. */ public String getName () { return name; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java index 47084c130..cbe0bd55e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java @@ -39,13 +39,10 @@ import com.esotericsoftware.spine.Skin; * Runtimes Guide. */ public interface AttachmentLoader { /** @return May be null to not load the attachment. */ - public @Null RegionAttachment newRegionAttachment (Skin skin, String name, String path); + public @Null RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence); /** @return May be null to not load the attachment. In that case null should also be returned for child meshes. */ - public @Null MeshAttachment newMeshAttachment (Skin skin, String name, String path); - - /** @return May be null to not load the attachment. */ - public @Null SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount); + public @Null MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence); /** @return May be null to not load the attachment. */ public @Null BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java index 7bb2a1ba8..99346857b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java @@ -46,16 +46,19 @@ public class BoundingBoxAttachment extends VertexAttachment { super(name); } + /** Copy constructor. */ + protected BoundingBoxAttachment (BoundingBoxAttachment other) { + super(other); + color.set(other.color); + } + /** The color of the bounding box as it was in Spine, or a default color if nonessential data was not exported. Bounding boxes * are not usually rendered at runtime. */ public Color getColor () { return color; } - public Attachment copy () { - BoundingBoxAttachment copy = new BoundingBoxAttachment(name); - copyTo(copy); - copy.color.set(color); - return copy; + public BoundingBoxAttachment copy () { + return new BoundingBoxAttachment(name); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/ClippingAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/ClippingAttachment.java index cd804cfc8..3b2825a80 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/ClippingAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/ClippingAttachment.java @@ -45,6 +45,13 @@ public class ClippingAttachment extends VertexAttachment { super(name); } + /** Copy constructor. */ + protected ClippingAttachment (ClippingAttachment other) { + super(other); + endSlot = other.endSlot; + color.set(other.color); + } + /** Clipping is performed between the clipping attachment's slot and the end slot. If null clipping is done until the end of * the skeleton's rendering. */ public @Null SlotData getEndSlot () { @@ -61,11 +68,7 @@ public class ClippingAttachment extends VertexAttachment { return color; } - public Attachment copy () { - ClippingAttachment copy = new ClippingAttachment(name); - copyTo(copy); - copy.endSlot = endSlot; - copy.color.set(color); - return copy; + public ClippingAttachment copy () { + return new ClippingAttachment(name); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/HasTextureRegion.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/HasTextureRegion.java index 32247e169..6c37b6f48 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/HasTextureRegion.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/HasTextureRegion.java @@ -3,6 +3,7 @@ package com.esotericsoftware.spine.attachments; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Null; public interface HasTextureRegion { /** The name used to find the {@link #getRegion()}. */ @@ -10,16 +11,20 @@ public interface HasTextureRegion { public void setPath (String path); - /** Sets the region used to draw the attachment. If the region or its properties are changed, {@link #updateRegion()} must be - * called. */ - public void setRegion (TextureRegion region); - public TextureRegion getRegion (); - /** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after changing the region or - * the region's properties. */ + /** Sets the region used to draw the attachment. After setting the region or if the region's properties are changed, + * {@link #updateRegion()} must be called. */ + public void setRegion (TextureRegion region); + + /** 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. */ public void updateRegion (); /** The color to tint the attachment. */ public Color getColor (); + + public @Null Sequence getSequence (); + + public void setSequence (@Null Sequence sequence); } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java index a82a2a63d..c1eae3fc0 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java @@ -36,6 +36,8 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.Null; +import com.esotericsoftware.spine.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. *

@@ -48,6 +50,7 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion private final Color color = new Color(1, 1, 1, 1); private int hullLength; private @Null MeshAttachment parentMesh; + private @Null Sequence sequence; // Nonessential. private @Null short[] edges; @@ -57,6 +60,36 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion super(name); } + /** Copy constructor. Use {@link #newLinkedMesh()} if the other mesh is a linked mesh. */ + protected MeshAttachment (MeshAttachment other) { + super(other); + + if (parentMesh != null) throw new IllegalArgumentException("Use newLinkedMesh to copy a linked mesh."); + + region = other.region; + path = other.path; + color.set(other.color); + + regionUVs = new float[other.regionUVs.length]; + arraycopy(other.regionUVs, 0, regionUVs, 0, regionUVs.length); + + uvs = new float[other.uvs.length]; + arraycopy(other.uvs, 0, uvs, 0, uvs.length); + + triangles = new short[other.triangles.length]; + arraycopy(other.triangles, 0, triangles, 0, triangles.length); + + hullLength = other.hullLength; + + // Nonessential. + if (other.edges != null) { + edges = new short[other.edges.length]; + arraycopy(other.edges, 0, edges, 0, edges.length); + } + width = other.width; + height = other.height; + } + public void setRegion (TextureRegion region) { if (region == null) throw new IllegalArgumentException("region cannot be null."); this.region = region; @@ -67,8 +100,8 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion return region; } - /** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region or the - * region's properties. */ + /** Calculates {@link #uvs} using the {@link #regionUVs} and {@link #region}. Must be called if the {@link #region}, + * {@link #regionUVs}, or the region's properties are changed. */ public void updateRegion () { float[] regionUVs = this.regionUVs; if (this.uvs == null || this.uvs.length != regionUVs.length) this.uvs = new float[regionUVs.length]; @@ -131,6 +164,12 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion } } + /** If the attachment has a {@link #sequence}, the {@link #region} may be changed. */ + public void computeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride) { + if (sequence != null) sequence.apply(slot, this); + super.computeWorldVertices(slot, start, count, worldVertices, offset, stride); + } + /** Triplets of vertex indices which describe the mesh's triangulation. */ public short[] getTriangles () { return triangles; @@ -210,6 +249,14 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion this.height = height; } + public @Null Sequence getSequence () { + return sequence; + } + + public void setSequence (@Null Sequence sequence) { + this.sequence = sequence; + } + /** The parent mesh if this is a linked mesh, else null. A linked mesh shares the {@link #bones}, {@link #vertices}, * {@link #regionUVs}, {@link #triangles}, {@link #hullLength}, {@link #edges}, {@link #width}, and {@link #height} with the * parent mesh, but may have a different {@link #name} or {@link #path} (and therefore a different texture). */ @@ -232,42 +279,19 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion } } - public Attachment copy () { - if (parentMesh != null) return newLinkedMesh(); - - MeshAttachment copy = new MeshAttachment(name); - copy.region = region; - copy.path = path; - copy.color.set(color); - - copyTo(copy); - copy.regionUVs = new float[regionUVs.length]; - arraycopy(regionUVs, 0, copy.regionUVs, 0, regionUVs.length); - copy.uvs = new float[uvs.length]; - arraycopy(uvs, 0, copy.uvs, 0, uvs.length); - copy.triangles = new short[triangles.length]; - arraycopy(triangles, 0, copy.triangles, 0, triangles.length); - copy.hullLength = hullLength; - - // Nonessential. - if (edges != null) { - copy.edges = new short[edges.length]; - arraycopy(edges, 0, copy.edges, 0, edges.length); - } - copy.width = width; - copy.height = height; - return copy; - } - /** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. */ public MeshAttachment newLinkedMesh () { MeshAttachment mesh = new MeshAttachment(name); + mesh.timelineAttachment = timelineAttachment; mesh.region = region; mesh.path = path; mesh.color.set(color); - mesh.deformAttachment = deformAttachment; mesh.setParentMesh(parentMesh != null ? parentMesh : this); mesh.updateRegion(); return mesh; } + + public MeshAttachment copy () { + return parentMesh != null ? newLinkedMesh() : new MeshAttachment(this); + } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java index fdd622399..2f75df7ba 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PathAttachment.java @@ -49,6 +49,18 @@ public class PathAttachment extends VertexAttachment { super(name); } + /** Copy constructor. */ + protected PathAttachment (PathAttachment other) { + super(other); + + lengths = new float[other.lengths.length]; + arraycopy(other.lengths, 0, lengths, 0, lengths.length); + + closed = other.closed; + constantSpeed = other.constantSpeed; + color.set(other.color); + } + /** If true, the start and end knots are connected. */ public boolean getClosed () { return closed; @@ -83,14 +95,7 @@ public class PathAttachment extends VertexAttachment { return color; } - public Attachment copy () { - PathAttachment copy = new PathAttachment(name); - copyTo(copy); - copy.lengths = new float[lengths.length]; - arraycopy(lengths, 0, copy.lengths, 0, lengths.length); - copy.closed = closed; - copy.constantSpeed = constantSpeed; - copy.color.set(color); - return copy; + public PathAttachment copy () { + return new PathAttachment(this); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PointAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PointAttachment.java index b9b26a107..4a3e5a14a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PointAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PointAttachment.java @@ -51,6 +51,15 @@ public class PointAttachment extends Attachment { super(name); } + /** Copy constructor. */ + protected PointAttachment (PointAttachment other) { + super(other); + x = other.x; + y = other.y; + rotation = other.rotation; + color.set(other.color); + } + public float getX () { return x; } @@ -94,12 +103,7 @@ public class PointAttachment extends Attachment { return (float)Math.atan2(y, x) * radDeg; } - public Attachment copy () { - PointAttachment copy = new PointAttachment(name); - copy.x = x; - copy.y = y; - copy.rotation = rotation; - copy.color.set(color); - return copy; + public PointAttachment copy () { + return new PointAttachment(this); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java index 0f0a3e138..0301cdc1d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java @@ -34,21 +34,19 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.Bone; +import com.esotericsoftware.spine.Slot; /** An attachment that displays a textured quadrilateral. *

* See Region attachments in the Spine User Guide. */ public class RegionAttachment extends Attachment implements HasTextureRegion { - static public final int BLX = 0; - static public final int BLY = 1; - static public final int ULX = 2; - static public final int ULY = 3; - static public final int URX = 4; - static public final int URY = 5; - static public final int BRX = 6; - static public final int BRY = 7; + static public final int BLX = 0, BLY = 1; + static public final int ULX = 2, ULY = 3; + static public final int URX = 4, URY = 5; + static public final int BRX = 6, BRY = 7; private TextureRegion region; private String path; @@ -56,13 +54,31 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { private final float[] uvs = new float[8]; private final float[] offset = new float[8]; private final Color color = new Color(1, 1, 1, 1); + private @Null Sequence sequence; public RegionAttachment (String name) { super(name); } - /** Calculates the {@link #offset} using the {@link #region}. Must be called after changing the region or the region's - * properties. */ + /** Copy constructor. */ + protected RegionAttachment (RegionAttachment other) { + super(other); + region = other.region; + path = other.path; + x = other.x; + y = other.y; + scaleX = other.scaleX; + scaleY = other.scaleY; + rotation = other.rotation; + width = other.width; + height = other.height; + arraycopy(other.uvs, 0, uvs, 0, 8); + arraycopy(other.offset, 0, offset, 0, 8); + color.set(other.color); + } + + /** Calculates the {@link #offset} and {@link #uvs} using the {@link #region} and the attachment's transform. Must be called if + * the {@link #region}, transform, or the region's properties are changed. */ public void updateRegion () { float width = getWidth(); float height = getHeight(); @@ -70,11 +86,13 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { float localY2 = height / 2; float localX = -localX2; float localY = -localY2; + boolean rotated = false; if (region instanceof AtlasRegion) { AtlasRegion region = (AtlasRegion)this.region; localX += region.offsetX / region.originalWidth * width; localY += region.offsetY / region.originalHeight * height; if (region.degrees == 90) { + rotated = true; localX2 -= (region.originalWidth - region.offsetX - region.packedHeight) / region.originalWidth * width; localY2 -= (region.originalHeight - region.offsetY - region.packedWidth) / region.originalHeight * height; } else { @@ -110,13 +128,9 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { offset[URY] = localY2Cos + localX2Sin; offset[BRX] = localX2Cos - localYSin; offset[BRY] = localYCos + localX2Sin; - } - public void setRegion (TextureRegion region) { - if (region == null) throw new IllegalArgumentException("region cannot be null."); - this.region = region; float[] uvs = this.uvs; - if (region instanceof AtlasRegion && ((AtlasRegion)region).degrees == 90) { + if (rotated) { uvs[URX] = region.getU(); uvs[URY] = region.getV2(); uvs[BRX] = region.getU(); @@ -137,20 +151,29 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { } } + public void setRegion (TextureRegion region) { + if (region == null) throw new IllegalArgumentException("region cannot be null."); + this.region = region; + } + public TextureRegion getRegion () { if (region == null) throw new IllegalStateException("Region has not been set: " + name); return region; } - /** 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 + * {@link #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. */ - public void computeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride) { + public void computeWorldVertices (Slot slot, float[] worldVertices, int offset, int stride) { + if (sequence != null) sequence.apply(slot, this); + float[] vertexOffset = this.offset; + Bone bone = slot.getBone(); float x = bone.getWorldX(), y = bone.getWorldY(); float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); float offsetX, offsetY; @@ -265,20 +288,15 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { this.path = path; } - public Attachment copy () { - RegionAttachment copy = new RegionAttachment(name); - copy.region = region; - copy.path = path; - copy.x = x; - copy.y = y; - copy.scaleX = scaleX; - copy.scaleY = scaleY; - copy.rotation = rotation; - copy.width = width; - copy.height = height; - arraycopy(uvs, 0, copy.uvs, 0, 8); - arraycopy(offset, 0, copy.offset, 0, 8); - copy.color.set(color); - return copy; + public @Null Sequence getSequence () { + return sequence; + } + + public void setSequence (@Null Sequence sequence) { + this.sequence = sequence; + } + + public RegionAttachment copy () { + return new RegionAttachment(this); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java new file mode 100644 index 000000000..b7004ac0c --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java @@ -0,0 +1,124 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +package com.esotericsoftware.spine.attachments; + +import static com.esotericsoftware.spine.utils.SpineUtils.*; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; + +import com.esotericsoftware.spine.Slot; + +public class Sequence { + static private int nextID; + + private final int id = nextID(); + private final TextureRegion[] regions; + private int start, digits, setupIndex; + + public Sequence (int count) { + regions = new TextureRegion[count]; + } + + /** Copy constructor. */ + protected Sequence (Sequence other) { + regions = new TextureRegion[other.regions.length]; + arraycopy(other.regions, 0, regions, 0, regions.length); + + start = other.start; + digits = other.digits; + setupIndex = other.setupIndex; + } + + public void apply (Slot slot, T attachment) { + int index = slot.getSequenceIndex(); + if (index == -1) + index = setupIndex; + else if (index >= regions.length) // + index = regions.length - 1; + TextureRegion region = regions[index]; + if (attachment.getRegion() != region) { + attachment.setRegion(region); + attachment.updateRegion(); + } + } + + public String getPath (String basePath, int index) { + StringBuilder buffer = new StringBuilder(basePath.length() + digits); + buffer.append(basePath); + buffer.append(start + index); + while (buffer.length() < digits) + buffer.append('0'); + return buffer.toString(); + } + + public int getStart () { + return start; + } + + public void setStart (int start) { + this.start = start; + } + + public int getDigits () { + return digits; + } + + public void setDigits (int digits) { + this.digits = digits; + } + + /** The index of the region to show for the setup pose. */ + public int getSetupIndex () { + return setupIndex; + } + + public void setSetupIndex (int index) { + this.setupIndex = index; + } + + public TextureRegion[] getRegions () { + return regions; + } + + /** Returns a unique ID for this attachment. */ + public int getId () { + return id; + } + + static private synchronized int nextID () { + return nextID++; + } + + static public enum SequenceMode { + stop, play, loop, pingpong, playReverse, loopReverse, pingpongReverse; + + static public final SequenceMode[] values = values(); + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SequenceAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SequenceAttachment.java deleted file mode 100644 index 77f7c4a05..000000000 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SequenceAttachment.java +++ /dev/null @@ -1,151 +0,0 @@ -/****************************************************************************** - * 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. - *****************************************************************************/ - -package com.esotericsoftware.spine.attachments; - -import com.badlogic.gdx.graphics.g2d.TextureRegion; -import com.badlogic.gdx.math.MathUtils; - -import com.esotericsoftware.spine.Slot; - -/** An attachment that applies a sequence of texture atlas regions to a region or mesh attachment. - *

- * See Sequence attachments in the Spine User Guide. */ -public class SequenceAttachment extends Attachment { - private T attachment; - private String path; - private int frameCount; - private float frameTime; - private SequenceMode mode; - private TextureRegion[] regions; - - public SequenceAttachment (String name) { - super(name); - } - - /** Updates the {@link #attachment} with the {@link #regions region} for the slot's {@link Slot#getAttachmentTime()} and - * returns it. */ - public T updateAttachment (Slot slot) { - int index = (int)(slot.getAttachmentTime() / frameTime); - switch (mode) { - case forward: - index = Math.min(frameCount - 1, index); - break; - case backward: - index = Math.max(frameCount - index - 1, 0); - break; - case forwardLoop: - index = index % frameCount; - break; - case backwardLoop: - index = frameCount - (index % frameCount) - 1; - break; - case pingPong: - index = index % (frameCount << 1); - if (index >= frameCount) index = frameCount - 1 - (index - frameCount); - break; - case random: - index = MathUtils.random(frameCount - 1); - } - attachment.setRegion(regions[index]); - attachment.updateRegion(); - return attachment; - } - - public void setAttachment (T attachment) { - this.attachment = attachment; - } - - public T getAttachment () { - return attachment; - } - - /** The prefix used to find the {@link #regions} for this attachment. */ - public String getPath () { - return path; - } - - public void setPath (String path) { - this.path = path; - } - - public SequenceMode getMode () { - return mode; - } - - public void setMode (SequenceMode mode) { - if (mode == null) throw new IllegalArgumentException("mode cannot be null."); - this.mode = mode; - } - - public int getFrameCount () { - return frameCount; - } - - public void setFrameCount (int frameCount) { - this.frameCount = frameCount; - } - - /** The time in seconds each frame is shown. */ - public float getFrameTime () { - return frameTime; - } - - public void setFrameTime (float frameTime) { - this.frameTime = frameTime; - } - - public TextureRegion[] getRegions () { - if (regions == null) throw new IllegalStateException("Regions have not been set: " + name); - return regions; - } - - public void setRegions (TextureRegion[] regions) { - if (regions == null) throw new IllegalArgumentException("regions cannot be null."); - this.regions = regions; - } - - public Attachment copy () { - SequenceAttachment copy = new SequenceAttachment(name); - copy.attachment = attachment.copy(); - copy.path = path; - copy.frameCount = frameCount; - copy.frameTime = frameTime; - copy.frameTime = frameTime; - copy.mode = mode; - copy.regions = regions; - return copy; - } - - static public enum SequenceMode { - forward, backward, forwardLoop, backwardLoop, pingPong, random; - - static public final SequenceMode[] values = values(); - } -} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SkeletonAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SkeletonAttachment.java index ee31449c0..5fc6b7797 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SkeletonAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SkeletonAttachment.java @@ -41,8 +41,13 @@ public class SkeletonAttachment extends Attachment { super(name); } - /** @return May return null. */ - public Skeleton getSkeleton () { + /** Copy constructor. */ + protected SkeletonAttachment (SkeletonAttachment other) { + super(other); + skeleton = other.skeleton; + } + + public @Null Skeleton getSkeleton () { return skeleton; } @@ -50,9 +55,7 @@ public class SkeletonAttachment extends Attachment { this.skeleton = skeleton; } - public Attachment copy () { - SkeletonAttachment copy = new SkeletonAttachment(name); - copy.skeleton = skeleton; - return copy; + public SkeletonAttachment copy () { + return new SkeletonAttachment(this); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java index 8d9e65a00..03c41bdcf 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java @@ -44,15 +44,35 @@ abstract public class VertexAttachment extends Attachment { static private int nextID; private final int id = nextID(); + @Null Attachment timelineAttachment = this; @Null int[] bones; float[] vertices; int worldVerticesLength; - @Null VertexAttachment deformAttachment = this; public VertexAttachment (String name) { super(name); } + /** Copy constructor. */ + public VertexAttachment (VertexAttachment other) { + super(other); + timelineAttachment = other.timelineAttachment; + + if (other.bones != null) { + bones = new int[other.bones.length]; + arraycopy(other.bones, 0, bones, 0, bones.length); + } else + bones = null; + + if (other.vertices != null) { + vertices = new float[other.vertices.length]; + arraycopy(other.vertices, 0, vertices, 0, vertices.length); + } else + vertices = null; + + worldVerticesLength = other.worldVerticesLength; + } + /** Transforms the attachment's local {@link #getVertices()} to world coordinates. If the slot's {@link Slot#getDeform()} is * not empty, it is used to deform the vertices. *

@@ -120,17 +140,6 @@ abstract public class VertexAttachment extends Attachment { } } - /** Deform keys for the deform attachment are also applied to this attachment. - * @return May be null if no deform keys should be applied. */ - public @Null VertexAttachment getDeformAttachment () { - return deformAttachment; - } - - /** @param deformAttachment May be null if no deform keys should be applied. */ - public void setDeformAttachment (@Null VertexAttachment deformAttachment) { - this.deformAttachment = deformAttachment; - } - /** The bones which affect the {@link #getVertices()}. The array entries are, for each vertex, the number of bones affecting * the vertex followed by that many bone indices, which is the index of the bone in {@link Skeleton#getBones()}. Will be null * if this attachment has no weights. */ @@ -164,29 +173,22 @@ abstract public class VertexAttachment extends Attachment { this.worldVerticesLength = worldVerticesLength; } + /** Timelines for the timeline attachment are also applied to this attachment. + * @return May be null if no attachment-specific timelines should be applied. */ + public @Null Attachment getTimelineAttachment () { + return timelineAttachment; + } + + /** @param timelineAttachment May be null if no attachment-specific timelines should be applied. */ + public void setTimelineAttachment (Attachment timelineAttachment) { + this.timelineAttachment = timelineAttachment; + } + /** Returns a unique ID for this attachment. */ public int getId () { return id; } - /** Does not copy id (generated) or name (set on construction). */ - void copyTo (VertexAttachment attachment) { - if (bones != null) { - attachment.bones = new int[bones.length]; - arraycopy(bones, 0, attachment.bones, 0, bones.length); - } else - attachment.bones = null; - - if (vertices != null) { - attachment.vertices = new float[vertices.length]; - arraycopy(vertices, 0, attachment.vertices, 0, vertices.length); - } else - attachment.vertices = null; - - attachment.worldVerticesLength = worldVerticesLength; - attachment.deformAttachment = deformAttachment; - } - static private synchronized int nextID () { return nextID++; } diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java index d8ab6f51d..f72a48852 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -258,7 +258,6 @@ public class SkeletonViewer extends ApplicationAdapter { skeleton.setSlotsToSetupPose(); delta = Math.min(delta, 0.032f) * ui.speedSlider.getValue(); - skeleton.update(delta); state.update(delta); state.apply(skeleton); skeleton.updateWorldTransform(); From 3cb04204f427ea9a04903b7419a3aeb1348048b8 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 2 Oct 2021 03:03:22 -1000 Subject: [PATCH 02/10] Clean up. --- .../com/esotericsoftware/spine/Animation.java | 27 ++++++++++--------- .../spine/attachments/Sequence.java | 6 ++--- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index 028debe4f..73a1c77dd 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -2425,17 +2425,18 @@ public class Animation { } /** Changes a slot's {@link Slot#getSequenceIndex()} for an attachment's {@link Sequence}. */ - static public class SequenceTimeline extends Timeline implements SlotTimeline { + static public class SequenceTimeline extends Timeline implements SlotTimeline { static public final int ENTRIES = 3; - static private final int MODE = 1, FRAME_TIME = 2; + static private final int MODE = 1, DELAY = 2; final int slotIndex; - final T attachment; + final HasTextureRegion attachment; - public SequenceTimeline (int frameCount, int slotIndex, T attachment) { - super(frameCount, Property.sequence.ordinal() + "|" + slotIndex + "|" + attachment.getSequence().getId()); + public SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) { + super(frameCount, + Property.sequence.ordinal() + "|" + slotIndex + "|" + ((HasTextureRegion)attachment).getSequence().getId()); this.slotIndex = slotIndex; - this.attachment = attachment; + this.attachment = (HasTextureRegion)attachment; } public int getFrameEntries () { @@ -2446,18 +2447,18 @@ public class Animation { return slotIndex; } - public T getAttachment () { - return attachment; + public Attachment getAttachment () { + return (Attachment)attachment; } /** Sets the time, mode, index, and frame time for the specified frame. * @param frame Between 0 and frameCount, inclusive. - * @param time The frame time in seconds. */ - public void setFrame (int frame, float time, SequenceMode mode, int index, float frameTime) { + * @param time Seconds between frames. */ + public void setFrame (int frame, float time, SequenceMode mode, int index, float delay) { frame *= ENTRIES; frames[frame] = time; frames[frame + MODE] = mode.ordinal() | (index << 4); - frames[frame + FRAME_TIME] = frameTime; + frames[frame + DELAY] = delay; } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, @@ -2480,12 +2481,12 @@ public class Animation { int i = search(frames, time, ENTRIES); float before = frames[i]; int modeAndIndex = (int)frames[i + MODE]; - float frameTime = frames[i + FRAME_TIME]; + float delay = frames[i + DELAY]; int index = modeAndIndex >> 4, count = attachment.getSequence().getRegions().length; SequenceMode mode = SequenceMode.values[modeAndIndex & 0xf]; if (mode != SequenceMode.stop) { - index += (time - before) / frameTime; + index += (time - before) / delay; switch (mode) { case play: index = Math.min(count - 1, index); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java index b7004ac0c..edaae17ff 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java @@ -58,10 +58,8 @@ public class Sequence { public void apply (Slot slot, T attachment) { int index = slot.getSequenceIndex(); - if (index == -1) - index = setupIndex; - else if (index >= regions.length) // - index = regions.length - 1; + if (index == -1) index = setupIndex; + if (index >= regions.length) index = regions.length - 1; TextureRegion region = regions[index]; if (attachment.getRegion() != region) { attachment.setRegion(region); From eb5e19036d09a3987e3787e324823cb42a5304c5 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Mon, 4 Oct 2021 00:21:02 -1000 Subject: [PATCH 03/10] Attachment name in slot attachment timelines is now optional. --- .../src/com/esotericsoftware/spine/SkeletonJson.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 66ce3bbfe..b154e314a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -518,7 +518,7 @@ public class SkeletonJson extends SkeletonLoader { if (timelineName.equals("attachment")) { AttachmentTimeline timeline = new AttachmentTimeline(frames, slot.index); for (int frame = 0; keyMap != null; keyMap = keyMap.next, frame++) - timeline.setFrame(frame, keyMap.getFloat("time", 0), keyMap.getString("name")); + timeline.setFrame(frame, keyMap.getFloat("time", 0), keyMap.getString("name", null)); timelines.add(timeline); } else if (timelineName.equals("rgba")) { From 515d238886b2860249f75860cc21d24f20510dde Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Mon, 4 Oct 2021 00:57:25 -1000 Subject: [PATCH 04/10] JSON and binary loading for sequences. --- .../spine/SkeletonBinary.java | 134 +++++++++++------- .../esotericsoftware/spine/SkeletonJson.java | 122 ++++++++++------ .../spine/attachments/MeshAttachment.java | 3 +- .../spine/attachments/RegionAttachment.java | 3 +- 4 files changed, 161 insertions(+), 101 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index 9d12696e7..de09803a9 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -63,6 +63,7 @@ import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ScaleXTimeline; import com.esotericsoftware.spine.Animation.ScaleYTimeline; +import com.esotericsoftware.spine.Animation.SequenceTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline; import com.esotericsoftware.spine.Animation.ShearXTimeline; import com.esotericsoftware.spine.Animation.ShearYTimeline; @@ -85,6 +86,8 @@ import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PointAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; +import com.esotericsoftware.spine.attachments.Sequence; +import com.esotericsoftware.spine.attachments.Sequence.SequenceMode; import com.esotericsoftware.spine.attachments.VertexAttachment; /** Loads skeleton data in the Spine binary format. @@ -111,6 +114,9 @@ public class SkeletonBinary extends SkeletonLoader { static public final int SLOT_RGB2 = 4; static public final int SLOT_ALPHA = 5; + static public final int ATTACHMENT_DEFORM = 0; + static public final int ATTACHMENT_SEQUENCE = 1; + static public final int PATH_POSITION = 0; static public final int PATH_SPACING = 1; static public final int PATH_MIX = 2; @@ -302,7 +308,7 @@ public class SkeletonBinary extends SkeletonLoader { if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh); linkedMesh.mesh.setParentMesh((MeshAttachment)parent); - linkedMesh.mesh.updateRegion(); + if (linkedMesh.mesh.getSequence() == null) linkedMesh.mesh.updateRegion(); } linkedMeshes.clear(); @@ -395,11 +401,10 @@ public class SkeletonBinary extends SkeletonLoader { float width = input.readFloat(); float height = input.readFloat(); int color = input.readInt(); - - // BOZO! - Read sequence. + Sequence sequence = readSequence(input); if (path == null) path = name; - RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, null); + RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, sequence); if (region == null) return null; region.setPath(path); region.setX(x * scale); @@ -410,7 +415,8 @@ public class SkeletonBinary extends SkeletonLoader { region.setWidth(width * scale); region.setHeight(height * scale); Color.rgba8888ToColor(region.getColor(), color); - region.updateRegion(); + region.setSequence(sequence); + if (sequence == null) region.updateRegion(); return region; } case boundingbox: { @@ -434,6 +440,7 @@ public class SkeletonBinary extends SkeletonLoader { short[] triangles = readShortArray(input); Vertices vertices = readVertices(input, vertexCount); int hullLength = input.readInt(true); + Sequence sequence = readSequence(input); short[] edges = null; float width = 0, height = 0; if (nonessential) { @@ -442,10 +449,8 @@ public class SkeletonBinary extends SkeletonLoader { height = input.readFloat(); } - // BOZO! - Read sequence. - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, null); + MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.setPath(path); Color.rgba8888ToColor(mesh.getColor(), color); @@ -454,8 +459,9 @@ public class SkeletonBinary extends SkeletonLoader { mesh.setWorldVerticesLength(vertexCount << 1); mesh.setTriangles(triangles); mesh.setRegionUVs(uvs); - mesh.updateRegion(); + if (sequence == null) mesh.updateRegion(); mesh.setHullLength(hullLength << 1); + mesh.setSequence(sequence); if (nonessential) { mesh.setEdges(edges); mesh.setWidth(width * scale); @@ -469,19 +475,19 @@ public class SkeletonBinary extends SkeletonLoader { String skinName = input.readStringRef(); String parent = input.readStringRef(); boolean inheritTimelines = input.readBoolean(); + Sequence sequence = readSequence(input); float width = 0, height = 0; if (nonessential) { width = input.readFloat(); height = input.readFloat(); } - // BOZO! - Read sequence. - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, null); + MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.setPath(path); Color.rgba8888ToColor(mesh.getColor(), color); + mesh.setSequence(sequence); if (nonessential) { mesh.setWidth(width * scale); mesh.setHeight(height * scale); @@ -542,6 +548,15 @@ public class SkeletonBinary extends SkeletonLoader { return null; } + private Sequence readSequence (SkeletonInput input) throws IOException { + if (!input.readBoolean()) return null; + Sequence sequence = new Sequence(input.readInt(true)); + sequence.setStart(input.readInt(true)); + sequence.setDigits(input.readInt(true)); + sequence.setSetupIndex(input.readInt(true)); + return sequence; + } + private Vertices readVertices (SkeletonInput input, int vertexCount) throws IOException { float scale = this.scale; int verticesLength = vertexCount << 1; @@ -751,7 +766,6 @@ public class SkeletonBinary extends SkeletonLoader { a = a2; } timelines.add(timeline); - break; } } } @@ -897,57 +911,73 @@ public class SkeletonBinary extends SkeletonLoader { } } - // Deform timelines. + // Attachment timelines. for (int i = 0, n = input.readInt(true); i < n; i++) { Skin skin = skeletonData.skins.get(input.readInt(true)); for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { int slotIndex = input.readInt(true); for (int iii = 0, nnn = input.readInt(true); iii < nnn; iii++) { String attachmentName = input.readStringRef(); - VertexAttachment attachment = (VertexAttachment)skin.getAttachment(slotIndex, attachmentName); - if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName); - boolean weighted = attachment.getBones() != null; - float[] vertices = attachment.getVertices(); - int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length; + Attachment attachment = skin.getAttachment(slotIndex, attachmentName); + if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentName); - int frameCount = input.readInt(true), frameLast = frameCount - 1; - DeformTimeline timeline = new DeformTimeline(frameCount, input.readInt(true), slotIndex, attachment); + int timelineType = input.readByte(), frameCount = input.readInt(true), frameLast = frameCount - 1; + switch (timelineType) { + case ATTACHMENT_DEFORM: { + VertexAttachment vertexAttachment = (VertexAttachment)attachment; + boolean weighted = vertexAttachment.getBones() != null; + float[] vertices = vertexAttachment.getVertices(); + int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length; - float time = input.readFloat(); - for (int frame = 0, bezier = 0;; frame++) { - float[] deform; - int end = input.readInt(true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = input.readInt(true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = input.readFloat(); - } else { - for (int v = start; v < end; v++) - deform[v] = input.readFloat() * scale; + DeformTimeline timeline = new DeformTimeline(frameCount, input.readInt(true), slotIndex, vertexAttachment); + + float time = input.readFloat(); + for (int frame = 0, bezier = 0;; frame++) { + float[] deform; + int end = input.readInt(true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else { + deform = new float[deformLength]; + int start = input.readInt(true); + end += start; + if (scale == 1) { + for (int v = start; v < end; v++) + deform[v] = input.readFloat(); + } else { + for (int v = start; v < end; v++) + deform[v] = input.readFloat() * scale; + } + if (!weighted) { + for (int v = 0, vn = deform.length; v < vn; v++) + deform[v] += vertices[v]; + } } - if (!weighted) { - for (int v = 0, vn = deform.length; v < vn; v++) - deform[v] += vertices[v]; + timeline.setFrame(frame, time, deform); + if (frame == frameLast) break; + float 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; } - timeline.setFrame(frame, time, deform); - if (frame == frameLast) break; - float 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.add(timeline); + break; + } + case ATTACHMENT_SEQUENCE: + SequenceTimeline timeline = new SequenceTimeline(frameCount, slotIndex, attachment); + for (int frame = 0; frame < frameCount; frame++) { + float time = input.readFloat(); + int modeAndIndex = input.readInt(); + timeline.setFrame(frame, time, SequenceMode.values[modeAndIndex & 0xf], modeAndIndex >> 4, + input.readFloat()); + } + timelines.add(timeline); } - timelines.add(timeline); } } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index b154e314a..3dda14efc 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -41,6 +41,7 @@ import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.JsonReader; import com.badlogic.gdx.utils.JsonValue; +import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.SerializationException; import com.esotericsoftware.spine.Animation.AlphaTimeline; @@ -63,6 +64,7 @@ import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ScaleXTimeline; import com.esotericsoftware.spine.Animation.ScaleYTimeline; +import com.esotericsoftware.spine.Animation.SequenceTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline; import com.esotericsoftware.spine.Animation.ShearXTimeline; import com.esotericsoftware.spine.Animation.ShearYTimeline; @@ -84,6 +86,8 @@ import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PointAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; +import com.esotericsoftware.spine.attachments.Sequence; +import com.esotericsoftware.spine.attachments.Sequence.SequenceMode; import com.esotericsoftware.spine.attachments.VertexAttachment; /** Loads skeleton data in the Spine JSON format. @@ -323,7 +327,7 @@ public class SkeletonJson extends SkeletonLoader { if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh); linkedMesh.mesh.setParentMesh((MeshAttachment)parent); - linkedMesh.mesh.updateRegion(); + if (linkedMesh.mesh.getSequence() == null) linkedMesh.mesh.updateRegion(); } linkedMeshes.clear(); @@ -366,8 +370,8 @@ public class SkeletonJson extends SkeletonLoader { switch (AttachmentType.valueOf(map.getString("type", AttachmentType.region.name()))) { case region: { String path = map.getString("path", name); - // BOZO! - Read sequence. - RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, null); + Sequence sequence = readSequence(map.get("sequence")); + RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, sequence); if (region == null) return null; region.setPath(path); region.setX(map.getFloat("x", 0) * scale); @@ -377,11 +381,12 @@ public class SkeletonJson extends SkeletonLoader { region.setRotation(map.getFloat("rotation", 0)); region.setWidth(map.getFloat("width") * scale); region.setHeight(map.getFloat("height") * scale); + region.setSequence(sequence); String color = map.getString("color", null); if (color != null) Color.valueOf(color, region.getColor()); - region.updateRegion(); + if (region.getSequence() == null) region.updateRegion(); return region; } case boundingbox: { @@ -396,8 +401,8 @@ public class SkeletonJson extends SkeletonLoader { case mesh: case linkedmesh: { String path = map.getString("path", name); - // BOZO! - Read sequence. - MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, null); + Sequence sequence = readSequence(map.get("sequence")); + MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.setPath(path); @@ -406,6 +411,7 @@ public class SkeletonJson extends SkeletonLoader { mesh.setWidth(map.getFloat("width", 0) * scale); mesh.setHeight(map.getFloat("height", 0) * scale); + mesh.setSequence(sequence); String parent = map.getString("parent", null); if (parent != null) { @@ -418,7 +424,7 @@ public class SkeletonJson extends SkeletonLoader { readVertices(map, mesh, uvs.length); mesh.setTriangles(map.require("triangles").asShortArray()); mesh.setRegionUVs(uvs); - mesh.updateRegion(); + if (mesh.getSequence() == null) mesh.updateRegion(); if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() << 1); if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray()); @@ -474,6 +480,15 @@ public class SkeletonJson extends SkeletonLoader { return null; } + private Sequence readSequence (@Null JsonValue map) { + if (map == null) return null; + Sequence sequence = new Sequence(map.getInt("count")); + sequence.setStart(map.getInt("start", 1)); + sequence.setDigits(map.getInt("digits", 0)); + sequence.setSetupIndex(map.getInt("setup", 0)); + return sequence; + } + private void readVertices (JsonValue map, VertexAttachment attachment, int verticesLength) { attachment.setWorldVerticesLength(verticesLength); float[] vertices = map.require("vertices").asFloatArray(); @@ -862,8 +877,8 @@ public class SkeletonJson extends SkeletonLoader { } } - // Deform timelines. - for (JsonValue deformMap = map.getChild("deform"); deformMap != null; deformMap = deformMap.next) { + // Attachment timelines. + for (JsonValue deformMap = map.getChild("attachments"); deformMap != null; deformMap = deformMap.next) { Skin skin = skeletonData.findSkin(deformMap.name); if (skin == null) throw new SerializationException("Skin not found: " + deformMap.name); for (JsonValue slotMap = deformMap.child; slotMap != null; slotMap = slotMap.next) { @@ -873,46 +888,63 @@ public class SkeletonJson extends SkeletonLoader { JsonValue keyMap = timelineMap.child; if (keyMap == null) continue; - VertexAttachment attachment = (VertexAttachment)skin.getAttachment(slot.index, timelineMap.name); - if (attachment == null) throw new SerializationException("Deform attachment not found: " + timelineMap.name); - boolean weighted = attachment.getBones() != null; - float[] vertices = attachment.getVertices(); - int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length; + Attachment attachment = skin.getAttachment(slot.index, timelineMap.name); + if (attachment == null) throw new SerializationException("Timeline attachment not found: " + timelineMap.name); - DeformTimeline timeline = new DeformTimeline(timelineMap.size, timelineMap.size, slot.index, attachment); - float time = keyMap.getFloat("time", 0); - for (int frame = 0, bezier = 0;; frame++) { - float[] deform; - JsonValue verticesValue = keyMap.get("vertices"); - if (verticesValue == null) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = keyMap.getInt("offset", 0); - arraycopy(verticesValue.asFloatArray(), 0, deform, start, verticesValue.size); - if (scale != 1) { - for (int i = start, n = i + verticesValue.size; i < n; i++) - deform[i] *= scale; - } - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } + int frames = keyMap.size; + String timelineName = keyMap.name; + keyMap = keyMap.child; + if (timelineName.equals("deform")) { + VertexAttachment vertexAttachment = (VertexAttachment)attachment; + boolean weighted = vertexAttachment.getBones() != null; + float[] vertices = vertexAttachment.getVertices(); + int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length; - timeline.setFrame(frame, time, deform); - JsonValue nextMap = keyMap.next; - if (nextMap == null) { - timeline.shrink(bezier); - break; + DeformTimeline timeline = new DeformTimeline(frames, frames, slot.index, vertexAttachment); + float time = keyMap.getFloat("time", 0); + for (int frame = 0, bezier = 0;; frame++) { + float[] deform; + JsonValue verticesValue = keyMap.get("vertices"); + if (verticesValue == null) + deform = weighted ? new float[deformLength] : vertices; + else { + deform = new float[deformLength]; + int start = keyMap.getInt("offset", 0); + arraycopy(verticesValue.asFloatArray(), 0, deform, start, verticesValue.size); + if (scale != 1) { + for (int i = start, n = i + verticesValue.size; i < n; i++) + deform[i] *= scale; + } + if (!weighted) { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.setFrame(frame, time, deform); + JsonValue nextMap = keyMap.next; + if (nextMap == null) { + timeline.shrink(bezier); + break; + } + float time2 = nextMap.getFloat("time", 0); + JsonValue curve = keyMap.get("curve"); + if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + time = time2; + keyMap = nextMap; } - float time2 = nextMap.getFloat("time", 0); - JsonValue curve = keyMap.get("curve"); - if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); - time = time2; - keyMap = nextMap; + timelines.add(timeline); + } else if (timelineName.equals("sequence")) { + SequenceTimeline timeline = new SequenceTimeline(frames, slot.index, attachment); + float lastDelay = 0; + for (int frame = 0; keyMap != null; keyMap = keyMap.next, frame++) { + float delay = keyMap.getFloat("delay", lastDelay); + timeline.setFrame(frame, keyMap.getFloat("time", 0), SequenceMode.valueOf(keyMap.getString("mode", "stop")), + keyMap.getInt("index", 0), delay); + delay = lastDelay; + } + timelines.add(timeline); } - timelines.add(timeline); } } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java index c1eae3fc0..01cbcb491 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java @@ -95,8 +95,7 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion this.region = region; } - public TextureRegion getRegion () { - if (region == null) throw new IllegalStateException("Region has not been set: " + this); + public @Null TextureRegion getRegion () { return region; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java index 0301cdc1d..1ad4e5997 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java @@ -156,8 +156,7 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { this.region = region; } - public TextureRegion getRegion () { - if (region == null) throw new IllegalStateException("Region has not been set: " + name); + public @Null TextureRegion getRegion () { return region; } From f7ae127115b34da24a67db678a41171665fd755b Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Mon, 4 Oct 2021 00:58:42 -1000 Subject: [PATCH 05/10] Changed AnimationState animation time so it continues past animation end if non-looping and animation end is >= animation duration. This allows sequences to continue to play in the common case where animation end is not being used to stop the animation early. --- .../com/esotericsoftware/spine/AnimationState.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 9cbaa732d..f0f7692cc 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -1012,16 +1012,20 @@ public class AnimationState { nextAnimationLast = animationLast; } - /** Uses {@link #getTrackTime()} to compute the animationTime, which is between {@link #getAnimationStart()} - * and {@link #getAnimationEnd()}. When the trackTime is 0, the animationTime is equal to the - * animationStart time. */ + /** Uses {@link #getTrackTime()} to compute the animationTime. When the trackTime is 0, the + * animationTime is equal to the animationStart time. + *

+ * The animationTime is between {@link #getAnimationStart()} and {@link #getAnimationEnd()}, except if this + * track entry is non-looping and {@link #getAnimationEnd()} is >= to the animation {@link Animation#duration}, then + * animationTime continues to increase past {@link #getAnimationEnd()}. */ public float getAnimationTime () { if (loop) { float duration = animationEnd - animationStart; if (duration == 0) return animationStart; return (trackTime % duration) + animationStart; } - return Math.min(trackTime + animationStart, animationEnd); + float animationTime = trackTime + animationStart; + return animationEnd >= animation.duration ? animationTime : Math.min(animationTime, animationEnd); } /** Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or From d798334a5ed59b1a5530bc2c20dd7b168fd7478f Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Mon, 4 Oct 2021 20:48:58 -1000 Subject: [PATCH 06/10] Sequence attachment fixes. --- .../esotericsoftware/spine/SkeletonJson.java | 116 +++++++++--------- .../spine/attachments/MeshAttachment.java | 2 +- 2 files changed, 58 insertions(+), 60 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 3dda14efc..287cd7a7d 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -878,72 +878,70 @@ public class SkeletonJson extends SkeletonLoader { } // Attachment timelines. - for (JsonValue deformMap = map.getChild("attachments"); deformMap != null; deformMap = deformMap.next) { - Skin skin = skeletonData.findSkin(deformMap.name); - if (skin == null) throw new SerializationException("Skin not found: " + deformMap.name); - for (JsonValue slotMap = deformMap.child; slotMap != null; slotMap = slotMap.next) { + for (JsonValue attachmentsMap = map.getChild("attachments"); attachmentsMap != null; attachmentsMap = attachmentsMap.next) { + Skin skin = skeletonData.findSkin(attachmentsMap.name); + if (skin == null) throw new SerializationException("Skin not found: " + attachmentsMap.name); + for (JsonValue slotMap = attachmentsMap.child; slotMap != null; slotMap = slotMap.next) { SlotData slot = skeletonData.findSlot(slotMap.name); if (slot == null) throw new SerializationException("Slot not found: " + slotMap.name); - for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) { - JsonValue keyMap = timelineMap.child; - if (keyMap == null) continue; + for (JsonValue attachmentMap = slotMap.child; attachmentMap != null; attachmentMap = attachmentMap.next) { + Attachment attachment = skin.getAttachment(slot.index, attachmentMap.name); + if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentMap.name); + for (JsonValue timelineMap = attachmentMap.child; timelineMap != null; timelineMap = timelineMap.next) { + JsonValue keyMap = timelineMap.child; + int frames = timelineMap.size; + String timelineName = timelineMap.name; + if (timelineName.equals("deform")) { + VertexAttachment vertexAttachment = (VertexAttachment)attachment; + boolean weighted = vertexAttachment.getBones() != null; + float[] vertices = vertexAttachment.getVertices(); + int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length; - Attachment attachment = skin.getAttachment(slot.index, timelineMap.name); - if (attachment == null) throw new SerializationException("Timeline attachment not found: " + timelineMap.name); - - int frames = keyMap.size; - String timelineName = keyMap.name; - keyMap = keyMap.child; - if (timelineName.equals("deform")) { - VertexAttachment vertexAttachment = (VertexAttachment)attachment; - boolean weighted = vertexAttachment.getBones() != null; - float[] vertices = vertexAttachment.getVertices(); - int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length; - - DeformTimeline timeline = new DeformTimeline(frames, frames, slot.index, vertexAttachment); - float time = keyMap.getFloat("time", 0); - for (int frame = 0, bezier = 0;; frame++) { - float[] deform; - JsonValue verticesValue = keyMap.get("vertices"); - if (verticesValue == null) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = keyMap.getInt("offset", 0); - arraycopy(verticesValue.asFloatArray(), 0, deform, start, verticesValue.size); - if (scale != 1) { - for (int i = start, n = i + verticesValue.size; i < n; i++) - deform[i] *= scale; + DeformTimeline timeline = new DeformTimeline(frames, frames, slot.index, vertexAttachment); + float time = keyMap.getFloat("time", 0); + for (int frame = 0, bezier = 0;; frame++) { + float[] deform; + JsonValue verticesValue = keyMap.get("vertices"); + if (verticesValue == null) + deform = weighted ? new float[deformLength] : vertices; + else { + deform = new float[deformLength]; + int start = keyMap.getInt("offset", 0); + arraycopy(verticesValue.asFloatArray(), 0, deform, start, verticesValue.size); + if (scale != 1) { + for (int i = start, n = i + verticesValue.size; i < n; i++) + deform[i] *= scale; + } + if (!weighted) { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } } - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - timeline.setFrame(frame, time, deform); - JsonValue nextMap = keyMap.next; - if (nextMap == null) { - timeline.shrink(bezier); - break; + timeline.setFrame(frame, time, deform); + JsonValue nextMap = keyMap.next; + if (nextMap == null) { + timeline.shrink(bezier); + break; + } + float time2 = nextMap.getFloat("time", 0); + JsonValue curve = keyMap.get("curve"); + if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + time = time2; + keyMap = nextMap; } - float time2 = nextMap.getFloat("time", 0); - JsonValue curve = keyMap.get("curve"); - if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); - time = time2; - keyMap = nextMap; + timelines.add(timeline); + } else if (timelineName.equals("sequence")) { + SequenceTimeline timeline = new SequenceTimeline(frames, slot.index, attachment); + float lastDelay = 0; + for (int frame = 0; keyMap != null; keyMap = keyMap.next, frame++) { + float delay = keyMap.getFloat("delay", lastDelay); + timeline.setFrame(frame, keyMap.getFloat("time", 0), + SequenceMode.valueOf(keyMap.getString("mode", "stop")), keyMap.getInt("index", 0), delay); + lastDelay = delay; + } + timelines.add(timeline); } - timelines.add(timeline); - } else if (timelineName.equals("sequence")) { - SequenceTimeline timeline = new SequenceTimeline(frames, slot.index, attachment); - float lastDelay = 0; - for (int frame = 0; keyMap != null; keyMap = keyMap.next, frame++) { - float delay = keyMap.getFloat("delay", lastDelay); - timeline.setFrame(frame, keyMap.getFloat("time", 0), SequenceMode.valueOf(keyMap.getString("mode", "stop")), - keyMap.getInt("index", 0), delay); - delay = lastDelay; - } - timelines.add(timeline); } } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java index 01cbcb491..b3172167a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java @@ -286,7 +286,7 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion mesh.path = path; mesh.color.set(color); mesh.setParentMesh(parentMesh != null ? parentMesh : this); - mesh.updateRegion(); + if (mesh.getRegion() != null) mesh.updateRegion(); return mesh; } From 35347bf6a31f8c7bed40a62d945d2e344308c0db Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Mon, 4 Oct 2021 22:20:06 -1000 Subject: [PATCH 07/10] Only update the region if the attachment has a region. --- .../src/com/esotericsoftware/spine/SkeletonJson.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 287cd7a7d..e17396922 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -327,7 +327,7 @@ public class SkeletonJson extends SkeletonLoader { if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh); linkedMesh.mesh.setParentMesh((MeshAttachment)parent); - if (linkedMesh.mesh.getSequence() == null) linkedMesh.mesh.updateRegion(); + if (linkedMesh.mesh.getRegion() != null) linkedMesh.mesh.updateRegion(); } linkedMeshes.clear(); @@ -386,7 +386,7 @@ public class SkeletonJson extends SkeletonLoader { String color = map.getString("color", null); if (color != null) Color.valueOf(color, region.getColor()); - if (region.getSequence() == null) region.updateRegion(); + if (region.getRegion() != null) region.updateRegion(); return region; } case boundingbox: { @@ -424,7 +424,7 @@ public class SkeletonJson extends SkeletonLoader { readVertices(map, mesh, uvs.length); mesh.setTriangles(map.require("triangles").asShortArray()); mesh.setRegionUVs(uvs); - if (mesh.getSequence() == null) mesh.updateRegion(); + if (mesh.getRegion() != null) mesh.updateRegion(); if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() << 1); if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray()); From a037fbbb316371011b5aec3ef492e82a9169ce9c Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Mon, 4 Oct 2021 23:42:03 -1000 Subject: [PATCH 08/10] Sequence play -> once. --- .../spine-libgdx/src/com/esotericsoftware/spine/Animation.java | 2 +- .../src/com/esotericsoftware/spine/attachments/Sequence.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index 73a1c77dd..ac8ef9314 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -2488,7 +2488,7 @@ public class Animation { if (mode != SequenceMode.stop) { index += (time - before) / delay; switch (mode) { - case play: + case once: index = Math.min(count - 1, index); break; case loop: diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java index edaae17ff..9a68815fa 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java @@ -115,7 +115,7 @@ public class Sequence { } static public enum SequenceMode { - stop, play, loop, pingpong, playReverse, loopReverse, pingpongReverse; + stop, once, loop, pingpong, playReverse, loopReverse, pingpongReverse; static public final SequenceMode[] values = values(); } From f3b7d2acb27596e4f196d004d9c6594ef1594caf Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Mon, 4 Oct 2021 23:59:40 -1000 Subject: [PATCH 09/10] Sequence playReverse -> onceReverse. --- .../spine-libgdx/src/com/esotericsoftware/spine/Animation.java | 2 +- .../src/com/esotericsoftware/spine/attachments/Sequence.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index ac8ef9314..a72b98b8b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -2499,7 +2499,7 @@ public class Animation { index %= n; if (index >= count) index = n - index; break; - case playReverse: + case onceReverse: index = Math.max(count - 1 - index, 0); break; case loopReverse: diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java index 9a68815fa..3b732adbb 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/Sequence.java @@ -115,7 +115,7 @@ public class Sequence { } static public enum SequenceMode { - stop, once, loop, pingpong, playReverse, loopReverse, pingpongReverse; + stop, once, loop, pingpong, onceReverse, loopReverse, pingpongReverse; static public final SequenceMode[] values = values(); } From 39afd0e7eaded4ec746344808bc705212685b7fb Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Tue, 5 Oct 2021 00:35:54 -1000 Subject: [PATCH 10/10] Add small value to sequence timeline for float inaccuracy. --- .../spine-libgdx/src/com/esotericsoftware/spine/Animation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index a72b98b8b..805e58c75 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -2486,7 +2486,7 @@ public class Animation { int index = modeAndIndex >> 4, count = attachment.getSequence().getRegions().length; SequenceMode mode = SequenceMode.values[modeAndIndex & 0xf]; if (mode != SequenceMode.stop) { - index += (time - before) / delay; + index += (time - before) / delay + 0.00001f; switch (mode) { case once: index = Math.min(count - 1, index);