Skip to content

Commit

Permalink
- Fixes Issue #5048. The function Intersector.overlapConvexPolygons n…
Browse files Browse the repository at this point in the history
…ow should return the right minimum translation vector values. (#6185)

Fixes Intersector.overlapConvexPolygons (#5048)
  • Loading branch information
kdenzel committed Sep 17, 2020
1 parent d705e20 commit 1c3c587
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 125 deletions.
1 change: 1 addition & 0 deletions CHANGES
@@ -1,4 +1,5 @@
[1.9.12]
- Fixes Issue #5048. The function Intersector.overlapConvexPolygons now should return the right minimum translation vector values.
- Update to MobiVM 2.3.10
- API Change: Removed Pool constructor with preFill parameter in favor of using Pool#fill() method. See #6117
- API Addition: Slider can now be configured to only trigger on certain mouse button clicks (Slider#setButton(int)).
Expand Down
218 changes: 93 additions & 125 deletions gdx/src/com/badlogic/gdx/math/Intersector.java
Expand Up @@ -1025,15 +1025,15 @@ public static boolean overlaps (Circle c, Rectangle r) {
return closestX + closestY < c.radius * c.radius;
}

/** Check whether specified counter-clockwise wound convex polygons overlap.
/** Check whether specified convex polygons overlap (clockwise or counter-clockwise wound doesn't matter).
* @param p1 The first polygon.
* @param p2 The second polygon.
* @return Whether polygons overlap. */
public static boolean overlapConvexPolygons (Polygon p1, Polygon p2) {
return overlapConvexPolygons(p1, p2, null);
}

/** Check whether specified counter-clockwise wound convex polygons overlap. If they do, optionally obtain a Minimum
/** Check whether convex polygons overlap (clockwise or counter-clockwise wound doesn't matter). If they do, optionally obtain a Minimum
* Translation Vector indicating the minimum magnitude vector required to push the polygon p1 out of collision with polygon p2.
* @param p1 The first polygon.
* @param p2 The second polygon.
Expand All @@ -1048,159 +1048,127 @@ public static boolean overlapConvexPolygons (float[] verts1, float[] verts2, Min
return overlapConvexPolygons(verts1, 0, verts1.length, verts2, 0, verts2.length, mtv);
}

/** Check whether polygons defined by the given counter-clockwise wound vertex arrays overlap. If they do, optionally obtain a
/** Check whether polygons defined by the given vertex arrays overlap (clockwise or counter-clockwise wound doesn't matter). 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 offset1 the offset of the verts1 array
* @param count1 the amount that is added to the offset1
* @param verts2 Vertices of the second polygon.
* @param offset2 the offset of the verts2 array
* @param count2 the amount that is added to the offset2
* @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,
MinimumTranslationVector mtv) {
float overlap = Float.MAX_VALUE;
float smallestAxisX = 0;
float smallestAxisY = 0;
int numInNormalDir;
boolean overlaps;
if (mtv != null) {
mtv.depth = Float.MAX_VALUE;
mtv.normal.setZero();
}
overlaps = overlapsOnAxisOfShape(verts2, offset2, count2, verts1, offset1, count1, mtv, true);
if (overlaps) {
overlaps = overlapsOnAxisOfShape(verts1, offset1, count1, verts2, offset2, count2, mtv, false);
}

int end1 = offset1 + count1;
int end2 = offset2 + count2;
if (!overlaps) {
if (mtv != null) {
mtv.depth = 0;
mtv.normal.setZero();
}
return false;
}
return true;
}

// Get polygon1 axes
for (int i = offset1; i < end1; i += 2) {
/**
* Implementation of the separating axis theorem (SAT) algorithm
* @param verts1 the verts1
* @param offset1 offset of verts1
* @param count1 count of verts1
* @param verts2 the verts2
* @param offset2 offset of verts2
* @param count2 count of verts2
* @param mtv the minimum translation vector
* @param shapesShifted states if shape a and b are shifted. Important for calculating the axis translation for verts1.
* @return
*/
private static boolean overlapsOnAxisOfShape(float[] verts1, int offset1, int count1, float[] verts2, int offset2, int count2, MinimumTranslationVector mtv, boolean shapesShifted) {
int endA = offset1 + count1;
int endB = offset2 + count2;
//get axis of polygon A
for (int i = offset1; i < endA; i += 2) {
float x1 = verts1[i];
float y1 = verts1[i + 1];
float x2 = verts1[(i + 2) % count1];
float y2 = verts1[(i + 3) % count1];

//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 = offset1; v < endA; v += 2) {
float p = verts1[v] * axisX + verts1[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 = offset2; v < endB; v += 2) {
float p = verts2[v] * axisX + verts2[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,98 @@
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 shapeWidth = 1f;
private float shapeHeight = 1f;
//the vertices of the 2 shapes. Feel free to play with the values. It must be a convex Polygon.
private float[] vertsShape1 = {0f, 0f, shapeWidth, 0f, shapeWidth, shapeHeight};
private float[] vertsShape2 = {0f, 0f, shapeWidth *2, 0f, shapeWidth *2, shapeHeight *2,0, shapeHeight};

private Polygon shape1 = new Polygon();
private Polygon shape2 = 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();
//set inital position
shape1.setVertices(vertsShape1);
shape2.setVertices(vertsShape2);
shape1.setPosition(0, 0);
shape2.setPosition(1, 0);
}

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

private void update(float deltaTime) {
if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) {
Intersector.overlapConvexPolygons(shape1, shape2, mtv);
float x = shape1.getX() + (mtv.normal.x * mtv.depth);
float y = shape1.getY() + (mtv.normal.y * mtv.depth);
shape1.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);
shape1.setPosition(mouseCoords.x, mouseCoords.y);
boolean overlaps = Intersector.overlapConvexPolygons(shape1, shape2, mtv);
Gdx.app.debug(TAG, mtv.normal + " " + mtv.depth + " overlaps: " + overlaps + " " + mouseCoords);
} else if (Gdx.input.isButtonJustPressed(Input.Buttons.RIGHT)) {
shape1.rotate(90);
boolean overlaps = Intersector.overlapConvexPolygons(shape1, shape2, 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(shape1.getTransformedVertices());
shapeRenderer.setColor(Color.CYAN);
shapeRenderer.polygon(shape2.getTransformedVertices());
//Show axis starting at position 0/0 in pointing direction
shapeRenderer.setColor(Color.RED);
shapeRenderer.line(0, 0, mtv.normal.x * 100, mtv.normal.y * 100);
//Show axis starting at position 0/0 in opposite direction
shapeRenderer.setColor(Color.DARK_GRAY);
shapeRenderer.line(0, 0, mtv.normal.x * -100, mtv.normal.y * -100);
//Show depth on axis in pointing direction
shapeRenderer.setColor(Color.GREEN);
shapeRenderer.line(0, 0, mtv.normal.x * mtv.depth, mtv.normal.y * mtv.depth);
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 1c3c587

Please sign in to comment.