This commit is contained in:
Mario Zechner 2021-10-05 16:28:36 +02:00
commit f5c5b09a2e
29 changed files with 674 additions and 530 deletions

View File

@ -36,6 +36,7 @@ import com.badlogic.gdx.Files.FileType;
import com.badlogic.gdx.backends.lwjgl.LwjglFileHandle;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null;
import com.badlogic.gdx.utils.Pool;
import com.esotericsoftware.spine.AnimationState.AnimationStateListener;
@ -47,19 +48,15 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment;
import com.esotericsoftware.spine.attachments.Sequence;
public class AnimationStateTests {
final SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path) {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
return null;
}
public MeshAttachment newMeshAttachment (Skin skin, String name, String path) {
return null;
}
public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) {
public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
return null;
}
@ -893,7 +890,6 @@ public class AnimationStateTests {
state.apply(skeleton);
while (time < endTime) {
time += incr;
skeleton.update(incr);
state.update(incr);
// Reduce float discrepancies for tests.

View File

@ -30,6 +30,7 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection;
@ -40,21 +41,17 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment;
import com.esotericsoftware.spine.attachments.Sequence;
public class BonePlotting {
static public void main (String[] args) throws Exception {
// This example shows how to load skeleton data and plot a bone transform for each animation.
SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path) {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
return null;
}
public MeshAttachment newMeshAttachment (Skin skin, String name, String path) {
return null;
}
public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) {
public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
return null;
}

View File

@ -49,12 +49,14 @@ import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null;
import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.Sequence;
public class Box2DExample extends ApplicationAdapter {
SpriteBatch batch;
@ -85,7 +87,7 @@ public class Box2DExample extends ApplicationAdapter {
// This loader creates Box2dAttachments instead of RegionAttachments for an easy way to keep
// track of the Box2D body for each attachment.
AtlasAttachmentLoader atlasLoader = new AtlasAttachmentLoader(atlas) {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path) {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
Box2dAttachment attachment = new Box2dAttachment(name);
AtlasRegion region = atlas.findRegion(attachment.getName());
if (region == null) throw new RuntimeException("Region not found in atlas: " + attachment);

View File

@ -125,7 +125,6 @@ public class MixTest extends ApplicationAdapter {
}
skeleton.updateWorldTransform();
skeleton.update(Gdx.graphics.getDeltaTime());
batch.begin();
renderer.draw(batch, skeleton);

View File

@ -134,7 +134,6 @@ public class NormalMapTest extends ApplicationAdapter {
time += Gdx.graphics.getDeltaTime();
if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, MixBlend.first, MixDirection.in);
skeleton.updateWorldTransform();
skeleton.update(Gdx.graphics.getDeltaTime());
lightPosition.x = Gdx.input.getX();
lightPosition.y = (Gdx.graphics.getHeight() - 1 - Gdx.input.getY());

View File

@ -85,7 +85,6 @@ public class VertexEffectTest extends ApplicationAdapter {
public void render () {
// Update the skeleton and animation time.
float delta = Gdx.graphics.getDeltaTime();
skeleton.update(delta);
state.update(delta);
swirlTime += delta;

View File

@ -40,6 +40,9 @@ import com.badlogic.gdx.utils.Null;
import com.badlogic.gdx.utils.ObjectSet;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.HasTextureRegion;
import com.esotericsoftware.spine.attachments.Sequence;
import com.esotericsoftware.spine.attachments.Sequence.SequenceMode;
import com.esotericsoftware.spine.attachments.VertexAttachment;
/** Stores a list of timelines to animate a skeleton's pose over time. */
@ -175,7 +178,8 @@ public class Animation {
attachment, deform, //
event, drawOrder, //
ikConstraint, transformConstraint, //
pathConstraintPosition, pathConstraintSpacing, pathConstraintMix
pathConstraintPosition, pathConstraintSpacing, pathConstraintMix, //
sequence
}
/** The base class for all timelines. */
@ -1646,7 +1650,7 @@ public class Animation {
/** The attachment that will be deformed.
* <p>
* See {@link VertexAttachment#getDeformAttachment()}. */
* See {@link VertexAttachment#getTimelineAttachment()}. */
public VertexAttachment getAttachment () {
return attachment;
}
@ -1724,9 +1728,9 @@ public class Animation {
if (!slot.bone.active) return;
Attachment slotAttachment = slot.attachment;
if (!(slotAttachment instanceof VertexAttachment)
|| ((VertexAttachment)slotAttachment).getDeformAttachment() != attachment) return;
|| ((VertexAttachment)slotAttachment).getTimelineAttachment() != attachment) return;
FloatArray deformArray = slot.getDeform();
FloatArray deformArray = slot.deform;
if (deformArray.size == 0) blend = setup;
float[][] vertices = this.vertices;
@ -1734,7 +1738,6 @@ public class Animation {
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment;
switch (blend) {
case setup:
deformArray.clear();
@ -1745,6 +1748,7 @@ public class Animation {
return;
}
float[] deform = deformArray.setSize(vertexCount);
VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment;
if (vertexAttachment.getBones() == null) {
// Unweighted vertex positions.
float[] setupVertices = vertexAttachment.getVertices();
@ -2419,4 +2423,90 @@ public class Animation {
}
}
}
/** Changes a slot's {@link Slot#getSequenceIndex()} for an attachment's {@link Sequence}. */
static public class SequenceTimeline extends Timeline implements SlotTimeline {
static public final int ENTRIES = 3;
static private final int MODE = 1, DELAY = 2;
final int slotIndex;
final HasTextureRegion attachment;
public <T extends Attachment & HasTextureRegion> SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) {
super(frameCount,
Property.sequence.ordinal() + "|" + slotIndex + "|" + ((HasTextureRegion)attachment).getSequence().getId());
this.slotIndex = slotIndex;
this.attachment = (HasTextureRegion)attachment;
}
public int getFrameEntries () {
return ENTRIES;
}
public int getSlotIndex () {
return slotIndex;
}
public Attachment getAttachment () {
return (Attachment)attachment;
}
/** Sets the time, mode, index, and frame time for the specified frame.
* @param frame Between 0 and <code>frameCount</code>, inclusive.
* @param time Seconds between frames. */
public void setFrame (int frame, float time, SequenceMode mode, int index, float delay) {
frame *= ENTRIES;
frames[frame] = time;
frames[frame + MODE] = mode.ordinal() | (index << 4);
frames[frame + DELAY] = delay;
}
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) {
Slot slot = skeleton.slots.get(slotIndex);
if (!slot.bone.active) return;
Attachment slotAttachment = slot.attachment;
if (slotAttachment != attachment) {
if (!(slotAttachment instanceof VertexAttachment)
|| ((VertexAttachment)slotAttachment).getTimelineAttachment() != attachment) return;
}
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
if (blend == setup || blend == first) slot.setSequenceIndex(-1);
return;
}
int i = search(frames, time, ENTRIES);
float before = frames[i];
int modeAndIndex = (int)frames[i + MODE];
float delay = frames[i + DELAY];
int index = modeAndIndex >> 4, count = attachment.getSequence().getRegions().length;
SequenceMode mode = SequenceMode.values[modeAndIndex & 0xf];
if (mode != SequenceMode.stop) {
index += (time - before) / delay + 0.00001f;
switch (mode) {
case once:
index = Math.min(count - 1, index);
break;
case loop:
index %= count;
break;
case pingpong:
int n = (count << 1) - 2;
index %= n;
if (index >= count) index = n - index;
break;
case onceReverse:
index = Math.max(count - 1 - index, 0);
break;
case loopReverse:
index = count - 1 - (index % count);
}
}
slot.setSequenceIndex(index);
}
}
}

View File

@ -1012,16 +1012,20 @@ public class AnimationState {
nextAnimationLast = animationLast;
}
/** Uses {@link #getTrackTime()} to compute the <code>animationTime</code>, which is between {@link #getAnimationStart()}
* and {@link #getAnimationEnd()}. When the <code>trackTime</code> is 0, the <code>animationTime</code> is equal to the
* <code>animationStart</code> time. */
/** Uses {@link #getTrackTime()} to compute the <code>animationTime</code>. When the <code>trackTime</code> is 0, the
* <code>animationTime</code> is equal to the <code>animationStart</code> time.
* <p>
* The <code>animationTime</code> is between {@link #getAnimationStart()} and {@link #getAnimationEnd()}, except if this
* track entry is non-looping and {@link #getAnimationEnd()} is >= to the animation {@link Animation#duration}, then
* <code>animationTime</code> continues to increase past {@link #getAnimationEnd()}. */
public float getAnimationTime () {
if (loop) {
float duration = animationEnd - animationStart;
if (duration == 0) return animationStart;
return (trackTime % duration) + animationStart;
}
return Math.min(trackTime + animationStart, animationEnd);
float animationTime = trackTime + animationStart;
return animationEnd >= animation.duration ? animationTime : Math.min(animationTime, animationEnd);
}
/** Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or

View File

@ -42,7 +42,6 @@ import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment;
/** Stores the current pose for a skeleton.
* <p>
@ -60,7 +59,6 @@ public class Skeleton {
final Array<Updatable> updateCache = new Array();
@Null Skin skin;
final Color color;
float time;
float scaleX = 1, scaleY = 1;
float x, y;
@ -158,7 +156,6 @@ public class Skeleton {
skin = skeleton.skin;
color = new Color(skeleton.color);
time = skeleton.time;
scaleX = skeleton.scaleX;
scaleY = skeleton.scaleY;
@ -722,11 +719,11 @@ public class Skeleton {
int verticesLength = 0;
float[] vertices = null;
Attachment attachment = slot.attachment;
if (attachment instanceof SequenceAttachment) attachment = ((SequenceAttachment)attachment).updateAttachment(slot);
if (attachment instanceof RegionAttachment) {
RegionAttachment region = (RegionAttachment)attachment;
verticesLength = 8;
vertices = temp.setSize(8);
((RegionAttachment)attachment).computeWorldVertices(slot.getBone(), vertices, 0, 2);
region.computeWorldVertices(slot, vertices, 0, 2);
} else if (attachment instanceof MeshAttachment) {
MeshAttachment mesh = (MeshAttachment)attachment;
verticesLength = mesh.getWorldVerticesLength();
@ -812,22 +809,6 @@ public class Skeleton {
this.y = y;
}
/** Returns the skeleton's time. This can be used for tracking, such as with Slot {@link Slot#getAttachmentTime()}.
* <p>
* See {@link #update(float)}. */
public float getTime () {
return time;
}
public void setTime (float time) {
this.time = time;
}
/** Increments the skeleton's {@link #time}. */
public void update (float delta) {
time += delta;
}
public String toString () {
return data.name != null ? data.name : super.toString();
}

View File

@ -63,6 +63,7 @@ import com.esotericsoftware.spine.Animation.RotateTimeline;
import com.esotericsoftware.spine.Animation.ScaleTimeline;
import com.esotericsoftware.spine.Animation.ScaleXTimeline;
import com.esotericsoftware.spine.Animation.ScaleYTimeline;
import com.esotericsoftware.spine.Animation.SequenceTimeline;
import com.esotericsoftware.spine.Animation.ShearTimeline;
import com.esotericsoftware.spine.Animation.ShearXTimeline;
import com.esotericsoftware.spine.Animation.ShearYTimeline;
@ -85,9 +86,8 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment.SequenceMode;
import com.esotericsoftware.spine.attachments.HasTextureRegion;
import com.esotericsoftware.spine.attachments.Sequence;
import com.esotericsoftware.spine.attachments.Sequence.SequenceMode;
import com.esotericsoftware.spine.attachments.VertexAttachment;
/** Loads skeleton data in the Spine binary format.
@ -114,6 +114,9 @@ public class SkeletonBinary extends SkeletonLoader {
static public final int SLOT_RGB2 = 4;
static public final int SLOT_ALPHA = 5;
static public final int ATTACHMENT_DEFORM = 0;
static public final int ATTACHMENT_SEQUENCE = 1;
static public final int PATH_POSITION = 0;
static public final int PATH_SPACING = 1;
static public final int PATH_MIX = 2;
@ -303,9 +306,9 @@ public class SkeletonBinary extends SkeletonLoader {
if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh);
linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh);
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
linkedMesh.mesh.updateRegion();
if (linkedMesh.mesh.getSequence() == null) linkedMesh.mesh.updateRegion();
}
linkedMeshes.clear();
@ -398,9 +401,10 @@ public class SkeletonBinary extends SkeletonLoader {
float width = input.readFloat();
float height = input.readFloat();
int color = input.readInt();
Sequence sequence = readSequence(input);
if (path == null) path = name;
RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path);
RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, sequence);
if (region == null) return null;
region.setPath(path);
region.setX(x * scale);
@ -411,7 +415,8 @@ public class SkeletonBinary extends SkeletonLoader {
region.setWidth(width * scale);
region.setHeight(height * scale);
Color.rgba8888ToColor(region.getColor(), color);
region.updateRegion();
region.setSequence(sequence);
if (sequence == null) region.updateRegion();
return region;
}
case boundingbox: {
@ -435,6 +440,7 @@ public class SkeletonBinary extends SkeletonLoader {
short[] triangles = readShortArray(input);
Vertices vertices = readVertices(input, vertexCount);
int hullLength = input.readInt(true);
Sequence sequence = readSequence(input);
short[] edges = null;
float width = 0, height = 0;
if (nonessential) {
@ -444,7 +450,7 @@ public class SkeletonBinary extends SkeletonLoader {
}
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path);
MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence);
if (mesh == null) return null;
mesh.setPath(path);
Color.rgba8888ToColor(mesh.getColor(), color);
@ -453,8 +459,9 @@ public class SkeletonBinary extends SkeletonLoader {
mesh.setWorldVerticesLength(vertexCount << 1);
mesh.setTriangles(triangles);
mesh.setRegionUVs(uvs);
mesh.updateRegion();
if (sequence == null) mesh.updateRegion();
mesh.setHullLength(hullLength << 1);
mesh.setSequence(sequence);
if (nonessential) {
mesh.setEdges(edges);
mesh.setWidth(width * scale);
@ -467,7 +474,8 @@ public class SkeletonBinary extends SkeletonLoader {
int color = input.readInt();
String skinName = input.readStringRef();
String parent = input.readStringRef();
boolean inheritDeform = input.readBoolean();
boolean inheritTimelines = input.readBoolean();
Sequence sequence = readSequence(input);
float width = 0, height = 0;
if (nonessential) {
width = input.readFloat();
@ -475,15 +483,16 @@ public class SkeletonBinary extends SkeletonLoader {
}
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path);
MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence);
if (mesh == null) return null;
mesh.setPath(path);
Color.rgba8888ToColor(mesh.getColor(), color);
mesh.setSequence(sequence);
if (nonessential) {
mesh.setWidth(width * scale);
mesh.setHeight(height * scale);
}
linkedMeshes.add(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform));
linkedMeshes.add(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritTimelines));
return mesh;
}
case path: {
@ -521,7 +530,7 @@ public class SkeletonBinary extends SkeletonLoader {
if (nonessential) Color.rgba8888ToColor(point.getColor(), color);
return point;
}
case clipping: {
case clipping:
int endSlotIndex = input.readInt(true);
int vertexCount = input.readInt(true);
Vertices vertices = readVertices(input, vertexCount);
@ -536,28 +545,18 @@ public class SkeletonBinary extends SkeletonLoader {
if (nonessential) Color.rgba8888ToColor(clip.getColor(), color);
return clip;
}
case sequence:
Attachment attachment = readAttachment(input, skeletonData, skin, slotIndex, attachmentName, nonessential);
int frameCount = input.readInt(true);
float frameTime = input.readFloat();
SequenceMode mode = SequenceMode.values[input.readInt(true)];
if (attachment == null) return null;
String path = ((HasTextureRegion)attachment).getPath();
SequenceAttachment sequence = attachmentLoader.newSequenceAttachment(skin, name, path, frameCount);
if (sequence == null) return null;
sequence.setAttachment(attachment);
sequence.setPath(path);
sequence.setFrameCount(frameCount);
sequence.setFrameTime(frameTime);
sequence.setMode(mode);
return sequence;
}
return null;
}
private Sequence readSequence (SkeletonInput input) throws IOException {
if (!input.readBoolean()) return null;
Sequence sequence = new Sequence(input.readInt(true));
sequence.setStart(input.readInt(true));
sequence.setDigits(input.readInt(true));
sequence.setSetupIndex(input.readInt(true));
return sequence;
}
private Vertices readVertices (SkeletonInput input, int vertexCount) throws IOException {
float scale = this.scale;
int verticesLength = vertexCount << 1;
@ -767,7 +766,6 @@ public class SkeletonBinary extends SkeletonLoader {
a = a2;
}
timelines.add(timeline);
break;
}
}
}
@ -913,57 +911,73 @@ public class SkeletonBinary extends SkeletonLoader {
}
}
// Deform timelines.
// Attachment timelines.
for (int i = 0, n = input.readInt(true); i < n; i++) {
Skin skin = skeletonData.skins.get(input.readInt(true));
for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
int slotIndex = input.readInt(true);
for (int iii = 0, nnn = input.readInt(true); iii < nnn; iii++) {
String attachmentName = input.readStringRef();
VertexAttachment attachment = (VertexAttachment)skin.getAttachment(slotIndex, attachmentName);
if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName);
boolean weighted = attachment.getBones() != null;
float[] vertices = attachment.getVertices();
int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length;
Attachment attachment = skin.getAttachment(slotIndex, attachmentName);
if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentName);
int frameCount = input.readInt(true), frameLast = frameCount - 1;
DeformTimeline timeline = new DeformTimeline(frameCount, input.readInt(true), slotIndex, attachment);
int timelineType = input.readByte(), frameCount = input.readInt(true), frameLast = frameCount - 1;
switch (timelineType) {
case ATTACHMENT_DEFORM: {
VertexAttachment vertexAttachment = (VertexAttachment)attachment;
boolean weighted = vertexAttachment.getBones() != null;
float[] vertices = vertexAttachment.getVertices();
int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length;
float time = input.readFloat();
for (int frame = 0, bezier = 0;; frame++) {
float[] deform;
int end = input.readInt(true);
if (end == 0)
deform = weighted ? new float[deformLength] : vertices;
else {
deform = new float[deformLength];
int start = input.readInt(true);
end += start;
if (scale == 1) {
for (int v = start; v < end; v++)
deform[v] = input.readFloat();
} else {
for (int v = start; v < end; v++)
deform[v] = input.readFloat() * scale;
DeformTimeline timeline = new DeformTimeline(frameCount, input.readInt(true), slotIndex, vertexAttachment);
float time = input.readFloat();
for (int frame = 0, bezier = 0;; frame++) {
float[] deform;
int end = input.readInt(true);
if (end == 0)
deform = weighted ? new float[deformLength] : vertices;
else {
deform = new float[deformLength];
int start = input.readInt(true);
end += start;
if (scale == 1) {
for (int v = start; v < end; v++)
deform[v] = input.readFloat();
} else {
for (int v = start; v < end; v++)
deform[v] = input.readFloat() * scale;
}
if (!weighted) {
for (int v = 0, vn = deform.length; v < vn; v++)
deform[v] += vertices[v];
}
}
if (!weighted) {
for (int v = 0, vn = deform.length; v < vn; v++)
deform[v] += vertices[v];
timeline.setFrame(frame, time, deform);
if (frame == frameLast) break;
float time2 = input.readFloat();
switch (input.readByte()) {
case CURVE_STEPPED:
timeline.setStepped(frame);
break;
case CURVE_BEZIER:
setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1);
}
time = time2;
}
timeline.setFrame(frame, time, deform);
if (frame == frameLast) break;
float time2 = input.readFloat();
switch (input.readByte()) {
case CURVE_STEPPED:
timeline.setStepped(frame);
break;
case CURVE_BEZIER:
setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1);
}
time = time2;
timelines.add(timeline);
break;
}
case ATTACHMENT_SEQUENCE:
SequenceTimeline timeline = new SequenceTimeline(frameCount, slotIndex, attachment);
for (int frame = 0; frame < frameCount; frame++) {
float time = input.readFloat();
int modeAndIndex = input.readInt();
timeline.setFrame(frame, time, SequenceMode.values[modeAndIndex & 0xf], modeAndIndex >> 4,
input.readFloat());
}
timelines.add(timeline);
}
timelines.add(timeline);
}
}
}

View File

@ -41,6 +41,7 @@ import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.JsonReader;
import com.badlogic.gdx.utils.JsonValue;
import com.badlogic.gdx.utils.Null;
import com.badlogic.gdx.utils.SerializationException;
import com.esotericsoftware.spine.Animation.AlphaTimeline;
@ -63,6 +64,7 @@ import com.esotericsoftware.spine.Animation.RotateTimeline;
import com.esotericsoftware.spine.Animation.ScaleTimeline;
import com.esotericsoftware.spine.Animation.ScaleXTimeline;
import com.esotericsoftware.spine.Animation.ScaleYTimeline;
import com.esotericsoftware.spine.Animation.SequenceTimeline;
import com.esotericsoftware.spine.Animation.ShearTimeline;
import com.esotericsoftware.spine.Animation.ShearXTimeline;
import com.esotericsoftware.spine.Animation.ShearYTimeline;
@ -84,9 +86,8 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment.SequenceMode;
import com.esotericsoftware.spine.attachments.HasTextureRegion;
import com.esotericsoftware.spine.attachments.Sequence;
import com.esotericsoftware.spine.attachments.Sequence.SequenceMode;
import com.esotericsoftware.spine.attachments.VertexAttachment;
/** Loads skeleton data in the Spine JSON format.
@ -324,9 +325,9 @@ public class SkeletonJson extends SkeletonLoader {
if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh);
linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh);
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
linkedMesh.mesh.updateRegion();
if (linkedMesh.mesh.getRegion() != null) linkedMesh.mesh.updateRegion();
}
linkedMeshes.clear();
@ -369,7 +370,8 @@ public class SkeletonJson extends SkeletonLoader {
switch (AttachmentType.valueOf(map.getString("type", AttachmentType.region.name()))) {
case region: {
String path = map.getString("path", name);
RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path);
Sequence sequence = readSequence(map.get("sequence"));
RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, sequence);
if (region == null) return null;
region.setPath(path);
region.setX(map.getFloat("x", 0) * scale);
@ -379,11 +381,12 @@ public class SkeletonJson extends SkeletonLoader {
region.setRotation(map.getFloat("rotation", 0));
region.setWidth(map.getFloat("width") * scale);
region.setHeight(map.getFloat("height") * scale);
region.setSequence(sequence);
String color = map.getString("color", null);
if (color != null) Color.valueOf(color, region.getColor());
region.updateRegion();
if (region.getRegion() != null) region.updateRegion();
return region;
}
case boundingbox: {
@ -398,7 +401,8 @@ public class SkeletonJson extends SkeletonLoader {
case mesh:
case linkedmesh: {
String path = map.getString("path", name);
MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path);
Sequence sequence = readSequence(map.get("sequence"));
MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence);
if (mesh == null) return null;
mesh.setPath(path);
@ -407,11 +411,12 @@ 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) {
linkedMeshes
.add(new LinkedMesh(mesh, map.getString("skin", null), slotIndex, parent, map.getBoolean("deform", true)));
.add(new LinkedMesh(mesh, map.getString("skin", null), slotIndex, parent, map.getBoolean("timelines", true)));
return mesh;
}
@ -419,7 +424,7 @@ public class SkeletonJson extends SkeletonLoader {
readVertices(map, mesh, uvs.length);
mesh.setTriangles(map.require("triangles").asShortArray());
mesh.setRegionUVs(uvs);
mesh.updateRegion();
if (mesh.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());
@ -455,7 +460,7 @@ public class SkeletonJson extends SkeletonLoader {
if (color != null) Color.valueOf(color, point.getColor());
return point;
}
case clipping: {
case clipping:
ClippingAttachment clip = attachmentLoader.newClippingAttachment(skin, name);
if (clip == null) return null;
@ -472,23 +477,18 @@ public class SkeletonJson extends SkeletonLoader {
if (color != null) Color.valueOf(color, clip.getColor());
return clip;
}
case sequence:
Attachment attachment = readAttachment(map.getChild("attachment"), skin, slotIndex, name, skeletonData);
if (attachment == null) return null;
String path = ((HasTextureRegion)attachment).getPath();
int frameCount = map.getInt("count");
SequenceAttachment sequence = attachmentLoader.newSequenceAttachment(skin, name, path, frameCount);
if (sequence == null) return null;
sequence.setAttachment(attachment);
sequence.setPath(path);
sequence.setFrameCount(frameCount);
sequence.setFrameTime(map.getInt("time"));
sequence.setMode(SequenceMode.valueOf(map.getString("mode", SequenceMode.forward.name())));
return sequence;
}
return null;
}
private Sequence readSequence (@Null JsonValue map) {
if (map == null) return null;
Sequence sequence = new Sequence(map.getInt("count"));
sequence.setStart(map.getInt("start", 1));
sequence.setDigits(map.getInt("digits", 0));
sequence.setSetupIndex(map.getInt("setup", 0));
return sequence;
}
private void readVertices (JsonValue map, VertexAttachment attachment, int verticesLength) {
attachment.setWorldVerticesLength(verticesLength);
float[] vertices = map.require("vertices").asFloatArray();
@ -533,7 +533,7 @@ public class SkeletonJson extends SkeletonLoader {
if (timelineName.equals("attachment")) {
AttachmentTimeline timeline = new AttachmentTimeline(frames, slot.index);
for (int frame = 0; keyMap != null; keyMap = keyMap.next, frame++)
timeline.setFrame(frame, keyMap.getFloat("time", 0), keyMap.getString("name"));
timeline.setFrame(frame, keyMap.getFloat("time", 0), keyMap.getString("name", null));
timelines.add(timeline);
} else if (timelineName.equals("rgba")) {
@ -877,57 +877,72 @@ public class SkeletonJson extends SkeletonLoader {
}
}
// Deform timelines.
for (JsonValue deformMap = map.getChild("deform"); deformMap != null; deformMap = deformMap.next) {
Skin skin = skeletonData.findSkin(deformMap.name);
if (skin == null) throw new SerializationException("Skin not found: " + deformMap.name);
for (JsonValue slotMap = deformMap.child; slotMap != null; slotMap = slotMap.next) {
// Attachment timelines.
for (JsonValue attachmentsMap = map.getChild("attachments"); attachmentsMap != null; attachmentsMap = attachmentsMap.next) {
Skin skin = skeletonData.findSkin(attachmentsMap.name);
if (skin == null) throw new SerializationException("Skin not found: " + attachmentsMap.name);
for (JsonValue slotMap = attachmentsMap.child; slotMap != null; slotMap = slotMap.next) {
SlotData slot = skeletonData.findSlot(slotMap.name);
if (slot == null) throw new SerializationException("Slot not found: " + slotMap.name);
for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) {
JsonValue keyMap = timelineMap.child;
if (keyMap == null) continue;
for (JsonValue attachmentMap = slotMap.child; attachmentMap != null; attachmentMap = attachmentMap.next) {
Attachment attachment = skin.getAttachment(slot.index, attachmentMap.name);
if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentMap.name);
for (JsonValue timelineMap = attachmentMap.child; timelineMap != null; timelineMap = timelineMap.next) {
JsonValue keyMap = timelineMap.child;
int frames = timelineMap.size;
String timelineName = timelineMap.name;
if (timelineName.equals("deform")) {
VertexAttachment vertexAttachment = (VertexAttachment)attachment;
boolean weighted = vertexAttachment.getBones() != null;
float[] vertices = vertexAttachment.getVertices();
int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length;
VertexAttachment attachment = (VertexAttachment)skin.getAttachment(slot.index, timelineMap.name);
if (attachment == null) throw new SerializationException("Deform attachment not found: " + timelineMap.name);
boolean weighted = attachment.getBones() != null;
float[] vertices = attachment.getVertices();
int deformLength = weighted ? (vertices.length / 3) << 1 : vertices.length;
DeformTimeline timeline = new DeformTimeline(frames, frames, slot.index, vertexAttachment);
float time = keyMap.getFloat("time", 0);
for (int frame = 0, bezier = 0;; frame++) {
float[] deform;
JsonValue verticesValue = keyMap.get("vertices");
if (verticesValue == null)
deform = weighted ? new float[deformLength] : vertices;
else {
deform = new float[deformLength];
int start = keyMap.getInt("offset", 0);
arraycopy(verticesValue.asFloatArray(), 0, deform, start, verticesValue.size);
if (scale != 1) {
for (int i = start, n = i + verticesValue.size; i < n; i++)
deform[i] *= scale;
}
if (!weighted) {
for (int i = 0; i < deformLength; i++)
deform[i] += vertices[i];
}
}
DeformTimeline timeline = new DeformTimeline(timelineMap.size, timelineMap.size, slot.index, attachment);
float time = keyMap.getFloat("time", 0);
for (int frame = 0, bezier = 0;; frame++) {
float[] deform;
JsonValue verticesValue = keyMap.get("vertices");
if (verticesValue == null)
deform = weighted ? new float[deformLength] : vertices;
else {
deform = new float[deformLength];
int start = keyMap.getInt("offset", 0);
arraycopy(verticesValue.asFloatArray(), 0, deform, start, verticesValue.size);
if (scale != 1) {
for (int i = start, n = i + verticesValue.size; i < n; i++)
deform[i] *= scale;
timeline.setFrame(frame, time, deform);
JsonValue nextMap = keyMap.next;
if (nextMap == null) {
timeline.shrink(bezier);
break;
}
float time2 = nextMap.getFloat("time", 0);
JsonValue curve = keyMap.get("curve");
if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1);
time = time2;
keyMap = nextMap;
}
if (!weighted) {
for (int i = 0; i < deformLength; i++)
deform[i] += vertices[i];
timelines.add(timeline);
} else if (timelineName.equals("sequence")) {
SequenceTimeline timeline = new SequenceTimeline(frames, slot.index, attachment);
float lastDelay = 0;
for (int frame = 0; keyMap != null; keyMap = keyMap.next, frame++) {
float delay = keyMap.getFloat("delay", lastDelay);
timeline.setFrame(frame, keyMap.getFloat("time", 0),
SequenceMode.valueOf(keyMap.getString("mode", "stop")), keyMap.getInt("index", 0), delay);
lastDelay = delay;
}
timelines.add(timeline);
}
timeline.setFrame(frame, time, deform);
JsonValue nextMap = keyMap.next;
if (nextMap == null) {
timeline.shrink(bezier);
break;
}
float time2 = nextMap.getFloat("time", 0);
JsonValue curve = keyMap.get("curve");
if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1);
time = time2;
keyMap = nextMap;
}
timelines.add(timeline);
}
}
}
@ -1068,14 +1083,14 @@ public class SkeletonJson extends SkeletonLoader {
String parent, skin;
int slotIndex;
MeshAttachment mesh;
boolean inheritDeform;
boolean inheritTimelines;
public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent, boolean inheritDeform) {
public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent, boolean inheritTimelines) {
this.mesh = mesh;
this.skin = skin;
this.slotIndex = slotIndex;
this.parent = parent;
this.inheritDeform = inheritDeform;
this.inheritTimelines = inheritTimelines;
}
}
}

View File

@ -98,7 +98,7 @@ public class SkeletonRenderer {
Attachment attachment = slot.attachment;
if (attachment instanceof RegionAttachment) {
RegionAttachment region = (RegionAttachment)attachment;
region.computeWorldVertices(slot.getBone(), vertices, 0, 5);
region.computeWorldVertices(slot, vertices, 0, 5);
Color color = region.getColor(), slotColor = slot.getColor();
float alpha = a * slotColor.a * color.a * 255;
float multiplier = pmaColors ? alpha : 255;
@ -183,7 +183,7 @@ public class SkeletonRenderer {
RegionAttachment region = (RegionAttachment)attachment;
verticesLength = vertexSize << 2;
vertices = this.vertices.items;
region.computeWorldVertices(slot.getBone(), vertices, 0, vertexSize);
region.computeWorldVertices(slot, vertices, 0, vertexSize);
triangles = quadTriangles;
texture = region.getRegion().getTexture();
uvs = region.getUVs();
@ -309,7 +309,7 @@ public class SkeletonRenderer {
RegionAttachment region = (RegionAttachment)attachment;
verticesLength = vertexSize << 2;
vertices = this.vertices.items;
region.computeWorldVertices(slot.getBone(), vertices, 0, vertexSize);
region.computeWorldVertices(slot, vertices, 0, vertexSize);
triangles = quadTriangles;
texture = region.getRegion().getTexture();
uvs = region.getUVs();

View File

@ -127,7 +127,7 @@ public class SkeletonRendererDebug {
if (attachment instanceof RegionAttachment) {
RegionAttachment region = (RegionAttachment)attachment;
float[] vertices = this.vertices.items;
region.computeWorldVertices(slot.getBone(), vertices, 0, 2);
region.computeWorldVertices(slot, vertices, 0, 2);
shapes.line(vertices[0], vertices[1], vertices[2], vertices[3]);
shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
shapes.line(vertices[4], vertices[5], vertices[6], vertices[7]);

View File

@ -35,6 +35,7 @@ import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Animation.DeformTimeline;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.Sequence;
import com.esotericsoftware.spine.attachments.VertexAttachment;
/** Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store
@ -46,8 +47,8 @@ public class Slot {
final Color color = new Color();
@Null final Color darkColor;
@Null Attachment attachment;
private float attachmentTime;
private FloatArray deform = new FloatArray();
int sequenceIndex;
FloatArray deform = new FloatArray();
int attachmentState;
@ -69,7 +70,7 @@ public class Slot {
color.set(slot.color);
darkColor = slot.darkColor == null ? null : new Color(slot.darkColor);
attachment = slot.attachment;
attachmentTime = slot.attachmentTime;
sequenceIndex = slot.sequenceIndex;
deform.addAll(slot.deform);
}
@ -105,27 +106,28 @@ public class Slot {
return attachment;
}
/** Sets the slot's attachment and, if the attachment changed, resets {@link #attachmentTime} and clears the {@link #deform}.
* The deform is not cleared if the old attachment has the same {@link VertexAttachment#getDeformAttachment()} as the specified
* attachment. */
/** Sets the slot's attachment and, if the attachment changed, resets {@link #sequenceIndex} and clears the {@link #deform}.
* The deform is not cleared if the old attachment has the same {@link VertexAttachment#getTimelineAttachment()} as the
* specified attachment. */
public void setAttachment (@Null Attachment attachment) {
if (this.attachment == attachment) return;
if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment)
|| ((VertexAttachment)attachment).getDeformAttachment() != ((VertexAttachment)this.attachment).getDeformAttachment()) {
|| ((VertexAttachment)attachment).getTimelineAttachment() != ((VertexAttachment)this.attachment)
.getTimelineAttachment()) {
deform.clear();
}
this.attachment = attachment;
attachmentTime = bone.skeleton.time;
sequenceIndex = -1;
}
/** The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton
* {@link Skeleton#time}. */
public float getAttachmentTime () {
return bone.skeleton.time - attachmentTime;
/** The index of the texture region to display when the slot's attachment has a {@link Sequence}. -1 represents the
* {@link Sequence#getSetupIndex()}. */
public int getSequenceIndex () {
return sequenceIndex;
}
public void setAttachmentTime (float time) {
attachmentTime = bone.skeleton.time - time;
public void setSequenceIndex (int sequenceIndex) {
this.sequenceIndex = sequenceIndex;
}
/** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a

View File

@ -31,6 +31,8 @@ package com.esotericsoftware.spine.attachments;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Skin;
@ -47,32 +49,39 @@ public class AtlasAttachmentLoader implements AttachmentLoader {
this.atlas = atlas;
}
public RegionAttachment newRegionAttachment (Skin skin, String name, String path) {
AtlasRegion region = atlas.findRegion(path);
if (region == null) throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")");
RegionAttachment attachment = new RegionAttachment(name);
attachment.setRegion(region);
return attachment;
}
public MeshAttachment newMeshAttachment (Skin skin, String name, String path) {
AtlasRegion region = atlas.findRegion(path);
if (region == null) throw new RuntimeException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
MeshAttachment attachment = new MeshAttachment(name);
attachment.setRegion(region);
return attachment;
}
public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) {
AtlasRegion[] regions = new AtlasRegion[frameCount];
for (int i = 0; i < frameCount; i++) {
AtlasRegion region = atlas.findRegion(path + frameCount); // BOZO - Zero pad?
if (region == null)
throw new RuntimeException("Region not found in atlas: " + path + frameCount + " (sequence: " + name + ")");
private void loadSequence (String name, String basePath, Sequence sequence) {
TextureRegion[] regions = sequence.getRegions();
for (int i = 0, n = regions.length; i < n; i++) {
String path = sequence.getPath(basePath, i);
regions[i] = atlas.findRegion(path);
if (regions[i] == null) throw new RuntimeException("Region not found in atlas: " + path + " (sequence: " + name + ")");
}
SequenceAttachment sequence = new SequenceAttachment(name);
sequence.setRegions(regions);
return sequence;
}
public RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
RegionAttachment attachment = new RegionAttachment(name);
if (sequence != null)
loadSequence(name, path, sequence);
else {
AtlasRegion region = atlas.findRegion(path);
if (region == null)
throw new RuntimeException("Region not found in atlas: " + path + " (region attachment: " + name + ")");
attachment.setRegion(region);
}
return attachment;
}
public MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence) {
MeshAttachment attachment = new MeshAttachment(name);
if (sequence != null)
loadSequence(name, path, sequence);
else {
AtlasRegion region = atlas.findRegion(path);
if (region == null)
throw new RuntimeException("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
attachment.setRegion(region);
}
return attachment;
}
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {

View File

@ -31,13 +31,18 @@ package com.esotericsoftware.spine.attachments;
/** The base class for all attachments. */
abstract public class Attachment {
String name;
final String name;
public Attachment (String name) {
if (name == null) throw new IllegalArgumentException("name cannot be null.");
this.name = name;
}
/** Copy constructor. */
protected Attachment (Attachment other) {
name = other.name;
}
/** The attachment's name. */
public String getName () {
return name;

View File

@ -39,13 +39,10 @@ import com.esotericsoftware.spine.Skin;
* Runtimes Guide. */
public interface AttachmentLoader {
/** @return May be null to not load the attachment. */
public @Null RegionAttachment newRegionAttachment (Skin skin, String name, String path);
public @Null RegionAttachment newRegionAttachment (Skin skin, String name, String path, @Null Sequence sequence);
/** @return May be null to not load the attachment. In that case null should also be returned for child meshes. */
public @Null MeshAttachment newMeshAttachment (Skin skin, String name, String path);
/** @return May be null to not load the attachment. */
public @Null SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount);
public @Null MeshAttachment newMeshAttachment (Skin skin, String name, String path, @Null Sequence sequence);
/** @return May be null to not load the attachment. */
public @Null BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name);

View File

@ -46,16 +46,19 @@ public class BoundingBoxAttachment extends VertexAttachment {
super(name);
}
/** Copy constructor. */
protected BoundingBoxAttachment (BoundingBoxAttachment other) {
super(other);
color.set(other.color);
}
/** The color of the bounding box as it was in Spine, or a default color if nonessential data was not exported. Bounding boxes
* are not usually rendered at runtime. */
public Color getColor () {
return color;
}
public Attachment copy () {
BoundingBoxAttachment copy = new BoundingBoxAttachment(name);
copyTo(copy);
copy.color.set(color);
return copy;
public BoundingBoxAttachment copy () {
return new BoundingBoxAttachment(name);
}
}

View File

@ -45,6 +45,13 @@ public class ClippingAttachment extends VertexAttachment {
super(name);
}
/** Copy constructor. */
protected ClippingAttachment (ClippingAttachment other) {
super(other);
endSlot = other.endSlot;
color.set(other.color);
}
/** Clipping is performed between the clipping attachment's slot and the end slot. If null clipping is done until the end of
* the skeleton's rendering. */
public @Null SlotData getEndSlot () {
@ -61,11 +68,7 @@ public class ClippingAttachment extends VertexAttachment {
return color;
}
public Attachment copy () {
ClippingAttachment copy = new ClippingAttachment(name);
copyTo(copy);
copy.endSlot = endSlot;
copy.color.set(color);
return copy;
public ClippingAttachment copy () {
return new ClippingAttachment(name);
}
}

View File

@ -3,6 +3,7 @@ package com.esotericsoftware.spine.attachments;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Null;
public interface HasTextureRegion {
/** The name used to find the {@link #getRegion()}. */
@ -10,16 +11,20 @@ public interface HasTextureRegion {
public void setPath (String path);
/** Sets the region used to draw the attachment. If the region or its properties are changed, {@link #updateRegion()} must be
* called. */
public void setRegion (TextureRegion region);
public TextureRegion getRegion ();
/** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after changing the region or
* the region's properties. */
/** Sets the region used to draw the attachment. After setting the region or if the region's properties are changed,
* {@link #updateRegion()} must be called. */
public void setRegion (TextureRegion region);
/** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after setting the
* {@link #getRegion()} or if the region's properties are changed. */
public void updateRegion ();
/** The color to tint the attachment. */
public Color getColor ();
public @Null Sequence getSequence ();
public void setSequence (@Null Sequence sequence);
}

View File

@ -36,6 +36,8 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Slot;
/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not
* supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
* <p>
@ -48,6 +50,7 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion
private final Color color = new Color(1, 1, 1, 1);
private int hullLength;
private @Null MeshAttachment parentMesh;
private @Null Sequence sequence;
// Nonessential.
private @Null short[] edges;
@ -57,18 +60,47 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion
super(name);
}
/** Copy constructor. Use {@link #newLinkedMesh()} if the other mesh is a linked mesh. */
protected MeshAttachment (MeshAttachment other) {
super(other);
if (parentMesh != null) throw new IllegalArgumentException("Use newLinkedMesh to copy a linked mesh.");
region = other.region;
path = other.path;
color.set(other.color);
regionUVs = new float[other.regionUVs.length];
arraycopy(other.regionUVs, 0, regionUVs, 0, regionUVs.length);
uvs = new float[other.uvs.length];
arraycopy(other.uvs, 0, uvs, 0, uvs.length);
triangles = new short[other.triangles.length];
arraycopy(other.triangles, 0, triangles, 0, triangles.length);
hullLength = other.hullLength;
// Nonessential.
if (other.edges != null) {
edges = new short[other.edges.length];
arraycopy(other.edges, 0, edges, 0, edges.length);
}
width = other.width;
height = other.height;
}
public void setRegion (TextureRegion region) {
if (region == null) throw new IllegalArgumentException("region cannot be null.");
this.region = region;
}
public TextureRegion getRegion () {
if (region == null) throw new IllegalStateException("Region has not been set: " + this);
public @Null TextureRegion getRegion () {
return region;
}
/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region or the
* region's properties. */
/** Calculates {@link #uvs} using the {@link #regionUVs} and {@link #region}. Must be called if the {@link #region},
* {@link #regionUVs}, or the region's properties are changed. */
public void updateRegion () {
float[] regionUVs = this.regionUVs;
if (this.uvs == null || this.uvs.length != regionUVs.length) this.uvs = new float[regionUVs.length];
@ -131,6 +163,12 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion
}
}
/** If the attachment has a {@link #sequence}, the {@link #region} may be changed. */
public void computeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride) {
if (sequence != null) sequence.apply(slot, this);
super.computeWorldVertices(slot, start, count, worldVertices, offset, stride);
}
/** Triplets of vertex indices which describe the mesh's triangulation. */
public short[] getTriangles () {
return triangles;
@ -210,6 +248,14 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion
this.height = height;
}
public @Null Sequence getSequence () {
return sequence;
}
public void setSequence (@Null Sequence sequence) {
this.sequence = sequence;
}
/** The parent mesh if this is a linked mesh, else null. A linked mesh shares the {@link #bones}, {@link #vertices},
* {@link #regionUVs}, {@link #triangles}, {@link #hullLength}, {@link #edges}, {@link #width}, and {@link #height} with the
* parent mesh, but may have a different {@link #name} or {@link #path} (and therefore a different texture). */
@ -232,42 +278,19 @@ public class MeshAttachment extends VertexAttachment implements HasTextureRegion
}
}
public Attachment copy () {
if (parentMesh != null) return newLinkedMesh();
MeshAttachment copy = new MeshAttachment(name);
copy.region = region;
copy.path = path;
copy.color.set(color);
copyTo(copy);
copy.regionUVs = new float[regionUVs.length];
arraycopy(regionUVs, 0, copy.regionUVs, 0, regionUVs.length);
copy.uvs = new float[uvs.length];
arraycopy(uvs, 0, copy.uvs, 0, uvs.length);
copy.triangles = new short[triangles.length];
arraycopy(triangles, 0, copy.triangles, 0, triangles.length);
copy.hullLength = hullLength;
// Nonessential.
if (edges != null) {
copy.edges = new short[edges.length];
arraycopy(edges, 0, copy.edges, 0, edges.length);
}
copy.width = width;
copy.height = height;
return copy;
}
/** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. */
public MeshAttachment newLinkedMesh () {
MeshAttachment mesh = new MeshAttachment(name);
mesh.timelineAttachment = timelineAttachment;
mesh.region = region;
mesh.path = path;
mesh.color.set(color);
mesh.deformAttachment = deformAttachment;
mesh.setParentMesh(parentMesh != null ? parentMesh : this);
mesh.updateRegion();
if (mesh.getRegion() != null) mesh.updateRegion();
return mesh;
}
public MeshAttachment copy () {
return parentMesh != null ? newLinkedMesh() : new MeshAttachment(this);
}
}

View File

@ -49,6 +49,18 @@ public class PathAttachment extends VertexAttachment {
super(name);
}
/** Copy constructor. */
protected PathAttachment (PathAttachment other) {
super(other);
lengths = new float[other.lengths.length];
arraycopy(other.lengths, 0, lengths, 0, lengths.length);
closed = other.closed;
constantSpeed = other.constantSpeed;
color.set(other.color);
}
/** If true, the start and end knots are connected. */
public boolean getClosed () {
return closed;
@ -83,14 +95,7 @@ public class PathAttachment extends VertexAttachment {
return color;
}
public Attachment copy () {
PathAttachment copy = new PathAttachment(name);
copyTo(copy);
copy.lengths = new float[lengths.length];
arraycopy(lengths, 0, copy.lengths, 0, lengths.length);
copy.closed = closed;
copy.constantSpeed = constantSpeed;
copy.color.set(color);
return copy;
public PathAttachment copy () {
return new PathAttachment(this);
}
}

View File

@ -51,6 +51,15 @@ public class PointAttachment extends Attachment {
super(name);
}
/** Copy constructor. */
protected PointAttachment (PointAttachment other) {
super(other);
x = other.x;
y = other.y;
rotation = other.rotation;
color.set(other.color);
}
public float getX () {
return x;
}
@ -94,12 +103,7 @@ public class PointAttachment extends Attachment {
return (float)Math.atan2(y, x) * radDeg;
}
public Attachment copy () {
PointAttachment copy = new PointAttachment(name);
copy.x = x;
copy.y = y;
copy.rotation = rotation;
copy.color.set(color);
return copy;
public PointAttachment copy () {
return new PointAttachment(this);
}
}

View File

@ -34,21 +34,19 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Bone;
import com.esotericsoftware.spine.Slot;
/** An attachment that displays a textured quadrilateral.
* <p>
* See <a href="http://esotericsoftware.com/spine-regions">Region attachments</a> in the Spine User Guide. */
public class RegionAttachment extends Attachment implements HasTextureRegion {
static public final int BLX = 0;
static public final int BLY = 1;
static public final int ULX = 2;
static public final int ULY = 3;
static public final int URX = 4;
static public final int URY = 5;
static public final int BRX = 6;
static public final int BRY = 7;
static public final int BLX = 0, BLY = 1;
static public final int ULX = 2, ULY = 3;
static public final int URX = 4, URY = 5;
static public final int BRX = 6, BRY = 7;
private TextureRegion region;
private String path;
@ -56,13 +54,31 @@ public class RegionAttachment extends Attachment implements HasTextureRegion {
private final float[] uvs = new float[8];
private final float[] offset = new float[8];
private final Color color = new Color(1, 1, 1, 1);
private @Null Sequence sequence;
public RegionAttachment (String name) {
super(name);
}
/** Calculates the {@link #offset} using the {@link #region}. Must be called after changing the region or the region's
* properties. */
/** Copy constructor. */
protected RegionAttachment (RegionAttachment other) {
super(other);
region = other.region;
path = other.path;
x = other.x;
y = other.y;
scaleX = other.scaleX;
scaleY = other.scaleY;
rotation = other.rotation;
width = other.width;
height = other.height;
arraycopy(other.uvs, 0, uvs, 0, 8);
arraycopy(other.offset, 0, offset, 0, 8);
color.set(other.color);
}
/** Calculates the {@link #offset} and {@link #uvs} using the {@link #region} and the attachment's transform. Must be called if
* the {@link #region}, transform, or the region's properties are changed. */
public void updateRegion () {
float width = getWidth();
float height = getHeight();
@ -70,11 +86,13 @@ public class RegionAttachment extends Attachment implements HasTextureRegion {
float localY2 = height / 2;
float localX = -localX2;
float localY = -localY2;
boolean rotated = false;
if (region instanceof AtlasRegion) {
AtlasRegion region = (AtlasRegion)this.region;
localX += region.offsetX / region.originalWidth * width;
localY += region.offsetY / region.originalHeight * height;
if (region.degrees == 90) {
rotated = true;
localX2 -= (region.originalWidth - region.offsetX - region.packedHeight) / region.originalWidth * width;
localY2 -= (region.originalHeight - region.offsetY - region.packedWidth) / region.originalHeight * height;
} else {
@ -110,13 +128,9 @@ public class RegionAttachment extends Attachment implements HasTextureRegion {
offset[URY] = localY2Cos + localX2Sin;
offset[BRX] = localX2Cos - localYSin;
offset[BRY] = localYCos + localX2Sin;
}
public void setRegion (TextureRegion region) {
if (region == null) throw new IllegalArgumentException("region cannot be null.");
this.region = region;
float[] uvs = this.uvs;
if (region instanceof AtlasRegion && ((AtlasRegion)region).degrees == 90) {
if (rotated) {
uvs[URX] = region.getU();
uvs[URY] = region.getV2();
uvs[BRX] = region.getU();
@ -137,20 +151,28 @@ public class RegionAttachment extends Attachment implements HasTextureRegion {
}
}
public TextureRegion getRegion () {
if (region == null) throw new IllegalStateException("Region has not been set: " + name);
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.
/** Transforms the attachment's four vertices to world coordinates. If the attachment has a {@link #sequence}, the
* {@link #region} may be changed.
* <p>
* See <a href="http://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 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 (Bone bone, float[] worldVertices, int offset, int stride) {
public void computeWorldVertices (Slot slot, float[] worldVertices, int offset, int stride) {
if (sequence != null) sequence.apply(slot, this);
float[] vertexOffset = this.offset;
Bone bone = slot.getBone();
float x = bone.getWorldX(), y = bone.getWorldY();
float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD();
float offsetX, offsetY;
@ -265,20 +287,15 @@ public class RegionAttachment extends Attachment implements HasTextureRegion {
this.path = path;
}
public Attachment copy () {
RegionAttachment copy = new RegionAttachment(name);
copy.region = region;
copy.path = path;
copy.x = x;
copy.y = y;
copy.scaleX = scaleX;
copy.scaleY = scaleY;
copy.rotation = rotation;
copy.width = width;
copy.height = height;
arraycopy(uvs, 0, copy.uvs, 0, 8);
arraycopy(offset, 0, copy.offset, 0, 8);
copy.color.set(color);
return copy;
public @Null Sequence getSequence () {
return sequence;
}
public void setSequence (@Null Sequence sequence) {
this.sequence = sequence;
}
public RegionAttachment copy () {
return new RegionAttachment(this);
}
}

View File

@ -0,0 +1,122 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated September 24, 2021. Replaces all prior versions.
*
* Copyright (c) 2013-2021, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.attachments;
import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.esotericsoftware.spine.Slot;
public class Sequence {
static private int nextID;
private final int id = nextID();
private final TextureRegion[] regions;
private int start, digits, setupIndex;
public Sequence (int count) {
regions = new TextureRegion[count];
}
/** Copy constructor. */
protected Sequence (Sequence other) {
regions = new TextureRegion[other.regions.length];
arraycopy(other.regions, 0, regions, 0, regions.length);
start = other.start;
digits = other.digits;
setupIndex = other.setupIndex;
}
public <T extends Attachment & HasTextureRegion> void apply (Slot slot, T 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();
}
}
public String getPath (String basePath, int index) {
StringBuilder buffer = new StringBuilder(basePath.length() + digits);
buffer.append(basePath);
buffer.append(start + index);
while (buffer.length() < digits)
buffer.append('0');
return buffer.toString();
}
public int getStart () {
return start;
}
public void setStart (int start) {
this.start = start;
}
public int getDigits () {
return digits;
}
public void setDigits (int digits) {
this.digits = digits;
}
/** The index of the region to show for the setup pose. */
public int getSetupIndex () {
return setupIndex;
}
public void setSetupIndex (int index) {
this.setupIndex = index;
}
public TextureRegion[] getRegions () {
return regions;
}
/** Returns a unique ID for this attachment. */
public int getId () {
return id;
}
static private synchronized int nextID () {
return nextID++;
}
static public enum SequenceMode {
stop, once, loop, pingpong, onceReverse, loopReverse, pingpongReverse;
static public final SequenceMode[] values = values();
}
}

View File

@ -1,151 +0,0 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated September 24, 2021. Replaces all prior versions.
*
* Copyright (c) 2013-2021, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.attachments;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.esotericsoftware.spine.Slot;
/** An attachment that applies a sequence of texture atlas regions to a region or mesh attachment.
* <p>
* See <a href="http://esotericsoftware.com/spine-sequences">Sequence attachments</a> in the Spine User Guide. */
public class SequenceAttachment<T extends Attachment & HasTextureRegion> extends Attachment {
private T attachment;
private String path;
private int frameCount;
private float frameTime;
private SequenceMode mode;
private TextureRegion[] regions;
public SequenceAttachment (String name) {
super(name);
}
/** Updates the {@link #attachment} with the {@link #regions region} for the slot's {@link Slot#getAttachmentTime()} and
* returns it. */
public T updateAttachment (Slot slot) {
int index = (int)(slot.getAttachmentTime() / frameTime);
switch (mode) {
case forward:
index = Math.min(frameCount - 1, index);
break;
case backward:
index = Math.max(frameCount - index - 1, 0);
break;
case forwardLoop:
index = index % frameCount;
break;
case backwardLoop:
index = frameCount - (index % frameCount) - 1;
break;
case pingPong:
index = index % (frameCount << 1);
if (index >= frameCount) index = frameCount - 1 - (index - frameCount);
break;
case random:
index = MathUtils.random(frameCount - 1);
}
attachment.setRegion(regions[index]);
attachment.updateRegion();
return attachment;
}
public void setAttachment (T attachment) {
this.attachment = attachment;
}
public T getAttachment () {
return attachment;
}
/** The prefix used to find the {@link #regions} for this attachment. */
public String getPath () {
return path;
}
public void setPath (String path) {
this.path = path;
}
public SequenceMode getMode () {
return mode;
}
public void setMode (SequenceMode mode) {
if (mode == null) throw new IllegalArgumentException("mode cannot be null.");
this.mode = mode;
}
public int getFrameCount () {
return frameCount;
}
public void setFrameCount (int frameCount) {
this.frameCount = frameCount;
}
/** The time in seconds each frame is shown. */
public float getFrameTime () {
return frameTime;
}
public void setFrameTime (float frameTime) {
this.frameTime = frameTime;
}
public TextureRegion[] getRegions () {
if (regions == null) throw new IllegalStateException("Regions have not been set: " + name);
return regions;
}
public void setRegions (TextureRegion[] regions) {
if (regions == null) throw new IllegalArgumentException("regions cannot be null.");
this.regions = regions;
}
public Attachment copy () {
SequenceAttachment copy = new SequenceAttachment(name);
copy.attachment = attachment.copy();
copy.path = path;
copy.frameCount = frameCount;
copy.frameTime = frameTime;
copy.frameTime = frameTime;
copy.mode = mode;
copy.regions = regions;
return copy;
}
static public enum SequenceMode {
forward, backward, forwardLoop, backwardLoop, pingPong, random;
static public final SequenceMode[] values = values();
}
}

View File

@ -41,8 +41,13 @@ public class SkeletonAttachment extends Attachment {
super(name);
}
/** @return May return null. */
public Skeleton getSkeleton () {
/** Copy constructor. */
protected SkeletonAttachment (SkeletonAttachment other) {
super(other);
skeleton = other.skeleton;
}
public @Null Skeleton getSkeleton () {
return skeleton;
}
@ -50,9 +55,7 @@ public class SkeletonAttachment extends Attachment {
this.skeleton = skeleton;
}
public Attachment copy () {
SkeletonAttachment copy = new SkeletonAttachment(name);
copy.skeleton = skeleton;
return copy;
public SkeletonAttachment copy () {
return new SkeletonAttachment(this);
}
}

View File

@ -44,15 +44,35 @@ abstract public class VertexAttachment extends Attachment {
static private int nextID;
private final int id = nextID();
@Null Attachment timelineAttachment = this;
@Null int[] bones;
float[] vertices;
int worldVerticesLength;
@Null VertexAttachment deformAttachment = this;
public VertexAttachment (String name) {
super(name);
}
/** Copy constructor. */
public VertexAttachment (VertexAttachment other) {
super(other);
timelineAttachment = other.timelineAttachment;
if (other.bones != null) {
bones = new int[other.bones.length];
arraycopy(other.bones, 0, bones, 0, bones.length);
} else
bones = null;
if (other.vertices != null) {
vertices = new float[other.vertices.length];
arraycopy(other.vertices, 0, vertices, 0, vertices.length);
} else
vertices = null;
worldVerticesLength = other.worldVerticesLength;
}
/** Transforms the attachment's local {@link #getVertices()} to world coordinates. If the slot's {@link Slot#getDeform()} is
* not empty, it is used to deform the vertices.
* <p>
@ -120,17 +140,6 @@ abstract public class VertexAttachment extends Attachment {
}
}
/** Deform keys for the deform attachment are also applied to this attachment.
* @return May be null if no deform keys should be applied. */
public @Null VertexAttachment getDeformAttachment () {
return deformAttachment;
}
/** @param deformAttachment May be null if no deform keys should be applied. */
public void setDeformAttachment (@Null VertexAttachment deformAttachment) {
this.deformAttachment = deformAttachment;
}
/** The bones which affect the {@link #getVertices()}. The array entries are, for each vertex, the number of bones affecting
* the vertex followed by that many bone indices, which is the index of the bone in {@link Skeleton#getBones()}. Will be null
* if this attachment has no weights. */
@ -164,29 +173,22 @@ abstract public class VertexAttachment extends Attachment {
this.worldVerticesLength = worldVerticesLength;
}
/** Timelines for the timeline attachment are also applied to this attachment.
* @return May be null if no attachment-specific timelines should be applied. */
public @Null Attachment getTimelineAttachment () {
return timelineAttachment;
}
/** @param timelineAttachment May be null if no attachment-specific timelines should be applied. */
public void setTimelineAttachment (Attachment timelineAttachment) {
this.timelineAttachment = timelineAttachment;
}
/** Returns a unique ID for this attachment. */
public int getId () {
return id;
}
/** Does not copy id (generated) or name (set on construction). */
void copyTo (VertexAttachment attachment) {
if (bones != null) {
attachment.bones = new int[bones.length];
arraycopy(bones, 0, attachment.bones, 0, bones.length);
} else
attachment.bones = null;
if (vertices != null) {
attachment.vertices = new float[vertices.length];
arraycopy(vertices, 0, attachment.vertices, 0, vertices.length);
} else
attachment.vertices = null;
attachment.worldVerticesLength = worldVerticesLength;
attachment.deformAttachment = deformAttachment;
}
static private synchronized int nextID () {
return nextID++;
}

View File

@ -258,7 +258,6 @@ public class SkeletonViewer extends ApplicationAdapter {
skeleton.setSlotsToSetupPose();
delta = Math.min(delta, 0.032f) * ui.speedSlider.getValue();
skeleton.update(delta);
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();