mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +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.backends.lwjgl3.Lwjgl3FileHandle;
|
||||||
import com.badlogic.gdx.math.MathUtils;
|
import com.badlogic.gdx.math.MathUtils;
|
||||||
import com.badlogic.gdx.utils.Array;
|
import com.badlogic.gdx.utils.Array;
|
||||||
import com.badlogic.gdx.utils.Null;
|
|
||||||
import com.badlogic.gdx.utils.Pool;
|
import com.badlogic.gdx.utils.Pool;
|
||||||
|
|
||||||
import com.esotericsoftware.spine.AnimationState.AnimationStateListener;
|
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. */
|
/** Unit tests to ensure {@link AnimationState} is working as expected. */
|
||||||
public class AnimationStateTests {
|
public class AnimationStateTests {
|
||||||
final SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
|
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;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,6 @@
|
|||||||
package com.esotericsoftware.spine;
|
package com.esotericsoftware.spine;
|
||||||
|
|
||||||
import com.badlogic.gdx.files.FileHandle;
|
import com.badlogic.gdx.files.FileHandle;
|
||||||
import com.badlogic.gdx.utils.Null;
|
|
||||||
|
|
||||||
import com.esotericsoftware.spine.Animation.MixBlend;
|
import com.esotericsoftware.spine.Animation.MixBlend;
|
||||||
import com.esotericsoftware.spine.Animation.MixDirection;
|
import com.esotericsoftware.spine.Animation.MixDirection;
|
||||||
@ -48,11 +47,11 @@ public class BonePlotting {
|
|||||||
static public void main (String[] args) throws Exception {
|
static public void main (String[] args) throws Exception {
|
||||||
// Create a skeleton loader that doesn't use an atlas and doesn't create any attachments.
|
// Create a skeleton loader that doesn't use an atlas and doesn't create any attachments.
|
||||||
SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
|
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;
|
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;
|
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.PolygonShape;
|
||||||
import com.badlogic.gdx.physics.box2d.World;
|
import com.badlogic.gdx.physics.box2d.World;
|
||||||
import com.badlogic.gdx.utils.Array;
|
import com.badlogic.gdx.utils.Array;
|
||||||
import com.badlogic.gdx.utils.Null;
|
|
||||||
import com.badlogic.gdx.utils.ScreenUtils;
|
import com.badlogic.gdx.utils.ScreenUtils;
|
||||||
|
|
||||||
import com.esotericsoftware.spine.Animation.MixBlend;
|
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
|
// This loader creates Box2dAttachments instead of RegionAttachments for an easy way to keep track of the Box2D body for
|
||||||
// each attachment.
|
// each attachment.
|
||||||
AtlasAttachmentLoader atlasLoader = new AtlasAttachmentLoader(atlas) {
|
AtlasAttachmentLoader atlasLoader = new AtlasAttachmentLoader(atlas) {
|
||||||
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
|
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, Sequence sequence) {
|
||||||
Box2dAttachment attachment = new Box2dAttachment(name);
|
findRegions(name, path, sequence);
|
||||||
AtlasRegion region = atlas.findRegion(attachment.getName());
|
return new Box2dAttachment(name, sequence);
|
||||||
if (region == null) throw new RuntimeException("Region not found in atlas: " + attachment);
|
|
||||||
attachment.setRegion(region);
|
|
||||||
return attachment;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
SkeletonJson json = new SkeletonJson(atlasLoader);
|
SkeletonJson json = new SkeletonJson(atlasLoader);
|
||||||
@ -231,8 +227,8 @@ public class Box2DExample extends ApplicationAdapter {
|
|||||||
static class Box2dAttachment extends RegionAttachment {
|
static class Box2dAttachment extends RegionAttachment {
|
||||||
Body body;
|
Body body;
|
||||||
|
|
||||||
public Box2dAttachment (String name) {
|
public Box2dAttachment (String name, Sequence sequence) {
|
||||||
super(name);
|
super(name, sequence);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ package com.esotericsoftware.spine.utils;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class JsonWriter {
|
public class JsonWriter {
|
||||||
private final StringBuffer buffer = new StringBuffer();
|
private final StringBuilder buffer = new StringBuilder();
|
||||||
private int depth = 0;
|
private int depth = 0;
|
||||||
private boolean needsComma = false;
|
private boolean needsComma = false;
|
||||||
|
|
||||||
|
|||||||
@ -2479,10 +2479,12 @@ public class SkeletonSerializer {
|
|||||||
json.writeValue("MeshAttachment");
|
json.writeValue("MeshAttachment");
|
||||||
|
|
||||||
json.writeName("region");
|
json.writeName("region");
|
||||||
if (obj.getRegion() == null) {
|
Sequence sequence = obj.getSequence();
|
||||||
|
TextureRegion region = sequence.getRegion(sequence.getSetupIndex());
|
||||||
|
if (region == null) {
|
||||||
json.writeNull();
|
json.writeNull();
|
||||||
} else {
|
} else {
|
||||||
writeTextureRegion(obj.getRegion());
|
writeTextureRegion(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
json.writeName("triangles");
|
json.writeName("triangles");
|
||||||
@ -2501,8 +2503,10 @@ public class SkeletonSerializer {
|
|||||||
|
|
||||||
json.writeName("uVs");
|
json.writeName("uVs");
|
||||||
json.writeArrayStart();
|
json.writeArrayStart();
|
||||||
for (float item : obj.getUVs()) {
|
float[] uvs = sequence.getUVs(sequence.getSetupIndex());
|
||||||
json.writeValue(item);
|
if (uvs != null) {
|
||||||
|
for (float item : uvs)
|
||||||
|
json.writeValue(item);
|
||||||
}
|
}
|
||||||
json.writeArrayEnd();
|
json.writeArrayEnd();
|
||||||
|
|
||||||
@ -2954,24 +2958,32 @@ public class SkeletonSerializer {
|
|||||||
json.writeName("type");
|
json.writeName("type");
|
||||||
json.writeValue("RegionAttachment");
|
json.writeValue("RegionAttachment");
|
||||||
|
|
||||||
|
Sequence sequence = obj.getSequence();
|
||||||
|
int setupIndex = sequence.getSetupIndex();
|
||||||
|
TextureRegion region = sequence.getRegion(setupIndex);
|
||||||
|
|
||||||
json.writeName("region");
|
json.writeName("region");
|
||||||
if (obj.getRegion() == null) {
|
if (region == null) {
|
||||||
json.writeNull();
|
json.writeNull();
|
||||||
} else {
|
} else {
|
||||||
writeTextureRegion(obj.getRegion());
|
writeTextureRegion(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
json.writeName("offset");
|
json.writeName("offset");
|
||||||
json.writeArrayStart();
|
json.writeArrayStart();
|
||||||
for (float item : obj.getOffset()) {
|
float[] offset = sequence.getOffsets(setupIndex);
|
||||||
json.writeValue(item);
|
if (offset != null) {
|
||||||
|
for (float item : offset)
|
||||||
|
json.writeValue(item);
|
||||||
}
|
}
|
||||||
json.writeArrayEnd();
|
json.writeArrayEnd();
|
||||||
|
|
||||||
json.writeName("uVs");
|
json.writeName("uVs");
|
||||||
json.writeArrayStart();
|
json.writeArrayStart();
|
||||||
for (float item : obj.getUVs()) {
|
float[] uvs = sequence.getUVs(setupIndex);
|
||||||
json.writeValue(item);
|
if (uvs != null) {
|
||||||
|
for (float item : uvs)
|
||||||
|
json.writeValue(item);
|
||||||
}
|
}
|
||||||
json.writeArrayEnd();
|
json.writeArrayEnd();
|
||||||
|
|
||||||
@ -3003,11 +3015,7 @@ public class SkeletonSerializer {
|
|||||||
json.writeValue(obj.getPath());
|
json.writeValue(obj.getPath());
|
||||||
|
|
||||||
json.writeName("sequence");
|
json.writeName("sequence");
|
||||||
if (obj.getSequence() == null) {
|
writeSequence(obj.getSequence());
|
||||||
json.writeNull();
|
|
||||||
} else {
|
|
||||||
writeSequence(obj.getSequence());
|
|
||||||
}
|
|
||||||
|
|
||||||
json.writeName("name");
|
json.writeName("name");
|
||||||
json.writeValue(obj.getName());
|
json.writeValue(obj.getName());
|
||||||
|
|||||||
@ -43,7 +43,7 @@ import com.badlogic.gdx.utils.ObjectSet;
|
|||||||
|
|
||||||
import com.esotericsoftware.spine.BoneData.Inherit;
|
import com.esotericsoftware.spine.BoneData.Inherit;
|
||||||
import com.esotericsoftware.spine.attachments.Attachment;
|
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;
|
||||||
import com.esotericsoftware.spine.attachments.Sequence.SequenceMode;
|
import com.esotericsoftware.spine.attachments.Sequence.SequenceMode;
|
||||||
import com.esotericsoftware.spine.attachments.VertexAttachment;
|
import com.esotericsoftware.spine.attachments.VertexAttachment;
|
||||||
@ -1700,13 +1700,12 @@ public class Animation {
|
|||||||
static private final int MODE = 1, DELAY = 2;
|
static private final int MODE = 1, DELAY = 2;
|
||||||
|
|
||||||
final int slotIndex;
|
final int slotIndex;
|
||||||
final HasTextureRegion attachment;
|
final HasSequence attachment;
|
||||||
|
|
||||||
public SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) {
|
public SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) {
|
||||||
super(frameCount,
|
super(frameCount, Property.sequence.ordinal() + "|" + slotIndex + "|" + ((HasSequence)attachment).getSequence().getId());
|
||||||
Property.sequence.ordinal() + "|" + slotIndex + "|" + ((HasTextureRegion)attachment).getSequence().getId());
|
|
||||||
this.slotIndex = slotIndex;
|
this.slotIndex = slotIndex;
|
||||||
this.attachment = (HasTextureRegion)attachment;
|
this.attachment = (HasSequence)attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFrameEntries () {
|
public int getFrameEntries () {
|
||||||
@ -1743,8 +1742,6 @@ public class Animation {
|
|||||||
if (!(slotAttachment instanceof VertexAttachment vertexAttachment)
|
if (!(slotAttachment instanceof VertexAttachment vertexAttachment)
|
||||||
|| vertexAttachment.getTimelineAttachment() != attachment) return;
|
|| vertexAttachment.getTimelineAttachment() != attachment) return;
|
||||||
}
|
}
|
||||||
Sequence sequence = ((HasTextureRegion)slotAttachment).getSequence();
|
|
||||||
if (sequence == null) return;
|
|
||||||
|
|
||||||
if (direction == out) {
|
if (direction == out) {
|
||||||
if (blend == setup) pose.setSequenceIndex(-1);
|
if (blend == setup) pose.setSequenceIndex(-1);
|
||||||
@ -1762,7 +1759,7 @@ public class Animation {
|
|||||||
int modeAndIndex = (int)frames[i + MODE];
|
int modeAndIndex = (int)frames[i + MODE];
|
||||||
float delay = frames[i + DELAY];
|
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];
|
SequenceMode mode = SequenceMode.values[modeAndIndex & 0xf];
|
||||||
if (mode != SequenceMode.hold) {
|
if (mode != SequenceMode.hold) {
|
||||||
index += (time - before) / delay + 0.0001f;
|
index += (time - before) / delay + 0.0001f;
|
||||||
|
|||||||
@ -495,7 +495,7 @@ public class Skeleton {
|
|||||||
if (attachment instanceof RegionAttachment region) {
|
if (attachment instanceof RegionAttachment region) {
|
||||||
verticesLength = 8;
|
verticesLength = 8;
|
||||||
vertices = temp.setSize(8);
|
vertices = temp.setSize(8);
|
||||||
region.computeWorldVertices(slot, vertices, 0, 2);
|
region.computeWorldVertices(slot, region.getOffsets(slot.applied), vertices, 0, 2);
|
||||||
triangles = quadTriangles;
|
triangles = quadTriangles;
|
||||||
} else if (attachment instanceof MeshAttachment mesh) {
|
} else if (attachment instanceof MeshAttachment mesh) {
|
||||||
verticesLength = mesh.getWorldVerticesLength();
|
verticesLength = mesh.getWorldVerticesLength();
|
||||||
|
|||||||
@ -475,7 +475,7 @@ public class SkeletonBinary extends SkeletonLoader {
|
|||||||
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
|
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
|
||||||
linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh);
|
linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh);
|
||||||
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
|
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
|
||||||
if (linkedMesh.mesh.getRegion() == null) linkedMesh.mesh.updateRegion();
|
linkedMesh.mesh.updateSequence();
|
||||||
}
|
}
|
||||||
linkedMeshes.clear();
|
linkedMeshes.clear();
|
||||||
|
|
||||||
@ -561,7 +561,7 @@ public class SkeletonBinary extends SkeletonLoader {
|
|||||||
case region -> {
|
case region -> {
|
||||||
String path = (flags & 16) != 0 ? input.readStringRef() : null;
|
String path = (flags & 16) != 0 ? input.readStringRef() : null;
|
||||||
int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff;
|
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 rotation = (flags & 128) != 0 ? input.readFloat() : 0;
|
||||||
float x = input.readFloat();
|
float x = input.readFloat();
|
||||||
float y = input.readFloat();
|
float y = input.readFloat();
|
||||||
@ -582,8 +582,7 @@ public class SkeletonBinary extends SkeletonLoader {
|
|||||||
region.setWidth(width * scale);
|
region.setWidth(width * scale);
|
||||||
region.setHeight(height * scale);
|
region.setHeight(height * scale);
|
||||||
Color.rgba8888ToColor(region.getColor(), color);
|
Color.rgba8888ToColor(region.getColor(), color);
|
||||||
region.setSequence(sequence);
|
region.updateSequence();
|
||||||
if (region.getRegion() != null) region.updateRegion();
|
|
||||||
yield region;
|
yield region;
|
||||||
}
|
}
|
||||||
case boundingbox -> {
|
case boundingbox -> {
|
||||||
@ -601,7 +600,7 @@ public class SkeletonBinary extends SkeletonLoader {
|
|||||||
case mesh -> {
|
case mesh -> {
|
||||||
String path = (flags & 16) != 0 ? input.readStringRef() : name;
|
String path = (flags & 16) != 0 ? input.readStringRef() : name;
|
||||||
int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff;
|
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);
|
int hullLength = input.readInt(true);
|
||||||
Vertices vertices = readVertices(input, (flags & 128) != 0);
|
Vertices vertices = readVertices(input, (flags & 128) != 0);
|
||||||
float[] uvs = readFloatArray(input, vertices.length, 1);
|
float[] uvs = readFloatArray(input, vertices.length, 1);
|
||||||
@ -619,25 +618,24 @@ public class SkeletonBinary extends SkeletonLoader {
|
|||||||
if (mesh == null) yield null;
|
if (mesh == null) yield null;
|
||||||
mesh.setPath(path);
|
mesh.setPath(path);
|
||||||
Color.rgba8888ToColor(mesh.getColor(), color);
|
Color.rgba8888ToColor(mesh.getColor(), color);
|
||||||
|
mesh.setHullLength(hullLength << 1);
|
||||||
mesh.setBones(vertices.bones);
|
mesh.setBones(vertices.bones);
|
||||||
mesh.setVertices(vertices.vertices);
|
mesh.setVertices(vertices.vertices);
|
||||||
mesh.setWorldVerticesLength(vertices.length);
|
mesh.setWorldVerticesLength(vertices.length);
|
||||||
mesh.setTriangles(triangles);
|
|
||||||
mesh.setRegionUVs(uvs);
|
mesh.setRegionUVs(uvs);
|
||||||
if (mesh.getRegion() != null) mesh.updateRegion();
|
mesh.setTriangles(triangles);
|
||||||
mesh.setHullLength(hullLength << 1);
|
|
||||||
mesh.setSequence(sequence);
|
|
||||||
if (nonessential) {
|
if (nonessential) {
|
||||||
mesh.setEdges(edges);
|
mesh.setEdges(edges);
|
||||||
mesh.setWidth(width * scale);
|
mesh.setWidth(width * scale);
|
||||||
mesh.setHeight(height * scale);
|
mesh.setHeight(height * scale);
|
||||||
}
|
}
|
||||||
|
mesh.updateSequence();
|
||||||
yield mesh;
|
yield mesh;
|
||||||
}
|
}
|
||||||
case linkedmesh -> {
|
case linkedmesh -> {
|
||||||
String path = (flags & 16) != 0 ? input.readStringRef() : name;
|
String path = (flags & 16) != 0 ? input.readStringRef() : name;
|
||||||
int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff;
|
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;
|
boolean inheritTimelines = (flags & 128) != 0;
|
||||||
int skinIndex = input.readInt(true);
|
int skinIndex = input.readInt(true);
|
||||||
String parent = input.readStringRef();
|
String parent = input.readStringRef();
|
||||||
@ -651,7 +649,6 @@ public class SkeletonBinary extends SkeletonLoader {
|
|||||||
if (mesh == null) yield null;
|
if (mesh == null) yield null;
|
||||||
mesh.setPath(path);
|
mesh.setPath(path);
|
||||||
Color.rgba8888ToColor(mesh.getColor(), color);
|
Color.rgba8888ToColor(mesh.getColor(), color);
|
||||||
mesh.setSequence(sequence);
|
|
||||||
if (nonessential) {
|
if (nonessential) {
|
||||||
mesh.setWidth(width * scale);
|
mesh.setWidth(width * scale);
|
||||||
mesh.setHeight(height * scale);
|
mesh.setHeight(height * scale);
|
||||||
@ -711,8 +708,9 @@ public class SkeletonBinary extends SkeletonLoader {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Sequence readSequence (SkeletonInput input) throws IOException {
|
private Sequence readSequence (SkeletonInput input, boolean hasPathSuffix) throws IOException {
|
||||||
var sequence = new Sequence(input.readInt(true));
|
if (!hasPathSuffix) return new Sequence(1, false);
|
||||||
|
var sequence = new Sequence(input.readInt(true), true);
|
||||||
sequence.setStart(input.readInt(true));
|
sequence.setStart(input.readInt(true));
|
||||||
sequence.setDigits(input.readInt(true));
|
sequence.setDigits(input.readInt(true));
|
||||||
sequence.setSetupIndex(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);
|
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
|
||||||
linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh);
|
linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh);
|
||||||
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
|
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
|
||||||
if (linkedMesh.mesh.getRegion() != null) linkedMesh.mesh.updateRegion();
|
linkedMesh.mesh.updateSequence();
|
||||||
}
|
}
|
||||||
linkedMeshes.clear();
|
linkedMeshes.clear();
|
||||||
|
|
||||||
@ -580,12 +580,11 @@ public class SkeletonJson extends SkeletonLoader {
|
|||||||
region.setRotation(map.getFloat("rotation", 0));
|
region.setRotation(map.getFloat("rotation", 0));
|
||||||
region.setWidth(map.getFloat("width") * scale);
|
region.setWidth(map.getFloat("width") * scale);
|
||||||
region.setHeight(map.getFloat("height") * scale);
|
region.setHeight(map.getFloat("height") * scale);
|
||||||
region.setSequence(sequence);
|
|
||||||
|
|
||||||
String color = map.getString("color", null);
|
String color = map.getString("color", null);
|
||||||
if (color != null) Color.valueOf(color, region.getColor());
|
if (color != null) Color.valueOf(color, region.getColor());
|
||||||
|
|
||||||
if (region.getRegion() != null) region.updateRegion();
|
region.updateSequence();
|
||||||
yield region;
|
yield region;
|
||||||
}
|
}
|
||||||
case boundingbox -> {
|
case boundingbox -> {
|
||||||
@ -609,7 +608,6 @@ public class SkeletonJson extends SkeletonLoader {
|
|||||||
|
|
||||||
mesh.setWidth(map.getFloat("width", 0) * scale);
|
mesh.setWidth(map.getFloat("width", 0) * scale);
|
||||||
mesh.setHeight(map.getFloat("height", 0) * scale);
|
mesh.setHeight(map.getFloat("height", 0) * scale);
|
||||||
mesh.setSequence(sequence);
|
|
||||||
|
|
||||||
String parent = map.getString("parent", null);
|
String parent = map.getString("parent", null);
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
@ -622,10 +620,11 @@ public class SkeletonJson extends SkeletonLoader {
|
|||||||
readVertices(map, mesh, uvs.length);
|
readVertices(map, mesh, uvs.length);
|
||||||
mesh.setTriangles(map.require("triangles").asShortArray());
|
mesh.setTriangles(map.require("triangles").asShortArray());
|
||||||
mesh.setRegionUVs(uvs);
|
mesh.setRegionUVs(uvs);
|
||||||
if (mesh.getRegion() != null) mesh.updateRegion();
|
|
||||||
|
|
||||||
if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() << 1);
|
if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() << 1);
|
||||||
if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray());
|
if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray());
|
||||||
|
|
||||||
|
mesh.updateSequence();
|
||||||
yield mesh;
|
yield mesh;
|
||||||
}
|
}
|
||||||
case path -> {
|
case path -> {
|
||||||
@ -680,8 +679,8 @@ public class SkeletonJson extends SkeletonLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Sequence readSequence (@Null JsonValue map) {
|
private Sequence readSequence (@Null JsonValue map) {
|
||||||
if (map == null) return null;
|
if (map == null) return new Sequence(1, false);
|
||||||
var sequence = new Sequence(map.getInt("count"));
|
var sequence = new Sequence(map.getInt("count"), true);
|
||||||
sequence.setStart(map.getInt("start", 1));
|
sequence.setStart(map.getInt("start", 1));
|
||||||
sequence.setDigits(map.getInt("digits", 0));
|
sequence.setDigits(map.getInt("digits", 0));
|
||||||
sequence.setSetupIndex(map.getInt("setup", 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.ClippingAttachment;
|
||||||
import com.esotericsoftware.spine.attachments.MeshAttachment;
|
import com.esotericsoftware.spine.attachments.MeshAttachment;
|
||||||
import com.esotericsoftware.spine.attachments.RegionAttachment;
|
import com.esotericsoftware.spine.attachments.RegionAttachment;
|
||||||
|
import com.esotericsoftware.spine.attachments.Sequence;
|
||||||
import com.esotericsoftware.spine.attachments.SkeletonAttachment;
|
import com.esotericsoftware.spine.attachments.SkeletonAttachment;
|
||||||
import com.esotericsoftware.spine.utils.SkeletonClipping;
|
import com.esotericsoftware.spine.utils.SkeletonClipping;
|
||||||
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
|
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
|
||||||
@ -83,7 +84,11 @@ public class SkeletonRenderer {
|
|||||||
SlotPose pose = slot.applied;
|
SlotPose pose = slot.applied;
|
||||||
Attachment attachment = pose.attachment;
|
Attachment attachment = pose.attachment;
|
||||||
if (attachment instanceof RegionAttachment region) {
|
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();
|
Color color = region.getColor(), slotColor = pose.getColor();
|
||||||
float alpha = a * slotColor.a * color.a * 255;
|
float alpha = a * slotColor.a * color.a * 255;
|
||||||
float multiplier = pmaColors ? alpha : 255;
|
float multiplier = pmaColors ? alpha : 255;
|
||||||
@ -102,14 +107,13 @@ public class SkeletonRenderer {
|
|||||||
| (int)(b * slotColor.b * color.b * multiplier) << 16 //
|
| (int)(b * slotColor.b * color.b * multiplier) << 16 //
|
||||||
| (int)(g * slotColor.g * color.g * multiplier) << 8 //
|
| (int)(g * slotColor.g * color.g * multiplier) << 8 //
|
||||||
| (int)(r * slotColor.r * color.r * multiplier));
|
| (int)(r * slotColor.r * color.r * multiplier));
|
||||||
float[] uvs = region.getUVs();
|
|
||||||
for (int u = 0, v = 2; u < 8; u += 2, v += 5) {
|
for (int u = 0, v = 2; u < 8; u += 2, v += 5) {
|
||||||
vertices[v] = c;
|
vertices[v] = c;
|
||||||
vertices[v + 1] = uvs[u];
|
vertices[v + 1] = uvs[u];
|
||||||
vertices[v + 2] = uvs[u + 1];
|
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) {
|
} else if (attachment instanceof ClippingAttachment) {
|
||||||
throw new RuntimeException(batch.getClass().getSimpleName()
|
throw new RuntimeException(batch.getClass().getSimpleName()
|
||||||
@ -153,10 +157,12 @@ public class SkeletonRenderer {
|
|||||||
if (attachment instanceof RegionAttachment region) {
|
if (attachment instanceof RegionAttachment region) {
|
||||||
verticesLength = 20;
|
verticesLength = 20;
|
||||||
vertices = this.vertices.items;
|
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;
|
triangles = quadTriangles;
|
||||||
texture = region.getRegion().getTexture();
|
texture = sequence.getRegion(sequenceIndex).getTexture();
|
||||||
uvs = region.getUVs();
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
color = region.getColor();
|
color = region.getColor();
|
||||||
|
|
||||||
} else if (attachment instanceof MeshAttachment mesh) {
|
} else if (attachment instanceof MeshAttachment mesh) {
|
||||||
@ -165,8 +171,10 @@ public class SkeletonRenderer {
|
|||||||
vertices = this.vertices.setSize(verticesLength);
|
vertices = this.vertices.setSize(verticesLength);
|
||||||
mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 5);
|
mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 5);
|
||||||
triangles = mesh.getTriangles();
|
triangles = mesh.getTriangles();
|
||||||
texture = mesh.getRegion().getTexture();
|
Sequence sequence = mesh.getSequence();
|
||||||
uvs = mesh.getUVs();
|
int sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
texture = sequence.getRegion(sequenceIndex).getTexture();
|
||||||
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
color = mesh.getColor();
|
color = mesh.getColor();
|
||||||
|
|
||||||
} else if (attachment instanceof ClippingAttachment clip) {
|
} else if (attachment instanceof ClippingAttachment clip) {
|
||||||
@ -248,10 +256,12 @@ public class SkeletonRenderer {
|
|||||||
if (attachment instanceof RegionAttachment region) {
|
if (attachment instanceof RegionAttachment region) {
|
||||||
verticesLength = 24;
|
verticesLength = 24;
|
||||||
vertices = this.vertices.items;
|
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;
|
triangles = quadTriangles;
|
||||||
texture = region.getRegion().getTexture();
|
texture = sequence.getRegion(sequenceIndex).getTexture();
|
||||||
uvs = region.getUVs();
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
color = region.getColor();
|
color = region.getColor();
|
||||||
|
|
||||||
} else if (attachment instanceof MeshAttachment mesh) {
|
} else if (attachment instanceof MeshAttachment mesh) {
|
||||||
@ -260,8 +270,10 @@ public class SkeletonRenderer {
|
|||||||
vertices = this.vertices.setSize(verticesLength);
|
vertices = this.vertices.setSize(verticesLength);
|
||||||
mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 6);
|
mesh.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 6);
|
||||||
triangles = mesh.getTriangles();
|
triangles = mesh.getTriangles();
|
||||||
texture = mesh.getRegion().getTexture();
|
Sequence sequence = mesh.getSequence();
|
||||||
uvs = mesh.getUVs();
|
int sequenceIndex = sequence.resolveIndex(pose);
|
||||||
|
texture = sequence.getRegion(sequenceIndex).getTexture();
|
||||||
|
uvs = sequence.getUVs(sequenceIndex);
|
||||||
color = mesh.getColor();
|
color = mesh.getColor();
|
||||||
|
|
||||||
} else if (attachment instanceof ClippingAttachment clip) {
|
} else if (attachment instanceof ClippingAttachment clip) {
|
||||||
|
|||||||
@ -126,7 +126,8 @@ public class SkeletonRendererDebug {
|
|||||||
if (!slot.bone.active) continue;
|
if (!slot.bone.active) continue;
|
||||||
if (slot.pose.attachment instanceof RegionAttachment region) {
|
if (slot.pose.attachment instanceof RegionAttachment region) {
|
||||||
float[] vertices = this.vertices.items;
|
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[0], vertices[1], vertices[2], vertices[3]);
|
||||||
shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
|
shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
|
||||||
shapes.line(vertices[4], vertices[5], vertices[6], vertices[7]);
|
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;
|
||||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
|
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
|
||||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||||
import com.badlogic.gdx.utils.Null;
|
|
||||||
|
|
||||||
import com.esotericsoftware.spine.Skin;
|
import com.esotericsoftware.spine.Skin;
|
||||||
|
|
||||||
@ -55,40 +54,27 @@ public class AtlasAttachmentLoader implements AttachmentLoader {
|
|||||||
this.allowMissingRegions = allowMissingRegions;
|
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();
|
TextureRegion[] regions = sequence.getRegions();
|
||||||
for (int i = 0, n = regions.length; i < n; i++) {
|
for (int i = 0, n = regions.length; i < n; i++)
|
||||||
String path = sequence.getPath(basePath, i);
|
regions[i] = findRegion(name, 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 + ")");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
|
protected AtlasRegion findRegion (String name, String path) {
|
||||||
var attachment = new RegionAttachment(name);
|
AtlasRegion region = atlas.findRegion(path);
|
||||||
if (sequence != null)
|
if (region == null && !allowMissingRegions)
|
||||||
loadSequence(name, path, sequence);
|
throw new RuntimeException("Region not found in atlas: " + path + " (attachment: " + name + ")");
|
||||||
else {
|
return region;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
|
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, Sequence sequence) {
|
||||||
var attachment = new MeshAttachment(name);
|
findRegions(name, path, sequence);
|
||||||
if (sequence != null)
|
return new RegionAttachment(name, sequence);
|
||||||
loadSequence(name, path, sequence);
|
}
|
||||||
else {
|
|
||||||
AtlasRegion region = atlas.findRegion(path);
|
public MeshAttachment newMeshAttachment (Skin skin, String name, String path, Sequence sequence) {
|
||||||
if (region == null && !allowMissingRegions)
|
findRegions(name, path, sequence);
|
||||||
throw new RuntimeException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
|
return new MeshAttachment(name, sequence);
|
||||||
attachment.setRegion(region);
|
|
||||||
}
|
|
||||||
return attachment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
|
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
|
||||||
|
|||||||
@ -39,10 +39,10 @@ import com.esotericsoftware.spine.Skin;
|
|||||||
* Runtimes Guide. */
|
* Runtimes Guide. */
|
||||||
public interface AttachmentLoader {
|
public interface AttachmentLoader {
|
||||||
/** @return May be null to not load the attachment. */
|
/** @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. */
|
/** @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. */
|
/** @return May be null to not load the attachment. */
|
||||||
public @Null BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name);
|
public @Null BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name);
|
||||||
|
|||||||
@ -30,29 +30,16 @@
|
|||||||
package com.esotericsoftware.spine.attachments;
|
package com.esotericsoftware.spine.attachments;
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color;
|
import com.badlogic.gdx.graphics.Color;
|
||||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
|
||||||
import com.badlogic.gdx.utils.Null;
|
|
||||||
|
|
||||||
public interface HasTextureRegion {
|
public interface HasSequence {
|
||||||
/** The name used to find the {@link #getRegion()}. */
|
|
||||||
public String getPath ();
|
public String getPath ();
|
||||||
|
|
||||||
public void setPath (String path);
|
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 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.graphics.g2d.TextureRegion;
|
||||||
import com.badlogic.gdx.utils.Null;
|
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
|
/** 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.
|
* supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
|
||||||
* <p>
|
* <p>
|
||||||
* See <a href="https://esotericsoftware.com/spine-meshes">Mesh attachments</a> in the Spine User Guide. */
|
* See <a href="https://esotericsoftware.com/spine-meshes">Mesh attachments</a> in the Spine User Guide. */
|
||||||
public class MeshAttachment extends VertexAttachment implements HasTextureRegion {
|
public class MeshAttachment extends VertexAttachment implements HasSequence {
|
||||||
private TextureRegion region;
|
private final Sequence sequence;
|
||||||
private String path;
|
float[] regionUVs;
|
||||||
private float[] regionUVs, uvs;
|
|
||||||
private short[] triangles;
|
private short[] triangles;
|
||||||
private final Color color = new Color(1, 1, 1, 1);
|
|
||||||
private int hullLength;
|
private int hullLength;
|
||||||
|
private String path;
|
||||||
|
private final Color color = new Color(1, 1, 1, 1);
|
||||||
private @Null MeshAttachment parentMesh;
|
private @Null MeshAttachment parentMesh;
|
||||||
private @Null Sequence sequence;
|
|
||||||
|
|
||||||
// Nonessential.
|
// Nonessential.
|
||||||
private @Null short[] edges;
|
private @Null short[] edges;
|
||||||
private float width, height;
|
private float width, height;
|
||||||
|
|
||||||
public MeshAttachment (String name) {
|
public MeshAttachment (String name, Sequence sequence) {
|
||||||
super(name);
|
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. */
|
/** 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.");
|
if (parentMesh != null) throw new IllegalArgumentException("Use newLinkedMesh to copy a linked mesh.");
|
||||||
|
|
||||||
region = other.region;
|
|
||||||
path = other.path;
|
path = other.path;
|
||||||
color.set(other.color);
|
color.set(other.color);
|
||||||
|
|
||||||
regionUVs = new float[other.regionUVs.length];
|
regionUVs = new float[other.regionUVs.length];
|
||||||
arraycopy(other.regionUVs, 0, regionUVs, 0, 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];
|
triangles = new short[other.triangles.length];
|
||||||
arraycopy(other.triangles, 0, triangles, 0, triangles.length);
|
arraycopy(other.triangles, 0, triangles, 0, triangles.length);
|
||||||
|
|
||||||
hullLength = other.hullLength;
|
hullLength = other.hullLength;
|
||||||
sequence = other.sequence != null ? new Sequence(other.sequence) : null;
|
sequence = new Sequence(other.sequence);
|
||||||
|
|
||||||
// Nonessential.
|
// Nonessential.
|
||||||
if (other.edges != null) {
|
if (other.edges != null) {
|
||||||
@ -92,99 +86,6 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion
|
|||||||
height = other.height;
|
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. */
|
/** The UV pair for each vertex, normalized within the texture region. */
|
||||||
public float[] getRegionUVs () {
|
public float[] getRegionUVs () {
|
||||||
return regionUVs;
|
return regionUVs;
|
||||||
@ -195,27 +96,13 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion
|
|||||||
this.regionUVs = regionUVs;
|
this.regionUVs = regionUVs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The UV pair for each vertex, normalized within the entire texture.
|
/** Triplets of vertex indices which describe the mesh's triangulation. */
|
||||||
* <p>
|
public short[] getTriangles () {
|
||||||
* See {@link #updateRegion()}. */
|
return triangles;
|
||||||
public float[] getUVs () {
|
|
||||||
return uvs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUVs (float[] uvs) {
|
public void setTriangles (short[] triangles) {
|
||||||
this.uvs = uvs;
|
this.triangles = triangles;
|
||||||
}
|
|
||||||
|
|
||||||
public Color getColor () {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPath () {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPath (String path) {
|
|
||||||
this.path = path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The number of entries at the beginning of {@link #vertices} that make up the mesh hull. */
|
/** 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;
|
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) {
|
public void setEdges (short[] edges) {
|
||||||
this.edges = edges;
|
this.edges = edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Vertex index pairs describing edges for controlling triangulation, or be null if nonessential data was not exported. Mesh
|
/** 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 () {
|
public @Null short[] getEdges () {
|
||||||
return edges;
|
return edges;
|
||||||
}
|
}
|
||||||
@ -255,14 +162,6 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion
|
|||||||
this.height = height;
|
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},
|
/** 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
|
* {@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). */
|
* 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. */
|
/** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. */
|
||||||
public MeshAttachment newLinkedMesh () {
|
public MeshAttachment newLinkedMesh () {
|
||||||
var mesh = new MeshAttachment(name);
|
var mesh = new MeshAttachment(name, new Sequence(sequence));
|
||||||
mesh.timelineAttachment = timelineAttachment;
|
mesh.timelineAttachment = timelineAttachment;
|
||||||
mesh.region = region;
|
|
||||||
mesh.path = path;
|
mesh.path = path;
|
||||||
mesh.color.set(color);
|
mesh.color.set(color);
|
||||||
mesh.setParentMesh(parentMesh != null ? parentMesh : this);
|
mesh.setParentMesh(parentMesh != null ? parentMesh : this);
|
||||||
if (mesh.getRegion() != null) mesh.updateRegion();
|
mesh.updateSequence();
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MeshAttachment copy () {
|
public MeshAttachment copy () {
|
||||||
return parentMesh != null ? newLinkedMesh() : new MeshAttachment(this);
|
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.BonePose;
|
||||||
import com.esotericsoftware.spine.Slot;
|
import com.esotericsoftware.spine.Slot;
|
||||||
|
import com.esotericsoftware.spine.SlotPose;
|
||||||
|
|
||||||
/** An attachment that displays a textured quadrilateral.
|
/** An attachment that displays a textured quadrilateral.
|
||||||
* <p>
|
* <p>
|
||||||
* See <a href="https://esotericsoftware.com/spine-regions">Region attachments</a> in the Spine User Guide. */
|
* 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 BLX = 0, BLY = 1;
|
||||||
static public final int ULX = 2, ULY = 3;
|
static public final int ULX = 2, ULY = 3;
|
||||||
static public final int URX = 4, URY = 5;
|
static public final int URX = 4, URY = 5;
|
||||||
static public final int BRX = 6, BRY = 7;
|
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 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 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);
|
super(name);
|
||||||
|
if (sequence == null) throw new IllegalArgumentException("sequence cannot be null.");
|
||||||
|
this.sequence = sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Copy constructor. */
|
/** Copy constructor. */
|
||||||
protected RegionAttachment (RegionAttachment other) {
|
protected RegionAttachment (RegionAttachment other) {
|
||||||
super(other);
|
super(other);
|
||||||
region = other.region;
|
|
||||||
path = other.path;
|
path = other.path;
|
||||||
x = other.x;
|
x = other.x;
|
||||||
y = other.y;
|
y = other.y;
|
||||||
@ -72,148 +71,50 @@ public class RegionAttachment extends Attachment implements HasTextureRegion {
|
|||||||
rotation = other.rotation;
|
rotation = other.rotation;
|
||||||
width = other.width;
|
width = other.width;
|
||||||
height = other.height;
|
height = other.height;
|
||||||
arraycopy(other.uvs, 0, uvs, 0, 8);
|
|
||||||
arraycopy(other.offset, 0, offset, 0, 8);
|
|
||||||
color.set(other.color);
|
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
|
/** Transforms the attachment's four vertices to world coordinates.
|
||||||
* 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.
|
|
||||||
* <p>
|
* <p>
|
||||||
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
|
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
|
||||||
* Runtimes Guide.
|
* Runtimes Guide.
|
||||||
* @param worldVertices The output world vertices. Must have a length >= <code>offset</code> + 8.
|
* @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 offset The <code>worldVertices</code> index to begin writing values.
|
||||||
* @param stride The number of <code>worldVertices</code> entries between the value pairs written. */
|
* @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) {
|
public void computeWorldVertices (Slot slot, float[] vertexOffsets, float[] worldVertices, int offset, int stride) {
|
||||||
if (sequence != null) sequence.apply(slot.getAppliedPose(), this);
|
|
||||||
|
|
||||||
float[] vertexOffset = this.offset;
|
|
||||||
BonePose bone = slot.getBone().getAppliedPose();
|
BonePose bone = slot.getBone().getAppliedPose();
|
||||||
float x = bone.getWorldX(), y = bone.getWorldY();
|
float x = bone.getWorldX(), y = bone.getWorldY();
|
||||||
float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD();
|
float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD();
|
||||||
float offsetX, offsetY;
|
|
||||||
|
|
||||||
offsetX = vertexOffset[BRX];
|
float offsetX = vertexOffsets[BRX];
|
||||||
offsetY = vertexOffset[BRY];
|
float offsetY = vertexOffsets[BRY];
|
||||||
worldVertices[offset] = offsetX * a + offsetY * b + x; // br
|
worldVertices[offset] = offsetX * a + offsetY * b + x; // br
|
||||||
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
||||||
offset += stride;
|
offset += stride;
|
||||||
|
|
||||||
offsetX = vertexOffset[BLX];
|
offsetX = vertexOffsets[BLX];
|
||||||
offsetY = vertexOffset[BLY];
|
offsetY = vertexOffsets[BLY];
|
||||||
worldVertices[offset] = offsetX * a + offsetY * b + x; // bl
|
worldVertices[offset] = offsetX * a + offsetY * b + x; // bl
|
||||||
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
||||||
offset += stride;
|
offset += stride;
|
||||||
|
|
||||||
offsetX = vertexOffset[ULX];
|
offsetX = vertexOffsets[ULX];
|
||||||
offsetY = vertexOffset[ULY];
|
offsetY = vertexOffsets[ULY];
|
||||||
worldVertices[offset] = offsetX * a + offsetY * b + x; // ul
|
worldVertices[offset] = offsetX * a + offsetY * b + x; // ul
|
||||||
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
||||||
offset += stride;
|
offset += stride;
|
||||||
|
|
||||||
offsetX = vertexOffset[URX];
|
offsetX = vertexOffsets[URX];
|
||||||
offsetY = vertexOffset[URY];
|
offsetY = vertexOffsets[URY];
|
||||||
worldVertices[offset] = offsetX * a + offsetY * b + x; // ur
|
worldVertices[offset] = offsetX * a + offsetY * b + x; // ur
|
||||||
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
|
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.
|
/** Returns the vertex {@link Sequence#getOffsets(int) offsets} for the specified slot pose. */
|
||||||
* <p>
|
public float[] getOffsets (SlotPose pose) {
|
||||||
* See {@link #updateRegion()}. */
|
return sequence.getOffsets(sequence.resolveIndex(pose));
|
||||||
public float[] getOffset () {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] getUVs () {
|
|
||||||
return uvs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The local x translation. */
|
/** The local x translation. */
|
||||||
@ -279,8 +180,12 @@ public class RegionAttachment extends Attachment implements HasTextureRegion {
|
|||||||
this.height = height;
|
this.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color getColor () {
|
public Sequence getSequence () {
|
||||||
return color;
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateSequence () {
|
||||||
|
sequence.update(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPath () {
|
public String getPath () {
|
||||||
@ -291,15 +196,80 @@ public class RegionAttachment extends Attachment implements HasTextureRegion {
|
|||||||
this.path = path;
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Null Sequence getSequence () {
|
public Color getColor () {
|
||||||
return sequence;
|
return color;
|
||||||
}
|
|
||||||
|
|
||||||
public void setSequence (@Null Sequence sequence) {
|
|
||||||
this.sequence = sequence;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RegionAttachment copy () {
|
public RegionAttachment copy () {
|
||||||
return new RegionAttachment(this);
|
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;
|
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 {
|
public class Sequence {
|
||||||
static private int nextID;
|
static private int nextID;
|
||||||
|
|
||||||
private final int id = nextID();
|
private final int id = nextID();
|
||||||
private final TextureRegion[] regions;
|
private final TextureRegion[] regions;
|
||||||
|
private final boolean pathSuffix;
|
||||||
|
private float[][] uvs, offsets;
|
||||||
private int start, digits, setupIndex;
|
private int start, digits, setupIndex;
|
||||||
|
|
||||||
public Sequence (int count) {
|
public Sequence (int count, boolean pathSuffix) {
|
||||||
regions = new TextureRegion[count];
|
regions = new TextureRegion[count];
|
||||||
|
this.pathSuffix = pathSuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Copy constructor. */
|
/** Copy constructor. */
|
||||||
protected Sequence (Sequence other) {
|
protected Sequence (Sequence other) {
|
||||||
regions = new TextureRegion[other.regions.length];
|
int regionCount = other.regions.length;
|
||||||
arraycopy(other.regions, 0, regions, 0, regions.length);
|
regions = new TextureRegion[regionCount];
|
||||||
|
arraycopy(other.regions, 0, regions, 0, regionCount);
|
||||||
|
|
||||||
start = other.start;
|
start = other.start;
|
||||||
digits = other.digits;
|
digits = other.digits;
|
||||||
setupIndex = other.setupIndex;
|
setupIndex = other.setupIndex;
|
||||||
}
|
pathSuffix = other.pathSuffix;
|
||||||
|
|
||||||
public void apply (SlotPose slot, HasTextureRegion attachment) {
|
if (other.uvs != null) {
|
||||||
int index = slot.getSequenceIndex();
|
int length = other.uvs[0].length;
|
||||||
if (index == -1) index = setupIndex;
|
uvs = new float[regionCount][length];
|
||||||
if (index >= regions.length) index = regions.length - 1;
|
for (int i = 0; i < regionCount; i++)
|
||||||
TextureRegion region = regions[index];
|
arraycopy(other.uvs[i], 0, uvs[i], 0, length);
|
||||||
if (attachment.getRegion() != region) {
|
}
|
||||||
attachment.setRegion(region);
|
if (other.offsets != null) {
|
||||||
attachment.updateRegion();
|
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) {
|
/** Computes UVs and offsets for the specified attachment. Must be called if the regions or attachment properties are
|
||||||
var buffer = new StringBuilder(basePath.length() + digits);
|
* changed. */
|
||||||
buffer.append(basePath);
|
public void update (HasSequence attachment) {
|
||||||
String frame = Integer.toString(start + index);
|
int regionCount = regions.length;
|
||||||
for (int i = digits - frame.length(); i > 0; i--)
|
if (attachment instanceof RegionAttachment region) {
|
||||||
buffer.append('0');
|
uvs = new float[regionCount][8];
|
||||||
buffer.append(frame);
|
offsets = new float[regionCount][8];
|
||||||
return buffer.toString();
|
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 () {
|
public int getStart () {
|
||||||
@ -102,8 +144,19 @@ public class Sequence {
|
|||||||
this.setupIndex = index;
|
this.setupIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextureRegion[] getRegions () {
|
public boolean getPathSuffix () {
|
||||||
return regions;
|
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. */
|
/** Returns a unique ID for this attachment. */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user