/******************************************************************************
* Copyright (C) Ultraleap, Inc. 2011-2021. *
* *
* Use subject to the terms of the Apache License 2.0 available at *
* http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
* between Ultraleap and you, your company or other organization. *
******************************************************************************/
using Leap.Unity.Infix;
using Leap.Unity.Swizzle;
using System;
using UnityEngine;
namespace Leap.Unity.Geometry
{
public static class Collision
{
#region Short-hand
private static float Dot(Vector3 a, Vector3 b)
{
return Vector3.Dot(a, b);
}
private static float Dot(Vector2 a, Vector2 b)
{
return Vector2.Dot(a, b);
}
#endregion
#region Box-Sphere
public static bool DoesOverlap(Box box, Sphere sphere)
{
return DoesOverlap(sphere, box);
}
public static bool DoesOverlap(Sphere sphere, Box box)
{
var sphereCenter_world = sphere.matrix.MultiplyPoint3x4(Vector3.zero);
var sphereCenter_box = box.matrix.inverse
.MultiplyPoint3x4(sphereCenter_world).Abs();
float x = sphereCenter_box.x,
y = sphereCenter_box.y,
z = sphereCenter_box.z;
float bx = box.radii.x.Abs(),
by = box.radii.y.Abs(),
bz = box.radii.z.Abs();
float dx = x - Mathf.Min(bx, x),
dy = y - Mathf.Min(by, y),
dz = z - Mathf.Min(bz, z);
if (dx == 0 && dy == 0 && dz == 0) return true;
var boxSurfaceToSphereCenter_box = new Vector3(dx, dy, dz);
var boxSurfaceToSphereCenter_world
= box.matrix.MultiplyVector(boxSurfaceToSphereCenter_box);
var boxSurfaceToSphereCenter_sphere
= sphere.matrix.inverse.MultiplyVector(boxSurfaceToSphereCenter_world);
return boxSurfaceToSphereCenter_sphere.sqrMagnitude < sphere.radius * sphere.radius;
}
#endregion
#region Rect-Sphere
///
/// Returns the distance between the closest points on the Rect and the Sphere,
/// or 0 if the two overlap.
///
public static float DistanceTo(Rect rect, Sphere sphere)
{
return DistanceBetween(sphere, rect);
}
///
/// Returns the distance between the closest points on the Rect and the Sphere,
/// or 0 if the two overlap.
///
public static float DistanceBetween(Sphere sphere, Rect rect)
{
var sphereCenter_world = sphere.matrix.MultiplyPoint3x4(Vector3.zero);
var sphereCenter_rect = rect.matrix.inverse
.MultiplyPoint3x4(sphereCenter_world).Abs();
float x = sphereCenter_rect.x,
y = sphereCenter_rect.y,
z = sphereCenter_rect.z;
float rx = rect.radii.x.Abs(),
ry = rect.radii.y.Abs(),
rz = 0f;
float dx = x - Mathf.Min(rx, x),
dy = y - Mathf.Min(ry, y),
dz = z - Mathf.Min(rz, z);
if (dx == 0 && dy == 0 && dz == 0) return 0f;
var rectSurfaceToSphereCenter_rect = new Vector3(dx, dy, dz);
var rectSurfaceToSphereCenter_world
= rect.matrix.MultiplyVector(rectSurfaceToSphereCenter_rect);
return Mathf.Max(0f, rectSurfaceToSphereCenter_world.magnitude - sphere.radius);
}
public static bool DoesOverlap(Rect rect, Sphere sphere)
{
return DoesOverlap(sphere, rect);
}
public static bool DoesOverlap(Sphere sphere, Rect rect)
{
var sphereCenter_world = sphere.matrix.MultiplyPoint3x4(Vector3.zero);
var sphereCenter_rect = rect.matrix.inverse
.MultiplyPoint3x4(sphereCenter_world).Abs();
float x = sphereCenter_rect.x,
y = sphereCenter_rect.y,
z = sphereCenter_rect.z;
float rx = rect.radii.x.Abs(),
ry = rect.radii.y.Abs(),
rz = 0f;
float dx = x - Mathf.Min(rx, x),
dy = y - Mathf.Min(ry, y),
dz = z - Mathf.Min(rz, z);
if (dx == 0 && dy == 0 && dz == 0) return true;
var rectSurfaceToSphereCenter_rect = new Vector3(dx, dy, dz);
var rectSurfaceToSphereCenter_world
= rect.matrix.MultiplyVector(rectSurfaceToSphereCenter_rect);
var rectSurfaceToSphereCenter_sphere
= sphere.matrix.inverse.MultiplyVector(rectSurfaceToSphereCenter_world);
return rectSurfaceToSphereCenter_sphere.sqrMagnitude < sphere.radius * sphere.radius;
}
#endregion
#region Rect-Segment3
//[ThreadStatic]
//private static LocalSegment3[] _rectEdgesBuffer = new LocalSegment3[4];
public static float Intersect(LocalSegment3 segment, Rect rect)
{
return Intersect(rect, segment);
}
public static float Intersect(Rect rect, LocalSegment3 segment)
{
Vector3 closestPointOnSegment_unused, closestPointOnRect_unused;
return Intersect(rect, segment,
out closestPointOnRect_unused, out closestPointOnSegment_unused);
}
public static float Intersect(LocalSegment3 segment, Rect rect,
out Vector3 closestPointOnSegment,
out Vector3 closestPointOnRect)
{
return Intersect(rect, segment, out closestPointOnRect, out closestPointOnSegment);
}
public static float Intersect(Rect rect, LocalSegment3 segment,
out Vector3 closestPointOnRect,
out Vector3 closestPointOnSegment)
{
float closestSqrDist = float.PositiveInfinity;
closestPointOnRect = closestPointOnSegment = default(Vector3);
Vector3 segmentA_rect;
bool aProjectsInside = rect.ContainsProjectedPoint(segment.a, out segmentA_rect);
Vector3 segmentB_rect;
bool bProjectsInside = rect.ContainsProjectedPoint(segment.b, out segmentB_rect);
// Test if the segment intersects with the rect plane and is inside the rect.
var plane_rect = rect.ToLocalPlane();
var segment_rect = new LocalSegment3(segmentA_rect, segmentB_rect);
Vector3 pointOnPlane_rect; float amountAlongSegment; bool isLineCoplanar;
var intersectsPlane = Intersect(plane_rect, segment_rect,
out pointOnPlane_rect, out amountAlongSegment,
out isLineCoplanar);
if (intersectsPlane)
{
var absPointOnPlane_rect = pointOnPlane_rect.Abs();
var absRadii = rect.radii.Abs();
if (absPointOnPlane_rect.x <= absRadii.x && absPointOnPlane_rect.y <= absRadii.y)
{
closestPointOnRect = rect.matrix.MultiplyPoint3x4(pointOnPlane_rect);
closestPointOnSegment = segment.Evaluate(amountAlongSegment);
return 0f;
}
}
// Must test the rect segments and plane-projection distances to find the closest
// pair of points and return their squared-distance.
// Segment point A <> plane, when A projects inside rect.
if (aProjectsInside)
{
var testPointRect = rect.matrix.MultiplyPoint3x4(segmentA_rect.WithZ(0f));
var testSqrDist = (testPointRect - segment.a).sqrMagnitude;
if (testSqrDist < closestSqrDist)
{
closestSqrDist = testSqrDist;
closestPointOnRect = testPointRect;
closestPointOnSegment = segment.a;
}
}
// Segment point B <> plane, when B projects inside rect.
if (bProjectsInside)
{
var testPointRect = rect.matrix.MultiplyPoint3x4(segmentB_rect.WithZ(0f));
var testSqrDist = (testPointRect - segment.b).sqrMagnitude;
if (testSqrDist < closestSqrDist)
{
closestSqrDist = testSqrDist;
closestPointOnRect = testPointRect;
closestPointOnSegment = segment.b;
}
}
if (!aProjectsInside || !bProjectsInside)
{
// Closest point segment <> {AB, BC, CD, DA}.
foreach (var rectSegment in rect.segments)
{
Vector3 testClosestPointRectSegment;
Vector3 testClosestPointSegment;
float unusedT1, unusedT2;
var testSqrDist = Intersect(rectSegment, segment,
out unusedT1, out unusedT2,
out testClosestPointRectSegment,
out testClosestPointSegment);
// Debug
//RuntimeGizmos.RuntimeGizmoDrawer drawer;
//if (RuntimeGizmos.RuntimeGizmoManager.TryGetGizmoDrawer(out drawer)) {
// drawer.DrawLine(testClosestPointRectSegment, testClosestPointSegment);
//}
if (testSqrDist < closestSqrDist)
{
closestSqrDist = testSqrDist;
closestPointOnRect = testClosestPointRectSegment;
closestPointOnSegment = testClosestPointSegment;
}
}
}
return closestSqrDist;
}
#endregion
#region Plane-Segment3
// https://stackoverflow.com/a/18543221 for line-plane intersection.
///
/// Returns true if the line segment intersects with the plane, or false otherwise.
///
/// Specify output parameters to receive more detailed information about the
/// intersection, such as the point at which it occurs and whether the line lies on
/// the plane.
///
public static bool Intersect(LocalPlane plane, LocalSegment3 line)
{
Vector3 pointOnPlane_unused;
float amountAlongSegment_unused;
bool isLineCoplanar_unused;
return Intersect(plane, line,
out pointOnPlane_unused,
out amountAlongSegment_unused,
out isLineCoplanar_unused);
}
///
/// Returns true if the line segment intersects with the plane, or false otherwise.
///
/// If intersectInfiniteLine is specified, returns true if the line defined by the
/// line segment intersects with the plane, or false otherwise.
///
/// If this method returns true, no out parameters are set to NaN, and reasonable
/// defaults are chosen in edge-cases (such as a coplanar line). If this method
/// returns false, some or all output parameters may have no reasonable value and
/// are set to NaN.
///
/// pointOnPlane is the point along the line defined by the segment that intersects
/// with the plane. If the line segment is parallel to the plane and _on_ the plane,
/// pointOnPlane will be set to the center of the line segment as a convenience.
/// Otherwise, it will be set to a Vector3 containing all NaNs.
///
/// amountAlongSegment is the normalized amount from A to B along the line segment
/// that intersects with the plane. The line segment intersects with the plane only
/// if this value is between 0 and 1 (inclusive). If the line segment is parallel to
/// the plane and _on_ the plane, amountAlongSegment will be set to 0.5f. Otherwise,
/// amountAlongSegment will be set to NaN.
///
/// isLineCoplanar is true if the line defined by the line segment is wholly on
/// the plane, or false otherwise.
///
public static bool Intersect(LocalPlane plane, LocalSegment3 line,
out Vector3 pointOnPlane,
out float amountAlongSegment,
out bool isLineCoplanar,
bool intersectInfiniteLine = false)
{
var planePosition = plane.position;
var planeNormal = plane.normal;
var a = line.a;
var b = line.b;
var u = b - a;
var uDotN = planeNormal.Dot(u);
if (uDotN.Abs() > float.Epsilon)
{
isLineCoplanar = false;
var w = a - planePosition;
amountAlongSegment = -(planeNormal.Dot(w)) / uDotN;
pointOnPlane = a + u * amountAlongSegment;
return intersectInfiniteLine
|| (amountAlongSegment >= 0f && amountAlongSegment <= 1f);
}
else
{
var pa = a - planePosition;
if (planeNormal.Dot(pa).Abs() < float.Epsilon)
{
isLineCoplanar = true;
}
else
{
isLineCoplanar = false;
}
if (isLineCoplanar)
{
pointOnPlane = (a + b) / 2f;
amountAlongSegment = 0.5f;
return true;
}
else
{
pointOnPlane = new Vector3(float.NaN, float.NaN, float.NaN);
amountAlongSegment = float.NaN;
return false;
}
}
}
#endregion
#region Segment-Segment
///
/// Returns 2 times the signed triangle area. The result is positive if abc is
/// counter-clockwise, negative if abc is clockwise, and zero if abc is degenerate.
///
private static float Signed2DTriArea(Vector2 a, Vector2 b, Vector2 c)
{
return (a.x - c.x) * (b.y - c.y)
- (a.y - c.y) * (b.x - c.x);
}
///
/// Returns whether seg1 and seg2 overlap. If they do, computes and outputs the
/// intersection t value along seg1 and outputs the intersection point p. Otherwise,
/// outputs default values.
///
public static bool Intersect(LocalSegment2 seg1, LocalSegment2 seg2,
out float t, out Vector2 p)
{
Vector2 a = seg1.a, b = seg1.b, c = seg2.a, d = seg2.b;
// Sign of areas correspond to which side of ab the points c and d are.
float a1 = Signed2DTriArea(a, b, d); // Compute winding of abd (+ or -).
float a2 = Signed2DTriArea(a, b, c); // To intersect, must have opposite sign.
// If c and d are on different sides of ab, areas have different signs.
// Also verify that a1 and a2 are non-zero (areas are zero for degenerate triangles).
if (a1 * a2 < 0f && a1 != 0f && a2 != 0f)
{
// Compute signs for a and b with respect to cd.
float a3 = Signed2DTriArea(c, d, a); // Winding of cda (+ or -).
// Since area is constant, a1 - a2 = a3 - a4, or a4 = a3 + a2 - a1
// float a4 = Signed2DTriArea(c, d, b);
float a4 = a3 + a2 - a1;
// The points a and b are on different sides of cd if areas have different signs.
if (a3 * a4 < 0f)
{
// The segments intersect. Find intersection point along L(t) = a + t * (b - a).
// Given the height h1 of a over cd and height h2 of b over cd,
// t = h1 / (h1 - h2) = (b*h1/2) / (b*h1/2 - b*h2/2) = a3 / (a3 - a4),
// where b (the base of the triangles cda and cdb, i.e., the length of cd)
// cancels out.
t = a3 / (a3 - a4);
p = a + t * (b - a);
return true;
}
}
// Segments don't intersect, or are collinear.
t = default(float); p = default(Vector2);
return false;
}
///
/// Computes the squared distance between two 3D line segments, outputting the amount
/// along each segment (0 to 1) corresponding to the closest points (t1 and t2), and
/// outputting the closest points themselves (c1 and c2).
///
public static float Intersect(LocalSegment3 seg1, LocalSegment3 seg2,
out float t1, out float t2,
out Vector3 c1, out Vector3 c2)
{
Vector3 d1 = seg1.b - seg1.a; // Direction vector of seg1.
Vector3 d2 = seg2.b - seg2.a; // Direction vector of seg2.
Vector3 r = seg1.a - seg2.a;
float a = Dot(d1, d1); // Squared length of seg1.
float e = Dot(d2, d2); // Squared length of seg2.
float f = Dot(d2, r);
// Check if either or both segments degenerate into points.
if (a <= float.Epsilon && e <= float.Epsilon)
{
// Both segments degenerate into points.
t1 = t2 = 0f;
c1 = seg1.a; c2 = seg2.a;
return Dot(c1 - c2, c1 - c2);
}
if (a <= float.Epsilon)
{
// First segment degenerates into a point.
t1 = 0f;
t2 = (f / e).Clamped01();
}
else
{
float c = Dot(d1, r);
if (e <= float.Epsilon)
{
// Second segment degenerates into a point.
t2 = 0f;
t1 = (-c / a).Clamped01();
}
else
{
// General, non-degenerate case.
float b = Dot(d1, d2);
float denom = a * e - b * b; // Always non-negative.
// If the segments are not parallel, compute the closest point on seg1 to seg2
// and clamp to seg1. Otherwise, pick an arbitrary t1 (here 0).
if (denom != 0f)
{
t1 = ((b * f - c * e) / denom).Clamped01();
}
else
{
t1 = 0f;
}
// Compute the point on seg2 closest to t1.
float t2nom = b * t1 + f; // avoid dividing e until later.
// If t2 in [0, 1], we're done. Otherwise, recompute t1 for the new value of
// t2 and clamp to [0, 1].
if (t2nom < 0f)
{
t2 = 0f;
t1 = (-c / a).Clamped01();
}
else if (t2nom > e)
{
t2 = 1f;
t1 = ((b - c) / a).Clamped01();
}
else
{
t2 = t2nom / e;
}
}
}
c1 = seg1.a + d1 * t1;
c2 = seg2.a + d2 * t2;
return (c1 - c2).sqrMagnitude;
}
#region ARCHIVE
// 2D intersection:
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
// https://ideone.com/PnPJgb
///
/// Returns true if the two segments are not parallel or colinear and intersect in 2D,
/// ignoring the segments' Z components.
///
public static bool Intersect2D(LocalSegment3 segment0, LocalSegment3 segment1,
out Vector2 intersectionPoint,
out float amountAlongSegment0,
out float amountAlongSegment1)
{
Vector2 p = segment0.a, q = segment0.b, r = segment1.a, s = segment1.b;
// t = (q − p) × s / (r × s)
// u = (q − p) × r / (r × s)
var denom = Fake2DCross(r, s);
if (denom == 0)
{
// Lines are collinear or parallel.
amountAlongSegment0 = float.NaN;
amountAlongSegment1 = float.NaN;
intersectionPoint = new Vector2(float.NaN, float.NaN);
return false;
}
var tNumer = Fake2DCross(q - p, s);
var uNumer = Fake2DCross(q - p, r);
amountAlongSegment0 = tNumer / denom;
amountAlongSegment1 = uNumer / denom;
if (amountAlongSegment0 < 0 || amountAlongSegment0 > 1
|| amountAlongSegment1 < 0 || amountAlongSegment1 > 1)
{
// Line segments do not intersect within their ranges.
intersectionPoint = default(Vector2);
return false;
}
intersectionPoint = p + r * amountAlongSegment0;
return true;
}
private static float Fake2DCross(Vector2 a, Vector2 b)
{
return a.x * b.y - a.y * b.x;
}
#endregion
#endregion
#region Point-Segment
///
/// Returns the squared-distance of the point p from the segment.
///
public static float SqrDistPointSegment(LocalSegment3 segment, Vector3 p)
{
return SqrDistPointSegment(segment.a, segment.b, p);
}
///
/// Returns the squared-distance of the point c from the segment defined by a, b.
///
public static float SqrDistPointSegment(Vector3 a, Vector3 b, Vector3 c)
{
Vector3 ab = b - a, ac = c - a, bc = c - b;
float e = Dot(ac, ab);
// Handle cases where C projects outside AB.
if (e <= 0f) return Dot(ac, ac);
float f = Dot(ab, ab);
if (e >= f) return Dot(bc, bc);
// Handle cases where C projects onto AB.
return Dot(ac, ac) - e * e / f;
}
///
/// Returns the closest point to point p on the segment.
///
public static Vector3 ClosestPtPointSegment(LocalSegment3 segment, Vector3 p)
{
float unusedT;
return ClosestPtPointSegment(segment.a, segment.b, p, out unusedT);
}
///
/// Returns the closest point to point p on the segment. Also outputs the
/// parameterization from 0 to 1 along ab that results in the closest point:
/// closestPt(t) = a + t*(b - a).
///
public static Vector3 ClosestPtPointSegment(LocalSegment3 segment, Vector3 p,
out float t)
{
return ClosestPtPointSegment(segment.a, segment.b, p, out t);
}
///
/// Returns the closest point to point c on the segment ab. Also outputs the
/// parameterization from 0 to 1 along ab that results in the closest point:
/// closestPt(t) = a + t*(b - a).
///
public static Vector3 ClosestPtPointSegment(Vector3 a, Vector3 b, Vector3 c,
out float t)
{
var ab = b - a;
// Project c onto ab, computing parameterized position of the point: a + t*(b - a)
t = Dot(c - a, ab) / Dot(ab, ab);
// If outside the segment, clamp the parameterization to the closest endpoint.
if (t < 0f) t = 0f;
if (t > 1f) t = 1f;
// Compute and output the projected position from the clamped parameterization.
return a + t * ab;
}
#endregion
}
}