Added PointAttachment.

This commit is contained in:
NathanSweet 2016-11-30 01:11:09 +01:00
parent 8e0972d732
commit efd0b31723
13 changed files with 195 additions and 20 deletions

View File

@ -43,6 +43,7 @@ import com.esotericsoftware.spine.attachments.AttachmentLoader;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
public class AnimationStateTests {
@ -62,6 +63,10 @@ public class AnimationStateTests {
public PathAttachment newPathAttachment (Skin skin, String name) {
return null;
}
public PointAttachment newPointAttachment (Skin skin, String name) {
return null;
}
});
final AnimationStateListener stateListener = new AnimationStateListener() {

View File

@ -36,6 +36,7 @@ import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
public class BonePlotting {
static public void main (String[] args) throws Exception {
@ -56,6 +57,10 @@ public class BonePlotting {
public PathAttachment newPathAttachment (Skin skin, String name) {
return null;
}
public PointAttachment newPointAttachment (Skin skin, String name) {
return null;
}
});
SkeletonData skeletonData = json.readSkeletonData(new FileHandle("assets/spineboy/spineboy.json"));
Skeleton skeleton = new Skeleton(skeletonData);

View File

@ -581,7 +581,7 @@ public class Skeleton {
if (attachment instanceof RegionAttachment) {
verticesLength = 8;
vertices = temp.setSize(8);
((RegionAttachment)attachment).computeWorldVertices(slot, vertices, 0, 2);
((RegionAttachment)attachment).computeWorldVertices(slot.getBone(), vertices, 0, 2);
} else if (attachment instanceof MeshAttachment) {
MeshAttachment mesh = (MeshAttachment)attachment;
verticesLength = mesh.getWorldVerticesLength();

View File

@ -70,6 +70,7 @@ import com.esotericsoftware.spine.attachments.AttachmentType;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
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.VertexAttachment;
@ -468,6 +469,20 @@ public class SkeletonBinary {
if (nonessential) Color.rgba8888ToColor(path.getColor(), color);
return path;
}
case point: {
float rotation = input.readFloat();
float x = input.readFloat();
float y = input.readFloat();
int color = nonessential ? input.readInt() : 0;
PointAttachment point = attachmentLoader.newPointAttachment(skin, name);
if (point == null) return null;
point.setX(x * scale);
point.setY(y * scale);
point.setRotation(rotation);
if (nonessential) Color.rgba8888ToColor(point.getColor(), color);
return point;
}
}
return null;
}

View File

@ -67,6 +67,7 @@ import com.esotericsoftware.spine.attachments.AttachmentType;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
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.VertexAttachment;
@ -391,6 +392,17 @@ public class SkeletonJson {
if (color != null) path.getColor().set(Color.valueOf(color));
return path;
}
case point: {
PointAttachment point = attachmentLoader.newPointAttachment(skin, name);
if (point == null) return null;
point.setX(map.getFloat("x", 0) * scale);
point.setY(map.getFloat("y", 0) * scale);
point.setRotation(map.getFloat("rotation", 0));
String color = map.getString("color", null);
if (color != null) point.getColor().set(Color.valueOf(color));
return point;
}
}
return null;
}

View File

