org.mini2Dx.gdx.math.Intersector Maven / Gradle / Ivy
/*******************************************************************************
* 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 org.mini2Dx.gdx.math;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.mini2Dx.gdx.math.Plane.PlaneSide;
import org.mini2Dx.gdx.math.collision.BoundingBox;
import org.mini2Dx.gdx.math.collision.Ray;
import org.mini2Dx.gdx.utils.Array;
import org.mini2Dx.gdx.utils.FloatArray;
/** Class offering various static methods for intersection testing between different geometric objects.
*
* @author [email protected]
* @author jan.stria
* @author Nathan Sweet */
public final class Intersector {
private final static Vector3 v0 = new Vector3();
private final static Vector3 v1 = new Vector3();
private final static Vector3 v2 = new Vector3();
private final static FloatArray floatArray = new FloatArray();
private final static FloatArray floatArray2 = new FloatArray();
/** 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;
}
/** Returns true if the given point is inside the triangle. */
public static boolean isPointInTriangle (Vector2 p, Vector2 a, Vector2 b, Vector2 c) {
float px1 = p.x - a.x;
float py1 = p.y - a.y;
boolean side12 = (b.x - a.x) * py1 - (b.y - a.y) * px1 > 0;
if ((c.x - a.x) * py1 - (c.y - a.y) * px1 > 0 == side12) return false;
if ((c.x - b.x) * (p.y - b.y) - (c.y - b.y) * (p.x - b.x) > 0 != side12) return false;
return true;
}
/** Returns true if the given point is inside the triangle. */
public static boolean isPointInTriangle (float px, float py, float ax, float ay, float bx, float by, float cx, float cy) {
float px1 = px - ax;
float py1 = py - ay;
boolean side12 = (bx - ax) * py1 - (by - ay) * px1 > 0;
if ((cx - ax) * py1 - (cy - ay) * px1 > 0 == side12) return false;
if ((cx - bx) * (py - by) - (cy - by) * (px - bx) > 0 != side12) return false;
return true;
}
public static boolean intersectSegmentPlane (Vector3 start, Vector3 end, Plane plane, Vector3 intersection) {
Vector3 dir = v0.set(end).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.scl(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 passed as an array
* @param point The point
* @return true if the point is in the polygon */
public static boolean isPointInPolygon (Array polygon, Vector2 point) {
Vector2 lastVertice = polygon.peek();
boolean oddNodes = false;
for (int i = 0; i < polygon.size; i++) {
Vector2 vertice = polygon.get(i);
if ((vertice.y < point.y && lastVertice.y >= point.y) || (lastVertice.y < point.y && vertice.y >= point.y)) {
if (vertice.x + (point.y - vertice.y) / (lastVertice.y - vertice.y) * (lastVertice.x - vertice.x) < point.x) {
oddNodes = !oddNodes;
}
}
lastVertice = vertice;
}
return oddNodes;
}
/** Returns true if the specified point is in the polygon.
* @param offset Starting polygon index.
* @param count Number of array indices to use after offset. */
public static boolean isPointInPolygon (float[] polygon, int offset, int count, float x, float y) {
boolean oddNodes = false;
int j = offset + count - 2;
for (int i = offset, n = j; i <= n; i += 2) {
float yi = polygon[i + 1];
float yj = polygon[j + 1];
if ((yi < y && yj >= y) || (yj < y && yi >= y)) {
float xi = polygon[i];
if (xi + (y - yi) / (yj - yi) * (polygon[j] - xi) < x) oddNodes = !oddNodes;
}
j = i;
}
return oddNodes;
}
private final static Vector2 ip = new Vector2();
private final static Vector2 ep1 = new Vector2();
private final static Vector2 ep2 = new Vector2();
private final static Vector2 s = new Vector2();
private final static Vector2 e = new Vector2();
/** Intersects two resulting polygons with the same winding and sets the overlap polygon resulting from the intersection.
* Follows the Sutherland-Hodgman algorithm.
*
* @param p1 The polygon that is being clipped
* @param p2 The clip polygon
* @param overlap The intersection of the two polygons (optional)
* @return Whether the two polygons intersect. */
public static boolean intersectPolygons (Polygon p1, Polygon p2, Polygon overlap) {
// reusable points to trace edges around polygon
floatArray2.clear();
floatArray.clear();
floatArray2.addAll(p1.getTransformedVertices());
if (p1.getVertices().length == 0 || p2.getVertices().length == 0) {
return false;
}
for (int i = 0; i < p2.getTransformedVertices().length; i += 2) {
ep1.set(p2.getTransformedVertices()[i], p2.getTransformedVertices()[i + 1]);
// wrap around to beginning of array if index points to end;
if (i < p2.getTransformedVertices().length - 2) {
ep2.set(p2.getTransformedVertices()[i + 2], p2.getTransformedVertices()[i + 3]);
} else {
ep2.set(p2.getTransformedVertices()[0], p2.getTransformedVertices()[1]);
}
if (floatArray2.size == 0) {
return false;
}
s.set(floatArray2.get(floatArray2.size - 2), floatArray2.get(floatArray2.size - 1));
for (int j = 0; j < floatArray2.size; j += 2) {
e.set(floatArray2.get(j), floatArray2.get(j + 1));
// determine if point is inside clip edge
if (Intersector.pointLineSide(ep2, ep1, e) > 0) {
if (!(Intersector.pointLineSide(ep2, ep1, s) > 0)) {
Intersector.intersectLines(s, e, ep1, ep2, ip);
if (floatArray.size < 2 || floatArray.get(floatArray.size - 2) != ip.x
|| floatArray.get(floatArray.size - 1) != ip.y) {
floatArray.add(ip.x);
floatArray.add(ip.y);
}
}
floatArray.add(e.x);
floatArray.add(e.y);
} else if (Intersector.pointLineSide(ep2, ep1, s) > 0) {
Intersector.intersectLines(s, e, ep1, ep2, ip);
floatArray.add(ip.x);
floatArray.add(ip.y);
}
s.set(e.x, e.y);
}
floatArray2.clear();
floatArray2.addAll(floatArray);
floatArray.clear();
}
if (!(floatArray2.size == 0)) {
overlap.setVertices(floatArray2.toArray());
return true;
} else {
return false;
}
}
/** 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 the distance between the given segment and point. */
public static float distanceSegmentPoint (float startX, float startY, float endX, float endY, float pointX, float pointY) {
return nearestSegmentPoint(startX, startY, endX, endY, pointX, pointY, v2tmp).dst(pointX, pointY);
}
/** Returns the distance between the given segment and point. */
public static float distanceSegmentPoint (Vector2 start, Vector2 end, Vector2 point) {
return nearestSegmentPoint(start, end, point, v2tmp).dst(point);
}
/** Returns a point on the segment nearest to the specified point. */
public static Vector2 nearestSegmentPoint (Vector2 start, Vector2 end, Vector2 point, Vector2 nearest) {
float length2 = start.dst2(end);
if (length2 == 0) return nearest.set(start);
float t = ((point.x - start.x) * (end.x - start.x) + (point.y - start.y) * (end.y - start.y)) / length2;
if (t < 0) return nearest.set(start);
if (t > 1) return nearest.set(end);
return nearest.set(start.x + t * (end.x - start.x), start.y + t * (end.y - start.y));
}
/** Returns a point on the segment nearest to the specified point. */
public static Vector2 nearestSegmentPoint (float startX, float startY, float endX, float endY, float pointX, float pointY,
Vector2 nearest) {
final float xDiff = endX - startX;
final float yDiff = endY - startY;
float length2 = xDiff * xDiff + yDiff * yDiff;
if (length2 == 0) return nearest.set(startX, startY);
float t = ((pointX - startX) * (endX - startX) + (pointY - startY) * (endY - startY)) / length2;
if (t < 0) return nearest.set(startX, startY);
if (t > 1) return nearest.set(endX, endY);
return nearest.set(startX + t * (endX - startX), startY + t * (endY - startY));
}
/** 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.scl(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.scl(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;
}
/** Intersect two 2D Rays and return the scalar parameter of the first ray at the intersection point. You can get the
* intersection point by: Vector2 point(direction1).scl(scalar).add(start1); For more information, check:
* http://stackoverflow.com/a/565282/1091440
* @param start1 Where the first ray start
* @param direction1 The direction the first ray is pointing
* @param start2 Where the second ray start
* @param direction2 The direction the second ray is pointing
* @return scalar parameter on the first ray describing the point where the intersection happens. May be negative. In case the
* rays are collinear, Float.POSITIVE_INFINITY will be returned. */
public static float intersectRayRay (Vector2 start1, Vector2 direction1, Vector2 start2, Vector2 direction2) {
float difx = start2.x - start1.x;
float dify = start2.y - start1.y;
float d1xd2 = direction1.x * direction2.y - direction1.y * direction2.x;
if (d1xd2 == 0.0f) {
return Float.POSITIVE_INFINITY; // collinear
}
float d2sx = direction2.x / d1xd2;
float d2sy = direction2.y / d1xd2;
return difx * d2sy - dify * d2sx;
}
/** 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(v0.set(ray.direction).scl(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 (intersection != null) intersection.set(origin).add(direction.scl(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) {
Vector3 edge1 = v0.set(t2).sub(t1);
Vector3 edge2 = v1.set(t3).sub(t1);
Vector3 pvec = v2.set(ray.direction).crs(edge2);
float det = edge1.dot(pvec);
if (MathUtils.isZero(det)) {
p.set(t1, t2, t3);
if (p.testPoint(ray.origin) == PlaneSide.OnPlane && Intersector.isPointInTriangle(ray.origin, t1, t2, t3)) {
if (intersection != null) intersection.set(ray.origin);
return true;
}
return false;
}
det = 1.0f / det;
Vector3 tvec = i.set(ray.origin).sub(t1);
float u = tvec.dot(pvec) * det;
if (u < 0.0f || u > 1.0f) return false;
Vector3 qvec = tvec.crs(edge1);
float v = ray.direction.dot(qvec) * det;
if (v < 0.0f || u + v > 1.0f) return false;
float t = edge2.dot(qvec) * det;
if (t < 0) return false;
if (intersection != null) {
if (t <= MathUtils.FLOAT_ROUNDING_ERROR) {
intersection.set(ray.origin);
} else {
ray.getEndPoint(intersection, t);
}
}
return true;
}
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, the direction component must be normalized before calling this method
* @param center The center of the sphere
* @param radius The radius of the sphere
* @param intersection The intersection point (optional, can be null)
* @return Whether an intersection is present. */
public static boolean intersectRaySphere (Ray ray, Vector3 center, float radius, Vector3 intersection) {
final float len = ray.direction.dot(center.x - ray.origin.x, center.y - ray.origin.y, center.z - ray.origin.z);
if (len < 0.f) // behind the ray
return false;
final float dst2 = center.dst2(ray.origin.x + ray.direction.x * len, ray.origin.y + ray.direction.y * len,
ray.origin.z + ray.direction.z * len);
final float r2 = radius * radius;
if (dst2 > r2) return false;
if (intersection != null) intersection.set(ray.direction).scl(len - (float)Math.sqrt(r2 - dst2)).add(ray.origin);
return true;
}
/** Intersects a {@link Ray} and a {@link BoundingBox}, returning the intersection point in intersection. This intersection is
* defined as the point on the ray closest to the origin which is within the specified bounds.
*
*
* The returned intersection (if any) is guaranteed to be within the bounds of the bounding box, but it can occasionally
* diverge slightly from ray, due to small floating-point errors.
*
*
*
* If the origin of the ray is inside the box, this method returns true and the intersection point is set to the origin of the
* ray, accordingly to the definition above.
*
*
* @param ray The ray
* @param box The box
* @param intersection The intersection point (optional)
* @return Whether an intersection is present. */
public static boolean intersectRayBounds (Ray ray, BoundingBox box, Vector3 intersection) {
if (box.contains(ray.origin)) {
if (intersection != null) intersection.set(ray.origin);
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) {
v2.set(ray.direction).scl(t).add(ray.origin);
if (v2.y >= box.min.y && v2.y <= box.max.y && v2.z >= box.min.z && v2.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) {
v2.set(ray.direction).scl(t).add(ray.origin);
if (v2.y >= box.min.y && v2.y <= box.max.y && v2.z >= box.min.z && v2.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) {
v2.set(ray.direction).scl(t).add(ray.origin);
if (v2.x >= box.min.x && v2.x <= box.max.x && v2.z >= box.min.z && v2.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) {
v2.set(ray.direction).scl(t).add(ray.origin);
if (v2.x >= box.min.x && v2.x <= box.max.x && v2.z >= box.min.z && v2.z <= box.max.z && (!hit || t < lowest)) {
hit = true;
lowest = t;
}
}
}
// min z
if (ray.origin.z <= box.min.z && ray.direction.z > 0) {
t = (box.min.z - ray.origin.z) / ray.direction.z;
if (t >= 0) {
v2.set(ray.direction).scl(t).add(ray.origin);
if (v2.x >= box.min.x && v2.x <= box.max.x && v2.y >= box.min.y && v2.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) {
v2.set(ray.direction).scl(t).add(ray.origin);
if (v2.x >= box.min.x && v2.x <= box.max.x && v2.y >= box.min.y && v2.y <= box.max.y && (!hit || t < lowest)) {
hit = true;
lowest = t;
}
}
}
if (hit && intersection != null) {
intersection.set(ray.direction).scl(lowest).add(ray.origin);
if (intersection.x < box.min.x) {
intersection.x = box.min.x;
} else if (intersection.x > box.max.x) {
intersection.x = box.max.x;
}
if (intersection.y < box.min.y) {
intersection.y = box.min.y;
} else if (intersection.y > box.max.y) {
intersection.y = box.max.y;
}
if (intersection.z < box.min.z) {
intersection.z = box.min.z;
} else if (intersection.z > box.max.z) {
intersection.z = box.max.z;
}
}
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) {
return intersectRayBoundsFast(ray, box.getCenter(tmp1), box.getDimensions(tmp2));
}
/** Quick check whether the given {@link Ray} and {@link BoundingBox} intersect.
*
* @param ray The ray
* @param center The center of the bounding box
* @param dimensions The dimensions (width, height and depth) of the bounding box
* @return Whether the ray and the bounding box intersect. */
static public boolean intersectRayBoundsFast (Ray ray, Vector3 center, Vector3 dimensions) {
final float divX = 1f / ray.direction.x;
final float divY = 1f / ray.direction.y;
final float divZ = 1f / ray.direction.z;
float minx = ((center.x - dimensions.x * .5f) - ray.origin.x) * divX;
float maxx = ((center.x + dimensions.x * .5f) - ray.origin.x) * divX;
if (minx > maxx) {
final float t = minx;
minx = maxx;
maxx = t;
}
float miny = ((center.y - dimensions.y * .5f) - ray.origin.y) * divY;
float maxy = ((center.y + dimensions.y * .5f) - ray.origin.y) * divY;
if (miny > maxy) {
final float t = miny;
miny = maxy;
maxy = t;
}
float minz = ((center.z - dimensions.z * .5f) - ray.origin.z) * divZ;
float maxz = ((center.z + dimensions.z * .5f) - ray.origin.z) * divZ;
if (minz > maxz) {
final float t = minz;
minz = maxz;
maxz = t;
}
float min = Math.max(Math.max(minx, miny), minz);
float max = Math.min(Math.min(maxx, maxy), maxz);
return max >= 0 && max >= min;
}
static Vector3 best = new Vector3();
static Vector3 tmp = new Vector3();
static Vector3 tmp1 = new Vector3();
static Vector3 tmp2 = new Vector3();
static Vector3 tmp3 = new Vector3();
static Vector2 v2tmp = new Vector2();
/** 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.dst2(tmp);
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.dst2(tmp);
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 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.dst2(tmp);
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;
}
}
/** 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. May be null.
* @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 d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (d == 0) return false;
if (intersection != null) {
float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d;
intersection.set(x1 + (x2 - x1) * ua, y1 + (y2 - y1) * ua);
}
return true;
}
/** Intersects the two lines and returns the intersection point in intersection.
* @param intersection The intersection point, or null.
* @return Whether the two lines intersect */
public static boolean intersectLines (float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4,
Vector2 intersection) {
float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (d == 0) return false;
if (intersection != null) {
float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d;
intersection.set(x1 + (x2 - x1) * ua, y1 + (y2 - y1) * ua);
}
return true;
}
/** Check whether the given line and {@link Polygon} intersect.
* @param p1 The first point of the line
* @param p2 The second point of the line
* @param polygon The polygon
* @return Whether polygon and line intersects */
public static boolean intersectLinePolygon (Vector2 p1, Vector2 p2, Polygon polygon) {
float[] vertices = polygon.getTransformedVertices();
float x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y;
int n = vertices.length;
float x3 = vertices[n - 2], y3 = vertices[n - 1];
for (int i = 0; i < n; i += 2) {
float x4 = vertices[i], y4 = vertices[i + 1];
float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (d != 0) {
float yd = y1 - y3;
float xd = x1 - x3;
float ua = ((x4 - x3) * yd - (y4 - y3) * xd) / d;
if (ua >= 0 && ua <= 1) {
return true;
}
}
x3 = x4;
y3 = y4;
}
return false;
}
/** Determines whether the given rectangles intersect and, if they do, sets the supplied {@code intersection} rectangle to the
* area of overlap.
*
* @return Whether the rectangles intersect */
static public boolean intersectRectangles (Rectangle rectangle1, Rectangle rectangle2, Rectangle intersection) {
if (rectangle1.overlaps(rectangle2)) {
intersection.x = Math.max(rectangle1.x, rectangle2.x);
intersection.width = Math.min(rectangle1.x + rectangle1.width, rectangle2.x + rectangle2.width) - intersection.x;
intersection.y = Math.max(rectangle1.y, rectangle2.y);
intersection.height = Math.min(rectangle1.y + rectangle1.height, rectangle2.y + rectangle2.height) - intersection.y;
return true;
}
return false;
}
/** Check whether the given line segment and {@link Polygon} intersect.
* @param p1 The first point of the segment
* @param p2 The second point of the segment
* @return Whether polygon and segment intersect */
public static boolean intersectSegmentPolygon (Vector2 p1, Vector2 p2, Polygon polygon) {
float[] vertices = polygon.getTransformedVertices();
float x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y;
int n = vertices.length;
float x3 = vertices[n - 2], y3 = vertices[n - 1];
for (int i = 0; i < n; i += 2) {
float x4 = vertices[i], y4 = vertices[i + 1];
float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (d != 0) {
float yd = y1 - y3;
float xd = x1 - x3;
float ua = ((x4 - x3) * yd - (y4 - y3) * xd) / d;
if (ua >= 0 && ua <= 1) {
float ub = ((x2 - x1) * yd - (y2 - y1) * xd) / d;
if (ub >= 0 && ub <= 1) {
return true;
}
}
}
x3 = x4;
y3 = y4;
}
return false;
}
/** 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. May be null.
* @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 yd = y1 - y3;
float xd = x1 - x3;
float ua = ((x4 - x3) * yd - (y4 - y3) * xd) / d;
if (ua < 0 || ua > 1) return false;
float ub = ((x2 - x1) * yd - (y2 - y1) * xd) / d;
if (ub < 0 || ub > 1) return false;
if (intersection != null) intersection.set(x1 + (x2 - x1) * ua, y1 + (y2 - y1) * ua);
return true;
}
/** @param intersection May be null. */
public static boolean intersectSegments (float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4,
Vector2 intersection) {
float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (d == 0) return false;
float yd = y1 - y3;
float xd = x1 - x3;
float ua = ((x4 - x3) * yd - (y4 - y3) * xd) / d;
if (ua < 0 || ua > 1) return false;
float ub = ((x2 - x1) * yd - (y2 - y1) * xd) / d;
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 overlaps (Circle c1, Circle c2) {
return c1.overlaps(c2);
}
public static boolean overlaps (Rectangle r1, Rectangle r2) {
return r1.overlaps(r2);
}
public static boolean overlaps (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 counter-clockwise wound 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 counter-clockwise wound convex polygons overlap. 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.
* @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 (Polygon p1, Polygon p2, MinimumTranslationVector mtv) {
return overlapConvexPolygons(p1.getTransformedVertices(), p2.getTransformedVertices(), mtv);
}
/** @see #overlapConvexPolygons(float[], int, int, float[], int, int, MinimumTranslationVector) */
public static boolean overlapConvexPolygons (float[] verts1, float[] verts2, MinimumTranslationVector mtv) {
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
* 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.
* @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;
int end1 = offset1 + count1;
int end2 = offset2 + count2;
// 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];
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;
}
}
// 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;
}
}
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;
}
}
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;
}
}
// 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 (!(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;
}
}
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;
}
/** 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:
*
*
* - Triangle is fully in front/behind: {@link SplitTriangle#front} or {@link SplitTriangle#back} will contain the original
* triangle, {@link SplitTriangle#total} will be one.
* - 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.
* - 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.
*
*
* The input triangle should have the form: x, y, z, x2, y2, z2, x3, y3, z3. 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 ? 0 : 1) + (r2 ? 0 : 1) + (r3 ? 0 : 1);
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 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;
}
}
/** Minimum translation required to separate two polygons. */
public static class MinimumTranslationVector {
/** Unit length vector that indicates the direction for the separation */
public Vector2 normal = new Vector2();
/** Distance of the translation required for the separation */
public float depth = 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy