Skip to content

Instantly share code, notes, and snippets.

@Tom-Ski
Created August 3, 2025 01:08
Show Gist options
  • Select an option

  • Save Tom-Ski/65a0d966a3f1696ccf93487c030cad14 to your computer and use it in GitHub Desktop.

Select an option

Save Tom-Ski/65a0d966a3f1696ccf93487c030cad14 to your computer and use it in GitHub Desktop.
Earcut comparison test
package com.badlogic.gdx.tests.lwjgl3;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.EarClippingTriangulator;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.utils.ShortArray;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import earcut4j.Earcut;
import java.util.List;
public class Test extends ApplicationAdapter {
private float[] vertices;
private double[] doubleVertices;
private ShapeRenderer shapeRenderer;
private float minX, minY, maxX, maxY;
private ExtendViewport viewport;
private CameraController cameraContoller;
static class Point {
float x;
float y;
Point () {
}
}
static class Coordinates {
Point[] coordinates;
public Coordinates () {
}
}
@Override
public void create () {
super.create();
shapeRenderer = new ShapeRenderer();
Json json = new Json();
Coordinates coordinates = json.fromJson(Coordinates.class, Gdx.files.internal("data/polys.json"));
minX = Float.MAX_VALUE;
minY = Float.MAX_VALUE;
maxX = -Float.MAX_VALUE;
maxY = -Float.MAX_VALUE;
int length = coordinates.coordinates.length;
length *= 2;
vertices = new float[length];
doubleVertices = new double[length];
for (int i = 0; i < coordinates.coordinates.length; i++) {
Point point = coordinates.coordinates[i];
vertices[i * 2] = point.x;
vertices[i * 2 + 1] = point.y;
doubleVertices[i * 2] = point.x;
doubleVertices[i * 2 + 1] = point.y;
if (point.x < minX) {
minX = point.x;
}
if (point.y < minY) {
minY = point.y;
}
if (point.x > maxX) {
maxX = point.x;
}
if (point.y > maxY) {
maxY = point.y;
}
}
float worldWidth = maxX - minX;
float worldHeight = maxY - minY;
float bufferWidth = worldWidth * 0.1f;
float bufferHeight = worldHeight * 0.1f;
worldWidth += bufferWidth;
worldHeight += bufferHeight;
viewport = new ExtendViewport(worldWidth, worldHeight);
viewport.getCamera().position.set(worldWidth / 2f - bufferWidth/2f, worldHeight / 2f - bufferHeight/2f, 0);
viewport.getCamera().update();
cameraContoller = new CameraController((OrthographicCamera) viewport.getCamera());
Gdx.input.setInputProcessor(cameraContoller);
}
EarClippingTriangulator triangulator = new EarClippingTriangulator();
@Override
public void render () {
super.render();
long start = System.nanoTime();
ShortArray gdxTriangles = triangulator.computeTriangles(vertices);
long end = System.nanoTime();
System.out.println("EarClippingTriangulator took " + (end - start) / 1000000f + " ms");
start = System.nanoTime();
List<Integer> earcutTriangles = Earcut.earcut(doubleVertices);
end = System.nanoTime();
System.out.println("Earcut4j took " + (end - start) / 1000000f + " ms");
start = System.nanoTime();
IntArray gdxEarcutTriangles = GdxEarcut.earcut(vertices);
end = System.nanoTime();
System.out.println("GdxEarcut4j took " + (end - start) / 1000000f + " ms");
ScreenUtils.clear(0, 0, 0, 1f);
viewport.apply();
shapeRenderer.setProjectionMatrix(viewport.getCamera().combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
boolean renderGdx = Gdx.input.isKeyPressed(Input.Keys.NUM_1);
boolean renderEarcut = Gdx.input.isKeyPressed(Input.Keys.NUM_2);
boolean renderGdxEarcut = Gdx.input.isKeyPressed(Input.Keys.NUM_3);
if (renderGdx) {
shapeRenderer.setColor(Color.RED);
for (int i = 0; i < gdxTriangles.size; i += 3) {
int tri1 = gdxTriangles.get(i);
int tri2 = gdxTriangles.get(i + 1);
int tri3 = gdxTriangles.get(i + 2);
float triangleX1 = vertices[tri1 * 2];
float triangleY1 = vertices[tri1 * 2 + 1];
float triangleX2 = vertices[tri2 * 2];
float triangleY2 = vertices[tri2 * 2 + 1];
float triangleX3 = vertices[tri3 * 2];
float triangleY3 = vertices[tri3 * 2 + 1];
shapeRenderer.triangle(triangleX1, triangleY1, triangleX2, triangleY2, triangleX3, triangleY3);
}
}
if (renderEarcut) {
shapeRenderer.setColor(Color.GREEN);
for (int i = 0; i < earcutTriangles.size(); i += 3) {
int tri1 = gdxTriangles.get(i);
int tri2 = gdxTriangles.get(i + 1);
int tri3 = gdxTriangles.get(i + 2);
float triangleX1 = vertices[tri1 * 2];
float triangleY1 = vertices[tri1 * 2 + 1];
float triangleX2 = vertices[tri2 * 2];
float triangleY2 = vertices[tri2 * 2 + 1];
float triangleX3 = vertices[tri3 * 2];
float triangleY3 = vertices[tri3 * 2 + 1];
shapeRenderer.triangle(triangleX1, triangleY1, triangleX2, triangleY2, triangleX3, triangleY3);
}
}
if (renderGdxEarcut) {
shapeRenderer.setColor(Color.BLUE);
for (int i = 0; i < gdxEarcutTriangles.size; i += 3) {
int tri1 = gdxTriangles.get(i);
int tri2 = gdxTriangles.get(i + 1);
int tri3 = gdxTriangles.get(i + 2);
float triangleX1 = vertices[tri1 * 2];
float triangleY1 = vertices[tri1 * 2 + 1];
float triangleX2 = vertices[tri2 * 2];
float triangleY2 = vertices[tri2 * 2 + 1];
float triangleX3 = vertices[tri3 * 2];
float triangleY3 = vertices[tri3 * 2 + 1];
shapeRenderer.triangle(triangleX1, triangleY1, triangleX2, triangleY2, triangleX3, triangleY3);
}
}
shapeRenderer.end();
}
@Override
public void resize (int width, int height) {
viewport.update(width, height);
}
public static class CameraController extends InputAdapter {
private final OrthographicCamera camera;
private final Vector3 tmp = new Vector3();
private final Vector3 lastTouch = new Vector3();
public float minZoom = 0.0001f;
public float maxZoom = 10000f;
public CameraController (OrthographicCamera camera) {
this.camera = camera;
}
@Override
public boolean touchDown (int screenX, int screenY, int pointer, int button) {
if (button == Input.Buttons.LEFT) {
lastTouch.set(screenX, screenY, 0);
}
return true;
}
@Override
public boolean touchDragged (int screenX, int screenY, int pointer) {
tmp.set(screenX, screenY, 0);
camera.unproject(tmp);
Vector3 lastWorld = new Vector3(lastTouch);
camera.unproject(lastWorld);
camera.position.add(lastWorld.x - tmp.x, lastWorld.y - tmp.y, 0);
lastTouch.set(screenX, screenY, 0);
return true;
}
@Override
public boolean scrolled (float amountX, float amountY) {
float zoomFactor = 1.1f;
if (amountY > 0)
camera.zoom *= zoomFactor;
else if (amountY < 0)
camera.zoom /= zoomFactor;
camera.zoom = MathUtils.clamp(camera.zoom, minZoom, maxZoom);
return true;
}
}
public static void main (String[] args) {
new Lwjgl3Application(new Test(), new Lwjgl3ApplicationConfiguration());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment