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 d05be3144..dd2489c45 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,7 +36,6 @@ import com.badlogic.gdx.Files.FileType; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3FileHandle; 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; @@ -53,11 +52,11 @@ import com.esotericsoftware.spine.attachments.Sequence; /** Unit tests to ensure {@link AnimationState} is working as expected. */ public class AnimationStateTests { final SkeletonJson json = new SkeletonJson(new AttachmentLoader() { - public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) { + public RegionAttachment newRegionAttachment (Skin skin, String name, String path, Sequence sequence) { return null; } - public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) { + public MeshAttachment newMeshAttachment (Skin skin, String name, String path, Sequence sequence) { 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 eb5e0446e..41c95f282 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,7 +30,6 @@ 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; @@ -48,11 +47,11 @@ public class BonePlotting { static public void main (String[] args) throws Exception { // Create a skeleton loader that doesn't use an atlas and doesn't create any attachments. SkeletonJson json = new SkeletonJson(new AttachmentLoader() { - public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) { + public RegionAttachment newRegionAttachment (Skin skin, String name, String path, Sequence sequence) { return null; } - public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) { + public MeshAttachment newMeshAttachment (Skin skin, String name, String path, 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 3dd50e650..10fa8e0c6 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,7 +49,6 @@ 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; @@ -88,12 +87,9 @@ 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, @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); - attachment.setRegion(region); - return attachment; + public RegionAttachment newRegionAttachment (Skin skin, String name, String path, Sequence sequence) { + findRegions(name, path, sequence); + return new Box2dAttachment(name, sequence); } }; SkeletonJson json = new SkeletonJson(atlasLoader); @@ -231,8 +227,8 @@ public class Box2DExample extends ApplicationAdapter { static class Box2dAttachment extends RegionAttachment { Body body; - public Box2dAttachment (String name) { - super(name); + public Box2dAttachment (String name, Sequence sequence) { + super(name, sequence); } } diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/JsonWriter.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/JsonWriter.java index 6efdc35e5..ddd262757 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/JsonWriter.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/JsonWriter.java @@ -4,7 +4,7 @@ package com.esotericsoftware.spine.utils; import java.util.Locale; public class JsonWriter { - private final StringBuffer buffer = new StringBuffer(); + private final StringBuilder buffer = new StringBuilder(); private int depth = 0; private boolean needsComma = false; diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java index a45a7d4c3..df5f5ee5b 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java @@ -2479,10 +2479,12 @@ public class SkeletonSerializer { json.writeValue("MeshAttachment"); json.writeName("region"); - if (obj.getRegion() == null) { + Sequence sequence = obj.getSequence(); + TextureRegion region = sequence.getRegion(sequence.getSetupIndex()); + if (region == null) { json.writeNull(); } else { - writeTextureRegion(obj.getRegion()); + writeTextureRegion(region); } json.writeName("triangles"); @@ -2501,8 +2503,10 @@ public class SkeletonSerializer { json.writeName("uVs"); json.writeArrayStart(); - for (float item : obj.getUVs()) { - json.writeValue(item); + float[] uvs = sequence.getUVs(sequence.getSetupIndex()); + if (uvs != null) { + for (float item : uvs) + json.writeValue(item); } json.writeArrayEnd(); @@ -2954,24 +2958,32 @@ public class SkeletonSerializer { json.writeName("type"); json.writeValue("RegionAttachment"); + Sequence sequence = obj.getSequence(); + int setupIndex = sequence.getSetupIndex(); + TextureRegion region = sequence.getRegion(setupIndex); + json.writeName("region"); - if (obj.getRegion() == null) { + if (region == null) { json.writeNull(); } else { - writeTextureRegion(obj.getRegion()); + writeTextureRegion(region); } json.writeName("offset"); json.writeArrayStart(); - for (float item : obj.getOffset()) { - json.writeValue(item); + float[] offset = sequence.getOffsets(setupIndex); + if (offset != null) { + for (float item : offset) + json.writeValue(item); } json.writeArrayEnd(); json.writeName("uVs"); json.writeArrayStart(); - for (float item : obj.getUVs()) { - json.writeValue(item); + float[] uvs = sequence.getUVs(setupIndex); + if (uvs != null) { + for (float item : uvs) + json.writeValue(item); } json.writeArrayEnd(); @@ -3003,11 +3015,7 @@ public class SkeletonSerializer { json.writeValue(obj.getPath()); json.writeName("sequence"); - if (obj.getSequence() == null) { - json.writeNull(); - } else { - writeSequence(obj.getSequence()); - } + writeSequence(obj.getSequence()); json.writeName("name"); json.writeValue(obj.getName()); 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 95b4957de..e72153701 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -43,7 +43,7 @@ import com.badlogic.gdx.utils.ObjectSet; import com.esotericsoftware.spine.BoneData.Inherit; import com.esotericsoftware.spine.attachments.Attachment; -import com.esotericsoftware.spine.attachments.HasTextureRegion; +import com.esotericsoftware.spine.attachments.HasSequence; import com.esotericsoftware.spine.attachments.Sequence; import com.esotericsoftware.spine.attachments.Sequence.SequenceMode; import com.esotericsoftware.spine.attachments.VertexAttachment; @@ -1700,13 +1700,12 @@ public class Animation { static private final int MODE = 1, DELAY = 2; final int slotIndex; - final HasTextureRegion attachment; + final HasSequence attachment; public SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) { - super(frameCount, - Property.sequence.ordinal() + "|" + slotIndex + "|" + ((HasTextureRegion)attachment).getSequence().getId()); + super(frameCount, Property.sequence.ordinal() + "|" + slotIndex + "|" + ((HasSequence)attachment).getSequence().getId()); this.slotIndex = slotIndex; - this.attachment = (HasTextureRegion)attachment; + this.attachment = (HasSequence)attachment; } public int getFrameEntries () { @@ -1743,8 +1742,6 @@ public class Animation { if (!(slotAttachment instanceof VertexAttachment vertexAttachment) || vertexAttachment.getTimelineAttachment() != attachment) return; } - Sequence sequence = ((HasTextureRegion)slotAttachment).getSequence(); - if (sequence == null) return; if (direction == out) { if (blend == setup) pose.setSequenceIndex(-1); @@ -1762,7 +1759,7 @@ public class Animation { int modeAndIndex = (int)frames[i + MODE]; float delay = frames[i + DELAY]; - int index = modeAndIndex >> 4, count = sequence.getRegions().length; + int index = modeAndIndex >> 4, count = (((HasSequence)slotAttachment).getSequence()).getRegions().length; SequenceMode mode = SequenceMode.values[modeAndIndex & 0xf]; if (mode != SequenceMode.hold) { index += (time - before) / delay + 0.0001f; 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 d7e5b9d53..50bc00f42 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -495,7 +495,7 @@ public class Skeleton { if (attachment instanceof RegionAttachment region) { verticesLength = 8; vertices = temp.setSize(8); - region.computeWorldVertices(slot, vertices, 0, 2); + region.computeWorldVertices(slot, region.getOffsets(slot.applied), vertices, 0, 2); triangles = quadTriangles; } else if (attachment instanceof MeshAttachment mesh) { verticesLength = mesh.getWorldVerticesLength(); 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 2342a9ddc..a8279d4bf 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -475,7 +475,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); - if (linkedMesh.mesh.getRegion() == null) linkedMesh.mesh.updateRegion(); + linkedMesh.mesh.updateSequence(); } linkedMeshes.clear(); @@ -561,7 +561,7 @@ public class SkeletonBinary extends SkeletonLoader { case region -> { String path = (flags & 16) != 0 ? input.readStringRef() : null; int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff; - Sequence sequence = (flags & 64) != 0 ? readSequence(input) : null; + Sequence sequence = readSequence(input, (flags & 64) != 0); float rotation = (flags & 128) != 0 ? input.readFloat() : 0; float x = input.readFloat(); float y = input.readFloat(); @@ -582,8 +582,7 @@ public class SkeletonBinary extends SkeletonLoader { region.setWidth(width * scale); region.setHeight(height * scale); Color.rgba8888ToColor(region.getColor(), color); - region.setSequence(sequence); - if (region.getRegion() != null) region.updateRegion(); + region.updateSequence(); yield region; } case boundingbox -> { @@ -601,7 +600,7 @@ public class SkeletonBinary extends SkeletonLoader { case mesh -> { String path = (flags & 16) != 0 ? input.readStringRef() : name; int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff; - Sequence sequence = (flags & 64) != 0 ? readSequence(input) : null; + Sequence sequence = readSequence(input, (flags & 64) != 0); int hullLength = input.readInt(true); Vertices vertices = readVertices(input, (flags & 128) != 0); float[] uvs = readFloatArray(input, vertices.length, 1); @@ -619,25 +618,24 @@ public class SkeletonBinary extends SkeletonLoader { if (mesh == null) yield null; mesh.setPath(path); Color.rgba8888ToColor(mesh.getColor(), color); + mesh.setHullLength(hullLength << 1); mesh.setBones(vertices.bones); mesh.setVertices(vertices.vertices); mesh.setWorldVerticesLength(vertices.length); - mesh.setTriangles(triangles); mesh.setRegionUVs(uvs); - if (mesh.getRegion() != null) mesh.updateRegion(); - mesh.setHullLength(hullLength << 1); - mesh.setSequence(sequence); + mesh.setTriangles(triangles); if (nonessential) { mesh.setEdges(edges); mesh.setWidth(width * scale); mesh.setHeight(height * scale); } + mesh.updateSequence(); yield mesh; } case linkedmesh -> { String path = (flags & 16) != 0 ? input.readStringRef() : name; int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff; - Sequence sequence = (flags & 64) != 0 ? readSequence(input) : null; + Sequence sequence = readSequence(input, (flags & 64) != 0); boolean inheritTimelines = (flags & 128) != 0; int skinIndex = input.readInt(true); String parent = input.readStringRef(); @@ -651,7 +649,6 @@ public class SkeletonBinary extends SkeletonLoader { if (mesh == null) yield null; mesh.setPath(path); Color.rgba8888ToColor(mesh.getColor(), color); - mesh.setSequence(sequence); if (nonessential) { mesh.setWidth(width * scale); mesh.setHeight(height * scale); @@ -711,8 +708,9 @@ public class SkeletonBinary extends SkeletonLoader { }; } - private Sequence readSequence (SkeletonInput input) throws IOException { - var sequence = new Sequence(input.readInt(true)); + private Sequence readSequence (SkeletonInput input, boolean hasPathSuffix) throws IOException { + if (!hasPathSuffix) return new Sequence(1, false); + var sequence = new Sequence(input.readInt(true), true); sequence.setStart(input.readInt(true)); sequence.setDigits(input.readInt(true)); sequence.setSetupIndex(input.readInt(true)); 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 64cdeca78..2981712c2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -492,7 +492,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.getRegion() != null) linkedMesh.mesh.updateRegion(); + linkedMesh.mesh.updateSequence(); } linkedMeshes.clear(); @@ -580,12 +580,11 @@ 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()); - if (region.getRegion() != null) region.updateRegion(); + region.updateSequence(); yield region; } case boundingbox -> { @@ -609,7 +608,6 @@ 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) { @@ -622,10 +620,11 @@ public class SkeletonJson extends SkeletonLoader { readVertices(map, mesh, uvs.length); mesh.setTriangles(map.require("triangles").asShortArray()); mesh.setRegionUVs(uvs); - 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()); + + mesh.updateSequence(); yield mesh; } case path -> { @@ -680,8 +679,8 @@ public class SkeletonJson extends SkeletonLoader { } private Sequence readSequence (@Null JsonValue map) { - if (map == null) return null; - var sequence = new Sequence(map.getInt("count")); + if (map == null) return new Sequence(1, false); + var sequence = new Sequence(map.getInt("count"), true); sequence.setStart(map.getInt("start", 1)); sequence.setDigits(map.getInt("digits", 0)); sequence.setSetupIndex(map.getInt("setup", 0)); 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 b6546053d..3e4c7fac7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -41,6 +41,7 @@ import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; +import com.esotericsoftware.spine.attachments.Sequence; import com.esotericsoftware.spine.attachments.SkeletonAttachment; import com.esotericsoftware.spine.utils.SkeletonClipping; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; @@ -83,7 +84,11 @@ public class SkeletonRenderer { SlotPose pose = slot.applied; Attachment attachment = pose.attachment; if (attachment instanceof RegionAttachment region) { - region.computeWorldVertices(slot, vertices, 0, 5); + Sequence sequence = region.getSequence(); + int sequenceIndex = sequence.resolveIndex(pose); + Texture texture = sequence.getRegion(sequenceIndex).getTexture(); + float[] uvs = sequence.getUVs(sequenceIndex); + region.computeWorldVertices(slot, sequence.getOffsets(sequenceIndex), vertices, 0, 5); Color color = region.getColor(), slotColor = pose.getColor(); float alpha = a * slotColor.a * color.a * 255; float multiplier = pmaColors ? alpha : 255; @@ -102,14 +107,13 @@ public class SkeletonRenderer { | (int)(b * slotColor.b * color.b * multiplier) << 16 // | (int)(g * slotColor.g * color.g * multiplier) << 8 // | (int)(r * slotColor.r * color.r * multiplier)); - float[] uvs = region.getUVs(); for (int u = 0, v = 2; u < 8; u += 2, v += 5) { vertices[v] = c; vertices[v + 1] = uvs[u]; vertices[v + 2] = uvs[u + 1]; } - batch.draw(region.getRegion().getTexture(), vertices, 0, 20); + batch.draw(texture, vertices, 0, 20); } else if (attachment instanceof ClippingAttachment) { throw new RuntimeException(batch.getClass().getSimpleName() @@ -153,10 +157,12 @@ public class SkeletonRenderer { if (attachment instanceof RegionAttachment region) { verticesLength = 20; vertices = this.vertices.items; - region.computeWorldVertices(slot, vertices, 0, 5); + Sequence sequence = region.getSequence(); + int sequenceIndex = sequence.resolveIndex(pose); + region.computeWorldVertices(slot, sequence.getOffsets(sequenceIndex), vertices, 0, 5); triangles = quadTriangles; - texture = region.getRegion().getTexture(); - uvs = region.getUVs(); + texture = sequence.getRegion(sequenceIndex).getTexture(); + uvs = sequence.getUVs(sequenceIndex); color = region.getColor(); } else if (attachment instanceof MeshAttachment mesh) { @@ -165,8 +171,10 @@ public class SkeletonRenderer { vertices = this.vertices.setSize(verticesLength); mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 5); triangles = mesh.getTriangles(); - texture = mesh.getRegion().getTexture(); - uvs = mesh.getUVs(); + Sequence sequence = mesh.getSequence(); + int sequenceIndex = sequence.resolveIndex(pose); + texture = sequence.getRegion(sequenceIndex).getTexture(); + uvs = sequence.getUVs(sequenceIndex); color = mesh.getColor(); } else if (attachment instanceof ClippingAttachment clip) { @@ -248,10 +256,12 @@ public class SkeletonRenderer { if (attachment instanceof RegionAttachment region) { verticesLength = 24; vertices = this.vertices.items; - region.computeWorldVertices(slot, vertices, 0, 6); + Sequence sequence = region.getSequence(); + int sequenceIndex = sequence.resolveIndex(pose); + region.computeWorldVertices(slot, sequence.getOffsets(sequenceIndex), vertices, 0, 6); triangles = quadTriangles; - texture = region.getRegion().getTexture(); - uvs = region.getUVs(); + texture = sequence.getRegion(sequenceIndex).getTexture(); + uvs = sequence.getUVs(sequenceIndex); color = region.getColor(); } else if (attachment instanceof MeshAttachment mesh) { @@ -260,8 +270,10 @@ public class SkeletonRenderer { vertices = this.vertices.setSize(verticesLength); mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 6); triangles = mesh.getTriangles(); - texture = mesh.getRegion().getTexture(); - uvs = mesh.getUVs(); + Sequence sequence = mesh.getSequence(); + int sequenceIndex = sequence.resolveIndex(pose); + texture = sequence.getRegion(sequenceIndex).getTexture(); + uvs = sequence.getUVs(sequenceIndex); color = mesh.getColor(); } else if (attachment instanceof ClippingAttachment clip) { 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 b6537a06b..8950daa7b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java @@ -126,7 +126,8 @@ public class SkeletonRendererDebug { if (!slot.bone.active) continue; if (slot.pose.attachment instanceof RegionAttachment region) { float[] vertices = this.vertices.items; - region.computeWorldVertices(slot, vertices, 0, 2); + float[] offsets = region.getOffsets(slot.applied); + region.computeWorldVertices(slot, offsets, 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/attachments/AtlasAttachmentLoader.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java index 0de792108..8aa8a71f3 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 @@ -32,7 +32,6 @@ 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; @@ -55,40 +54,27 @@ public class AtlasAttachmentLoader implements AttachmentLoader { this.allowMissingRegions = allowMissingRegions; } - private void loadSequence (String name, String basePath, Sequence sequence) { + protected void findRegions (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 && !allowMissingRegions) - throw new RuntimeException("Region not found in atlas: " + path + " (sequence: " + name + ")"); - } + for (int i = 0, n = regions.length; i < n; i++) + regions[i] = findRegion(name, sequence.getPath(basePath, i)); } - public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) { - var attachment = new RegionAttachment(name); - if (sequence != null) - loadSequence(name, path, sequence); - else { - AtlasRegion region = atlas.findRegion(path); - if (region == null && !allowMissingRegions) - throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")"); - attachment.setRegion(region); - } - return attachment; + protected AtlasRegion findRegion (String name, String path) { + AtlasRegion region = atlas.findRegion(path); + if (region == null && !allowMissingRegions) + throw new RuntimeException("Region not found in atlas: " + path + " (attachment: " + name + ")"); + return region; } - public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) { - var attachment = new MeshAttachment(name); - if (sequence != null) - loadSequence(name, path, sequence); - else { - AtlasRegion region = atlas.findRegion(path); - if (region == null && !allowMissingRegions) - throw new RuntimeException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); - attachment.setRegion(region); - } - return attachment; + public RegionAttachment newRegionAttachment (Skin skin, String name, String path, Sequence sequence) { + findRegions(name, path, sequence); + return new RegionAttachment(name, sequence); + } + + public MeshAttachment newMeshAttachment (Skin skin, String name, String path, Sequence sequence) { + findRegions(name, path, sequence); + return new MeshAttachment(name, sequence); } public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String 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 b1f956388..d62196e55 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,10 +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, @Null Sequence sequence); + public @Null RegionAttachment newRegionAttachment (Skin skin, String name, String path, 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, @Null Sequence sequence); + public @Null MeshAttachment newMeshAttachment (Skin skin, String name, String path, 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/HasTextureRegion.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/HasSequence.java similarity index 70% rename from spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/HasTextureRegion.java rename to spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/HasSequence.java index e36b2df4b..20e1d2600 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/HasTextureRegion.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/HasSequence.java @@ -30,29 +30,16 @@ 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()}. */ +public interface HasSequence { public String getPath (); public void setPath (String path); - public TextureRegion getRegion (); - - /** 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 (); + /** Calls {@link Sequence#update(HasSequence)} on this attachment's sequence. */ + public void updateSequence (); - public void setSequence (@Null Sequence sequence); + public Sequence getSequence (); } 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 f63a74739..a49516583 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,29 +36,27 @@ 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.Skeleton; -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. *
* See Mesh attachments in the Spine User Guide. */ -public class MeshAttachment extends VertexAttachment implements HasTextureRegion { - private TextureRegion region; - private String path; - private float[] regionUVs, uvs; +public class MeshAttachment extends VertexAttachment implements HasSequence { + private final Sequence sequence; + float[] regionUVs; private short[] triangles; - private final Color color = new Color(1, 1, 1, 1); private int hullLength; + private String path; + private final Color color = new Color(1, 1, 1, 1); private @Null MeshAttachment parentMesh; - private @Null Sequence sequence; // Nonessential. private @Null short[] edges; private float width, height; - public MeshAttachment (String name) { + public MeshAttachment (String name, Sequence sequence) { super(name); + if (sequence == null) throw new IllegalArgumentException("sequence cannot be null."); + this.sequence = sequence; } /** Copy constructor. Use {@link #newLinkedMesh()} if the other mesh is a linked mesh. */ @@ -67,21 +65,17 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion 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; - sequence = other.sequence != null ? new Sequence(other.sequence) : null; + sequence = new Sequence(other.sequence); // Nonessential. if (other.edges != null) { @@ -92,99 +86,6 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion height = other.height; } - public void setRegion (TextureRegion region) { - if (region == null) throw new IllegalArgumentException("region cannot be null."); - this.region = region; - } - - public @Null TextureRegion getRegion () { - return region; - } - - /** Calculates {@link #uvs} using the {@link #regionUVs} and region. Must be called if the region, the region's properties, or - * the {@link #regionUVs} are changed. */ - 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; - int n = uvs.length; - float u, v, width, height; - if (region instanceof AtlasRegion region) { - u = region.getU(); - v = region.getV(); - float textureWidth = region.getTexture().getWidth(), textureHeight = region.getTexture().getHeight(); - switch (region.degrees) { - case 90 -> { - u -= (region.originalHeight - region.offsetY - region.packedWidth) / textureWidth; - v -= (region.originalWidth - region.offsetX - region.packedHeight) / textureHeight; - width = region.originalHeight / textureWidth; - height = region.originalWidth / textureHeight; - for (int i = 0; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + (1 - regionUVs[i]) * height; - } - return; - } - case 180 -> { - u -= (region.originalWidth - region.offsetX - region.packedWidth) / textureWidth; - v -= region.offsetY / textureHeight; - width = region.originalWidth / textureWidth; - height = region.originalHeight / textureHeight; - for (int i = 0; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i]) * width; - uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; - } - return; - } - case 270 -> { - u -= region.offsetY / textureWidth; - v -= region.offsetX / textureHeight; - width = region.originalHeight / textureWidth; - height = region.originalWidth / textureHeight; - for (int i = 0; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i + 1]) * width; - uvs[i + 1] = v + regionUVs[i] * height; - } - return; - } - default -> { - u -= region.offsetX / textureWidth; - v -= (region.originalHeight - region.offsetY - region.packedHeight) / textureHeight; - width = region.originalWidth / textureWidth; - height = region.originalHeight / textureHeight; - } - } - } else if (region == null) { - u = v = 0; - width = height = 1; - } else { - u = region.getU(); - v = region.getV(); - width = region.getU2() - u; - height = region.getV2() - v; - } - for (int i = 0; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - - /** If the attachment has a {@link #sequence}, the region may be changed. */ - public void computeWorldVertices (Skeleton skeleton, Slot slot, int start, int count, float[] worldVertices, int offset, - int stride) { - if (sequence != null) sequence.apply(slot.getAppliedPose(), this); - super.computeWorldVertices(skeleton, slot, start, count, worldVertices, offset, stride); - } - - /** Triplets of vertex indices which describe the mesh's triangulation. */ - public short[] getTriangles () { - return triangles; - } - - public void setTriangles (short[] triangles) { - this.triangles = triangles; - } - /** The UV pair for each vertex, normalized within the texture region. */ public float[] getRegionUVs () { return regionUVs; @@ -195,27 +96,13 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion this.regionUVs = regionUVs; } - /** The UV pair for each vertex, normalized within the entire texture. - *
- * See {@link #updateRegion()}. */ - public float[] getUVs () { - return uvs; + /** Triplets of vertex indices which describe the mesh's triangulation. */ + public short[] getTriangles () { + return triangles; } - public void setUVs (float[] uvs) { - this.uvs = uvs; - } - - public Color getColor () { - return color; - } - - public String getPath () { - return path; - } - - public void setPath (String path) { - this.path = path; + public void setTriangles (short[] triangles) { + this.triangles = triangles; } /** The number of entries at the beginning of {@link #vertices} that make up the mesh hull. */ @@ -227,12 +114,32 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion this.hullLength = hullLength; } + public Sequence getSequence () { + return sequence; + } + + public void updateSequence () { + sequence.update(this); + } + + public String getPath () { + return path; + } + + public void setPath (String path) { + this.path = path; + } + + public Color getColor () { + return color; + } + public void setEdges (short[] edges) { this.edges = edges; } /** Vertex index pairs describing edges for controlling triangulation, or be null if nonessential data was not exported. Mesh - * triangles will never cross edges. Triangulation is not performed at runtime. */ + * triangles never cross edges. Triangulation is not performed at runtime. */ public @Null short[] getEdges () { return edges; } @@ -255,14 +162,6 @@ 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). */ @@ -287,17 +186,81 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion /** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. */ public MeshAttachment newLinkedMesh () { - var mesh = new MeshAttachment(name); + var mesh = new MeshAttachment(name, new Sequence(sequence)); mesh.timelineAttachment = timelineAttachment; - mesh.region = region; mesh.path = path; mesh.color.set(color); mesh.setParentMesh(parentMesh != null ? parentMesh : this); - if (mesh.getRegion() != null) mesh.updateRegion(); + mesh.updateSequence(); return mesh; } public MeshAttachment copy () { return parentMesh != null ? newLinkedMesh() : new MeshAttachment(this); } + + /** Computes {@link Sequence#getUVs(int) UVs} for a mesh attachment. + * @param uvs Output array for the computed UVs, same length as regionUVs. */ + static void computeUVs (@Null TextureRegion region, float[] regionUVs, float[] uvs) { + int n = uvs.length; + float u, v, width, height; + if (region instanceof AtlasRegion r) { + u = r.getU(); + v = r.getV(); + float textureWidth = r.getTexture().getWidth(), textureHeight = r.getTexture().getHeight(); + switch (r.degrees) { + case 90 -> { + u -= (r.originalHeight - r.offsetY - r.packedWidth) / textureWidth; + v -= (r.originalWidth - r.offsetX - r.packedHeight) / textureHeight; + width = r.originalHeight / textureWidth; + height = r.originalWidth / textureHeight; + for (int i = 0; i < n; i += 2) { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + (1 - regionUVs[i]) * height; + } + return; + } + case 180 -> { + u -= (r.originalWidth - r.offsetX - r.packedWidth) / textureWidth; + v -= r.offsetY / textureHeight; + width = r.originalWidth / textureWidth; + height = r.originalHeight / textureHeight; + for (int i = 0; i < n; i += 2) { + uvs[i] = u + (1 - regionUVs[i]) * width; + uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; + } + return; + } + case 270 -> { + u -= r.offsetY / textureWidth; + v -= r.offsetX / textureHeight; + width = r.originalHeight / textureWidth; + height = r.originalWidth / textureHeight; + for (int i = 0; i < n; i += 2) { + uvs[i] = u + (1 - regionUVs[i + 1]) * width; + uvs[i + 1] = v + regionUVs[i] * height; + } + return; + } + default -> { + u -= r.offsetX / textureWidth; + v -= (r.originalHeight - r.offsetY - r.packedHeight) / textureHeight; + width = r.originalWidth / textureWidth; + height = r.originalHeight / textureHeight; + } + } + } else if (region == null) { + u = v = 0; + width = height = 1; + } else { + u = region.getU(); + v = region.getV(); + width = region.getU2() - u; + height = region.getV2() - v; + } + for (int i = 0; i < n; i += 2) { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } } 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 261bcb9b0..3471de9d8 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 @@ -38,32 +38,31 @@ import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.BonePose; import com.esotericsoftware.spine.Slot; +import com.esotericsoftware.spine.SlotPose; /** An attachment that displays a textured quadrilateral. *
* See Region attachments in the Spine User Guide. */ -public class RegionAttachment extends Attachment implements HasTextureRegion { +public class RegionAttachment extends Attachment implements HasSequence { 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 final Sequence sequence; + float x, y, scaleX = 1, scaleY = 1, rotation, width, height; private String path; - private float x, y, scaleX = 1, scaleY = 1, rotation, width, height; - 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) { + public RegionAttachment (String name, Sequence sequence) { super(name); + if (sequence == null) throw new IllegalArgumentException("sequence cannot be null."); + this.sequence = sequence; } /** Copy constructor. */ protected RegionAttachment (RegionAttachment other) { super(other); - region = other.region; path = other.path; x = other.x; y = other.y; @@ -72,148 +71,50 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { 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); - sequence = other.sequence != null ? new Sequence(other.sequence) : null; + sequence = new Sequence(other.sequence); } - /** Calculates the {@link #offset} and {@link #uvs} using the region and the attachment's transform. Must be called if the - * region, the region's properties, or the transform are changed. */ - public void updateRegion () { - float width = getWidth(), height = getHeight(); - float localX2 = width / 2; - float localY2 = height / 2; - float localX = -localX2; - float localY = -localY2; - boolean rotated = false; - if (region instanceof AtlasRegion 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 { - localX2 -= (region.originalWidth - region.offsetX - region.packedWidth) / region.originalWidth * width; - localY2 -= (region.originalHeight - region.offsetY - region.packedHeight) / region.originalHeight * height; - } - } - float scaleX = getScaleX(), scaleY = getScaleY(); - localX *= scaleX; - localY *= scaleY; - localX2 *= scaleX; - localY2 *= scaleY; - float r = getRotation() * degRad, cos = cos(r), sin = sin(r); - float x = getX(), y = getY(); - float localXCos = localX * cos + x; - float localXSin = localX * sin; - float localYCos = localY * cos + y; - float localYSin = localY * sin; - float localX2Cos = localX2 * cos + x; - float localX2Sin = localX2 * sin; - float localY2Cos = localY2 * cos + y; - float localY2Sin = localY2 * sin; - float[] offset = this.offset; - offset[BLX] = localXCos - localYSin; - offset[BLY] = localYCos + localXSin; - offset[ULX] = localXCos - localY2Sin; - offset[ULY] = localY2Cos + localXSin; - offset[URX] = localX2Cos - localY2Sin; - offset[URY] = localY2Cos + localX2Sin; - offset[BRX] = localX2Cos - localYSin; - offset[BRY] = localYCos + localX2Sin; - - float[] uvs = this.uvs; - if (region == null) { - uvs[BLX] = 0; - uvs[BLY] = 0; - uvs[ULX] = 0; - uvs[ULY] = 1; - uvs[URX] = 1; - uvs[URY] = 1; - uvs[BRX] = 1; - uvs[BRY] = 0; - } else if (rotated) { - uvs[BLX] = region.getU2(); - uvs[BLY] = region.getV(); - uvs[ULX] = region.getU2(); - uvs[ULY] = region.getV2(); - uvs[URX] = region.getU(); - uvs[URY] = region.getV2(); - uvs[BRX] = region.getU(); - uvs[BRY] = region.getV(); - } else { - uvs[BLX] = region.getU2(); - uvs[BLY] = region.getV2(); - uvs[ULX] = region.getU(); - uvs[ULY] = region.getV2(); - uvs[URX] = region.getU(); - uvs[URY] = region.getV(); - uvs[BRX] = region.getU2(); - uvs[BRY] = region.getV(); - } - } - - public void setRegion (TextureRegion region) { - if (region == null) throw new IllegalArgumentException("region cannot be null."); - this.region = region; - } - - public @Null TextureRegion getRegion () { - return region; - } - - /** Transforms the attachment's four vertices to world coordinates. If the attachment has a {@link #sequence}, the region may - * be changed. + /** Transforms the attachment's four vertices to world coordinates. *
* See World transforms in the Spine
* Runtimes Guide.
* @param worldVertices The output world vertices. Must have a length >= offset + 8.
+ * @param vertexOffsets The vertex {@link Sequence#getOffsets(int) offsets}.
* @param offset The worldVertices index to begin writing values.
* @param stride The number of worldVertices entries between the value pairs written. */
- public void computeWorldVertices (Slot slot, float[] worldVertices, int offset, int stride) {
- if (sequence != null) sequence.apply(slot.getAppliedPose(), this);
-
- float[] vertexOffset = this.offset;
+ public void computeWorldVertices (Slot slot, float[] vertexOffsets, float[] worldVertices, int offset, int stride) {
BonePose bone = slot.getBone().getAppliedPose();
float x = bone.getWorldX(), y = bone.getWorldY();
float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD();
- float offsetX, offsetY;
- offsetX = vertexOffset[BRX];
- offsetY = vertexOffset[BRY];
+ float offsetX = vertexOffsets[BRX];
+ float offsetY = vertexOffsets[BRY];
worldVertices[offset] = offsetX * a + offsetY * b + x; // br
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
offset += stride;
- offsetX = vertexOffset[BLX];
- offsetY = vertexOffset[BLY];
+ offsetX = vertexOffsets[BLX];
+ offsetY = vertexOffsets[BLY];
worldVertices[offset] = offsetX * a + offsetY * b + x; // bl
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
offset += stride;
- offsetX = vertexOffset[ULX];
- offsetY = vertexOffset[ULY];
+ offsetX = vertexOffsets[ULX];
+ offsetY = vertexOffsets[ULY];
worldVertices[offset] = offsetX * a + offsetY * b + x; // ul
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
offset += stride;
- offsetX = vertexOffset[URX];
- offsetY = vertexOffset[URY];
+ offsetX = vertexOffsets[URX];
+ offsetY = vertexOffsets[URY];
worldVertices[offset] = offsetX * a + offsetY * b + x; // ur
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
}
- /** For each of the 4 vertices, a pair of x,y values that is the local position of the vertex.
- *
- * See {@link #updateRegion()}. */ - public float[] getOffset () { - return offset; - } - - public float[] getUVs () { - return uvs; + /** Returns the vertex {@link Sequence#getOffsets(int) offsets} for the specified slot pose. */ + public float[] getOffsets (SlotPose pose) { + return sequence.getOffsets(sequence.resolveIndex(pose)); } /** The local x translation. */ @@ -279,8 +180,12 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { this.height = height; } - public Color getColor () { - return color; + public Sequence getSequence () { + return sequence; + } + + public void updateSequence () { + sequence.update(this); } public String getPath () { @@ -291,15 +196,80 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { this.path = path; } - public @Null Sequence getSequence () { - return sequence; - } - - public void setSequence (@Null Sequence sequence) { - this.sequence = sequence; + public Color getColor () { + return color; } public RegionAttachment copy () { return new RegionAttachment(this); } + + /** Computes {@link Sequence#getUVs(int) UVs} and {@link Sequence#getOffsets(int) offsets} for a region attachment. + * @param uvs Output array for the computed UVs, length of 8. + * @param offset Output array for the computed vertex offsets, length of 8. */ + static void computeUVs (@Null TextureRegion region, float x, float y, float scaleX, float scaleY, float rotation, float width, + float height, float[] offset, float[] uvs) { + float localX2 = width / 2, localY2 = height / 2; + float localX = -localX2, localY = -localY2; + boolean rotated = false; + if (region instanceof AtlasRegion r) { + localX += r.offsetX / r.originalWidth * width; + localY += r.offsetY / r.originalHeight * height; + if (r.degrees == 90) { + rotated = true; + localX2 -= (r.originalWidth - r.offsetX - r.packedHeight) / r.originalWidth * width; + localY2 -= (r.originalHeight - r.offsetY - r.packedWidth) / r.originalHeight * height; + } else { + localX2 -= (r.originalWidth - r.offsetX - r.packedWidth) / r.originalWidth * width; + localY2 -= (r.originalHeight - r.offsetY - r.packedHeight) / r.originalHeight * height; + } + } + localX *= scaleX; + localY *= scaleY; + localX2 *= scaleX; + localY2 *= scaleY; + float r = rotation * degRad, cos = cos(r), sin = sin(r); + float localXCos = localX * cos + x; + float localXSin = localX * sin; + float localYCos = localY * cos + y; + float localYSin = localY * sin; + float localX2Cos = localX2 * cos + x; + float localX2Sin = localX2 * sin; + float localY2Cos = localY2 * cos + y; + float localY2Sin = localY2 * sin; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + if (region == null) { + uvs[BLX] = 0; + uvs[BLY] = 0; + uvs[ULX] = 0; + uvs[ULY] = 1; + uvs[URX] = 1; + uvs[URY] = 1; + uvs[BRX] = 1; + uvs[BRY] = 0; + } else { + uvs[BLX] = region.getU2(); + uvs[ULY] = region.getV2(); + uvs[URX] = region.getU(); + uvs[BRY] = region.getV(); + if (rotated) { + uvs[BLY] = region.getV(); + uvs[ULX] = region.getU2(); + uvs[URY] = region.getV2(); + uvs[BRX] = region.getU(); + } else { + uvs[BLY] = region.getV2(); + uvs[ULX] = region.getU(); + uvs[URY] = region.getV(); + uvs[BRX] = region.getU2(); + } + } + } } 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 ba9db38b5..955d02f32 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 @@ -35,46 +35,88 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.esotericsoftware.spine.SlotPose; +/** Holds texture regions, UVs, and vertex offsets for rendering a region or mesh attachment. {@link #getRegions() Regions} must + * be populated and {@link #update(HasSequence)} called before use. */ public class Sequence { static private int nextID; private final int id = nextID(); private final TextureRegion[] regions; + private final boolean pathSuffix; + private float[][] uvs, offsets; private int start, digits, setupIndex; - public Sequence (int count) { + public Sequence (int count, boolean pathSuffix) { regions = new TextureRegion[count]; + this.pathSuffix = pathSuffix; } /** Copy constructor. */ protected Sequence (Sequence other) { - regions = new TextureRegion[other.regions.length]; - arraycopy(other.regions, 0, regions, 0, regions.length); + int regionCount = other.regions.length; + regions = new TextureRegion[regionCount]; + arraycopy(other.regions, 0, regions, 0, regionCount); start = other.start; digits = other.digits; setupIndex = other.setupIndex; - } + pathSuffix = other.pathSuffix; - public void apply (SlotPose slot, HasTextureRegion attachment) { - int index = slot.getSequenceIndex(); - if (index == -1) index = setupIndex; - if (index >= regions.length) index = regions.length - 1; - TextureRegion region = regions[index]; - if (attachment.getRegion() != region) { - attachment.setRegion(region); - attachment.updateRegion(); + if (other.uvs != null) { + int length = other.uvs[0].length; + uvs = new float[regionCount][length]; + for (int i = 0; i < regionCount; i++) + arraycopy(other.uvs[i], 0, uvs[i], 0, length); + } + if (other.offsets != null) { + offsets = new float[regionCount][8]; + for (int i = 0; i < regionCount; i++) + arraycopy(other.offsets[i], 0, offsets[i], 0, 8); } } - public String getPath (String basePath, int index) { - var buffer = new StringBuilder(basePath.length() + digits); - buffer.append(basePath); - String frame = Integer.toString(start + index); - for (int i = digits - frame.length(); i > 0; i--) - buffer.append('0'); - buffer.append(frame); - return buffer.toString(); + /** Computes UVs and offsets for the specified attachment. Must be called if the regions or attachment properties are + * changed. */ + public void update (HasSequence attachment) { + int regionCount = regions.length; + if (attachment instanceof RegionAttachment region) { + uvs = new float[regionCount][8]; + offsets = new float[regionCount][8]; + for (int i = 0; i < regionCount; i++) { + RegionAttachment.computeUVs(regions[i], region.x, region.y, region.scaleX, region.scaleY, region.rotation, + region.width, region.height, offsets[i], uvs[i]); + } + } else if (attachment instanceof MeshAttachment mesh) { + float[] regionUVs = mesh.regionUVs; + uvs = new float[regionCount][regionUVs.length]; + offsets = null; + for (int i = 0; i < regionCount; i++) + MeshAttachment.computeUVs(regions[i], regionUVs, uvs[i]); + } + } + + public TextureRegion[] getRegions () { + return regions; + } + + public int resolveIndex (SlotPose pose) { + int index = pose.getSequenceIndex(); + if (index == -1) index = setupIndex; + if (index >= regions.length) index = regions.length - 1; + return index; + } + + public TextureRegion getRegion (int index) { + return regions[index]; + } + + public float[] getUVs (int index) { + return uvs[index]; + } + + /** Returns vertex offsets from the center of a {@link RegionAttachment}. Invalid to call for a {@link MeshAttachment}. */ + public float[] getOffsets (int index) { + return offsets[index]; } public int getStart () { @@ -102,8 +144,19 @@ public class Sequence { this.setupIndex = index; } - public TextureRegion[] getRegions () { - return regions; + public boolean getPathSuffix () { + return pathSuffix; + } + + public String getPath (String basePath, int index) { + if (!pathSuffix) return basePath; + var buffer = new StringBuilder(basePath.length() + digits); + buffer.append(basePath); + String frame = Integer.toString(start + index); + for (int i = digits - frame.length(); i > 0; i--) + buffer.append('0'); + buffer.append(frame); + return buffer.toString(); } /** Returns a unique ID for this attachment. */