diff --git a/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index 207d791ae..77222cbaa 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -28,7 +28,7 @@ package com.esotericsoftware.spine; -import com.esotericsoftware.spine.attachments.MeshAttachment; +import com.esotericsoftware.spine.attachments.Attachment; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.MathUtils; @@ -427,7 +427,10 @@ public class Animation { float g = frames[i - 2]; float b = frames[i - 1]; float a = frames[i]; - color.set(r, g, b, a); + if (alpha < 1) + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); + else + color.set(r, g, b, a); return; } @@ -614,7 +617,7 @@ public class Animation { private final float[] frames; // time, ... private final float[][] frameVertices; int slotIndex; - MeshAttachment meshAttachment; + Attachment attachment; public FfdTimeline (int frameCount) { super(frameCount); @@ -630,12 +633,12 @@ public class Animation { return slotIndex; } - public void setMeshAttachment (MeshAttachment attachment) { - this.meshAttachment = attachment; + public void setAttachment (Attachment attachment) { + this.attachment = attachment; } - public MeshAttachment getMeshAttachment () { - return meshAttachment; + public Attachment getAttachment () { + return attachment; } public float[] getFrames () { @@ -654,7 +657,7 @@ public class Animation { public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha) { Slot slot = skeleton.slots.get(slotIndex); - if (slot.getAttachment() != meshAttachment) return; + if (slot.getAttachment() != attachment) return; FloatArray verticesArray = slot.getAttachmentVertices(); verticesArray.size = 0; @@ -669,7 +672,12 @@ public class Animation { float[] vertices = verticesArray.items; if (time >= frames[frames.length - 1]) { // Time is after last frame. - System.arraycopy(frameVertices[frames.length - 1], 0, vertices, 0, vertexCount); + float[] lastVertices = frameVertices[frames.length - 1]; + if (alpha < 1) { + for (int i = 0; i < vertexCount; i++) + vertices[i] += (lastVertices[i] - vertices[i]) * alpha; + } else + System.arraycopy(lastVertices, 0, vertices, 0, vertexCount); return; } @@ -682,10 +690,16 @@ public class Animation { float[] prevVertices = frameVertices[frameIndex - 1]; float[] nextVertices = frameVertices[frameIndex]; - // BOZO - FFD, use alpha for mixing? - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; + if (alpha < 1) { + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; + } + } else { + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } } } } diff --git a/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java b/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java index 9f5ca13c0..4781526c9 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java @@ -40,7 +40,7 @@ public class BoneData { boolean inheritScale = true, inheritRotation = true; // Nonessential. - final Color color = new Color(1, 1, 1, 1); + final Color color = new Color(0.61f, 0.61f, 0.61f, 1); /** @param parent May be null. */ public BoneData (String name, BoneData parent) { diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index a661a6a94..05922f37c 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -45,12 +45,15 @@ 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.esotericsoftware.spine.attachments.SkinnedMeshAttachment; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.DataInput; +import com.badlogic.gdx.utils.FloatArray; +import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.SerializationException; import java.io.IOException; @@ -218,17 +221,48 @@ public class SkeletonBinary { 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); + short[] triangles = readShortArray(input); + float[] vertices = readFloatArray(input, scale); + mesh.setMesh(vertices, triangles, uvs); Color.rgba8888ToColor(mesh.getColor(), input.readInt()); if (nonessential) { mesh.setEdges(readIntArray(input)); - mesh.setHullLength(input.readInt(true)); + mesh.setHullLength(input.readInt(true) * 2); + mesh.setWidth(input.readFloat() * scale); + mesh.setHeight(input.readFloat() * scale); + } + return mesh; + } + case skinnedmesh: { + String path = input.readString(); + if (path == null) path = name; + SkinnedMeshAttachment mesh = attachmentLoader.newSkinnedMeshAttachment(skin, name, path); + float[] uvs = readFloatArray(input, 1); + short[] triangles = readShortArray(input); + + int vertexCount = input.readInt(true); + FloatArray weights = new FloatArray(uvs.length * 3 * 3); + IntArray bones = new IntArray(uvs.length * 3); + for (int i = 0; i < vertexCount; i++) { + int boneCount = (int)input.readFloat(); + bones.add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) { + bones.add((int)input.readFloat()); + weights.add(input.readFloat() * scale); + weights.add(input.readFloat() * scale); + weights.add(input.readFloat()); + } + } + mesh.setMesh(bones.toArray(), weights.toArray(), uvs, triangles); + + Color.rgba8888ToColor(mesh.getColor(), input.readInt()); + if (nonessential) { + mesh.setEdges(readIntArray(input)); + mesh.setHullLength(input.readInt(true) * 2); mesh.setWidth(input.readFloat() * scale); mesh.setHeight(input.readFloat() * scale); } - mesh.setMesh(vertices, triangles, uvs); return mesh; } } @@ -238,8 +272,13 @@ public class SkeletonBinary { 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; + if (scale == 1) { + for (int i = 0; i < n; i++) + array[i] = input.readFloat(); + } else { + for (int i = 0; i < n; i++) + array[i] = input.readFloat() * scale; + } return array; } @@ -344,22 +383,45 @@ public class SkeletonBinary { 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++) { - MeshAttachment mesh = (MeshAttachment)skin.getAttachment(slotIndex, input.readString()); + Attachment attachment = skin.getAttachment(slotIndex, input.readString()); int frameCount = input.readInt(true); FfdTimeline timeline = new FfdTimeline(frameCount); timeline.slotIndex = slotIndex; - timeline.meshAttachment = mesh; + timeline.attachment = attachment; for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { float time = input.readFloat(); + float[] vertices; - int vertexCount = input.readInt(true); - if (vertexCount == 0) - vertices = mesh.getVertices(); - else { + int vertexCount; + if (attachment instanceof MeshAttachment) + vertexCount = ((MeshAttachment)attachment).getVertices().length; + else + vertexCount = ((SkinnedMeshAttachment)attachment).getWeights().length / 3 * 2; + + int end = input.readInt(true); + if (end == 0) { + if (attachment instanceof MeshAttachment) + vertices = ((MeshAttachment)attachment).getVertices(); + else + vertices = new float[vertexCount]; + } else { vertices = new float[vertexCount]; - for (int vertex = 0; vertex < vertexCount; vertex++) - vertices[vertex] = input.readFloat() * scale; + int start = input.readInt(true); + end += start; + if (scale == 1) { + for (int v = start; v < end; v++) + vertices[v] = input.readFloat(); + } else { + for (int v = start; v < end; v++) + vertices[v] = input.readFloat() * scale; + } + if (attachment instanceof MeshAttachment) { + float[] meshVertices = ((MeshAttachment)attachment).getVertices(); + for (int v = 0, vn = vertices.length; v < vn; v++) + vertices[v] += meshVertices[v]; + } } + timeline.setFrame(frameIndex, time, vertices); if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); } diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 5b1685a9c..6e3294785 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -45,11 +45,14 @@ 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.esotericsoftware.spine.attachments.SkinnedMeshAttachment; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.utils.Array; +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.SerializationException; @@ -194,15 +197,44 @@ public class SkeletonJson { } case mesh: { MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, map.getString("path", name)); + float[] uvs = map.require("uvs").asFloatArray(); + short[] triangles = map.require("triangles").asShortArray(); + float[] vertices = map.require("vertices").asFloatArray(); if (scale != 1) { for (int i = 0, n = vertices.length; i < n; i++) vertices[i] *= scale; } - short[] triangles = map.require("triangles").asShortArray(); - float[] uvs = map.require("uvs").asFloatArray(); mesh.setMesh(vertices, triangles, uvs); - if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt()); + + if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() * 2); + if (map.has("edges")) mesh.setEdges(map.require("edges").asIntArray()); + mesh.setWidth(map.getFloat("width", 0) * scale); + mesh.setHeight(map.getFloat("height", 0) * scale); + return mesh; + } + case skinnedmesh: { + SkinnedMeshAttachment mesh = attachmentLoader.newSkinnedMeshAttachment(skin, name, map.getString("path", name)); + float[] uvs = map.require("uvs").asFloatArray(); + short[] triangles = map.require("triangles").asShortArray(); + + float[] vertices = map.require("vertices").asFloatArray(); + FloatArray weights = new FloatArray(uvs.length * 3 * 3); + IntArray bones = new IntArray(uvs.length * 3); + for (int i = 0, n = vertices.length; i < n;) { + int boneCount = (int)vertices[i++]; + bones.add(boneCount); + for (int nn = i + boneCount * 4; i < nn;) { + bones.add((int)vertices[i]); + weights.add(vertices[i + 1] * scale); + weights.add(vertices[i + 2] * scale); + weights.add(vertices[i + 3]); + i += 4; + } + } + mesh.setMesh(bones.toArray(), weights.toArray(), uvs, triangles); + + if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() * 2); if (map.has("edges")) mesh.setEdges(map.require("edges").asIntArray()); mesh.setWidth(map.getFloat("width", 0) * scale); mesh.setHeight(map.getFloat("height", 0) * scale); @@ -222,6 +254,7 @@ public class SkeletonJson { } private void readAnimation (String name, JsonValue map, SkeletonData skeletonData) { + float scale = this.scale; Array timelines = new Array(); float duration = 0; @@ -315,24 +348,41 @@ public class SkeletonJson { if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name); for (JsonValue meshMap = slotMap.child; meshMap != null; meshMap = meshMap.next) { FfdTimeline timeline = new FfdTimeline(meshMap.size); - MeshAttachment mesh = (MeshAttachment)skin.getAttachment(slotIndex, meshMap.name); - if (mesh == null) throw new SerializationException("Mesh attachment not found: " + meshMap.name); + Attachment attachment = skin.getAttachment(slotIndex, meshMap.name); + if (attachment == null) throw new SerializationException("FFD attachment not found: " + meshMap.name); timeline.slotIndex = slotIndex; - timeline.meshAttachment = mesh; + timeline.attachment = attachment; int frameIndex = 0; for (JsonValue valueMap = meshMap.child; valueMap != null; valueMap = valueMap.next) { float[] vertices; + int vertexCount; + if (attachment instanceof MeshAttachment) + vertexCount = ((MeshAttachment)attachment).getVertices().length; + else + vertexCount = ((SkinnedMeshAttachment)attachment).getWeights().length / 3 * 2; + JsonValue verticesValue = valueMap.get("vertices"); - if (verticesValue == null) - vertices = mesh.getVertices(); - else { - vertices = verticesValue.asFloatArray(); + if (verticesValue == null) { + if (attachment instanceof MeshAttachment) + vertices = ((MeshAttachment)attachment).getVertices(); + else + vertices = new float[vertexCount]; + } else { + vertices = new float[vertexCount]; + int start = valueMap.getInt("offset", 0); + System.arraycopy(verticesValue.asFloatArray(), 0, vertices, start, verticesValue.size); if (scale != 1) { - for (int i = 0, n = vertices.length; i < n; i++) + for (int i = start, n = i + verticesValue.size; i < n; i++) vertices[i] *= scale; } + if (attachment instanceof MeshAttachment) { + float[] meshVertices = ((MeshAttachment)attachment).getVertices(); + for (int i = 0, n = vertices.length; i < n; i++) + vertices[i] += meshVertices[i]; + } } + timeline.setFrame(frameIndex, valueMap.getFloat("time"), vertices); readCurve(timeline, frameIndex, valueMap); frameIndex++; diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index c5605c0e6..bbc79eea8 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -32,6 +32,7 @@ import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.MeshAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.SkeletonAttachment; +import com.esotericsoftware.spine.attachments.SkinnedMeshAttachment; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; @@ -83,6 +84,14 @@ public class SkeletonRenderer { texture = mesh.getRegion().getTexture(); batch.draw(texture, vertices, 0, vertices.length, triangles, 0, triangles.length); + } else if (attachment instanceof SkinnedMeshAttachment) { + SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)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/SkeletonRendererDebug.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java index 103426421..23de9b8ac 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java @@ -29,7 +29,9 @@ 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.SkinnedMeshAttachment; import static com.badlogic.gdx.graphics.g2d.Batch.*; @@ -44,12 +46,14 @@ import com.badlogic.gdx.utils.FloatArray; public class SkeletonRendererDebug { static private final Color boneLineColor = Color.RED; static private final Color boneOriginColor = Color.GREEN; - static private final Color regionAttachmentLineColor = new Color(0, 0, 1, 0.5f); + static private final Color attachmentLineColor = new Color(0, 0, 1, 0.5f); + static private final Color triangleLineColor = new Color(1, 0.64f, 0, 0.5f); static private final Color boundingBoxColor = new Color(0, 1, 0, 0.8f); static private final Color aabbColor = new Color(0, 1, 0, 0.5f); private final ShapeRenderer renderer; private boolean drawBones = true, drawRegionAttachments = true, drawBoundingBoxes = true; + private boolean drawMeshHull = true, drawMeshTriangles = true; private final SkeletonBounds bounds = new SkeletonBounds(); private float scale = 1; @@ -78,7 +82,7 @@ public class SkeletonRendererDebug { } if (drawRegionAttachments) { - renderer.setColor(regionAttachmentLineColor); + renderer.setColor(attachmentLineColor); Array slots = skeleton.getSlots(); for (int i = 0, n = slots.size; i < n; i++) { Slot slot = slots.get(i); @@ -95,6 +99,48 @@ public class SkeletonRendererDebug { } } + if (drawMeshHull || drawMeshTriangles) { + Array slots = skeleton.getSlots(); + for (int i = 0, n = slots.size; i < n; i++) { + Slot slot = slots.get(i); + Attachment attachment = slot.attachment; + float[] vertices = null; + short[] triangles = null; + if (attachment instanceof MeshAttachment) { + MeshAttachment mesh = (MeshAttachment)attachment; + mesh.updateWorldVertices(slot, false); + vertices = mesh.getWorldVertices(); + triangles = mesh.getTriangles(); + } else if (attachment instanceof SkinnedMeshAttachment) { + SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment; + mesh.updateWorldVertices(slot, false); + vertices = mesh.getWorldVertices(); + triangles = mesh.getTriangles(); + } + if (vertices == null || triangles == null) continue; + if (drawMeshTriangles) { + renderer.setColor(triangleLineColor); + for (int ii = 0, nn = triangles.length; ii < nn; ii += 3) { + int v1 = triangles[ii] * 5, v2 = triangles[ii + 1] * 5, v3 = triangles[ii + 2] * 5; + renderer.triangle(vertices[v1], vertices[v1 + 1], // + vertices[v2], vertices[v2 + 1], // + vertices[v3], vertices[v3 + 1] // + ); + } + } + if (drawMeshHull) { + renderer.setColor(attachmentLineColor); + float lastX = vertices[vertices.length - 5], lastY = vertices[vertices.length - 4]; + for (int ii = 0, nn = vertices.length; ii < nn; ii += 5) { + float x = vertices[ii], y = vertices[ii + 1]; + renderer.line(x, y, lastX, lastY); + lastX = x; + lastY = y; + } + } + } + } + if (drawBoundingBoxes) { SkeletonBounds bounds = this.bounds; bounds.update(skeleton, true); @@ -142,4 +188,12 @@ public class SkeletonRendererDebug { public void setBoundingBoxes (boolean boundingBoxes) { this.drawBoundingBoxes = boundingBoxes; } + + public void setMeshHull (boolean meshHull) { + this.drawMeshHull = meshHull; + } + + public void setMeshTriangles (boolean meshTriangles) { + this.drawMeshTriangles = meshTriangles; + } } diff --git a/spine-libgdx/src/com/esotericsoftware/spine/Slot.java b/spine-libgdx/src/com/esotericsoftware/spine/Slot.java index c2ca250ad..41ac06a97 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/Slot.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/Slot.java @@ -100,7 +100,7 @@ public class Slot { if (this.attachment == attachment) return; this.attachment = attachment; attachmentTime = skeleton.time; - attachmentVertices.clear(); + //attachmentVertices.clear(); } public void setAttachmentTime (float time) { @@ -120,7 +120,7 @@ public class Slot { color.set(data.color); setAttachment(data.attachmentName == null ? null : skeleton.getAttachment(slotIndex, data.attachmentName)); // BOZO - Set mesh to setup pose. - // attachmentVertices.clear(); + attachmentVertices.clear(); } public void setToSetupPose () { diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java index 3e4c79949..88f0bc273 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java @@ -56,7 +56,17 @@ public class AtlasAttachmentLoader implements AttachmentLoader { attachment.setPath(path); AtlasRegion region = atlas.findRegion(path); if (region == null) - throw new RuntimeException("Region not found in atlas: " + attachment + " (region attachment: " + name + ")"); + throw new RuntimeException("Region not found in atlas: " + attachment + " (mesh attachment: " + name + ")"); + attachment.setRegion(region); + return attachment; + } + + public SkinnedMeshAttachment newSkinnedMeshAttachment (Skin skin, String name, String path) { + SkinnedMeshAttachment attachment = new SkinnedMeshAttachment(name); + attachment.setPath(path); + AtlasRegion region = atlas.findRegion(path); + if (region == null) + throw new RuntimeException("Region not found in atlas: " + attachment + " (skinned mesh attachment: " + name + ")"); attachment.setRegion(region); return attachment; } diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java index 46baf79f9..00d2e8e62 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java @@ -36,6 +36,9 @@ public interface AttachmentLoader { /** @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 SkinnedMeshAttachment newSkinnedMeshAttachment (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 56ac2ac04..f07f15925 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java @@ -29,5 +29,5 @@ package com.esotericsoftware.spine.attachments; public enum AttachmentType { - region, boundingbox, mesh + region, boundingbox, mesh, skinnedmesh } diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/SkinnedMeshAttachment.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/SkinnedMeshAttachment.java new file mode 100644 index 000000000..b4005c74d --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/SkinnedMeshAttachment.java @@ -0,0 +1,211 @@ +/****************************************************************************** + * Spine Runtimes Software License + * Version 2 + * + * Copyright (c) 2013, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable and + * non-transferable license to install, execute and perform the Spine Runtimes + * Software (the "Software") solely for internal use. Without the written + * permission of Esoteric Software, you may not (a) modify, translate, adapt or + * otherwise create derivative works, improvements of the Software or develop + * new applications using the Software 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 SOFTARE 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.FloatArray; +import com.badlogic.gdx.utils.NumberUtils; + +/** Attachment that displays a texture region. */ +public class SkinnedMeshAttachment extends Attachment { + private TextureRegion region; + private String path; + private int[] bones; + private float[] weights; + private short[] triangles; + private float[] worldVertices; + private final Color color = new Color(1, 1, 1, 1); + + // Nonessential. + private int[] edges; + private float width, height; + private int hullLength; + + public SkinnedMeshAttachment (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 meshColor = slot.getColor(); + Color regionColor = color; + float a = skeletonColor.a * meshColor.a * regionColor.a * 255; + float multiplier = premultipliedAlpha ? a : 255; + float color = NumberUtils.intToFloatColor( // + ((int)a << 24) // + | ((int)(skeletonColor.b * meshColor.b * regionColor.b * multiplier) << 16) // + | ((int)(skeletonColor.g * meshColor.g * regionColor.g * multiplier) << 8) // + | (int)(skeletonColor.r * meshColor.r * regionColor.r * multiplier)); + + float[] worldVertices = this.worldVertices; + float x = skeleton.getX(), y = skeleton.getY(); + Object[] skeletonBones = skeleton.getBones().items; + float[] weights = this.weights; + int[] bones = this.bones; + + FloatArray ffdArray = slot.getAttachmentVertices(); + if (ffdArray.size == 0) { + for (int w = 0, v = 0, b = 0, n = bones.length; v < n; w += 5) { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3) { + Bone bone = (Bone)skeletonBones[bones[v]]; + float vx = weights[b], vy = weights[b + 1], weight = weights[b + 2]; + wx += (vx * bone.getM00() + vy * bone.getM01() + bone.getWorldX()) * weight; + wy += (vx * bone.getM10() + vy * bone.getM11() + bone.getWorldY()) * weight; + } + worldVertices[w] = wx + x; + worldVertices[w + 1] = wy + y; + worldVertices[w + 2] = color; + } + } else { + float[] ffd = ffdArray.items; + for (int w = 0, v = 0, b = 0, f = 0, n = bones.length; v < n; w += 5) { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3, f += 2) { + Bone bone = (Bone)skeletonBones[bones[v]]; + float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[b + 2]; + wx += (vx * bone.getM00() + vy * bone.getM01() + bone.getWorldX()) * weight; + wy += (vx * bone.getM10() + vy * bone.getM11() + bone.getWorldY()) * weight; + } + worldVertices[w] = wx + x; + worldVertices[w + 1] = wy + y; + worldVertices[w + 2] = color; + } + } + } + + public float[] getWorldVertices () { + return worldVertices; + } + + public short[] getTriangles () { + return triangles; + } + + public int[] getBones () { + return bones; + } + + public float[] getWeights () { + return weights; + } + + 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 void setEdges (int[] edges) { + this.edges = edges; + } + + public int[] getEdges () { + return 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; + } + + /** @param bones For each vertex, the number of bones affecting the vertex followed by that many bone indices. Ie: count, + * boneIndex, ... + * @param weights For each bone affecting the vertex, the vertex position in the bone's coordinate system and the weight for + * the bone's influence. Ie: x, y, weight, ... + * @param uvs For each vertex, a texure coordinate pair. Ie: u, v, ... + * @param triangles Vertex number triplets which describe the mesh's triangulation. */ + public void setMesh (int[] bones, float[] weights, float[] uvs, short[] triangles) { + this.bones = bones; + this.weights = weights; + this.triangles = triangles; + + int uvsLength = uvs.length; + int worldVerticesLength = uvsLength / 2 * 5; + if (worldVertices == null || worldVertices.length != worldVerticesLength) worldVertices = new float[worldVerticesLength]; + + float u, v, w, h; + if (region == null) { + u = v = 0; + w = h = 1; + } else { + u = region.getU(); + v = region.getV(); + w = region.getU2() - u; + h = region.getV2() - v; + } + for (int i = 0, ii = 3; i < uvsLength; i += 2, ii += 5) { + worldVertices[ii] = u + uvs[i] * w; + worldVertices[ii + 1] = v + uvs[i + 1] * h; + } + } +} diff --git a/spine-libgdx/test/com/esotericsoftware/spine/NormalMapTest.java b/spine-libgdx/test/com/esotericsoftware/spine/NormalMapTest.java index ce0d73a17..cfe575b34 100644 --- a/spine-libgdx/test/com/esotericsoftware/spine/NormalMapTest.java +++ b/spine-libgdx/test/com/esotericsoftware/spine/NormalMapTest.java @@ -168,7 +168,7 @@ public class NormalMapTest extends ApplicationAdapter { public void resize (int width, int height) { batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height); - ui.stage.setViewport(width, height); + ui.stage.getViewport().update(width, height, true); resolution.set(width, height); } diff --git a/spine-libgdx/test/com/esotericsoftware/spine/SkeletonTest.java b/spine-libgdx/test/com/esotericsoftware/spine/SkeletonTest.java index c35d5df7f..3e486331d 100644 --- a/spine-libgdx/test/com/esotericsoftware/spine/SkeletonTest.java +++ b/spine-libgdx/test/com/esotericsoftware/spine/SkeletonTest.java @@ -63,6 +63,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Window; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.viewport.ScreenViewport; import java.awt.FileDialog; import java.awt.Frame; @@ -195,6 +196,8 @@ public class SkeletonTest extends ApplicationAdapter { debugRenderer.setBones(ui.debugBonesCheckbox.isChecked()); debugRenderer.setRegionAttachments(ui.debugRegionsCheckbox.isChecked()); debugRenderer.setBoundingBoxes(ui.debugBoundingBoxesCheckbox.isChecked()); + debugRenderer.setMeshHull(ui.debugMeshHullCheckbox.isChecked()); + debugRenderer.setMeshTriangles(ui.debugMeshTrianglesCheckbox.isChecked()); debugRenderer.draw(skeleton); } @@ -205,12 +208,12 @@ public class SkeletonTest extends ApplicationAdapter { public void resize (int width, int height) { batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height); debugRenderer.getShapeRenderer().setProjectionMatrix(batch.getProjectionMatrix()); - ui.stage.setViewport(width, height); + ui.stage.getViewport().update(width, height, true); if (!ui.minimizeButton.isChecked()) ui.window.setHeight(height); } class UI { - Stage stage = new Stage(); + Stage stage = new Stage(new ScreenViewport()); com.badlogic.gdx.scenes.scene2d.ui.Skin skin = new com.badlogic.gdx.scenes.scene2d.ui.Skin( Gdx.files.internal("skin/skin.json")); @@ -231,6 +234,8 @@ public class SkeletonTest extends ApplicationAdapter { CheckBox debugBonesCheckbox = new CheckBox(" Bones", skin); CheckBox debugRegionsCheckbox = new CheckBox(" Regions", skin); CheckBox debugBoundingBoxesCheckbox = new CheckBox(" Bounds", skin); + CheckBox debugMeshHullCheckbox = new CheckBox(" Mesh Hull", skin); + CheckBox debugMeshTrianglesCheckbox = new CheckBox(" Mesh Triangles", skin); Slider scaleSlider = new Slider(0.1f, 3, 0.01f, false, skin); Label scaleLabel = new Label("1.0", skin); TextButton pauseButton = new TextButton("Pause", skin, "toggle"); @@ -286,6 +291,8 @@ public class SkeletonTest extends ApplicationAdapter { root.add(table(flipXCheckbox, flipYCheckbox)).row(); root.add("Debug:"); root.add(table(debugBonesCheckbox, debugRegionsCheckbox, debugBoundingBoxesCheckbox)).row(); + root.add(); + root.add(table(debugMeshHullCheckbox, debugMeshTrianglesCheckbox)).row(); root.add("Alpha:"); root.add(premultipliedCheckbox).row(); root.add("Skin:");