diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index c0df0092c..bffbc6857 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -47,6 +47,7 @@ import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentType; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; +import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.badlogic.gdx.files.FileHandle; @@ -188,7 +189,7 @@ public class SkeletonBinary { if (name == null) name = attachmentName; switch (AttachmentType.values()[input.readByte()]) { - case region: + case region: { String path = input.readString(); if (path == null) path = name; RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path); @@ -203,19 +204,58 @@ public class SkeletonBinary { Color.rgba8888ToColor(region.getColor(), input.readInt()); region.updateOffset(); return region; - case boundingbox: + } + case boundingbox: { BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name); if (box == null) return null; - int n = input.readInt(true); - float[] points = new float[n]; - for (int i = 0; i < n; i++) - points[i] = input.readFloat(); - box.setVertices(points); + box.setVertices(readFloatArray(input, scale)); return box; } + case mesh: { + String path = input.readString(); + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path); + float[] vertices = readFloatArray(input, scale); + short[] triangles = readShortArray(input); + float[] uvs = readFloatArray(input, 1); + Color.rgba8888ToColor(mesh.getColor(), input.readInt()); + mesh.setEdges(readIntArray(input)); + if (mesh.getEdges().length > 0) { + mesh.setHullLength(input.readInt(true)); + mesh.setWidth(input.readFloat() * scale); + mesh.setHeight(input.readFloat() * scale); + } + mesh.setMesh(vertices, triangles, uvs); + return mesh; + } + } return null; } + private float[] readFloatArray (DataInput input, float scale) throws IOException { + int n = input.readInt(true); + float[] array = new float[n]; + for (int i = 0; i < n; i++) + array[i] = input.readFloat() * scale; + return array; + } + + private short[] readShortArray (DataInput input) throws IOException { + int n = input.readInt(true); + short[] array = new short[n]; + for (int i = 0; i < n; i++) + array[i] = input.readShort(); + return array; + } + + private int[] readIntArray (DataInput input) throws IOException { + int n = input.readInt(true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = input.readInt(true); + return array; + } + private void readAnimation (String name, DataInput input, SkeletonData skeletonData) { Array timelines = new Array(); float duration = 0; diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 5a24d246d..5264465d1 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -47,6 +47,7 @@ import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentType; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; +import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.badlogic.gdx.files.FileHandle; @@ -184,16 +185,24 @@ public class SkeletonJson { region.updateOffset(); return region; - case boundingbox: + case boundingbox: { BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name); - JsonValue verticesArray = map.require("vertices"); - float[] vertices = new float[verticesArray.size]; - int i = 0; - for (JsonValue point = verticesArray.child; point != null; point = point.next()) - vertices[i++] = point.asFloat() * scale; - box.setVertices(vertices); + box.setVertices(readFloatArray(map.require("vertices"), scale)); return box; } + case mesh: { + MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, map.getString("path", name)); + float[] vertices = readFloatArray(map.require("vertices"), scale); + short[] triangles = readShortArray(map.require("triangles")); + float[] uvs = readFloatArray(map.require("uvs"), 1); + mesh.setMesh(vertices, triangles, uvs); + if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt()); + if (map.has("edges")) mesh.setEdges(readIntArray(map.require("edges"))); + mesh.setWidth(map.getFloat("width", 0) * scale); + mesh.setHeight(map.getFloat("height", 0) * scale); + return mesh; + } + } // RegionSequenceAttachment regionSequenceAttachment = (RegionSequenceAttachment)attachment; // @@ -206,6 +215,30 @@ public class SkeletonJson { return null; } + private float[] readFloatArray (JsonValue jsonArray, float scale) { + float[] array = new float[jsonArray.size]; + int i = 0; + for (JsonValue point = jsonArray.child; point != null; point = point.next()) + array[i++] = point.asFloat() * scale; + return array; + } + + private short[] readShortArray (JsonValue jsonArray) { + short[] array = new short[jsonArray.size]; + int i = 0; + for (JsonValue point = jsonArray.child; point != null; point = point.next()) + array[i++] = (short)point.asInt(); + return array; + } + + private int[] readIntArray (JsonValue jsonArray) { + int[] array = new int[jsonArray.size]; + int i = 0; + for (JsonValue point = jsonArray.child; point != null; point = point.next()) + array[i++] = point.asInt(); + return array; + } + private void readAnimation (String name, JsonValue map, SkeletonData skeletonData) { Array timelines = new Array(); float duration = 0; diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index 31d1e448a..b12fd7e3b 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -34,6 +34,7 @@ package com.esotericsoftware.spine; import com.esotericsoftware.spine.attachments.Attachment; +import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.SkeletonAttachment; @@ -78,6 +79,15 @@ public class SkeletonRenderer { } batch.draw(texture, vertices, 0, vertices.length, triangles, 0, triangles.length); + + } else if (attachment instanceof MeshAttachment) { + MeshAttachment mesh = (MeshAttachment)attachment; + mesh.updateWorldVertices(slot, true); + vertices = mesh.getWorldVertices(); + triangles = mesh.getTriangles(); + texture = mesh.getRegion().getTexture(); + batch.draw(texture, vertices, 0, vertices.length, triangles, 0, triangles.length); + } else if (attachment instanceof SkeletonAttachment) { Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton(); if (attachmentSkeleton == null) continue; diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java index a8814f072..811c00d40 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java @@ -56,6 +56,16 @@ public class AtlasAttachmentLoader implements AttachmentLoader { return attachment; } + public MeshAttachment newMeshAttachment (Skin skin, String name, String path) { + MeshAttachment attachment = new MeshAttachment(name); + attachment.setPath(path); + AtlasRegion region = atlas.findRegion(path); + if (region == null) + throw new RuntimeException("Region not found in atlas: " + attachment + " (region attachment: " + name + ")"); + attachment.setRegion(region); + return attachment; + } + public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) { return new BoundingBoxAttachment(name); } diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java index c34cc38c7..7222fbce6 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java @@ -39,6 +39,9 @@ public interface AttachmentLoader { /** @return May be null to not load any attachment. */ public RegionAttachment newRegionAttachment (Skin skin, String name, String path); + /** @return May be null to not load any attachment. */ + public MeshAttachment newMeshAttachment (Skin skin, String name, String path); + /** @return May be null to not load any attachment. */ public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name); } diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java index 00f52b7c8..eb9e32f03 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java @@ -34,5 +34,5 @@ package com.esotericsoftware.spine.attachments; public enum AttachmentType { - region, boundingbox + region, boundingbox, mesh } diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java new file mode 100644 index 000000000..7f537bce3 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java @@ -0,0 +1,186 @@ +/****************************************************************************** + * Spine Runtime Software License - Version 1.1 + * + * Copyright (c) 2013, Esoteric Software + * All rights reserved. + * + * Redistribution and use in source and binary forms in whole or in part, with + * or without modification, are permitted provided that the following conditions + * are met: + * + * 1. A Spine Essential, Professional, Enterprise, or Education License must + * be purchased from Esoteric Software and the license must remain valid: + * http://esotericsoftware.com/ + * 2. Redistributions of source code must retain this license, which is the + * above copyright notice, this declaration of conditions and the following + * disclaimer. + * 3. Redistributions in binary form must reproduce this license, which is the + * above copyright notice, this declaration of conditions and the following + * disclaimer, in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 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 com.esotericsoftware.spine.Bone; +import com.esotericsoftware.spine.Skeleton; +import com.esotericsoftware.spine.Slot; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.NumberUtils; + +/** Attachment that displays a texture region. */ +public class MeshAttachment extends Attachment { + private TextureRegion region; + private String path; + private int hullLength; + private float[] vertices; + private short[] triangles; + private int[] edges; + private float[] worldVertices; + private final Color color = new Color(1, 1, 1, 1); + private float width, height; + + public MeshAttachment (String name) { + super(name); + } + + 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); + return region; + } + + public void updateWorldVertices (Slot slot, boolean premultipliedAlpha) { + Skeleton skeleton = slot.getSkeleton(); + Color skeletonColor = skeleton.getColor(); + Color slotColor = slot.getColor(); + Color regionColor = color; + float r = skeletonColor.r * slotColor.r * regionColor.r; + float g = skeletonColor.g * slotColor.g * regionColor.g; + float b = skeletonColor.b * slotColor.b * regionColor.b; + float a = skeletonColor.a * slotColor.a * regionColor.a * 255; + float color; + if (premultipliedAlpha) { + r *= a; + g *= a; + b *= a; + } else { + r *= 255; + g *= 255; + b *= 255; + } + color = NumberUtils.intToFloatColor( // + ((int)(a) << 24) // + | ((int)(b) << 16) // + | ((int)(g) << 8) // + | ((int)(r))); + + float[] worldVertices = this.worldVertices; + float[] vertices = this.vertices; + Bone bone1 = slot.getBone(); + float x = skeleton.getX(); + float y = skeleton.getY(); + float m00 = bone1.getM00(); + float m01 = bone1.getM01(); + float m10 = bone1.getM10(); + float m11 = bone1.getM11(); + + float vx, vy; + for (int v = 0, w = 0, n = vertices.length; v < n; v += 2, w += 5) { + vx = vertices[v]; + vy = vertices[v + 1]; + float wx1 = vx * m00 + vy * m01 + x + bone1.getWorldX(); + float wy1 = vx * m10 + vy * m11 + y + bone1.getWorldY(); + worldVertices[w] = wx1; + worldVertices[w + 1] = wy1; + worldVertices[w + 2] = Color.WHITE.toFloatBits(); + worldVertices[w + 2] = color; + } + } + + public float[] getWorldVertices () { + return worldVertices; + } + + public float[] getVertices () { + return vertices; + } + + public short[] getTriangles () { + return triangles; + } + + public Color getColor () { + return color; + } + + public String getPath () { + return path; + } + + public void setPath (String path) { + this.path = path; + } + + public int getHullLength () { + return hullLength; + } + + public void setHullLength (int hullLength) { + this.hullLength = hullLength; + } + + public int[] getEdges () { + return edges; + } + + public void setEdges (int[] edges) { + this.edges = edges; + } + + public float getWidth () { + return width; + } + + public void setWidth (float width) { + this.width = width; + } + + public float getHeight () { + return height; + } + + public void setHeight (float height) { + this.height = height; + } + + public void setMesh (float[] vertices, short[] triangles, float[] uvs) { + this.vertices = vertices; + this.triangles = triangles; + + int worldVerticesLength = vertices.length / 2 * 5; + if (worldVertices == null || worldVertices.length != worldVerticesLength) worldVertices = new float[worldVerticesLength]; + + for (int i = 0, w = 3, n = vertices.length; i < n; i += 2, w += 5) { + worldVertices[w] = uvs[i]; + worldVertices[w + 1] = uvs[i + 1]; + } + } +}