Skip to content

Commit

Permalink
-Fixes issue libgdx#5048, collision detection with the function overl…
Browse files Browse the repository at this point in the history
…apConvexPolygons in Intersector should now work as expected.

-Added graphical test.
  • Loading branch information
Kai Denzel committed Sep 11, 2020
1 parent a23543c commit 973ee9a
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 132 deletions.
227 changes: 95 additions & 132 deletions gdx/src/com/badlogic/gdx/math/Intersector.java
Expand Up @@ -1049,158 +1049,121 @@ public static boolean overlapConvexPolygons (float[] verts1, float[] verts2, Min
}

/** Check whether polygons defined by the given counter-clockwise wound vertex arrays overlap. If they do, optionally obtain a
* Minimum Translation Vector indicating the minimum magnitude vector required to push the polygon defined by verts1 out of the
* collision with the polygon defined by verts2.
* @param verts1 Vertices of the first polygon.
* @param verts2 Vertices of the second polygon.
* Minimum Translation Vector indicating the minimum magnitude vector required to push the polygon defined by shapeA out of the
* collision with the polygon defined by shapeB.
* @param shapeA Vertices of the first polygon.
* @param shapeB Vertices of the second polygon.
* @param mtv A Minimum Translation Vector to fill in the case of a collision, or null (optional).
* @return Whether polygons overlap. */
public static boolean overlapConvexPolygons (float[] verts1, int offset1, int count1, float[] verts2, int offset2, int count2,
public static boolean overlapConvexPolygons (float[] shapeA, int offsetA, int countA, float[] shapeB, int offsetB, int countB,
MinimumTranslationVector mtv) {
float overlap = Float.MAX_VALUE;
float smallestAxisX = 0;
float smallestAxisY = 0;
int numInNormalDir;

int end1 = offset1 + count1;
int end2 = offset2 + count2;
boolean overlaps;
if (mtv != null) {
mtv.depth = Float.MAX_VALUE;
mtv.normal.setZero();
}
overlaps = overlapsOnAxisOfShape(shapeB, offsetB, countB, shapeA, offsetA, countA, mtv, true);
if (overlaps) {
overlaps = overlapsOnAxisOfShape(shapeA, offsetA, countA, shapeB, offsetB, countB, mtv, false);
}

// Get polygon1 axes
for (int i = offset1; i < end1; i += 2) {
float x1 = verts1[i];
float y1 = verts1[i + 1];
float x2 = verts1[(i + 2) % count1];
float y2 = verts1[(i + 3) % count1];
if (!overlaps) {
if (mtv != null) {
mtv.depth = 0;
mtv.normal.setZero();
}
return false;
}
return true;
}

/**
* @param shapeA the shapeA
* @param offsetA offset of shapeA
* @param countA count of shapeA
* @param shapeB the shapeB
* @param offsetB offset of shapeB
* @param countB count of shapeB
* @param mtv the minimum translation vector
* @param shapesShifted states if shape a and b are shifted. Important for calculating the axis translation for shapeA.
* @return
*/
private static boolean overlapsOnAxisOfShape(float[] shapeA, int offsetA, int countA, float[] shapeB, int offsetB, int countB, MinimumTranslationVector mtv, boolean shapesShifted) {
int endA = offsetA + countA;
int endB = offsetB + countB;
//get axis of polygon A
for (int i = offsetA; i < endA; i += 2) {
float x1 = shapeA[i];
float y1 = shapeA[i + 1];
float x2 = shapeA[(i + 2) % countA];
float y2 = shapeA[(i + 3) % countA];

//Get the Axis for the 2 vertices
float axisX = y1 - y2;
float axisY = -(x1 - x2);

final float length = (float)Math.sqrt(axisX * axisX + axisY * axisY);
axisX /= length;
axisY /= length;

// -- Begin check for separation on this axis --//

// Project polygon1 onto this axis
float min1 = axisX * verts1[0] + axisY * verts1[1];
float max1 = min1;
for (int j = offset1; j < end1; j += 2) {
float p = axisX * verts1[j] + axisY * verts1[j + 1];
if (p < min1) {
min1 = p;
} else if (p > max1) {
max1 = p;
}
float len = (float) Math.sqrt(axisX * axisX + axisY * axisY);
//We got a normalized Vector
axisX /= len;
axisY /= len;
float minA = Float.MAX_VALUE;
float maxA = -Float.MAX_VALUE;
//project shape a on axis
for (int v = offsetA; v < endA; v += 2) {
float p = shapeA[v] * axisX + shapeA[v + 1] * axisY;
minA = Math.min(minA, p);
maxA = Math.max(maxA, p);
}

// Project polygon2 onto this axis
numInNormalDir = 0;
float min2 = axisX * verts2[0] + axisY * verts2[1];
float max2 = min2;
for (int j = offset2; j < end2; j += 2) {
// Counts the number of points that are within the projected area.
numInNormalDir -= pointLineSide(x1, y1, x2, y2, verts2[j], verts2[j + 1]);
float p = axisX * verts2[j] + axisY * verts2[j + 1];
if (p < min2) {
min2 = p;
} else if (p > max2) {
max2 = p;
}
}
float minB = Float.MAX_VALUE;
float maxB = -Float.MAX_VALUE;

if (!(min1 <= min2 && max1 >= min2 || min2 <= min1 && max2 >= min1)) {
//project shape b on axis
for (int v = offsetB; v < endB; v += 2) {
float p = shapeB[v] * axisX + shapeB[v + 1] * axisY;
minB = Math.min(minB, p);
maxB = Math.max(maxB, p);
}
//There is a gap
if (maxA < minB || maxB < minA) {
return false;
} else {
float o = Math.min(max1, max2) - Math.max(min1, min2);
if (min1 < min2 && max1 > max2 || min2 < min1 && max2 > max1) {
float mins = Math.abs(min1 - min2);
float maxs = Math.abs(max1 - max2);
if (mins < maxs) {
o += mins;
} else {
o += maxs;
if (mtv != null) {
float o = Math.min(maxA, maxB) - Math.max(minA, minB);
boolean aContainsB = minA < minB && maxA > maxB;
boolean bContainsA = minB < minA && maxB > maxA;
//if it contains one or another
float mins = 0;
float maxs = 0;
if (aContainsB || bContainsA) {
mins = Math.abs(minA - minB);
maxs = Math.abs(maxA - maxB);
o += Math.min(mins, maxs);
}
}
if (o < overlap) {
overlap = o;
// Adjusts the direction based on the number of points found
smallestAxisX = numInNormalDir >= 0 ? axisX : -axisX;
smallestAxisY = numInNormalDir >= 0 ? axisY : -axisY;
}
}
// -- End check for separation on this axis --//
}

// Get polygon2 axes
for (int i = offset2; i < end2; i += 2) {
float x1 = verts2[i];
float y1 = verts2[i + 1];
float x2 = verts2[(i + 2) % count2];
float y2 = verts2[(i + 3) % count2];

float axisX = y1 - y2;
float axisY = -(x1 - x2);

final float length = (float)Math.sqrt(axisX * axisX + axisY * axisY);
axisX /= length;
axisY /= length;

// -- Begin check for separation on this axis --//
numInNormalDir = 0;

// Project polygon1 onto this axis
float min1 = axisX * verts1[0] + axisY * verts1[1];
float max1 = min1;
for (int j = offset1; j < end1; j += 2) {
float p = axisX * verts1[j] + axisY * verts1[j + 1];
// Counts the number of points that are within the projected area.
numInNormalDir -= pointLineSide(x1, y1, x2, y2, verts1[j], verts1[j + 1]);
if (p < min1) {
min1 = p;
} else if (p > max1) {
max1 = p;
}
}
if (mtv.depth > o) {
mtv.depth = o;
boolean condition;
if (shapesShifted) {
condition = minA < minB;
axisX = condition ? axisX : -axisX;
axisY = condition ? axisY : -axisY;
} else {
condition = minA > minB;
axisX = condition ? axisX : -axisX;
axisY = condition ? axisY : -axisY;
}

// Project polygon2 onto this axis
float min2 = axisX * verts2[0] + axisY * verts2[1];
float max2 = min2;
for (int j = offset2; j < end2; j += 2) {
float p = axisX * verts2[j] + axisY * verts2[j + 1];
if (p < min2) {
min2 = p;
} else if (p > max2) {
max2 = p;
}
}
if(aContainsB || bContainsA){
condition = mins > maxs;
axisX = condition ? axisX : -axisX;
axisY = condition ? axisY : -axisY;
}

if (!(min1 <= min2 && max1 >= min2 || min2 <= min1 && max2 >= min1)) {
return false;
} else {
float o = Math.min(max1, max2) - Math.max(min1, min2);

if (min1 < min2 && max1 > max2 || min2 < min1 && max2 > max1) {
float mins = Math.abs(min1 - min2);
float maxs = Math.abs(max1 - max2);
if (mins < maxs) {
o += mins;
} else {
o += maxs;
mtv.normal.set(axisX, axisY);
}
}

if (o < overlap) {
overlap = o;
// Adjusts the direction based on the number of points found
smallestAxisX = numInNormalDir < 0 ? axisX : -axisX;
smallestAxisY = numInNormalDir < 0 ? axisY : -axisY;
}
}
// -- End check for separation on this axis --//
}
if (mtv != null) {
mtv.normal.set(smallestAxisX, smallestAxisY);
mtv.depth = overlap;
}
return true;
}
Expand Down
@@ -0,0 +1,94 @@
package com.badlogic.gdx.tests;

import com.badlogic.gdx.Application;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
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.math.Intersector;
import com.badlogic.gdx.math.Polygon;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.tests.utils.GdxTest;

public class IntersectorOverlapConvexPolygonsTest extends GdxTest {

private static final String TAG = IntersectorOverlapConvexPolygonsTest.class.getSimpleName();
private OrthographicCamera camera;
private ShapeRenderer shapeRenderer;

private float triangleWidth = 10f;
private float triangleHeight = 10f;
//2 triangle polygons intersect at 0,10 - 10,10 will return the wrong direction
private float[] vertsTriangle1 = {0f, 0f, triangleWidth, 0f, triangleWidth, triangleHeight};
private float[] vertsTriangle2 = {0f, 0f, triangleWidth*2, 0f, triangleWidth*2, triangleHeight*2};

private Polygon triangle1 = new Polygon();
private Polygon triangle2 = new Polygon();

private Intersector.MinimumTranslationVector mtv = new Intersector.MinimumTranslationVector();

private Vector3 mouseCoords = new Vector3();

@Override
public void create() {
Gdx.app.setLogLevel(Application.LOG_DEBUG);
camera = new OrthographicCamera();
camera.setToOrtho(false);
camera.position.set(0, 0, 0);
shapeRenderer = new ShapeRenderer();
shapeRenderer.setAutoShapeType(true);
//set inital position
triangle1.setVertices(vertsTriangle1);
triangle2.setVertices(vertsTriangle2);
triangle1.setPosition(0, 0);
triangle2.setPosition(10, 0);
}

@Override
public void resize(int width, int height) {
camera.viewportWidth = 80;
camera.viewportHeight = 60;
}

private void update(float deltaTime) {
if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) {
Intersector.overlapConvexPolygons(triangle1, triangle2, mtv);
float x = triangle1.getX() + (mtv.normal.x * mtv.depth);
float y = triangle1.getY() + (mtv.normal.y * mtv.depth);
triangle1.setPosition(x, y);
Gdx.app.debug(TAG, mtv.normal + " " + mtv.depth);
}
if (Gdx.input.isButtonPressed(Input.Buttons.LEFT)) {
mouseCoords.set(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(mouseCoords);
triangle1.setPosition(mouseCoords.x, mouseCoords.y);
boolean overlaps = Intersector.overlapConvexPolygons(triangle1, triangle2, mtv);
Gdx.app.debug(TAG, mtv.normal + " " + mtv.depth + " overlaps: " + overlaps + " " + mouseCoords);
} else if (Gdx.input.isButtonJustPressed(Input.Buttons.RIGHT)) {
triangle1.rotate(90);
boolean overlaps = Intersector.overlapConvexPolygons(triangle1, triangle2, mtv);
Gdx.app.debug(TAG, mtv.normal + " " + mtv.depth + " overlaps: " + overlaps);
}
}

@Override
public void render() {
update(Gdx.graphics.getDeltaTime());
camera.update();
Gdx.gl.glClearColor(0f, 0f, 0f, 0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
shapeRenderer.setColor(Color.GOLD);
shapeRenderer.polygon(triangle1.getTransformedVertices());
shapeRenderer.setColor(Color.CYAN);
shapeRenderer.polygon(triangle2.getTransformedVertices());
shapeRenderer.setColor(Color.RED);
shapeRenderer.line(mtv.normal.x, mtv.normal.y, mtv.normal.x * 10, mtv.normal.y * 10);
shapeRenderer.set(ShapeRenderer.ShapeType.Filled);
shapeRenderer.circle((mtv.normal.x), (mtv.normal.y), 0.5f);
shapeRenderer.end();
}
}
Expand Up @@ -167,6 +167,7 @@ public class GdxTests {
InputTest.class,
IntegerBitmapFontTest.class,
InterpolationTest.class,
IntersectorOverlapConvexPolygonsTest.class,
InverseKinematicsTest.class,
IsometricTileTest.class,
KinematicBodyTest.class,
Expand Down

0 comments on commit 973ee9a

Please sign in to comment.