mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 09:46:02 +08:00
[libgdx] Added software clipping to SkeletonRenderer. Requires a polygon sprite batch and convex clipping attachment.
This commit is contained in:
parent
c73971eb4c
commit
af3b5655fc
@ -32,17 +32,18 @@ package com.esotericsoftware.spine;
|
||||
|
||||
import com.badlogic.gdx.ApplicationAdapter;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Input.Keys;
|
||||
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
|
||||
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
|
||||
import com.badlogic.gdx.graphics.GL20;
|
||||
import com.badlogic.gdx.graphics.OrthographicCamera;
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.esotericsoftware.spine.attachments.ClippingAttachment;
|
||||
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
|
||||
|
||||
public class ClippingTest extends ApplicationAdapter {
|
||||
OrthographicCamera camera;
|
||||
SpriteBatch batch;
|
||||
TwoColorPolygonBatch batch;
|
||||
SkeletonRenderer renderer;
|
||||
SkeletonRendererDebug debugRenderer;
|
||||
|
||||
@ -52,9 +53,10 @@ public class ClippingTest extends ApplicationAdapter {
|
||||
|
||||
public void create () {
|
||||
camera = new OrthographicCamera();
|
||||
batch = new SpriteBatch();
|
||||
batch = new TwoColorPolygonBatch(2048);
|
||||
renderer = new SkeletonRenderer();
|
||||
renderer.setPremultipliedAlpha(true);
|
||||
renderer.setSoftwareClipping(true);
|
||||
debugRenderer = new SkeletonRendererDebug();
|
||||
debugRenderer.setBoundingBoxes(false);
|
||||
debugRenderer.setRegionAttachments(false);
|
||||
@ -81,19 +83,19 @@ public class ClippingTest extends ApplicationAdapter {
|
||||
// Create a clipping attachment, slot data, and slot.
|
||||
ClippingAttachment clip = new ClippingAttachment("clip");
|
||||
// Rectangle:
|
||||
// clip.setVertices(new float[] { //
|
||||
// -140, -50, //
|
||||
// 120, -50, //
|
||||
// 120, 50, //
|
||||
// -140, 50, //
|
||||
// });
|
||||
// Self intersection:
|
||||
clip.setVertices(new float[] { //
|
||||
-140, -50, //
|
||||
120, 50, //
|
||||
120, -50, //
|
||||
120, 50, //
|
||||
-140, 50, //
|
||||
});
|
||||
// Self intersection:
|
||||
// clip.setVertices(new float[] { //
|
||||
// -140, -50, //
|
||||
// 120, 50, //
|
||||
// 120, -50, //
|
||||
// -140, 50, //
|
||||
// });
|
||||
clip.setWorldVerticesLength(8);
|
||||
clip.setEnd(skeleton.findSlot("muzzle"));
|
||||
|
||||
@ -109,6 +111,7 @@ public class ClippingTest extends ApplicationAdapter {
|
||||
public void render () {
|
||||
state.update(Gdx.graphics.getDeltaTime() * 0.3f);
|
||||
|
||||
Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1);
|
||||
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
state.apply(skeleton);
|
||||
@ -123,6 +126,11 @@ public class ClippingTest extends ApplicationAdapter {
|
||||
batch.end();
|
||||
|
||||
debugRenderer.draw(skeleton);
|
||||
|
||||
if (Gdx.input.isKeyJustPressed(Keys.S)) {
|
||||
renderer.setSoftwareClipping(!renderer.getSoftwareClipping());
|
||||
System.out.println("Software clipping: " + renderer.getSoftwareClipping());
|
||||
}
|
||||
}
|
||||
|
||||
public void resize (int width, int height) {
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
|
||||
package com.esotericsoftware.spine;
|
||||
|
||||
import javax.management.BadBinaryOpValueExpException;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.badlogic.gdx.ApplicationAdapter;
|
||||
|
||||
@ -42,20 +42,30 @@ import com.badlogic.gdx.math.Matrix4;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.FloatArray;
|
||||
import com.badlogic.gdx.utils.NumberUtils;
|
||||
import com.badlogic.gdx.utils.ShortArray;
|
||||
import com.esotericsoftware.spine.attachments.Attachment;
|
||||
import com.esotericsoftware.spine.attachments.ClippingAttachment;
|
||||
import com.esotericsoftware.spine.attachments.MeshAttachment;
|
||||
import com.esotericsoftware.spine.attachments.RegionAttachment;
|
||||
import com.esotericsoftware.spine.attachments.SkeletonAttachment;
|
||||
import com.esotericsoftware.spine.utils.SutherlandHodgmanClipper;
|
||||
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
|
||||
|
||||
public class SkeletonRenderer {
|
||||
static private final short[] quadTriangles = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
private boolean softwareClipping;
|
||||
private boolean premultipliedAlpha;
|
||||
private final FloatArray vertices = new FloatArray(32);
|
||||
|
||||
private SutherlandHodgmanClipper clipper = new SutherlandHodgmanClipper();
|
||||
private ClippingAttachment clipAttachment;
|
||||
private Slot clipEnd;
|
||||
private FloatArray clippingArea = new FloatArray();
|
||||
private float[] clipInput = new float[6];
|
||||
private FloatArray clipOutput = new FloatArray(400);
|
||||
private FloatArray clippedVertices = new FloatArray(400);
|
||||
private ShortArray clippedTriangles = new ShortArray(400);
|
||||
private final Matrix4 combinedMatrix = new Matrix4();
|
||||
private ImmediateModeRenderer renderer; // BOZO! - Dispose.
|
||||
|
||||
@ -107,8 +117,10 @@ public class SkeletonRenderer {
|
||||
float oldScaleY = rootBone.getScaleY();
|
||||
float oldRotation = rootBone.getRotation();
|
||||
attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY());
|
||||
// rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
|
||||
// rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
|
||||
// rootBone.setScaleX(1 + bone.getWorldScaleX() -
|
||||
// oldScaleX);
|
||||
// rootBone.setScaleY(1 + bone.getWorldScaleY() -
|
||||
// oldScaleY);
|
||||
// Set shear.
|
||||
rootBone.setRotation(oldRotation + bone.getWorldRotationX());
|
||||
attachmentSkeleton.updateWorldTransform();
|
||||
@ -181,8 +193,10 @@ public class SkeletonRenderer {
|
||||
float oldScaleY = rootBone.getScaleY();
|
||||
float oldRotation = rootBone.getRotation();
|
||||
attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY());
|
||||
// rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
|
||||
// rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
|
||||
// rootBone.setScaleX(1 + bone.getWorldScaleX() -
|
||||
// oldScaleX);
|
||||
// rootBone.setScaleY(1 + bone.getWorldScaleY() -
|
||||
// oldScaleY);
|
||||
// Also set shear.
|
||||
rootBone.setRotation(oldRotation + bone.getWorldRotationX());
|
||||
attachmentSkeleton.updateWorldTransform();
|
||||
@ -214,11 +228,20 @@ public class SkeletonRenderer {
|
||||
blendMode = slotBlendMode;
|
||||
batch.setBlendFunction(blendMode.getSource(premultipliedAlpha), blendMode.getDest());
|
||||
}
|
||||
if (softwareClipping) {
|
||||
if (clipAttachment != null) {
|
||||
clipSoftware(vertices, 0, verticesLength, triangles, 0, triangles.length, clippedVertices, clippedTriangles, 0, c, false);
|
||||
batch.draw(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0, clippedTriangles.size);
|
||||
} else {
|
||||
batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
|
||||
}
|
||||
} else {
|
||||
batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (slot == clipEnd) {
|
||||
batch.flush();
|
||||
if (!softwareClipping) batch.flush();
|
||||
clipEnd();
|
||||
}
|
||||
}
|
||||
@ -275,8 +298,10 @@ public class SkeletonRenderer {
|
||||
float oldScaleY = rootBone.getScaleY();
|
||||
float oldRotation = rootBone.getRotation();
|
||||
attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY());
|
||||
// rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
|
||||
// rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
|
||||
// rootBone.setScaleX(1 + bone.getWorldScaleX() -
|
||||
// oldScaleX);
|
||||
// rootBone.setScaleY(1 + bone.getWorldScaleY() -
|
||||
// oldScaleY);
|
||||
// Also set shear.
|
||||
rootBone.setRotation(oldRotation + bone.getWorldRotationX());
|
||||
attachmentSkeleton.updateWorldTransform();
|
||||
@ -298,7 +323,8 @@ public class SkeletonRenderer {
|
||||
| ((int) (g * lightColor.g * color.g * alpha) << 8) //
|
||||
| (int) (r * lightColor.r * color.r * alpha));
|
||||
Color darkColor = slot.getDarkColor();
|
||||
if (darkColor == null) darkColor = Color.BLACK;
|
||||
if (darkColor == null)
|
||||
darkColor = Color.BLACK;
|
||||
float dark = NumberUtils.intToFloatColor( //
|
||||
((int) (b * darkColor.b * color.b * 255) << 16) //
|
||||
| ((int) (g * darkColor.g * color.g * 255) << 8) //
|
||||
@ -315,11 +341,21 @@ public class SkeletonRenderer {
|
||||
blendMode = slotBlendMode;
|
||||
batch.setBlendFunction(blendMode.getSource(premultipliedAlpha), blendMode.getDest());
|
||||
}
|
||||
|
||||
if (softwareClipping) {
|
||||
if (clipAttachment != null) {
|
||||
clipSoftware(vertices, 0, verticesLength, triangles, 0, triangles.length, clippedVertices, clippedTriangles, dark, light, true);
|
||||
batch.draw(texture, clippedVertices.items, 0, clippedVertices.size, clippedTriangles.items, 0, clippedTriangles.size);
|
||||
} else {
|
||||
batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
|
||||
}
|
||||
} else {
|
||||
batch.draw(texture, vertices, 0, verticesLength, triangles, 0, triangles.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (slot == clipEnd) {
|
||||
batch.flush();
|
||||
if (!softwareClipping) batch.flush();
|
||||
clipEnd();
|
||||
}
|
||||
}
|
||||
@ -327,8 +363,10 @@ public class SkeletonRenderer {
|
||||
|
||||
private void clipStart(Matrix4 transformMatrix, Matrix4 projectionMatrix, Slot slot, ClippingAttachment clip) {
|
||||
if (clipEnd != null) return;
|
||||
clipAttachment = clip;
|
||||
clipEnd = clip.getEnd();
|
||||
|
||||
if (!softwareClipping) {
|
||||
int n = clip.getWorldVerticesLength();
|
||||
float[] vertices = this.vertices.setSize(n);
|
||||
clip.computeWorldVertices(slot, 0, n, vertices, 0, 2);
|
||||
@ -351,14 +389,124 @@ public class SkeletonRenderer {
|
||||
Gdx.gl.glColorMask(true, true, true, true);
|
||||
Gdx.gl.glStencilFunc(clip.getInvert() ? GL20.GL_NOTEQUAL : GL20.GL_EQUAL, 1, 1);
|
||||
Gdx.gl.glStencilOp(GL20.GL_KEEP, GL20.GL_KEEP, GL20.GL_KEEP);
|
||||
} else {
|
||||
int n = clip.getWorldVerticesLength();
|
||||
float[] vertices = this.clippingArea.setSize(n);
|
||||
clip.computeWorldVertices(slot, 0, n, vertices, 0, 2);
|
||||
}
|
||||
}
|
||||
|
||||
private void clipEnd() {
|
||||
clipAttachment = null;
|
||||
clipEnd = null;
|
||||
Gdx.gl.glDisable(GL20.GL_STENCIL_TEST);
|
||||
if (!softwareClipping) Gdx.gl.glDisable(GL20.GL_STENCIL_TEST);
|
||||
}
|
||||
|
||||
private void clipSoftware(float[] vertices, int offset, int verticesLength, short[] triangles, int triangleOffset, int trianglesLength, FloatArray clippedVertices, ShortArray clippedTriangles, float dark, float light, boolean twoColor) {
|
||||
int idx = 0;
|
||||
clippedVertices.clear();
|
||||
clippedTriangles.clear();
|
||||
final int vertexSize = twoColor ? 6 : 5;
|
||||
final int uvOffset = twoColor ? 4 : 3;
|
||||
for (int i = 0; i < trianglesLength; i += 3) {
|
||||
int triOffset = triangles[i] * vertexSize;
|
||||
clipInput[0] = vertices[triOffset];
|
||||
clipInput[1] = vertices[triOffset + 1];
|
||||
float u1 = vertices[triOffset + uvOffset];
|
||||
float v1 = vertices[triOffset + uvOffset + 1];
|
||||
|
||||
triOffset = triangles[i + 1] * vertexSize;
|
||||
clipInput[2] = vertices[triOffset];
|
||||
clipInput[3] = vertices[triOffset + 1];
|
||||
float u2 = vertices[triOffset + uvOffset];
|
||||
float v2 = vertices[triOffset + uvOffset + 1];
|
||||
|
||||
triOffset = triangles[i + 2] * vertexSize;
|
||||
clipInput[4] = vertices[triOffset];
|
||||
clipInput[5] = vertices[triOffset + 1];
|
||||
float u3 = vertices[triOffset + uvOffset];
|
||||
float v3 = vertices[triOffset + uvOffset + 1];
|
||||
|
||||
boolean clipped = clipper.clip(clipInput, 0, 6, 2, clippingArea, clipOutput);
|
||||
if (clipped) {
|
||||
float x1 = clipInput[0];
|
||||
float y1 = clipInput[1];
|
||||
float x2 = clipInput[2];
|
||||
float y2 = clipInput[3];
|
||||
float x3 = clipInput[4];
|
||||
float y3 = clipInput[5];
|
||||
|
||||
float d0 = y2 - y3;
|
||||
float d1 = x3 - x2;
|
||||
float d2 = x1 - x3;
|
||||
float d3 = y1 - y3;
|
||||
float d4 = y3 - y1;
|
||||
|
||||
float denom = 1 / (d0 * d2 + d1 * d3);
|
||||
|
||||
for (int j = 0; j < clipOutput.size; j += 2) {
|
||||
float x = clipOutput.get(j);
|
||||
float y = clipOutput.get(j + 1);
|
||||
|
||||
float a = (d0 * (x - x3) + d1 * (y - y3)) * denom;
|
||||
float b = (d4 * (x - x3) + d2 * (y - y3)) * denom;
|
||||
float c = 1.0f - a - b;
|
||||
|
||||
float u = u1 * a + u2 * b + u3 * c;
|
||||
float v = v1 * a + v2 * b + v3 * c;
|
||||
clippedVertices.add(x);
|
||||
clippedVertices.add(y);
|
||||
clippedVertices.add(light);
|
||||
if (twoColor) clippedVertices.add(dark);
|
||||
clippedVertices.add(u);
|
||||
clippedVertices.add(v);
|
||||
}
|
||||
|
||||
for (int j = 1; j < (clipOutput.size >> 1) - 1; j++) {
|
||||
clippedTriangles.add(idx);
|
||||
clippedTriangles.add(idx + j);
|
||||
clippedTriangles.add(idx + j + 1);
|
||||
}
|
||||
|
||||
idx += clipOutput.size >> 1;
|
||||
} else {
|
||||
clippedVertices.add(clipInput[0]);
|
||||
clippedVertices.add(clipInput[1]);
|
||||
clippedVertices.add(light);
|
||||
if (twoColor) clippedVertices.add(dark);
|
||||
clippedVertices.add(u1);
|
||||
clippedVertices.add(v1);
|
||||
|
||||
clippedVertices.add(clipInput[2]);
|
||||
clippedVertices.add(clipInput[3]);
|
||||
clippedVertices.add(light);
|
||||
if (twoColor) clippedVertices.add(dark);
|
||||
clippedVertices.add(u2);
|
||||
clippedVertices.add(v2);
|
||||
|
||||
clippedVertices.add(clipInput[4]);
|
||||
clippedVertices.add(clipInput[5]);
|
||||
clippedVertices.add(light);
|
||||
if (twoColor) clippedVertices.add(dark);
|
||||
clippedVertices.add(u3);
|
||||
clippedVertices.add(v3);
|
||||
|
||||
clippedTriangles.add(idx++);
|
||||
clippedTriangles.add(idx++);
|
||||
clippedTriangles.add(idx++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPremultipliedAlpha(boolean premultipliedAlpha) {
|
||||
this.premultipliedAlpha = premultipliedAlpha;
|
||||
}
|
||||
|
||||
public boolean getSoftwareClipping () {
|
||||
return softwareClipping;
|
||||
}
|
||||
|
||||
public void setSoftwareClipping(boolean softwareClipping) {
|
||||
this.softwareClipping = softwareClipping;
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ public class SutherlandHodgmanClipper {
|
||||
edgeY2 = tmp;
|
||||
}
|
||||
|
||||
System.out.println("-- Edge " + i / 2 + ": (" + edgeX + ", " + edgeY + ")-(" + edgeX2 + ", " + edgeY2 + ")");
|
||||
// System.out.println("-- Edge " + i / 2 + ": (" + edgeX + ", " + edgeY + ")-(" + edgeX2 + ", " + edgeY2 + ")");
|
||||
|
||||
for (int j = 0; j < input.size; j += 2) {
|
||||
float inputX = input.items[j % input.size];
|
||||
@ -51,11 +51,11 @@ public class SutherlandHodgmanClipper {
|
||||
float inputX2 = input.items[(j + 2) % input.size];
|
||||
float inputY2 = input.items[(j + 3) % input.size];
|
||||
|
||||
System.out.println("\tinput " + j / 2 + ": (" + inputX + ", " + inputY + ")-(" + inputX2 + ", " + inputY2 + ")");
|
||||
// System.out.println("\tinput " + j / 2 + ": (" + inputX + ", " + inputY + ")-(" + inputX2 + ", " + inputY2 + ")");
|
||||
|
||||
int side = pointLineSide(edgeX2, edgeY2, edgeX, edgeY, inputX, inputY);
|
||||
int side2 = pointLineSide(edgeX2, edgeY2, edgeX, edgeY, inputX2, inputY2);
|
||||
System.out.println("\tv1: " + (side < 0 ? "outside" : "inside" ) + ", v2: " + (side2 < 0 ? "outside" : "inside"));
|
||||
// System.out.println("\tv1: " + (side < 0 ? "outside" : "inside" ) + ", v2: " + (side2 < 0 ? "outside" : "inside"));
|
||||
|
||||
// v1 inside, v2 inside
|
||||
if (side >= 0 && side2 >= 0) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user