/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package com.badlogic.gdx.math; import java.util.Arrays; import java.util.List; import com.badlogic.gdx.math.Plane.PlaneSide; import com.badlogic.gdx.math.collision.BoundingBox; import com.badlogic.gdx.math.collision.Ray; /** * Class offering various static methods for intersection testing between different geometric objects. * * @author badlogicgames@gmail.com * @author jan.stria */ public final class Intersector { /** * Returns the lowest positive root of the quadric equation given by a* x * x + b * x + c = 0. If no solution is * given Float.Nan is returned. * * @param a * the first coefficient of the quadric equation * @param b * the second coefficient of the quadric equation * @param c * the third coefficient of the quadric equation * @return the lowest positive root or Float.Nan */ public static float getLowestPositiveRoot(float a, float b, float c) { float det = b * b - 4 * a * c; if (det < 0) return Float.NaN; float sqrtD = (float) Math.sqrt(det); float invA = 1 / (2 * a); float r1 = (-b - sqrtD) * invA; float r2 = (-b + sqrtD) * invA; if (r1 > r2) { float tmp = r2; r2 = r1; r1 = tmp; } if (r1 > 0) return r1; if (r2 > 0) return r2; return Float.NaN; } private final static Vector3 v0 = new Vector3(); private final static Vector3 v1 = new Vector3(); private final static Vector3 v2 = new Vector3(); /** * Returns whether the given point is inside the triangle. This assumes that the point is on the plane of the * triangle. No check is performed that this is the case. * * @param point * the point * @param t1 * the first vertex of the triangle * @param t2 * the second vertex of the triangle * @param t3 * the third vertex of the triangle * @return whether the point is in the triangle */ public static boolean isPointInTriangle(Vector3 point, Vector3 t1, Vector3 t2, Vector3 t3) { v0.set(t1).sub(point); v1.set(t2).sub(point); v2.set(t3).sub(point); float ab = v0.dot(v1); float ac = v0.dot(v2); float bc = v1.dot(v2); float cc = v2.dot(v2); if (bc * ac - cc * ab < 0) return false; float bb = v1.dot(v1); if (ab * bc - ac * bb < 0) return false; return true; } public static boolean intersectSegmentPlane(Vector3 start, Vector3 end, Plane plane, Vector3 intersection) { Vector3 dir = end.tmp().sub(start); float denom = dir.dot(plane.getNormal()); float t = -(start.dot(plane.getNormal()) + plane.getD()) / denom; if (t < 0 || t > 1) return false; intersection.set(start).add(dir.mul(t)); return true; } /** * Determines on which side of the given line the point is. Returns -1 if the point is on the left side of the line, * 0 if the point is on the line and 1 if the point is on the right side of the line. Left and right are relative to * the lines direction which is linePoint1 to linePoint2. */ public static int pointLineSide(Vector2 linePoint1, Vector2 linePoint2, Vector2 point) { return (int) Math.signum((linePoint2.x - linePoint1.x) * (point.y - linePoint1.y) - (linePoint2.y - linePoint1.y) * (point.x - linePoint1.x)); } public static int pointLineSide(float linePoint1X, float linePoint1Y, float linePoint2X, float linePoint2Y, float pointX, float pointY) { return (int) Math.signum((linePoint2X - linePoint1X) * (pointY - linePoint1Y) - (linePoint2Y - linePoint1Y) * (pointX - linePoint1X)); } /** * Checks whether the given point is in the polygon. * * @param polygon * The polygon vertices * @param point * The point * @return true if the point is in the polygon */ public static boolean isPointInPolygon(List<Vector2> polygon, Vector2 point) { int j = polygon.size() - 1; boolean oddNodes = false; for (int i = 0; i < polygon.size(); i++) { if ((polygon.get(i).y < point.y && polygon.get(j).y >= point.y) || (polygon.get(j).y < point.y && polygon.get(i).y >= point.y)) { if (polygon.get(i).x + (point.y - polygon.get(i).y) / (polygon.get(j).y - polygon.get(i).y) * (polygon.get(j).x - polygon.get(i).x) < point.x) { oddNodes = !oddNodes; } } j = i; } return oddNodes; } /** * Returns the distance between the given line segment and point. * * @param start * The line start point * @param end * The line end point * @param point * The point * * @return The distance between the line segment and the point. */ public static float distanceLinePoint(Vector2 start, Vector2 end, Vector2 point) { tmp.set(end.x, end.y, 0); float l2 = tmp.sub(start.x, start.y, 0).len2(); if (l2 == 0.0f) // start == end return point.dst(start); tmp.set(point.x, point.y, 0); tmp.sub(start.x, start.y, 0); tmp2.set(end.x, end.y, 0); tmp2.sub(start.x, start.y, 0); float t = tmp.dot(tmp2) / l2; if (t < 0.0f) return point.dst(start); // Beyond 'start'-end of the segment else if (t > 1.0f) return point.dst(end); // Beyond 'end'-end of the segment tmp.set(end.x, end.y, 0); // Projection falls on the segment tmp.sub(start.x, start.y, 0).mul(t).add(start.x, start.y, 0); return tmp2.set(point.x, point.y, 0).dst(tmp); } /** Returns the distance between the given line and point. Note the specified line is not a line segment. */ public static float distanceLinePoint(float startX, float startY, float endX, float endY, float pointX, float pointY) { float normalLength = (float) Math.sqrt((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY)); return Math.abs((pointX - startX) * (endY - startY) - (pointY - startY) * (endX - startX)) / normalLength; } /** * Returns whether the given line segment intersects the given circle. * * @param start * The start point of the line segment * @param end * The end point of the line segment * @param center * The center of the circle * @param squareRadius * The squared radius of the circle * @return Whether the line segment and the circle intersect */ public static boolean intersectSegmentCircle(Vector2 start, Vector2 end, Vector2 center, float squareRadius) { tmp.set(end.x - start.x, end.y - start.y, 0); tmp1.set(center.x - start.x, center.y - start.y, 0); float l = tmp.len(); float u = tmp1.dot(tmp.nor()); if (u <= 0) { tmp2.set(start.x, start.y, 0); } else if (u >= l) { tmp2.set(end.x, end.y, 0); } else { tmp3.set(tmp.mul(u)); // remember tmp is already normalized tmp2.set(tmp3.x + start.x, tmp3.y + start.y, 0); } float x = center.x - tmp2.x; float y = center.y - tmp2.y; return x * x + y * y <= squareRadius; } /** * Checks whether the line segment and the circle intersect and returns by how much and in what direction the line * has to move away from the circle to not intersect. * * @param start * The line segment starting point * @param end * The line segment end point * @param point * The center of the circle * @param radius * The radius of the circle * @param displacement * The displacement vector set by the method having unit length * @return The displacement or Float.POSITIVE_INFINITY if no intersection is present */ public static float intersectSegmentCircleDisplace(Vector2 start, Vector2 end, Vector2 point, float radius, Vector2 displacement) { float u = (point.x - start.x) * (end.x - start.x) + (point.y - start.y) * (end.y - start.y); float d = start.dst(end); u /= (d * d); if (u < 0 || u > 1) return Float.POSITIVE_INFINITY; tmp.set(end.x, end.y, 0).sub(start.x, start.y, 0); tmp2.set(start.x, start.y, 0).add(tmp.mul(u)); d = tmp2.dst(point.x, point.y, 0); if (d < radius) { displacement.set(point).sub(tmp2.x, tmp2.y).nor(); return d; } else return Float.POSITIVE_INFINITY; } /** * Intersects a {@link Ray} and a {@link Plane}. The intersection point is stored in intersection in case an * intersection is present. * * @param ray * The ray * @param plane * The plane * @param intersection * The vector the intersection point is written to (optional) * @return Whether an intersection is present. */ public static boolean intersectRayPlane(Ray ray, Plane plane, Vector3 intersection) { float denom = ray.direction.dot(plane.getNormal()); if (denom != 0) { float t = -(ray.origin.dot(plane.getNormal()) + plane.getD()) / denom; if (t < 0) return false; if (intersection != null) intersection.set(ray.origin).add(ray.direction.tmp().mul(t)); return true; } else if (plane.testPoint(ray.origin) == Plane.PlaneSide.OnPlane) { if (intersection != null) intersection.set(ray.origin); return true; } else return false; } /** * Intersects a line and a plane. The intersection is returned as the distance from the first point to the plane. In * case an intersection happened, the return value is in the range [0,1]. The intersection point can be recovered by * point1 + t * (point2 - point1) where t is the return value of this method. * * @param x * @param y * @param z * @param x2 * @param y2 * @param z2 * @param plane */ public static float intersectLinePlane(float x, float y, float z, float x2, float y2, float z2, Plane plane, Vector3 intersection) { Vector3 direction = tmp.set(x2, y2, z2).sub(x, y, z); Vector3 origin = tmp2.set(x, y, z); float denom = direction.dot(plane.getNormal()); if (denom != 0) { float t = -(origin.dot(plane.getNormal()) + plane.getD()) / denom; if ((t >= 0 && t <= 1) && intersection != null) intersection.set(origin).add(direction.mul(t)); return t; } else if (plane.testPoint(origin) == Plane.PlaneSide.OnPlane) { if (intersection != null) intersection.set(origin); return 0; } return -1; } private static final Plane p = new Plane(new Vector3(), 0); private static final Vector3 i = new Vector3(); /** * Intersect a {@link Ray} and a triangle, returning the intersection point in intersection. * * @param ray * The ray * @param t1 * The first vertex of the triangle * @param t2 * The second vertex of the triangle * @param t3 * The third vertex of the triangle * @param intersection * The intersection point (optional) * @return True in case an intersection is present. */ public static boolean intersectRayTriangle(Ray ray, Vector3 t1, Vector3 t2, Vector3 t3, Vector3 intersection) { p.set(t1, t2, t3); if (!intersectRayPlane(ray, p, i)) return false; v0.set(t3).sub(t1); v1.set(t2).sub(t1); v2.set(i).sub(t1); float dot00 = v0.dot(v0); float dot01 = v0.dot(v1); float dot02 = v0.dot(v2); float dot11 = v1.dot(v1); float dot12 = v1.dot(v2); float denom = dot00 * dot11 - dot01 * dot01; if (denom == 0) return false; float u = (dot11 * dot02 - dot01 * dot12) / denom; float v = (dot00 * dot12 - dot01 * dot02) / denom; if (u >= 0 && v >= 0 && u + v <= 1) { if (intersection != null) intersection.set(i); return true; } else { return false; } } private static final Vector3 dir = new Vector3(); private static final Vector3 start = new Vector3(); /** * Intersects a {@link Ray} and a sphere, returning the intersection point in intersection. * * @param ray * The ray * @param center * The center of the sphere * @param radius * The radius of the sphere * @param intersection * The intersection point (optional) * @return Whether an intersection is present. */ public static boolean intersectRaySphere(Ray ray, Vector3 center, float radius, Vector3 intersection) { dir.set(ray.direction).nor(); start.set(ray.origin); float b = 2 * (dir.dot(start.tmp().sub(center))); float c = start.dst2(center) - radius * radius; float disc = b * b - 4 * c; if (disc < 0) return false; // compute q as described above float distSqrt = (float) Math.sqrt(disc); float q; if (b < 0) q = (-b - distSqrt) / 2.0f; else q = (-b + distSqrt) / 2.0f; // compute t0 and t1 float t0 = q / 1; float t1 = c / q; // make sure t0 is smaller than t1 if (t0 > t1) { // if t0 is bigger than t1 swap them around float temp = t0; t0 = t1; t1 = temp; } // if t1 is less than zero, the object is in the ray's negative // direction // and consequently the ray misses the sphere if (t1 < 0) return false; // if t0 is less than zero, the intersection point is at t1 if (t0 < 0) { if (intersection != null) intersection.set(start).add(dir.tmp().mul(t1)); return true; } // else the intersection point is at t0 else { if (intersection != null) intersection.set(start).add(dir.tmp().mul(t0)); return true; } } /** * Intersects a {@link Ray} and a {@link BoundingBox}, returning the intersection point in intersection. * * @param ray * The ray * @param box * The box * @param intersection * The intersection point (optional) * @return Whether an intersection is present. */ boolean intersectRayBounds(Ray ray, BoundingBox box, Vector3 intersection) { Vector3.tmp.set(ray.origin); Vector3.tmp2.set(ray.origin); Vector3.tmp.sub(box.min); Vector3.tmp.sub(box.max); if (Vector3.tmp.x > 0 && Vector3.tmp.y > 0 && Vector3.tmp.z > 0 && Vector3.tmp2.x < 0 && Vector3.tmp2.y < 0 && Vector3.tmp2.z < 0) { return true; } float lowest = 0, t; boolean hit = false; //min x if (ray.origin.x <= box.min.x && ray.direction.x > 0) { t = (box.min.x - ray.origin.x) / ray.direction.x; if (t >= 0) { Vector3.tmp3.set(ray.direction).mul(t).add(ray.origin); if (Vector3.tmp3.y >= box.min.y && Vector3.tmp3.y <= box.max.y && Vector3.tmp3.z >= box.min.z && Vector3.tmp3.z <= box.max.z && (!hit || t < lowest)) { hit = true; lowest = t; } } } //max x if (ray.origin.x >= box.max.x && ray.direction.x < 0) { t = (box.max.x - ray.origin.x) / ray.direction.x; if (t >= 0) { Vector3.tmp3.set(ray.direction).mul(t).add(ray.origin); if (Vector3.tmp3.y >= box.min.y && Vector3.tmp3.y <= box.max.y && Vector3.tmp3.z >= box.min.z && Vector3.tmp3.z <= box.max.z && (!hit || t < lowest)) { hit = true; lowest = t; } } } //min y if (ray.origin.y <= box.min.y && ray.direction.y > 0) { t = (box.min.y - ray.origin.y) / ray.direction.y; if (t >= 0) { Vector3.tmp3.set(ray.direction).mul(t).add(ray.origin); if (Vector3.tmp3.x >= box.min.x && Vector3.tmp3.x <= box.max.x && Vector3.tmp3.z >= box.min.z && Vector3.tmp3.z <= box.max.z && (!hit || t < lowest)) { hit = true; lowest = t; } } } //max y if (ray.origin.y >= box.max.y && ray.direction.y < 0) { t = (box.max.y - ray.origin.y) / ray.direction.y; if (t >= 0) { Vector3.tmp3.set(ray.direction).mul(t).add(ray.origin); if (Vector3.tmp3.x >= box.min.x && Vector3.tmp3.x <= box.max.x && Vector3.tmp3.z >= box.min.z && Vector3.tmp3.z <= box.max.z && (!hit || t < lowest)) { hit = true; lowest = t; } } } //min z if (ray.origin.z <= box.min.y && ray.direction.z > 0) { t = (box.min.z - ray.origin.z) / ray.direction.z; if (t >= 0) { Vector3.tmp3.set(ray.direction).mul(t).add(ray.origin); if (Vector3.tmp3.x >= box.min.x && Vector3.tmp3.x <= box.max.x && Vector3.tmp3.y >= box.min.y && Vector3.tmp3.y <= box.max.y && (!hit || t < lowest)) { hit = true; lowest = t; } } } //max y if (ray.origin.z >= box.max.z && ray.direction.z < 0) { t = (box.max.z - ray.origin.z) / ray.direction.z; if (t >= 0) { Vector3.tmp3.set(ray.direction).mul(t).add(ray.origin); if (Vector3.tmp3.x >= box.min.x && Vector3.tmp3.x <= box.max.x && Vector3.tmp3.y >= box.min.y && Vector3.tmp3.y <= box.max.y && (!hit || t < lowest)) { hit = true; lowest = t; } } } if (hit && intersection != null) { intersection.set(ray.direction).mul(lowest).add(ray.origin); } return hit; } /** * Quick check whether the given {@link Ray} and {@link BoundingBox} intersect. * * @param ray * The ray * @param box * The bounding box * @return Whether the ray and the bounding box intersect. */ static public boolean intersectRayBoundsFast(Ray ray, BoundingBox box) { float a, b; float min, max; float divX = 1 / ray.direction.x; float divY = 1 / ray.direction.y; float divZ = 1 / ray.direction.z; a = (box.min.x - ray.origin.x) * divX; b = (box.max.x - ray.origin.x) * divX; if (a < b) { min = a; max = b; } else { min = b; max = a; } a = (box.min.y - ray.origin.y) * divY; b = (box.max.y - ray.origin.y) * divY; if (a > b) { float t = a; a = b; b = t; } if (a > min) min = a; if (b < max) max = b; a = (box.min.z - ray.origin.z) * divZ; b = (box.max.z - ray.origin.z) * divZ; if (a > b) { float t = a; a = b; b = t; } if (a > min) min = a; if (b < max) max = b; return (max >= 0) && (max >= min); } static Vector3 tmp = new Vector3(); static Vector3 best = new Vector3(); static Vector3 tmp1 = new Vector3(); static Vector3 tmp2 = new Vector3(); static Vector3 tmp3 = new Vector3(); /** * Intersects the given ray with list of triangles. Returns the nearest intersection point in intersection * * @param ray * The ray * @param triangles * The triangles, each successive 3 elements from a vertex * @param intersection * The nearest intersection point (optional) * @return Whether the ray and the triangles intersect. */ public static boolean intersectRayTriangles(Ray ray, float[] triangles, Vector3 intersection) { float min_dist = Float.MAX_VALUE; boolean hit = false; if ((triangles.length / 3) % 3 != 0) throw new RuntimeException("triangle list size is not a multiple of 3"); for (int i = 0; i < triangles.length - 6; i += 9) { boolean result = intersectRayTriangle(ray, tmp1.set(triangles[i], triangles[i + 1], triangles[i + 2]), tmp2.set(triangles[i + 3], triangles[i + 4], triangles[i + 5]), tmp3.set(triangles[i + 6], triangles[i + 7], triangles[i + 8]), tmp); if (result == true) { float dist = ray.origin.tmp().sub(tmp).len2(); if (dist < min_dist) { min_dist = dist; best.set(tmp); hit = true; } } } if (hit == false) return false; else { if (intersection != null) intersection.set(best); return true; } } /** * Intersects the given ray with list of triangles. Returns the nearest intersection point in intersection * * @param ray * The ray * @param vertices * the vertices * @param indices * the indices, each successive 3 shorts index the 3 vertices of a triangle * @param vertexSize * the size of a vertex in floats * @param intersection * The nearest intersection point (optional) * @return Whether the ray and the triangles intersect. */ public static boolean intersectRayTriangles(Ray ray, float[] vertices, short[] indices, int vertexSize, Vector3 intersection) { float min_dist = Float.MAX_VALUE; boolean hit = false; if ((indices.length % 3) != 0) throw new RuntimeException("triangle list size is not a multiple of 3"); for (int i = 0; i < indices.length; i += 3) { int i1 = indices[i] * vertexSize; int i2 = indices[i + 1] * vertexSize; int i3 = indices[i + 2] * vertexSize; boolean result = intersectRayTriangle(ray, tmp1.set(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]), tmp2.set(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]), tmp3.set(vertices[i3], vertices[i3 + 1], vertices[i3 + 2]), tmp); if (result == true) { float dist = ray.origin.tmp().sub(tmp).len2(); if (dist < min_dist) { min_dist = dist; best.set(tmp); hit = true; } } } if (hit == false) return false; else { if (intersection != null) intersection.set(best); return true; } } /** * Intersects the given ray with list of triangles. Returns the nearest intersection point in intersection * * @param ray * The ray * @param triangles * The triangles * @param intersection * The nearest intersection point (optional) * @return Whether the ray and the triangles intersect. */ public static boolean intersectRayTriangles(Ray ray, List<Vector3> triangles, Vector3 intersection) { float min_dist = Float.MAX_VALUE; boolean hit = false; if (triangles.size() % 3 != 0) throw new RuntimeException("triangle list size is not a multiple of 3"); for (int i = 0; i < triangles.size() - 2; i += 3) { boolean result = intersectRayTriangle(ray, triangles.get(i), triangles.get(i + 1), triangles.get(i + 2), tmp); if (result == true) { float dist = ray.origin.tmp().sub(tmp).len2(); if (dist < min_dist) { min_dist = dist; best.set(tmp); hit = true; } } } if (!hit) return false; else { if (intersection != null) intersection.set(best); return true; } } /** * Returns whether the two rectangles intersect * * @param a * The first rectangle * @param b * The second rectangle * @return Whether the two rectangles intersect */ public static boolean intersectRectangles(Rectangle a, Rectangle b) { return !(a.getX() > b.getX() + b.getWidth() || a.getX() + a.getWidth() < b.getX() || a.getY() > b.getY() + b.getHeight() || a.getY() + a.getHeight() < b.getY()); } /** * Intersects the two lines and returns the intersection point in intersection. * * @param p1 * The first point of the first line * @param p2 * The second point of the first line * @param p3 * The first point of the second line * @param p4 * The second point of the second line * @param intersection * The intersection point * @return Whether the two lines intersect */ public static boolean intersectLines(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, Vector2 intersection) { float x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y, x3 = p3.x, y3 = p3.y, x4 = p4.x, y4 = p4.y; float det1 = det(x1, y1, x2, y2); float det2 = det(x3, y3, x4, y4); float det3 = det(x1 - x2, y1 - y2, x3 - x4, y3 - y4); float x = det(det1, x1 - x2, det2, x3 - x4) / det3; float y = det(det1, y1 - y2, det2, y3 - y4) / det3; intersection.x = x; intersection.y = y; return true; } /** * Intersects the two line segments and returns the intersection point in intersection. * * @param p1 * The first point of the first line segment * @param p2 * The second point of the first line segment * @param p3 * The first point of the second line segment * @param p4 * The second point of the second line segment * @param intersection * The intersection point (optional) * @return Whether the two line segments intersect */ public static boolean intersectSegments(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, Vector2 intersection) { float x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y, x3 = p3.x, y3 = p3.y, x4 = p4.x, y4 = p4.y; float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); if (d == 0) return false; float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d; float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d; if (ua < 0 || ua > 1) return false; if (ub < 0 || ub > 1) return false; if (intersection != null) intersection.set(x1 + (x2 - x1) * ua, y1 + (y2 - y1) * ua); return true; } static float det(float a, float b, float c, float d) { return a * d - b * c; } static double detd(double a, double b, double c, double d) { return a * d - b * c; } public static boolean overlapCircles(Circle c1, Circle c2) { float x = c1.x - c2.x; float y = c1.y - c2.y; float distance = x * x + y * y; float radiusSum = c1.radius + c2.radius; return distance <= radiusSum * radiusSum; } public static boolean overlapRectangles(Rectangle r1, Rectangle r2) { if (r1.x < r2.x + r2.width && r1.x + r1.width > r2.x && r1.y < r2.y + r2.height && r1.y + r1.height > r2.y) return true; else return false; } public static boolean overlapCircleRectangle(Circle c, Rectangle r) { float closestX = c.x; float closestY = c.y; if (c.x < r.x) { closestX = r.x; } else if (c.x > r.x + r.width) { closestX = r.x + r.width; } if (c.y < r.y) { closestY = r.y; } else if (c.y > r.y + r.height) { closestY = r.y + r.height; } closestX = closestX - c.x; closestX *= closestX; closestY = closestY - c.y; closestY *= closestY; return closestX + closestY < c.radius * c.radius; } /** * Check whether specified convex polygons overlap. * * @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 convex polygons overlap. If they do, optionally obtain a Minimum Translation Vector * indicating the minimum magnitude vector required to push the polygons out of the collision. * * @param p1 * The first polygon. * @param p2 * The second polygon. * @param mtv * A Minimum Translation Vector to fill in the case of a collision (optional). * @return Whether polygons overlap. */ public static boolean overlapConvexPolygons(Polygon p1, Polygon p2, MinimumTranslationVector mtv) { return overlapConvexPolygons(p1.getTransformedVertices(), p2.getTransformedVertices(), mtv); } /** * Check whether polygons defined by the given vertex arrays overlap. If they do, optionally obtain a Minimum * Translation Vector indicating the minimum magnitude vector required to push the polygons out of the collision. * * @param verts1 * Vertices of the first polygon. * @param verts2 * Vertices of the second polygon. * @param mtv * A Minimum Translation Vector to fill in the case of a collision (optional). * @return Whether polygons overlap. */ public static boolean overlapConvexPolygons(float[] verts1, float[] verts2, MinimumTranslationVector mtv) { float overlap = Float.MAX_VALUE; float smallestAxisX = 0; float smallestAxisY = 0; // Get polygon1 axes final int numAxes1 = verts1.length; for (int i = 0; i < numAxes1; i += 2) { float x1 = verts1[i]; float y1 = verts1[i + 1]; float x2 = verts1[(i + 2) % numAxes1]; float y2 = verts1[(i + 3) % numAxes1]; 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 = 2; j < verts1.length; j += 2) { float p = (axisX * verts1[j]) + (axisY * verts1[j + 1]); if (p < min1) { min1 = p; } else if (p > max1) { max1 = p; } } // Project polygon2 onto this axis float min2 = (axisX * verts2[0]) + (axisY * verts2[1]); float max2 = min2; for (int j = 2; j < verts2.length; j += 2) { float p = (axisX * verts2[j]) + (axisY * verts2[j + 1]); if (p < min2) { min2 = p; } else if (p > max2) { max2 = p; } } 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) { axisX = -axisX; axisY = -axisY; o += mins; } else { o += maxs; } } if (o < overlap) { overlap = o; smallestAxisX = axisX; smallestAxisY = axisY; } } // -- End check for separation on this axis --// } // Get polygon2 axes final int numAxes2 = verts2.length; for (int i = 0; i < numAxes2; i += 2) { float x1 = verts2[i]; float y1 = verts2[i + 1]; float x2 = verts2[(i + 2) % numAxes2]; float y2 = verts2[(i + 3) % numAxes2]; 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 = 2; j < verts1.length; j += 2) { float p = (axisX * verts1[j]) + (axisY * verts1[j + 1]); if (p < min1) { min1 = p; } else if (p > max1) { max1 = p; } } // Project polygon2 onto this axis float min2 = (axisX * verts2[0]) + (axisY * verts2[1]); float max2 = min2; for (int j = 2; j < verts2.length; j += 2) { float p = (axisX * verts2[j]) + (axisY * verts2[j + 1]); if (p < min2) { min2 = p; } else if (p > max2) { max2 = p; } } 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) { axisX = -axisX; axisY = -axisY; o += mins; } else { o += maxs; } } if (o < overlap) { overlap = o; smallestAxisX = axisX; smallestAxisY = axisY; } } // -- End check for separation on this axis --// } if (mtv != null) { mtv.normal.set(smallestAxisX, smallestAxisY); mtv.depth = overlap; } return true; } /** * Splits the triangle by the plane. The result is stored in the SplitTriangle instance. Depending on where the * triangle is relative to the plane, the result can be: * * <ul> * <li>Triangle is fully in front/behind: {@link SplitTriangle#front} or {@link SplitTriangle#back} will contain the * original triangle, {@link SplitTriangle#total} will be one.</li> * <li>Triangle has two vertices in front, one behind: {@link SplitTriangle#front} contains 2 triangles, * {@link SplitTriangle#back} contains 1 triangles, {@link SplitTriangle#total} will be 3.</li> * <li>Triangle has one vertex in front, two behind: {@link SplitTriangle#front} contains 1 triangle, * {@link SplitTriangle#back} contains 2 triangles, {@link SplitTriangle#total} will be 3.</li> * </ul> * * The input triangle should have the form: x, y, z, x2, y2, z2, x3, y3, y3. One can add additional attributes per * vertex which will be interpolated if split, such as texture coordinates or normals. Note that these additional * attributes won't be normalized, as might be necessary in case of normals. * * @param triangle * @param plane * @param split * output SplitTriangle */ public static void splitTriangle(float[] triangle, Plane plane, SplitTriangle split) { int stride = triangle.length / 3; boolean r1 = plane.testPoint(triangle[0], triangle[1], triangle[2]) == PlaneSide.Back; boolean r2 = plane.testPoint(triangle[0 + stride], triangle[1 + stride], triangle[2 + stride]) == PlaneSide.Back; boolean r3 = plane.testPoint(triangle[0 + stride * 2], triangle[1 + stride * 2], triangle[2 + stride * 2]) == PlaneSide.Back; split.reset(); // easy case, triangle is on one side (point on plane means front). if ((r1 == r2) && (r2 == r3)) { split.total = 1; if (r1) { split.numBack = 1; System.arraycopy(triangle, 0, split.back, 0, triangle.length); } else { split.numFront = 1; System.arraycopy(triangle, 0, split.front, 0, triangle.length); } return; } // set number of triangles split.total = 3; split.numFront = (r1 ? 1 : 0) + (r2 ? 1 : 0) + (r3 ? 1 : 0); split.numBack = split.total - split.numFront; // hard case, split the three edges on the plane // determine which array to fill first, front or back, flip if we // cross the plane split.setSide(r1); // split first edge int first = 0; int second = stride; if (r1 != r2) { // split the edge splitEdge(triangle, first, second, stride, plane, split.edgeSplit, 0); // add first edge vertex and new vertex to current side split.add(triangle, first, stride); split.add(split.edgeSplit, 0, stride); // flip side and add new vertex and second edge vertex to current side split.setSide(!split.getSide()); split.add(split.edgeSplit, 0, stride); } else { // add both vertices split.add(triangle, first, stride); } // split second edge first = stride; second = stride + stride; if (r2 != r3) { // split the edge splitEdge(triangle, first, second, stride, plane, split.edgeSplit, 0); // add first edge vertex and new vertex to current side split.add(triangle, first, stride); split.add(split.edgeSplit, 0, stride); // flip side and add new vertex and second edge vertex to current side split.setSide(!split.getSide()); split.add(split.edgeSplit, 0, stride); } else { // add both vertices split.add(triangle, first, stride); } // split third edge first = stride + stride; second = 0; if (r3 != r1) { // split the edge splitEdge(triangle, first, second, stride, plane, split.edgeSplit, 0); // add first edge vertex and new vertex to current side split.add(triangle, first, stride); split.add(split.edgeSplit, 0, stride); // flip side and add new vertex and second edge vertex to current side split.setSide(!split.getSide()); split.add(split.edgeSplit, 0, stride); } else { // add both vertices split.add(triangle, first, stride); } // triangulate the side with 2 triangles if (split.numFront == 2) { System.arraycopy(split.front, stride * 2, split.front, stride * 3, stride * 2); System.arraycopy(split.front, 0, split.front, stride * 5, stride); } else { System.arraycopy(split.back, stride * 2, split.back, stride * 3, stride * 2); System.arraycopy(split.back, 0, split.back, stride * 5, stride); } } static Vector3 intersection = new Vector3(); private static void splitEdge(float[] vertices, int s, int e, int stride, Plane plane, float[] split, int offset) { float t = Intersector.intersectLinePlane(vertices[s], vertices[s + 1], vertices[s + 2], vertices[e], vertices[e + 1], vertices[e + 2], plane, intersection); split[offset + 0] = intersection.x; split[offset + 1] = intersection.y; split[offset + 2] = intersection.z; for (int i = 3; i < stride; i++) { float a = vertices[s + i]; float b = vertices[e + i]; split[offset + i] = a + t * (b - a); } } public static void main(String[] args) { Plane plane = new Plane(new Vector3(1, 0, 0), 0); SplitTriangle split = new SplitTriangle(3); float[] fTriangle = { -10, 0, 10, -1, 0, 0, -10, 0, 10 }; Intersector.splitTriangle(fTriangle, plane, split); System.out.println(split); float[] triangle = { -10, 0, 10, 10, 0, 0, -10, 0, -10 }; Intersector.splitTriangle(triangle, plane, split); System.out.println(split); } public static class SplitTriangle { public float[] front; public float[] back; float[] edgeSplit; public int numFront; public int numBack; public int total; boolean frontCurrent = false; int frontOffset = 0; int backOffset = 0; /** * Creates a new instance, assuming numAttributes attributes per triangle vertex. * * @param numAttributes * must be >= 3 */ public SplitTriangle(int numAttributes) { front = new float[numAttributes * 3 * 2]; back = new float[numAttributes * 3 * 2]; edgeSplit = new float[numAttributes]; } @Override public String toString() { return "SplitTriangle [front=" + Arrays.toString(front) + ", back=" + Arrays.toString(back) + ", numFront=" + numFront + ", numBack=" + numBack + ", total=" + total + "]"; } void setSide(boolean front) { frontCurrent = front; } boolean getSide() { return frontCurrent; } void add(float[] vertex, int offset, int stride) { if (frontCurrent) { System.arraycopy(vertex, offset, front, frontOffset, stride); frontOffset += stride; } else { System.arraycopy(vertex, offset, back, backOffset, stride); backOffset += stride; } } void reset() { frontCurrent = false; frontOffset = 0; backOffset = 0; numFront = 0; numBack = 0; total = 0; } } public static class MinimumTranslationVector { public Vector2 normal = new Vector2(); public float depth = 0; } }