From 973ee9aea29395d69a501687e7cb06e1734da4f1 Mon Sep 17 00:00:00 2001 From: Kai Denzel Date: Fri, 11 Sep 2020 22:27:05 +0200 Subject: [PATCH] -Fixes issue #5048, collision detection with the function overlapConvexPolygons in Intersector should now work as expected. -Added graphical test. --- .../com/badlogic/gdx/math/Intersector.java | 227 ++++++++---------- .../IntersectorOverlapConvexPolygonsTest.java | 94 ++++++++ .../badlogic/gdx/tests/utils/GdxTests.java | 1 + 3 files changed, 190 insertions(+), 132 deletions(-) create mode 100644 tests/gdx-tests/src/com/badlogic/gdx/tests/IntersectorOverlapConvexPolygonsTest.java diff --git a/gdx/src/com/badlogic/gdx/math/Intersector.java b/gdx/src/com/badlogic/gdx/math/Intersector.java index e2b70db4ca7..458e3c0272e 100644 --- a/gdx/src/com/badlogic/gdx/math/Intersector.java +++ b/gdx/src/com/badlogic/gdx/math/Intersector.java @@ -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; } diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/IntersectorOverlapConvexPolygonsTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/IntersectorOverlapConvexPolygonsTest.java new file mode 100644 index 00000000000..fbcb9f9c2e7 --- /dev/null +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/IntersectorOverlapConvexPolygonsTest.java @@ -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(); + } +} diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java index 84cd71d3750..662c8993df0 100755 --- a/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java @@ -167,6 +167,7 @@ public class GdxTests { InputTest.class, IntegerBitmapFontTest.class, InterpolationTest.class, + IntersectorOverlapConvexPolygonsTest.class, InverseKinematicsTest.class, IsometricTileTest.class, KinematicBodyTest.class,