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 1c096d436..e74745f43 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 @@ -47,6 +47,7 @@ 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; public class AnimationStateTests { final SkeletonJson json = new SkeletonJson(new AttachmentLoader() { @@ -58,6 +59,10 @@ public class AnimationStateTests { return null; } + public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) { + return null; + } + public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) { return null; } 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 3c6d7f0f7..c5a1e93f7 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 @@ -40,6 +40,7 @@ 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; public class BonePlotting { static public void main (String[] args) throws Exception { @@ -53,6 +54,10 @@ public class BonePlotting { return null; } + public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) { + return null; + } + public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) { return null; } 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 057e2fc14..b743170ee 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -42,6 +42,7 @@ 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. *

@@ -721,6 +722,7 @@ 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) { verticesLength = 8; vertices = temp.setSize(8); 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 6fa7e874f..061c4c3d8 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -85,6 +85,9 @@ 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.TextureRegionAttachment; import com.esotericsoftware.spine.attachments.VertexAttachment; /** Loads skeleton data in the Spine binary format. @@ -302,7 +305,7 @@ public class SkeletonBinary extends SkeletonLoader { if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh); linkedMesh.mesh.setParentMesh((MeshAttachment)parent); - linkedMesh.mesh.updateUVs(); + linkedMesh.mesh.updateRegion(); } linkedMeshes.clear(); @@ -408,7 +411,7 @@ public class SkeletonBinary extends SkeletonLoader { region.setWidth(width * scale); region.setHeight(height * scale); Color.rgba8888ToColor(region.getColor(), color); - region.updateOffset(); + region.updateRegion(); return region; } case boundingbox: { @@ -450,7 +453,7 @@ public class SkeletonBinary extends SkeletonLoader { mesh.setWorldVerticesLength(vertexCount << 1); mesh.setTriangles(triangles); mesh.setRegionUVs(uvs); - mesh.updateUVs(); + mesh.updateRegion(); mesh.setHullLength(hullLength << 1); if (nonessential) { mesh.setEdges(edges); @@ -518,7 +521,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); @@ -533,6 +536,25 @@ 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 = ((TextureRegionAttachment)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 13032f6a5..6e5d42b82 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -323,7 +323,7 @@ public class SkeletonJson extends SkeletonLoader { if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh); linkedMesh.mesh.setParentMesh((MeshAttachment)parent); - linkedMesh.mesh.updateUVs(); + linkedMesh.mesh.updateRegion(); } linkedMeshes.clear(); @@ -380,7 +380,7 @@ public class SkeletonJson extends SkeletonLoader { String color = map.getString("color", null); if (color != null) Color.valueOf(color, region.getColor()); - region.updateOffset(); + region.updateRegion(); return region; } case boundingbox: { @@ -416,7 +416,7 @@ public class SkeletonJson extends SkeletonLoader { readVertices(map, mesh, uvs.length); mesh.setTriangles(map.require("triangles").asShortArray()); mesh.setRegionUVs(uvs); - mesh.updateUVs(); + mesh.updateRegion(); if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() << 1); if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray()); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraint.java index 9ca091503..1f9418402 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SpringConstraint.java @@ -1,8 +1,8 @@ /****************************************************************************** * Spine Runtimes License Agreement - * Last updated January 1, 2020. Replaces all prior versions. + * Last updated September 24, 2021. Replaces all prior versions. * - * Copyright (c) 2013-2020, Esoteric Software LLC + * 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 @@ -37,6 +37,7 @@ import com.badlogic.gdx.utils.Array; public class SpringConstraint implements Updatable { final SpringConstraintData data; final Array bones; + // BOZO! - stiffness -> strength. stiffness, damping, rope, stretch -> move to spring. float mix, friction, gravity, wind, stiffness, damping; boolean rope, stretch; 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 9d12fa048..e5ba26041 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 @@ -63,6 +63,18 @@ public class AtlasAttachmentLoader implements AttachmentLoader { 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 + ")"); + } + SequenceAttachment sequence = new SequenceAttachment(name); + sequence.setRegions(regions); + return sequence; + } + public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) { return new BoundingBoxAttachment(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 cacc1a0a7..47084c130 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 @@ -44,6 +44,9 @@ public interface AttachmentLoader { /** @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); + /** @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/AttachmentType.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java index 71ce9fb4d..edf320a44 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java @@ -30,7 +30,7 @@ package com.esotericsoftware.spine.attachments; public enum AttachmentType { - region, boundingbox, mesh, linkedmesh, path, point, clipping; + region, boundingbox, mesh, linkedmesh, path, point, clipping, sequence; static public final AttachmentType[] values = values(); } 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 82efe3e9a..84f5b97f8 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 @@ -40,7 +40,7 @@ import com.badlogic.gdx.utils.Null; * supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh. *

* See Mesh attachments in the Spine User Guide. */ -public class MeshAttachment extends VertexAttachment { +public class MeshAttachment extends VertexAttachment implements TextureRegionAttachment { private TextureRegion region; private String path; private float[] regionUVs, uvs; @@ -67,9 +67,9 @@ public class MeshAttachment extends VertexAttachment { return region; } - /** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region UVs or - * region. */ - public void updateUVs () { + /** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region or the + * region's properties. */ + public void updateRegion () { float[] regionUVs = this.regionUVs; if (this.uvs == null || this.uvs.length != regionUVs.length) this.uvs = new float[regionUVs.length]; float[] uvs = this.uvs; @@ -152,7 +152,7 @@ public class MeshAttachment extends VertexAttachment { /** The UV pair for each vertex, normalized within the entire texture. *

- * See {@link #updateUVs}. */ + * See {@link #updateRegion()}. */ public float[] getUVs () { return uvs; } @@ -161,12 +161,10 @@ public class MeshAttachment extends VertexAttachment { this.uvs = uvs; } - /** The color to tint the mesh. */ public Color getColor () { return color; } - /** The name of the texture region for this attachment. */ public String getPath () { return path; } @@ -269,7 +267,7 @@ public class MeshAttachment extends VertexAttachment { mesh.color.set(color); mesh.deformAttachment = deformAttachment; mesh.setParentMesh(parentMesh != null ? parentMesh : this); - mesh.updateUVs(); + mesh.updateRegion(); return mesh; } } 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 e2b14627e..cfecb95c2 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 @@ -40,7 +40,7 @@ import com.esotericsoftware.spine.Bone; /** An attachment that displays a textured quadrilateral. *

* See Region attachments in the Spine User Guide. */ -public class RegionAttachment extends Attachment { +public class RegionAttachment extends Attachment implements TextureRegionAttachment { static public final int BLX = 0; static public final int BLY = 1; static public final int ULX = 2; @@ -61,8 +61,9 @@ public class RegionAttachment extends Attachment { super(name); } - /** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */ - public void updateOffset () { + /** Calculates the {@link #offset} using the {@link #region}. Must be called after changing the region or the region's + * properties. */ + public void updateRegion () { float width = getWidth(); float height = getHeight(); float localX2 = width / 2; @@ -137,7 +138,7 @@ public class RegionAttachment extends Attachment { } public TextureRegion getRegion () { - if (region == null) throw new IllegalStateException("Region has not been set: " + this); + if (region == null) throw new IllegalStateException("Region has not been set: " + name); return region; } @@ -180,7 +181,7 @@ public class RegionAttachment extends Attachment { /** For each of the 4 vertices, a pair of x,y values that is the local position of the vertex. *

- * See {@link #updateOffset()}. */ + * See {@link #updateRegion()}. */ public float[] getOffset () { return offset; } @@ -252,12 +253,10 @@ public class RegionAttachment extends Attachment { this.height = height; } - /** The color to tint the region attachment. */ public Color getColor () { return color; } - /** The name of the texture region for this attachment. */ public String getPath () { return path; } 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 new file mode 100644 index 000000000..3148c3f1f --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SequenceAttachment.java @@ -0,0 +1,151 @@ +/****************************************************************************** + * 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 frameIndex = (int)(slot.getAttachmentTime() / frameTime); + switch (mode) { + case forward: + frameIndex = Math.min(frameCount - 1, frameIndex); + break; + case backward: + frameIndex = Math.max(frameCount - frameIndex - 1, 0); + break; + case forwardLoop: + frameIndex = frameIndex % frameCount; + break; + case backwardLoop: + frameIndex = frameCount - (frameIndex % frameCount) - 1; + break; + case pingPong: + frameIndex = frameIndex % (frameCount << 1); + if (frameIndex >= frameCount) frameIndex = frameCount - 1 - (frameIndex - frameCount); + break; + case random: + frameIndex = MathUtils.random(frameCount - 1); + } + attachment.setRegion(regions[frameIndex]); + 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/TextureRegionAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/TextureRegionAttachment.java new file mode 100644 index 000000000..d35fb522f --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/TextureRegionAttachment.java @@ -0,0 +1,25 @@ + +package com.esotericsoftware.spine.attachments; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; + +public interface TextureRegionAttachment { + /** 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. */ + public void updateRegion (); + + /** The color to tint the attachment. */ + public Color getColor (); + + /** The name used to find the {@link #getRegion()}. */ + public String getPath (); + + public void setPath (String path); +}