[xna] Added SkeletonDebugRenderer, modified sample to reproduce clipping issue.

This commit is contained in:
badlogic 2017-07-21 15:54:55 +02:00
parent 580b27c68d
commit a5d2519f86
13 changed files with 4384 additions and 11 deletions

1
.gitignore vendored
View File

@ -55,6 +55,7 @@ spine-xna/obj
spine-xna/example/bin
spine-xna/example/obj
spine-xna/example-content/obj/
spine-xna/.vs/
spine-unity/Assets/spine-csharp*
!spine-unity/Assets/spine-csharp/Place spine-csharp src here.*

View File

@ -140,6 +140,7 @@
* Removed `RegionBatcher` and `SkeletonRegionRenderer`, renamed `SkeletonMeshRenderer` to `SkeletonRenderer`
* Added support for two color tint. For it to work, you need to add the `SpineEffect.fx` file to your content project, then load it via `var effect = Content.Load<Effect>("SpineEffect");`, and set it on the `SkeletonRenderer`. See the example project for code.
* Added support for any `Effect` to be used by `SkeletonRenderer`
* Added `SkeletonDebugRenderer`
## Java
* **Breaking changes**

View File

@ -258,7 +258,7 @@ namespace Spine {
return clipped;
}
static void MakeClockwise (ExposedList<float> polygon) {
public static void MakeClockwise (ExposedList<float> polygon) {
float[] vertices = polygon.Items;
int verticeslength = polygon.Count;

View File

@ -31,7 +31,7 @@
using System;
namespace Spine {
internal class Triangulator {
public class Triangulator {
private readonly ExposedList<ExposedList<float>> convexPolygons = new ExposedList<ExposedList<float>>();
private readonly ExposedList<ExposedList<int>> convexPolygonsIndices = new ExposedList<ExposedList<int>>();

View File

@ -0,0 +1,13 @@
skeleton.png
size: 256,256
format: RGBA8888
filter: Linear,Linear
repeat: none
body
rotate: false
xy: 2, 2
size: 192, 129
orig: 192, 129
offset: 0, 0
index: -1

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -128,9 +128,18 @@
<None Include="data\raptor.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="data\skeleton.atlas">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="data\skeleton.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="data\spineboy.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="data\skeleton.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="data\tank.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@ -43,6 +43,7 @@ namespace Spine {
public class Example : Microsoft.Xna.Framework.Game {
GraphicsDeviceManager graphics;
SkeletonRenderer skeletonRenderer;
SkeletonDebugRenderer skeletonDebugRenderer;
Skeleton skeleton;
Slot headSlot;
AnimationState state;
@ -79,11 +80,16 @@ namespace Spine {
skeletonRenderer.PremultipliedAlpha = false;
skeletonRenderer.Effect = spineEffect;
skeletonDebugRenderer = new SkeletonDebugRenderer(GraphicsDevice);
skeletonDebugRenderer.DisableAll();
skeletonDebugRenderer.DrawClipping = true;
// String name = "spineboy-ess";
// String name = "goblins-pro";
// String name = "raptor-pro";
// String name = "tank-pro";
String name = "coin-pro";
// String name = "coin-pro";
String name = "skeleton";
String atlasName = name.Replace("-pro", "").Replace("-ess", "");
bool binaryData = false;
@ -122,27 +128,34 @@ namespace Spine {
state.Complete += Complete;
state.Event += Event;
state.SetAnimation(0, "test", false);
state.SetAnimation(0, "run", true);
TrackEntry entry = state.AddAnimation(0, "jump", false, 0);
entry.End += End; // Event handling for queued animations.
state.AddAnimation(0, "run", true, 0);
}
else if (name == "raptor-pro") {
state.SetAnimation(0, "walk", true);
state.AddAnimation(1, "gungrab", false, 2);
state.AddAnimation(1, "gun-grab", false, 2);
}
else if (name == "coin-pro") {
state.SetAnimation(0, "rotate", true);
}
else if (name == "tank-pro") {
skeleton.X += 300;
state.SetAnimation(0, "drive", true);
}
else if (name == "skeleton") {
skeleton.SetSkin("Pig_Normal");
skeleton.FlipY = true;
skeleton.Y -= 200;
state.SetAnimation(0, "BattleIdle", true);
}
else {
state.SetAnimation(0, "walk", true);
}
skeleton.X = 400 + (name == "tank-pro" ? 300: 0);
skeleton.Y = GraphicsDevice.Viewport.Height;
skeleton.X += 400;
skeleton.Y += GraphicsDevice.Viewport.Height;
skeleton.UpdateWorldTransform();
headSlot = skeleton.FindSlot("head");
@ -176,6 +189,11 @@ namespace Spine {
skeletonRenderer.Draw(skeleton);
skeletonRenderer.End();
skeletonDebugRenderer.Effect.Projection = Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 1, 0);
skeletonDebugRenderer.Begin();
skeletonDebugRenderer.Draw(skeleton);
skeletonDebugRenderer.End();
bounds.Update(skeleton, true);
MouseState mouse = Mouse.GetState();
if (headSlot != null) {

View File

@ -110,6 +110,8 @@
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="src\MeshBatcher.cs" />
<Compile Include="src\ShapeRenderer.cs" />
<Compile Include="src\SkeletonDebugRenderer.cs" />
<Compile Include="src\XnaTextureLoader.cs" />
<Compile Include="src\Util.cs" />
</ItemGroup>

View File

@ -0,0 +1,167 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.3
*
* Copyright (c) 2013-2015, 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 (the "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 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 SOFTWARE 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.
*****************************************************************************/
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Spine {
/// <summary>
/// Batch drawing of lines and shapes that can be derrived from lines.
///
/// Call drawing methods in between Begin()/End()
/// </summary>
public class ShapeRenderer {
GraphicsDevice device;
List<VertexPositionColor> vertices = new List<VertexPositionColor>();
Color color = Color.White;
BasicEffect effect;
public BasicEffect Effect { get { return effect; } set { effect = value; } }
public ShapeRenderer(GraphicsDevice device) {
this.device = device;
this.effect = new BasicEffect(device);
effect.World = Matrix.Identity;
effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up);
effect.TextureEnabled = false;
effect.VertexColorEnabled = true;
}
public void SetColor(Color color) {
this.color = color;
}
public void Begin() {
device.RasterizerState = new RasterizerState();
device.BlendState = BlendState.AlphaBlend;
}
public void Line(float x1, float y1, float x2, float y2) {
vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color));
vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color));
}
/** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */
public void Circle(float x, float y, float radius) {
Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f))));
}
/** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */
public void Circle(float x, float y, float radius, int segments) {
if (segments <= 0) throw new ArgumentException("segments must be > 0.");
float angle = 2 * MathUtils.PI / segments;
float cos = MathUtils.Cos(angle);
float sin = MathUtils.Sin(angle);
float cx = radius, cy = 0;
float temp = 0;
for (int i = 0; i < segments; i++) {
vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color));
temp = cx;
cx = cos * cx - sin * cy;
cy = sin * temp + cos * cy;
vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color));
}
vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color));
temp = cx;
cx = radius;
cy = 0;
vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color));
}
public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) {
vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color));
vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color));
vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color));
vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color));
vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color));
vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color));
}
public void X(float x, float y, float len) {
Line(x + len, y + len, x - len, y - len);
Line(x - len, y + len, x + len, y - len);
}
public void Polygon(float[] polygonVertices, int offset, int count) {
if (count< 3) throw new ArgumentException("Polygon must contain at least 3 vertices");
offset <<= 1;
count <<= 1;
var firstX = polygonVertices[offset];
var firstY = polygonVertices[offset + 1];
var last = offset + count;
for (int i = offset, n = offset + count - 2; i<n; i += 2) {
var x1 = polygonVertices[i];
var y1 = polygonVertices[i + 1];
var x2 = 0f;
var y2 = 0f;
if (i + 2 >= last) {
x2 = firstX;
y2 = firstY;
} else {
x2 = polygonVertices[i + 2];
y2 = polygonVertices[i + 3];
}
Line(x1, y1, x2, y2);
}
}
public void Rect(float x, float y, float width, float height) {
Line(x, y, x + width, y);
Line(x + width, y, x + width, y + height);
Line(x + width, y + height, x, y + height);
Line(x, y + height, x, y);
}
public void End() {
var verticesArray = vertices.ToArray();
foreach (EffectPass pass in effect.CurrentTechnique.Passes) {
pass.Apply();
device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2);
}
vertices.Clear();
}
}
}

