mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
[libgdx] Added SutherlandHodgmanClipper and corresponding SoftwareClippingTest
This commit is contained in:
parent
9a086a5c18
commit
b3ac8a7f72
@ -0,0 +1,159 @@
|
|||||||
|
|
||||||
|
package com.esotericsoftware.spine;
|
||||||
|
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.ApplicationAdapter;
|
||||||
|
import com.badlogic.gdx.Gdx;
|
||||||
|
import com.badlogic.gdx.Input.Buttons;
|
||||||
|
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
|
||||||
|
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
|
||||||
|
import com.badlogic.gdx.graphics.Color;
|
||||||
|
import com.badlogic.gdx.graphics.GL20;
|
||||||
|
import com.badlogic.gdx.graphics.OrthographicCamera;
|
||||||
|
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||||
|
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
|
||||||
|
import com.badlogic.gdx.math.Vector3;
|
||||||
|
import com.badlogic.gdx.utils.FloatArray;
|
||||||
|
import com.esotericsoftware.spine.utils.SutherlandHodgmanClipper;
|
||||||
|
|
||||||
|
public class SoftwareClippingTest extends ApplicationAdapter {
|
||||||
|
OrthographicCamera sceneCamera;
|
||||||
|
ShapeRenderer shapes;
|
||||||
|
|
||||||
|
float[] triangle = {100, 100, 300, 100, 200, 300};
|
||||||
|
FloatArray clippingPolygon = new FloatArray();
|
||||||
|
FloatArray clippedPolygon = new FloatArray();
|
||||||
|
|
||||||
|
boolean isCreatingClippingArea = false;
|
||||||
|
Vector3 tmp = new Vector3();
|
||||||
|
SutherlandHodgmanClipper clipper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void create () {
|
||||||
|
sceneCamera = new OrthographicCamera();
|
||||||
|
shapes = new ShapeRenderer();
|
||||||
|
clipper = new SutherlandHodgmanClipper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resize (int width, int height) {
|
||||||
|
sceneCamera.setToOrtho(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render () {
|
||||||
|
Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1);
|
||||||
|
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
processInput();
|
||||||
|
renderScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processInput () {
|
||||||
|
tmp.set(Gdx.input.getX(), Gdx.input.getY(), 0);
|
||||||
|
sceneCamera.unproject(tmp);
|
||||||
|
|
||||||
|
if (Gdx.input.justTouched()) {
|
||||||
|
if (!isCreatingClippingArea) {
|
||||||
|
clippingPolygon.clear();
|
||||||
|
isCreatingClippingArea = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
clippingPolygon.add(tmp.x);
|
||||||
|
clippingPolygon.add(tmp.y);
|
||||||
|
|
||||||
|
if (Gdx.input.isButtonPressed(Buttons.RIGHT)) {
|
||||||
|
isCreatingClippingArea = false;
|
||||||
|
clip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderScene () {
|
||||||
|
sceneCamera.update();
|
||||||
|
shapes.setProjectionMatrix(sceneCamera.combined);
|
||||||
|
shapes.begin(ShapeType.Line);
|
||||||
|
|
||||||
|
// triangle
|
||||||
|
shapes.setColor(Color.GREEN);
|
||||||
|
shapes.polygon(triangle);
|
||||||
|
|
||||||
|
// clipped polygons
|
||||||
|
shapes.setColor(Color.RED);
|
||||||
|
if (isCreatingClippingArea) {
|
||||||
|
tmp.set(Gdx.input.getX(), Gdx.input.getY(), 0);
|
||||||
|
sceneCamera.unproject(tmp);
|
||||||
|
clippingPolygon.add(tmp.x);
|
||||||
|
clippingPolygon.add(tmp.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (clippingPolygon.size) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
shapes.end();
|
||||||
|
shapes.begin(ShapeType.Point);
|
||||||
|
GL11.glPointSize(4);
|
||||||
|
shapes.point(clippingPolygon.get(0), clippingPolygon.get(1), 0);
|
||||||
|
shapes.end();
|
||||||
|
shapes.begin(ShapeType.Line);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
shapes.line(clippingPolygon.get(0), clippingPolygon.get(1), clippingPolygon.get(2), clippingPolygon.get(3));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
shapes.polygon(clippingPolygon.items, 0, clippingPolygon.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// edge normals
|
||||||
|
shapes.setColor(Color.YELLOW);
|
||||||
|
if (clippingPolygon.size > 2) {
|
||||||
|
boolean clockwise = SutherlandHodgmanClipper.clockwise(clippingPolygon);
|
||||||
|
for (int i = 0; i < clippingPolygon.size; i += 2) {
|
||||||
|
float x = clippingPolygon.get(i);
|
||||||
|
float y = clippingPolygon.get(i + 1);
|
||||||
|
float x2 = clippingPolygon.get((i + 2) % clippingPolygon.size);
|
||||||
|
float y2 = clippingPolygon.get((i + 3) % clippingPolygon.size);
|
||||||
|
|
||||||
|
float mx = x + (x2 - x) / 2;
|
||||||
|
float my = y + (y2 - y) / 2;
|
||||||
|
float nx = (y2 - y);
|
||||||
|
float ny = -(x2 - x);
|
||||||
|
if (!clockwise) {
|
||||||
|
nx = -nx;
|
||||||
|
ny = -ny;
|
||||||
|
}
|
||||||
|
float l = 1 / (float)Math.sqrt(nx * nx + ny * ny);
|
||||||
|
nx *= l * 20;
|
||||||
|
ny *= l * 20;
|
||||||
|
|
||||||
|
shapes.line(mx, my, mx + nx, my + ny);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCreatingClippingArea) {
|
||||||
|
clippingPolygon.setSize(clippingPolygon.size - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clipped polygon
|
||||||
|
shapes.setColor(Color.PINK);
|
||||||
|
if (clippedPolygon.size > 0) {
|
||||||
|
shapes.polygon(clippedPolygon.items, 0, clippedPolygon.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
shapes.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clip () {
|
||||||
|
FloatArray input = new FloatArray();
|
||||||
|
input.addAll(triangle, 0, triangle.length);
|
||||||
|
clippedPolygon.clear();
|
||||||
|
System.out.println("Clipped: " + (clipper.clip(input, clippingPolygon, clippedPolygon) != null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main (String[] args) {
|
||||||
|
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
|
||||||
|
new LwjglApplication(new SoftwareClippingTest(), config);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
|
||||||
|
package com.esotericsoftware.spine.utils;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.Intersector;
|
||||||
|
import com.badlogic.gdx.math.Vector2;
|
||||||
|
import com.badlogic.gdx.utils.FloatArray;
|
||||||
|
|
||||||
|
public class SutherlandHodgmanClipper {
|
||||||
|
Vector2 tmp = new Vector2();
|
||||||
|
|
||||||
|
public FloatArray clip (FloatArray input, FloatArray clippingArea, FloatArray output) {
|
||||||
|
boolean isClockwise = clockwise(clippingArea);
|
||||||
|
|
||||||
|
FloatArray originalOutput = output;
|
||||||
|
boolean clipped = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < clippingArea.size; i += 2) {
|
||||||
|
float edgeX = clippingArea.items[i];
|
||||||
|
float edgeY = clippingArea.items[i + 1];
|
||||||
|
float edgeX2 = clippingArea.items[(i + 2) % clippingArea.size];
|
||||||
|
float edgeY2 = clippingArea.items[(i + 3) % clippingArea.size];
|
||||||
|
|
||||||
|
if (!isClockwise) {
|
||||||
|
float tmp = edgeX;
|
||||||
|
edgeX = edgeX2;
|
||||||
|
edgeX2 = tmp;
|
||||||
|
|
||||||
|
tmp = edgeY;
|
||||||
|
edgeY = edgeY2;
|
||||||
|
edgeY2 = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
float inputY = input.items[(j + 1) % input.size];
|
||||||
|
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 + ")");
|
||||||
|
|
||||||
|
int side = Intersector.pointLineSide(edgeX2, edgeY2, edgeX, edgeY, inputX, inputY);
|
||||||
|
int side2 = Intersector.pointLineSide(edgeX2, edgeY2, edgeX, edgeY, inputX2, inputY2);
|
||||||
|
System.out.println("\tv1: " + (side < 0 ? "outside" : "inside" ) + ", v2: " + (side2 < 0 ? "outside" : "inside"));
|
||||||
|
|
||||||
|
// v1 inside, v2 inside
|
||||||
|
if (side >= 0 && side2 >= 0) {
|
||||||
|
output.add(inputX2);
|
||||||
|
output.add(inputY2);
|
||||||
|
}
|
||||||
|
// v1 inside, v2 outside
|
||||||
|
else if (side >= 0 && side2 < 0) {
|
||||||
|
if (!Intersector.intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp)) {
|
||||||
|
throw new RuntimeException("Lines should intersect, but didn't");
|
||||||
|
}
|
||||||
|
output.add(tmp.x);
|
||||||
|
output.add(tmp.y);
|
||||||
|
clipped = true;
|
||||||
|
}
|
||||||
|
// v1 outside, v2 outside
|
||||||
|
else if (side < 0 && side2 < 0) {
|
||||||
|
// no output
|
||||||
|
clipped = true;
|
||||||
|
}
|
||||||
|
// v1 outside, v2 inside
|
||||||
|
else if (side < 0 && side2 >= 0) {
|
||||||
|
if (!Intersector.intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp)) {
|
||||||
|
throw new RuntimeException("Lines should intersect, but didn't");
|
||||||
|
}
|
||||||
|
output.add(tmp.x);
|
||||||
|
output.add(tmp.y);
|
||||||
|
output.add(inputX2);
|
||||||
|
output.add(inputY2);
|
||||||
|
clipped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < clippingArea.size - 2) {
|
||||||
|
FloatArray tmp = output;
|
||||||
|
output = input;
|
||||||
|
output.clear();
|
||||||
|
input = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originalOutput != output) {
|
||||||
|
originalOutput.clear();
|
||||||
|
originalOutput.addAll(output.items, 0, output.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clipped ? output : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int pointLineSide(float lineX, float lineY, float lineX2, float lineY2, float pointX, float pointY) {
|
||||||
|
return (int)Math.signum((lineX2 - lineX) * (pointY - lineY) - (lineY2 - lineY) * (pointX - lineX));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean intersectLines (float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4,
|
||||||
|
Vector2 intersection) {
|
||||||
|
float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
|
||||||
|
if (d == 0) return false;
|
||||||
|
|
||||||
|
if (intersection != null) {
|
||||||
|
float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d;
|
||||||
|
intersection.set(x1 + (x2 - x1) * ua, y1 + (y2 - y1) * ua);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean clockwise (FloatArray poly) {
|
||||||
|
return area(poly) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float area (FloatArray poly) {
|
||||||
|
float area = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < poly.size; i += 2) {
|
||||||
|
float x = poly.items[i];
|
||||||
|
float y = poly.items[i + 1];
|
||||||
|
float x2 = poly.items[(i + 2) % poly.size];
|
||||||
|
float y2 = poly.items[(i + 3) % poly.size];
|
||||||
|
|
||||||
|
area += x * y2 - y * x2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user