@ -60,7 +60,7 @@ public class SkeletonRenderer {
Attachment attachment = slot.attachment;
if (attachment instanceof RegionAttachment) {
RegionAttachment region = (RegionAttachment)attachment;
region.computeWorldVertices(slot, vertices, 0, 5);
region.computeWorldVertices(slot.getBone(), vertices, 0, 5);
Color color = region.getColor(), slotColor = slot.getColor();
float alpha = a * slotColor.a * color.a * 255;
float c = NumberUtils.intToFloatColor(((int)alpha << 24) //
@ -125,7 +125,7 @@ public class SkeletonRenderer {
RegionAttachment region = (RegionAttachment)attachment;
verticesLength = 20;
vertices = this.vertices.items;
region.computeWorldVertices(slot, vertices, 0, 5);
region.computeWorldVertices(slot.getBone(), vertices, 0, 5);
triangles = quadTriangles;
texture = region.getRegion().getTexture();
uvs = region.getUVs();
@ -207,7 +207,7 @@ public class SkeletonRenderer {
RegionAttachment region = (RegionAttachment)attachment;
verticesLength = 24;
vertices = this.vertices.items;
region.computeWorldVertices(slot, vertices, 0, 6);
region.computeWorldVertices(slot.getBone(), vertices, 0, 6);
triangles = quadTriangles;
texture = region.getRegion().getTexture();
uvs = region.getUVs();

View File

@ -35,12 +35,14 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
public class SkeletonRendererDebug {
@ -51,13 +53,14 @@ public class SkeletonRendererDebug {
static private final Color aabbColor = new Color(0, 1, 0, 0.5f);
private final ShapeRenderer shapes;
private boolean drawBones = true, drawRegionAttachments = true, drawBoundingBoxes = true;
private boolean drawBones = true, drawRegionAttachments = true, drawBoundingBoxes = true, drawPoints = true;
private boolean drawMeshHull = true, drawMeshTriangles = true, drawPaths = true;
private final SkeletonBounds bounds = new SkeletonBounds();
private final FloatArray vertices = new FloatArray(32);
private float scale = 1;
private float boneWidth = 2;
private boolean premultipliedAlpha;
private final Vector2 temp1 = new Vector2(), temp2 = new Vector2();
public SkeletonRendererDebug () {
shapes = new ShapeRenderer();
@ -73,10 +76,12 @@ public class SkeletonRendererDebug {
Gdx.gl.glBlendFunc(srcFunc, GL20.GL_ONE_MINUS_SRC_ALPHA);
ShapeRenderer shapes = this.shapes;
Array<Bone> bones = skeleton.getBones();
Array<Slot> slots = skeleton.getSlots();
shapes.begin(ShapeType.Filled);
if (drawBones) {
shapes.begin(ShapeType.Filled);
for (int i = 0, n = bones.size; i < n; i++) {
Bone bone = bones.get(i);
if (bone.parent == null) continue;
@ -91,22 +96,35 @@ public class SkeletonRendererDebug {
float y = length * bone.c + bone.worldY;
shapes.rectLine(bone.worldX, bone.worldY, x, y, width * scale);
}
shapes.end();
shapes.begin(ShapeType.Line);
shapes.x(skeleton.getX(), skeleton.getY(), 4 * scale);
} else
shapes.begin(ShapeType.Line);
}
if (drawPoints) {
shapes.setColor(boneOriginColor);
for (int i = 0, n = slots.size; i < n; i++) {
Slot slot = slots.get(i);
Attachment attachment = slot.attachment;
if (!(attachment instanceof PointAttachment)) continue;
PointAttachment point = (PointAttachment)attachment;
point.computeWorldPosition(slot.getBone(), temp1);
temp2.set(8, 0).rotate(point.computeWorldRotation(slot.getBone()));
shapes.rectLine(temp1, temp2, boneWidth / 2 * scale);
}
}
shapes.end();
shapes.begin(ShapeType.Line);
if (drawRegionAttachments) {
shapes.setColor(attachmentLineColor);
Array<Slot> slots = skeleton.getSlots();
for (int i = 0, n = slots.size; i < n; i++) {
Slot slot = slots.get(i);
Attachment attachment = slot.attachment;
if (attachment instanceof RegionAttachment) {
RegionAttachment region = (RegionAttachment)attachment;
float[] vertices = this.vertices.items;
region.computeWorldVertices(slot, vertices, 0, 2);
region.computeWorldVertices(slot.getBone(), 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]);
@ -116,7 +134,6 @@ public class SkeletonRendererDebug {
}
if (drawMeshHull || drawMeshTriangles) {
Array<Slot> slots = skeleton.getSlots();
for (int i = 0, n = slots.size; i < n; i++) {
Slot slot = slots.get(i);
Attachment attachment = slot.attachment;
@ -164,7 +181,6 @@ public class SkeletonRendererDebug {
}
if (drawPaths) {
Array<Slot> slots = skeleton.getSlots();
for (int i = 0, n = slots.size; i < n; i++) {
Slot slot = slots.get(i);
Attachment attachment = slot.attachment;
@ -208,11 +224,22 @@ public class SkeletonRendererDebug {
shapes.setColor(boneOriginColor);
for (int i = 0, n = bones.size; i < n; i++) {
Bone bone = bones.get(i);
shapes.setColor(Color.GREEN);
shapes.circle(bone.worldX, bone.worldY, 3 * scale, 8);
}
}
if (drawPoints) {
shapes.setColor(boneOriginColor);
for (int i = 0, n = slots.size; i < n; i++) {
Slot slot = slots.get(i);
Attachment attachment = slot.attachment;
if (!(attachment instanceof PointAttachment)) continue;
PointAttachment point = (PointAttachment)attachment;
point.computeWorldPosition(slot.getBone(), temp1);
shapes.circle(temp1.x, temp1.y, 3 * scale, 8);
}
}
shapes.end();
}
@ -249,6 +276,10 @@ public class SkeletonRendererDebug {
this.drawPaths = paths;
}
public void setPoints (boolean points) {
this.drawPoints = points;
}
public void setPremultipliedAlpha (boolean premultipliedAlpha) {
this.premultipliedAlpha = premultipliedAlpha;
}

View File

@ -71,4 +71,8 @@ public class AtlasAttachmentLoader implements AttachmentLoader {
public PathAttachment newPathAttachment (Skin skin, String name) {
return new PathAttachment(name);
}
public PointAttachment newPointAttachment (Skin skin, String name) {
return new PointAttachment(name);
}
}

View File

@ -48,4 +48,7 @@ public interface AttachmentLoader {
/** @return May be null to not load the attachment. */
public PathAttachment newPathAttachment (Skin skin, String name);
/** @return May be null to not load the attachment. */
public PointAttachment newPointAttachment (Skin skin, String name);
}

View File

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

View File

@ -0,0 +1,95 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine.attachments;
import static com.badlogic.gdx.math.MathUtils.*;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2;
import com.esotericsoftware.spine.Bone;
/** An attachment which is a single point and a rotation. A bone can by used in similar ways, but a PointAttachment has an
* advantage in that it can be hidden, shown, and placed in a skin.
* <p>
* See <a href="http://esotericsoftware.com/spine-point-attachments">Point Attachments</a> in the Spine User Guide. */
public class PointAttachment extends Attachment {
float x, y, rotation;
// Nonessential.
final Color color = new Color(0.38f, 0.94f, 0, 1);
public PointAttachment (String name) {
super(name);
}
public float getX () {
return x;
}
public void setX (float x) {
this.x = x;
}
public float getY () {
return y;
}
public void setY (float y) {
this.y = y;
}
public float getRotation () {
return rotation;
}
public void setRotation (float rotation) {
this.rotation = rotation;
}
/** The color of the point attachment as it was in Spine. Available only when nonessential data was exported. Point attachments
* are not usually rendered at runtime. */
public Color getColor () {
return color;
}
public Vector2 computeWorldPosition (Bone bone, Vector2 point) {
point.x = x * bone.getA() + y * bone.getB() + bone.getWorldX();
point.y = x * bone.getC() + y * bone.getD() + bone.getWorldY();
return point;
}
public float computeWorldRotation (Bone bone) {
float x = cos(rotation), y = sin(rotation);
x = x * bone.getA() + y * bone.getB() + bone.getWorldX();
y = x * bone.getC() + y * bone.getD() + bone.getWorldY();
return (float)Math.atan2(y, x);
}
}

View File

@ -35,7 +35,6 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.esotericsoftware.spine.Bone;
import com.esotericsoftware.spine.Slot;
/** An attachment that displays a textured quadrilateral.
* <p>
@ -150,9 +149,8 @@ public class RegionAttachment extends Attachment {
* @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 (Slot slot, float[] worldVertices, int offset, int stride) {
public void computeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride) {
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;

View File

@ -288,6 +288,7 @@ public class SkeletonViewer extends ApplicationAdapter {
debugRenderer.setMeshHull(ui.debugMeshHullCheckbox.isChecked());
debugRenderer.setMeshTriangles(ui.debugMeshTrianglesCheckbox.isChecked());
debugRenderer.setPaths(ui.debugPathsCheckbox.isChecked());
debugRenderer.setPoints(ui.debugPointsCheckbox.isChecked());
debugRenderer.draw(skeleton);
}
@ -374,6 +375,7 @@ public class SkeletonViewer extends ApplicationAdapter {
CheckBox debugMeshHullCheckbox = new CheckBox("Mesh hull", skin);
CheckBox debugMeshTrianglesCheckbox = new CheckBox("Triangles", skin);
CheckBox debugPathsCheckbox = new CheckBox("Paths", skin);
CheckBox debugPointsCheckbox = new CheckBox("Points", skin);
Slider scaleSlider = new Slider(0.1f, 3, 0.01f, false, skin);
Label scaleLabel = new Label("1.0", skin);
TextButton minimizeButton = new TextButton("-", skin);
@ -448,7 +450,9 @@ public class SkeletonViewer extends ApplicationAdapter {
root.add("Debug:");
root.add(table(debugBonesCheckbox, debugRegionsCheckbox, debugBoundingBoxesCheckbox)).row();
root.add();
root.add(table(debugMeshHullCheckbox, debugMeshTrianglesCheckbox, debugPathsCheckbox)).row();
root.add(table(debugPathsCheckbox, debugPointsCheckbox)).row();
root.add();
root.add(table(debugMeshHullCheckbox, debugMeshTrianglesCheckbox)).row();
root.add("Atlas alpha:");
root.add(premultipliedCheckbox).row();
@ -685,6 +689,7 @@ public class SkeletonViewer extends ApplicationAdapter {
debugMeshHullCheckbox.addListener(savePrefsListener);
debugMeshTrianglesCheckbox.addListener(savePrefsListener);
debugPathsCheckbox.addListener(savePrefsListener);
debugPointsCheckbox.addListener(savePrefsListener);
premultipliedCheckbox.addListener(savePrefsListener);
loopCheckbox.addListener(savePrefsListener);
speedSlider.addListener(savePrefsListener);
@ -743,6 +748,7 @@ public class SkeletonViewer extends ApplicationAdapter {
prefs.putBoolean("debugMeshHull", debugMeshHullCheckbox.isChecked());
prefs.putBoolean("debugMeshTriangles", debugMeshTrianglesCheckbox.isChecked());
prefs.putBoolean("debugPaths", debugPathsCheckbox.isChecked());
prefs.putBoolean("debugPoints", debugPointsCheckbox.isChecked());
prefs.putBoolean("premultiplied", premultipliedCheckbox.isChecked());
prefs.putBoolean("loop", loopCheckbox.isChecked());
prefs.putFloat("speed", speedSlider.getValue());
@ -766,6 +772,7 @@ public class SkeletonViewer extends ApplicationAdapter {
debugMeshHullCheckbox.setChecked(prefs.getBoolean("debugMeshHull", false));
debugMeshTrianglesCheckbox.setChecked(prefs.getBoolean("debugMeshTriangles", false));
debugPathsCheckbox.setChecked(prefs.getBoolean("debugPaths", true));
debugPointsCheckbox.setChecked(prefs.getBoolean("debugPoints", true));
premultipliedCheckbox.setChecked(prefs.getBoolean("premultiplied", true));
loopCheckbox.setChecked(prefs.getBoolean("loop", false));
speedSlider.setValue(prefs.getFloat("speed", 0.3f));