mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-25 22:23:42 +08:00
parent
b920589757
commit
3789ec027d
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 ();
|
||||
}
|
||||
@ -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.
|
||||
* <p>
|
||||
* See <a href="https://esotericsoftware.com/spine-meshes">Mesh attachments</a> 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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
* <p>
|
||||
* See <a href="https://esotericsoftware.com/spine-regions">Region attachments</a> 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.
|
||||
* <p>
|
||||
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
|
||||
* Runtimes Guide.
|
||||
* @param worldVertices The output world vertices. Must have a length >= <code>offset</code> + 8.
|
||||
* @param vertexOffsets The vertex {@link Sequence#getOffsets(int) offsets}.
|
||||
* @param offset The <code>worldVertices</code> index to begin writing values.
|
||||
* @param stride The number of <code>worldVertices</code> 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 <code>x,y</code> values that is the local position of the vertex.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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. */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user