Initial comment for sequence attachments (frame-by-frame).

EsotericSoftware/spine-editor#9
This commit is contained in:
Nathan Sweet 2021-09-24 20:53:57 -10:00
parent 96a998b82d
commit 457aa3a894
13 changed files with 248 additions and 25 deletions

View File

@ -47,6 +47,7 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment;
public class AnimationStateTests {
final SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
@ -58,6 +59,10 @@ public class AnimationStateTests {
return null;
}
public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) {
return null;
}
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
return null;
}

View File

@ -40,6 +40,7 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment;
public class BonePlotting {
static public void main (String[] args) throws Exception {
@ -53,6 +54,10 @@ public class BonePlotting {
return null;
}
public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) {
return null;
}
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
return null;
}

View File

@ -42,6 +42,7 @@ import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment;
/** Stores the current pose for a skeleton.
* <p>
@ -721,6 +722,7 @@ public class Skeleton {
int verticesLength = 0;
float[] vertices = null;
Attachment attachment = slot.attachment;
if (attachment instanceof SequenceAttachment) attachment = ((SequenceAttachment)attachment).updateAttachment(slot);
if (attachment instanceof RegionAttachment) {
verticesLength = 8;
vertices = temp.setSize(8);

View File

@ -85,6 +85,9 @@ import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment;
import com.esotericsoftware.spine.attachments.SequenceAttachment.SequenceMode;
import com.esotericsoftware.spine.attachments.TextureRegionAttachment;
import com.esotericsoftware.spine.attachments.VertexAttachment;
/** Loads skeleton data in the Spine binary format.
@ -302,7 +305,7 @@ public class SkeletonBinary extends SkeletonLoader {
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh);
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
linkedMesh.mesh.updateUVs();
linkedMesh.mesh.updateRegion();
}
linkedMeshes.clear();
@ -408,7 +411,7 @@ public class SkeletonBinary extends SkeletonLoader {
region.setWidth(width * scale);
region.setHeight(height * scale);
Color.rgba8888ToColor(region.getColor(), color);
region.updateOffset();
region.updateRegion();
return region;
}
case boundingbox: {
@ -450,7 +453,7 @@ public class SkeletonBinary extends SkeletonLoader {
mesh.setWorldVerticesLength(vertexCount << 1);
mesh.setTriangles(triangles);
mesh.setRegionUVs(uvs);
mesh.updateUVs();
mesh.updateRegion();
mesh.setHullLength(hullLength << 1);
if (nonessential) {
mesh.setEdges(edges);
@ -518,7 +521,7 @@ public class SkeletonBinary extends SkeletonLoader {
if (nonessential) Color.rgba8888ToColor(point.getColor(), color);
return point;
}
case clipping:
case clipping: {
int endSlotIndex = input.readInt(true);
int vertexCount = input.readInt(true);
Vertices vertices = readVertices(input, vertexCount);
@ -533,6 +536,25 @@ public class SkeletonBinary extends SkeletonLoader {
if (nonessential) Color.rgba8888ToColor(clip.getColor(), color);
return clip;
}
case sequence:
Attachment attachment = readAttachment(input, skeletonData, skin, slotIndex, attachmentName, nonessential);
int frameCount = input.readInt(true);
float frameTime = input.readFloat();
SequenceMode mode = SequenceMode.values[input.readInt(true)];
if (attachment == null) return null;
String path = ((TextureRegionAttachment)attachment).getPath();
SequenceAttachment sequence = attachmentLoader.newSequenceAttachment(skin, name, path, frameCount);
if (sequence == null) return null;
sequence.setAttachment(attachment);
sequence.setPath(path);
sequence.setFrameCount(frameCount);
sequence.setFrameTime(frameTime);
sequence.setMode(mode);
return sequence;
}
return null;
}

View File

@ -323,7 +323,7 @@ public class SkeletonJson extends SkeletonLoader {
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.setDeformAttachment(linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh);
linkedMesh.mesh.setParentMesh((MeshAttachment)parent);
linkedMesh.mesh.updateUVs();
linkedMesh.mesh.updateRegion();
}
linkedMeshes.clear();
@ -380,7 +380,7 @@ public class SkeletonJson extends SkeletonLoader {
String color = map.getString("color", null);
if (color != null) Color.valueOf(color, region.getColor());
region.updateOffset();
region.updateRegion();
return region;
}
case boundingbox: {
@ -416,7 +416,7 @@ public class SkeletonJson extends SkeletonLoader {
readVertices(map, mesh, uvs.length);
mesh.setTriangles(map.require("triangles").asShortArray());
mesh.setRegionUVs(uvs);
mesh.updateUVs();
mesh.updateRegion();
if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() << 1);
if (map.has("edges")) mesh.setEdges(map.require("edges").asShortArray());

View File

@ -1,8 +1,8 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated January 1, 2020. Replaces all prior versions.
* Last updated September 24, 2021. Replaces all prior versions.
*
* Copyright (c) 2013-2020, Esoteric Software LLC
* Copyright (c) 2013-2021, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
@ -37,6 +37,7 @@ import com.badlogic.gdx.utils.Array;
public class SpringConstraint implements Updatable {
final SpringConstraintData data;
final Array<Bone> bones;
// BOZO! - stiffness -> strength. stiffness, damping, rope, stretch -> move to spring.
float mix, friction, gravity, wind, stiffness, damping;
boolean rope, stretch;

View File

@ -63,6 +63,18 @@ public class AtlasAttachmentLoader implements AttachmentLoader {
return attachment;
}
public SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount) {
AtlasRegion[] regions = new AtlasRegion[frameCount];
for (int i = 0; i < frameCount; i++) {
AtlasRegion region = atlas.findRegion(path + frameCount); // BOZO - Zero pad?
if (region == null)
throw new RuntimeException("Region not found in atlas: " + path + frameCount + " (sequence: " + name + ")");
}
SequenceAttachment sequence = new SequenceAttachment(name);
sequence.setRegions(regions);
return sequence;
}
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
return new BoundingBoxAttachment(name);
}

View File

@ -44,6 +44,9 @@ public interface AttachmentLoader {
/** @return May be null to not load the attachment. In that case null should also be returned for child meshes. */
public @Null MeshAttachment newMeshAttachment (Skin skin, String name, String path);
/** @return May be null to not load the attachment. */
public @Null SequenceAttachment newSequenceAttachment (Skin skin, String name, String path, int frameCount);
/** @return May be null to not load the attachment. */
public @Null BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name);