View File

@ -0,0 +1,224 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.3
*
* Copyright (c) 2013-2015, 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 (the "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 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 SOFTWARE 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.
*****************************************************************************/
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Spine {
public class SkeletonDebugRenderer {
ShapeRenderer renderer;
private static Color boneLineColor = new Color(1f, 0f, 0f, 1f);
private static Color boneOriginColor = new Color(0f, 1f, 0f, 1f);
private static Color attachmentLineColor = new Color(0f, 0f, 1f, 0.5f);
private static Color triangleLineColor = new Color(1f, 0.64f, 0f, 0.5f);
private static Color pathColor = new Color(1f, 0.5f, 0f, 1f);
private static Color clipColor = new Color(0.8f, 0f, 0f, 1f);
private static Color aabbColor = new Color(0f, 1f, 0f, 0.5f);
public BasicEffect Effect { get { return renderer.Effect; } set { renderer.Effect = value; } }
public bool DrawBones { get; set; }
public bool DrawRegionAttachments { get; set; }
public bool DrawBoundingBoxes { get; set; }
public bool DrawMeshHull { get; set; }
public bool DrawMeshTriangles { get; set; }
public bool DrawPaths { get; set; }
public bool DrawClipping { get; set; }
public bool DrawSkeletonXY { get; set; }
public void DisableAll() {
DrawBones = false;
DrawRegionAttachments = false;
DrawBoundingBoxes = false;
DrawMeshHull = false;
DrawMeshTriangles = false;
DrawPaths = false;
DrawClipping = false;
DrawSkeletonXY = false;
}
public void EnableAll() {
DrawBones = true;
DrawRegionAttachments = true;
DrawBoundingBoxes = true;
DrawMeshHull = true;
DrawMeshTriangles = true;
DrawPaths = true;
DrawClipping = true;
DrawSkeletonXY = true;
}
private float[] vertices = new float[1024 * 2];
private SkeletonBounds bounds = new SkeletonBounds();
private Triangulator triangulator = new Triangulator();
public SkeletonDebugRenderer (GraphicsDevice device) {
renderer = new ShapeRenderer(device);
EnableAll();
}
public void Begin() {
renderer.Begin();
}
public void Draw(Skeleton skeleton) {
var skeletonX = skeleton.X;
var skeletonY = skeleton.Y;
var bones = skeleton.Bones;
if (DrawBones) {
renderer.SetColor(boneLineColor);
for (int i = 0, n = bones.Count; i < n; i++) {
var bone = bones.Items[i];
if (bone.Parent == null) continue;
var x = bone.Data.Length * bone.A + bone.WorldX;
var y = bone.Data.Length * bone.C + bone.WorldY;
renderer.Line(bone.WorldX, bone.WorldY, x, y);
}
if (DrawSkeletonXY) renderer.X(skeletonX, skeletonY, 4);
}
if (DrawRegionAttachments) {
renderer.SetColor(attachmentLineColor);
var slots = skeleton.Slots;
for (int i = 0, n = slots.Count; i < n; i++) {
var slot = slots.Items[i];
var attachment = slot.Attachment;
if (attachment is RegionAttachment) {
var regionAttachment = (RegionAttachment) attachment;
var vertices = this.vertices;
regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2);
renderer.Line(vertices[0], vertices[1], vertices[2], vertices[3]);
renderer.Line(vertices[2], vertices[3], vertices[4], vertices[5]);
renderer.Line(vertices[4], vertices[5], vertices[6], vertices[7]);
renderer.Line(vertices[6], vertices[7], vertices[0], vertices[1]);
}
}
}
if (DrawMeshHull || DrawMeshTriangles) {
var slots = skeleton.Slots;
for (int i = 0, n = slots.Count; i < n; i++) {
var slot = slots.Items[i];
var attachment = slot.Attachment;
if (!(attachment is MeshAttachment)) continue;
var mesh = (MeshAttachment)attachment;
var world = vertices = vertices.Length < mesh.WorldVerticesLength ? new float[mesh.WorldVerticesLength] : vertices;
mesh.ComputeWorldVertices(slot, 0, mesh.WorldVerticesLength, world, 0, 2);
int[] triangles = mesh.Triangles;
var hullLength = mesh.HullLength;
if (DrawMeshTriangles) {
renderer.SetColor(triangleLineColor);
for (int ii = 0, nn = triangles.Count(); ii < nn; ii += 3) {
int v1 = triangles[ii] * 2, v2 = triangles[ii + 1] * 2, v3 = triangles[ii + 2] * 2;
renderer.Triangle(world[v1], world[v1 + 1], //
world[v2], world[v2 + 1], //
world[v3], world[v3 + 1] //
);
}
}
if (DrawMeshHull && hullLength > 0) {
renderer.SetColor(attachmentLineColor);
hullLength = (hullLength >> 1) * 2;
float lastX = vertices[hullLength - 2], lastY = vertices[hullLength - 1];
for (int ii = 0, nn = hullLength; ii < nn; ii += 2) {
float x = vertices[ii], y = vertices[ii + 1];
renderer.Line(x, y, lastX, lastY);
lastX = x;
lastY = y;
}
}
}
}
if (DrawBoundingBoxes) {
var bounds = this.bounds;
bounds.Update(skeleton, true);
renderer.SetColor(aabbColor);
renderer.Rect(bounds.MinX, bounds.MinY, bounds.Width, bounds.Height);
var polygons = bounds.Polygons;
var boxes = bounds.BoundingBoxes;
for (int i = 0, n = polygons.Count; i < n; i++) {
var polygon = polygons.Items[i];
renderer.Polygon(polygon.Vertices, 0, polygon.Count);
}
}
if (DrawBones) {
renderer.SetColor(boneOriginColor);
for (int i = 0, n = bones.Count; i < n; i++) {
var bone = bones.Items[i];
renderer.Circle(bone.WorldX, bone.WorldY, 3);
}
}
if (DrawClipping) {
var slots = skeleton.Slots;
renderer.SetColor(clipColor);
for (int i = 0, n = slots.Count; i < n; i++) {
var slot = slots.Items[i];
var attachment = slot.Attachment;
if (!(attachment is ClippingAttachment)) continue;
var clip = (ClippingAttachment)attachment;
var nn = clip.WorldVerticesLength;
var world = vertices = vertices.Length < nn ? new float[nn] : vertices;
clip.ComputeWorldVertices(slot, 0, nn, world, 0, 2);
ExposedList<float> clippingPolygon = new ExposedList<float>();
for (int ii = 0; ii < nn; ii += 2) {
var x = world[ii];
var y = world[ii + 1];
var x2 = world[(ii + 2) % nn];
var y2 = world[(ii + 3) % nn];
renderer.Line(x, y, x2, y2);
clippingPolygon.Add(x);
clippingPolygon.Add(y);
}
if (!skeleton.FlipY) SkeletonClipping.MakeClockwise(clippingPolygon);
var clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon));
foreach (var polygon in clippingPolygons) {
if (!skeleton.FlipY) SkeletonClipping.MakeClockwise(polygon);
polygon.Add(polygon.Items[0]);
polygon.Add(polygon.Items[1]);
renderer.Polygon(polygon.Items, 0, polygon.Count >> 1);
}
}
}
}
public void End() {
renderer.End();
}
}
}