View File

@ -30,7 +30,7 @@
package com.esotericsoftware.spine.attachments;
public enum AttachmentType {
region, boundingbox, mesh, linkedmesh, path, point, clipping;
region, boundingbox, mesh, linkedmesh, path, point, clipping, sequence;
static public final AttachmentType[] values = values();
}

View File

@ -40,7 +40,7 @@ import com.badlogic.gdx.utils.Null;
* supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
* <p>
* See <a href="http://esotericsoftware.com/spine-meshes">Mesh attachments</a> in the Spine User Guide. */
public class MeshAttachment extends VertexAttachment {
public class MeshAttachment extends VertexAttachment implements TextureRegionAttachment {
private TextureRegion region;
private String path;
private float[] regionUVs, uvs;
@ -67,9 +67,9 @@ public class MeshAttachment extends VertexAttachment {
return region;
}
/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region UVs or
* region. */
public void updateUVs () {
/** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region or the
* region's properties. */
public void updateRegion () {
float[] regionUVs = this.regionUVs;
if (this.uvs == null || this.uvs.length != regionUVs.length) this.uvs = new float[regionUVs.length];
float[] uvs = this.uvs;
@ -152,7 +152,7 @@ public class MeshAttachment extends VertexAttachment {
/** The UV pair for each vertex, normalized within the entire texture.
* <p>
* See {@link #updateUVs}. */
* See {@link #updateRegion()}. */
public float[] getUVs () {
return uvs;
}
@ -161,12 +161,10 @@ public class MeshAttachment extends VertexAttachment {
this.uvs = uvs;
}
/** The color to tint the mesh. */
public Color getColor () {
return color;
}
/** The name of the texture region for this attachment. */
public String getPath () {
return path;
}
@ -269,7 +267,7 @@ public class MeshAttachment extends VertexAttachment {
mesh.color.set(color);
mesh.deformAttachment = deformAttachment;
mesh.setParentMesh(parentMesh != null ? parentMesh : this);
mesh.updateUVs();
mesh.updateRegion();
return mesh;
}
}

View File

@ -40,7 +40,7 @@ import com.esotericsoftware.spine.Bone;
/** 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 {
public class RegionAttachment extends Attachment implements TextureRegionAttachment {
static public final int BLX = 0;
static public final int BLY = 1;
static public final int ULX = 2;
@ -61,8 +61,9 @@ public class RegionAttachment extends Attachment {
super(name);
}
/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
public void updateOffset () {
/** Calculates the {@link #offset} using the {@link #region}. Must be called after changing the region or the region's
* properties. */
public void updateRegion () {
float width = getWidth();
float height = getHeight();
float localX2 = width / 2;
@ -137,7 +138,7 @@ public class RegionAttachment extends Attachment {
}
public TextureRegion getRegion () {
if (region == null) throw new IllegalStateException("Region has not been set: " + this);
if (region == null) throw new IllegalStateException("Region has not been set: " + name);
return region;
}
@ -180,7 +181,7 @@ public class RegionAttachment extends Attachment {
/** For each of the 4 vertices, a pair of <code>x,y</code> values that is the local position of the vertex.
* <p>
* See {@link #updateOffset()}. */
* See {@link #updateRegion()}. */
public float[] getOffset () {
return offset;
}
@ -252,12 +253,10 @@ public class RegionAttachment extends Attachment {
this.height = height;
}
/** The color to tint the region attachment. */
public Color getColor () {
return color;
}
/** The name of the texture region for this attachment. */
public String getPath () {
return path;
}

View File

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

View File

@ -0,0 +1,25 @@
package com.esotericsoftware.spine.attachments;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
public interface TextureRegionAttachment {
/** Sets the region used to draw the attachment. If the region or its properties are changed, {@link #updateRegion()} must be
* called. */
public void setRegion (TextureRegion region);
public TextureRegion getRegion ();
/** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after changing the region or
* the region's properties. */
public void updateRegion ();
/** The color to tint the attachment. */
public Color getColor ();
/** The name used to find the {@link #getRegion()}. */
public String getPath ();
public void setPath (String path);
}