using UnityEngine;
namespace ProceduralToolkit
{
///
/// Collection of closest point(s) algorithms
///
public static partial class Closest
{
#region Point-Line
///
/// Projects the point onto the line
///
public static Vector2 PointLine(Vector2 point, Line2 line)
{
return PointLine(point, line.origin, line.direction, out float projectedX);
}
///
/// Projects the point onto the line
///
/// Position of the projected point on the line relative to the origin
public static Vector2 PointLine(Vector2 point, Line2 line, out float projectedX)
{
return PointLine(point, line.origin, line.direction, out projectedX);
}
///
/// Projects the point onto the line
///
/// Normalized direction of the line
public static Vector2 PointLine(Vector2 point, Vector2 lineOrigin, Vector2 lineDirection)
{
return PointLine(point, lineOrigin, lineDirection, out float projectedX);
}
///
/// Projects the point onto the line
///
/// Normalized direction of the line
/// Position of the projected point on the line relative to the origin
public static Vector2 PointLine(Vector2 point, Vector2 lineOrigin, Vector2 lineDirection, out float projectedX)
{
// In theory, sqrMagnitude should be 1, but in practice this division helps with numerical stability
projectedX = Vector2.Dot(lineDirection, point - lineOrigin)/lineDirection.sqrMagnitude;
return lineOrigin + lineDirection*projectedX;
}
#endregion Point-Line
#region Point-Ray
///
/// Projects the point onto the ray
///
public static Vector2 PointRay(Vector2 point, Ray2D ray)
{
return PointRay(point, ray.origin, ray.direction, out float projectedX);
}
///
/// Projects the point onto the ray
///
/// Position of the projected point on the ray relative to the origin
public static Vector2 PointRay(Vector2 point, Ray2D ray, out float projectedX)
{
return PointRay(point, ray.origin, ray.direction, out projectedX);
}
///
/// Projects the point onto the ray
///
/// Normalized direction of the ray
public static Vector2 PointRay(Vector2 point, Vector2 rayOrigin, Vector2 rayDirection)
{
return PointRay(point, rayOrigin, rayDirection, out float projectedX);
}
///
/// Projects the point onto the ray
///
/// Normalized direction of the ray
/// Position of the projected point on the ray relative to the origin
public static Vector2 PointRay(Vector2 point, Vector2 rayOrigin, Vector2 rayDirection, out float projectedX)
{
float pointProjection = Vector2.Dot(rayDirection, point - rayOrigin);
if (pointProjection <= 0)
{
projectedX = 0;
return rayOrigin;
}
// In theory, sqrMagnitude should be 1, but in practice this division helps with numerical stability
projectedX = pointProjection/rayDirection.sqrMagnitude;
return rayOrigin + rayDirection*projectedX;
}
#endregion Point-Ray
#region Point-Segment
///
/// Projects the point onto the segment
///
public static Vector2 PointSegment(Vector2 point, Segment2 segment)
{
return PointSegment(point, segment.a, segment.b, out float projectedX);
}
///
/// Projects the point onto the segment
///
/// Normalized position of the projected point on the segment.
/// Value of zero means that the projected point coincides with segment.a.
/// Value of one means that the projected point coincides with segment.b.
public static Vector2 PointSegment(Vector2 point, Segment2 segment, out float projectedX)
{
return PointSegment(point, segment.a, segment.b, out projectedX);
}
///
/// Projects the point onto the segment
///
public static Vector2 PointSegment(Vector2 point, Vector2 segmentA, Vector2 segmentB)
{
return PointSegment(point, segmentA, segmentB, out float projectedX);
}
///
/// Projects the point onto the segment
///
/// Normalized position of the projected point on the segment.
/// Value of zero means that the projected point coincides with .
/// Value of one means that the projected point coincides with .
public static Vector2 PointSegment(Vector2 point, Vector2 segmentA, Vector2 segmentB, out float projectedX)
{
Vector2 segmentDirection = segmentB - segmentA;
float sqrSegmentLength = segmentDirection.sqrMagnitude;
if (sqrSegmentLength < Geometry.Epsilon)
{
// The segment is a point
projectedX = 0;
return segmentA;
}
float pointProjection = Vector2.Dot(segmentDirection, point - segmentA);
if (pointProjection <= 0)
{
projectedX = 0;
return segmentA;
}
if (pointProjection >= sqrSegmentLength)
{
projectedX = 1;
return segmentB;
}
projectedX = pointProjection/sqrSegmentLength;
return segmentA + segmentDirection*projectedX;
}
private static Vector2 PointSegment(Vector2 point, Vector2 segmentA, Vector2 segmentB, Vector2 segmentDirection, float segmentLength)
{
float pointProjection = Vector2.Dot(segmentDirection, point - segmentA);
if (pointProjection <= 0)
{
return segmentA;
}
if (pointProjection >= segmentLength)
{
return segmentB;
}
return segmentA + segmentDirection*pointProjection;
}
#endregion Point-Segment
#region Point-Circle
///
/// Projects the point onto the circle
///
public static Vector2 PointCircle(Vector2 point, Circle2 circle)
{
return PointCircle(point, circle.center, circle.radius);
}
///
/// Projects the point onto the circle
///
public static Vector2 PointCircle(Vector2 point, Vector2 circleCenter, float circleRadius)
{
return circleCenter + (point - circleCenter).normalized*circleRadius;
}
#endregion Point-Circle
#region Line-Line
///
/// Finds closest points on the lines
///
public static void LineLine(Line2 lineA, Line2 lineB, out Vector2 pointA, out Vector2 pointB)
{
LineLine(lineA.origin, lineA.direction, lineB.origin, lineB.direction, out pointA, out pointB);
}
///
/// Finds closest points on the lines
///
public static void LineLine(Vector2 originA, Vector2 directionA, Vector2 originB, Vector2 directionB,
out Vector2 pointA, out Vector2 pointB)
{
Vector2 originBToA = originA - originB;
float denominator = VectorE.PerpDot(directionA, directionB);
float perpDotB = VectorE.PerpDot(directionB, originBToA);
if (Mathf.Abs(denominator) < Geometry.Epsilon)
{
// Parallel
if (Mathf.Abs(perpDotB) > Geometry.Epsilon ||
Mathf.Abs(VectorE.PerpDot(directionA, originBToA)) > Geometry.Epsilon)
{
// Not collinear
pointA = originA;
pointB = originB + directionB*Vector2.Dot(directionB, originBToA);
return;
}
// Collinear
pointA = pointB = originA;
return;
}
// Not parallel
pointA = pointB = originA + directionA*(perpDotB/denominator);
}
#endregion Line-Line
#region Line-Ray
///
/// Finds closest points on the line and the ray
///
public static void LineRay(Line2 line, Ray2D ray, out Vector2 linePoint, out Vector2 rayPoint)
{
LineRay(line.origin, line.direction, ray.origin, ray.direction, out linePoint, out rayPoint);
}
///
/// Finds closest points on the line and the ray
///
public static void LineRay(Vector2 lineOrigin, Vector2 lineDirection, Vector2 rayOrigin, Vector2 rayDirection,
out Vector2 linePoint, out Vector2 rayPoint)
{
Vector2 rayOriginToLineOrigin = lineOrigin - rayOrigin;
float denominator = VectorE.PerpDot(lineDirection, rayDirection);
float perpDotA = VectorE.PerpDot(lineDirection, rayOriginToLineOrigin);
if (Mathf.Abs(denominator) < Geometry.Epsilon)
{
// Parallel
float perpDotB = VectorE.PerpDot(rayDirection, rayOriginToLineOrigin);
if (Mathf.Abs(perpDotA) > Geometry.Epsilon || Mathf.Abs(perpDotB) > Geometry.Epsilon)
{
// Not collinear
float rayOriginProjection = Vector2.Dot(lineDirection, rayOriginToLineOrigin);
linePoint = lineOrigin - lineDirection*rayOriginProjection;
rayPoint = rayOrigin;
return;
}
// Collinear
linePoint = rayPoint = rayOrigin;
return;
}
// Not parallel
float rayDistance = perpDotA/denominator;
if (rayDistance < -Geometry.Epsilon)
{
// No intersection
float rayOriginProjection = Vector2.Dot(lineDirection, rayOriginToLineOrigin);
linePoint = lineOrigin - lineDirection*rayOriginProjection;
rayPoint = rayOrigin;
return;
}
// Point intersection
linePoint = rayPoint = rayOrigin + rayDirection*rayDistance;
}
#endregion Line-Ray
#region Line-Segment
///
/// Finds closest points on the line and the segment
///
public static void LineSegment(Line2 line, Segment2 segment, out Vector2 linePoint, out Vector2 segmentPoint)
{
LineSegment(line.origin, line.direction, segment.a, segment.b, out linePoint, out segmentPoint);
}
///
/// Finds closest points on the line and the segment
///
public static void LineSegment(Vector2 lineOrigin, Vector2 lineDirection, Vector2 segmentA, Vector2 segmentB,
out Vector2 linePoint, out Vector2 segmentPoint)
{
Vector2 segmentDirection = segmentB - segmentA;
Vector2 segmentAToOrigin = lineOrigin - segmentA;
float denominator = VectorE.PerpDot(lineDirection, segmentDirection);
float perpDotA = VectorE.PerpDot(lineDirection, segmentAToOrigin);
if (Mathf.Abs(denominator) < Geometry.Epsilon)
{
// Parallel
bool codirected = Vector2.Dot(lineDirection, segmentDirection) > 0;
// Normalized direction gives more stable results
float perpDotB = VectorE.PerpDot(segmentDirection.normalized, segmentAToOrigin);
if (Mathf.Abs(perpDotA) > Geometry.Epsilon || Mathf.Abs(perpDotB) > Geometry.Epsilon)
{
// Not collinear
if (codirected)
{
float segmentAProjection = Vector2.Dot(lineDirection, segmentAToOrigin);
linePoint = lineOrigin - lineDirection*segmentAProjection;
segmentPoint = segmentA;
}
else
{
float segmentBProjection = Vector2.Dot(lineDirection, lineOrigin - segmentB);
linePoint = lineOrigin - lineDirection*segmentBProjection;
segmentPoint = segmentB;
}
return;
}
// Collinear
if (codirected)
{
linePoint = segmentPoint = segmentA;
}
else
{
linePoint = segmentPoint = segmentB;
}
return;
}
// Not parallel
float segmentDistance = perpDotA/denominator;
if (segmentDistance < -Geometry.Epsilon || segmentDistance > 1 + Geometry.Epsilon)
{
// No intersection
segmentPoint = segmentA + segmentDirection*Mathf.Clamp01(segmentDistance);
float segmentPointProjection = Vector2.Dot(lineDirection, segmentPoint - lineOrigin);
linePoint = lineOrigin + lineDirection*segmentPointProjection;
return;
}
// Point intersection
linePoint = segmentPoint = segmentA + segmentDirection*segmentDistance;
}
#endregion Line-Segment
#region Line-Circle
///
/// Finds closest points on the line and the circle
///
public static void LineCircle(Line2 line, Circle2 circle, out Vector2 linePoint, out Vector2 circlePoint)
{
LineCircle(line.origin, line.direction, circle.center, circle.radius, out linePoint, out circlePoint);
}
///
/// Finds closest points on the line and the circle
///
public static void LineCircle(Vector2 lineOrigin, Vector2 lineDirection, Vector2 circleCenter, float circleRadius,
out Vector2 linePoint, out Vector2 circlePoint)
{
Vector2 originToCenter = circleCenter - lineOrigin;
float centerProjection = Vector2.Dot(lineDirection, originToCenter);
float sqrDistanceToLine = originToCenter.sqrMagnitude - centerProjection*centerProjection;
float sqrDistanceToIntersection = circleRadius*circleRadius - sqrDistanceToLine;
if (sqrDistanceToIntersection < -Geometry.Epsilon)
{
// No intersection
linePoint = lineOrigin + lineDirection*centerProjection;
circlePoint = circleCenter + (linePoint - circleCenter).normalized*circleRadius;
return;
}
if (sqrDistanceToIntersection < Geometry.Epsilon)
{
// Point intersection
linePoint = circlePoint = lineOrigin + lineDirection*centerProjection;
return;
}
// Two points intersection
float distanceToIntersection = Mathf.Sqrt(sqrDistanceToIntersection);
float distanceA = centerProjection - distanceToIntersection;
linePoint = circlePoint = lineOrigin + lineDirection*distanceA;
}
#endregion Line-Circle
#region Ray-Ray
///
/// Finds closest points on the rays
///
public static void RayRay(Ray2D rayA, Ray2D rayB, out Vector2 pointA, out Vector2 pointB)
{
RayRay(rayA.origin, rayA.direction, rayB.origin, rayB.direction, out pointA, out pointB);
}
///
/// Finds closest points on the rays
///
public static void RayRay(Vector2 originA, Vector2 directionA, Vector2 originB, Vector2 directionB,
out Vector2 pointA, out Vector2 pointB)
{
Vector2 originBToA = originA - originB;
float denominator = VectorE.PerpDot(directionA, directionB);
float perpDotA = VectorE.PerpDot(directionA, originBToA);
float perpDotB = VectorE.PerpDot(directionB, originBToA);
bool codirected = Vector2.Dot(directionA, directionB) > 0;
if (Mathf.Abs(denominator) < Geometry.Epsilon)
{
// Parallel
float originBProjection = Vector2.Dot(directionA, originBToA);
if (Mathf.Abs(perpDotA) > Geometry.Epsilon || Mathf.Abs(perpDotB) > Geometry.Epsilon)
{
// Not collinear
if (codirected)
{
if (originBProjection > -Geometry.Epsilon)
{
// Projection of originA is on rayB
pointA = originA;
pointB = originB + directionA*originBProjection;
return;
}
else
{
pointA = originA - directionA*originBProjection;
pointB = originB;
return;
}
}
else
{
if (originBProjection > 0)
{
pointA = originA;
pointB = originB;
return;
}
else
{
// Projection of originA is on rayB
pointA = originA;
pointB = originB + directionA*originBProjection;
return;
}
}
}
// Collinear
if (codirected)
{
// Ray intersection
if (originBProjection > -Geometry.Epsilon)
{
// Projection of originA is on rayB
pointA = pointB = originA;
return;
}
else
{
pointA = pointB = originB;
return;
}
}
else
{
if (originBProjection > 0)
{
// No intersection
pointA = originA;
pointB = originB;
return;
}
else
{
// Segment intersection
pointA = pointB = originA;
return;
}
}
}
// Not parallel
float distanceA = perpDotB/denominator;
float distanceB = perpDotA/denominator;
if (distanceA < -Geometry.Epsilon || distanceB < -Geometry.Epsilon)
{
// No intersection
if (codirected)
{
float originAProjection = Vector2.Dot(directionB, originBToA);
if (originAProjection > -Geometry.Epsilon)
{
pointA = originA;
pointB = originB + directionB*originAProjection;
return;
}
float originBProjection = -Vector2.Dot(directionA, originBToA);
if (originBProjection > -Geometry.Epsilon)
{
pointA = originA + directionA*originBProjection;
pointB = originB;
return;
}
pointA = originA;
pointB = originB;
return;
}
else
{
if (distanceA > -Geometry.Epsilon)
{
float originBProjection = -Vector2.Dot(directionA, originBToA);
if (originBProjection > -Geometry.Epsilon)
{
pointA = originA + directionA*originBProjection;
pointB = originB;
return;
}
}
else if (distanceB > -Geometry.Epsilon)
{
float originAProjection = Vector2.Dot(directionB, originBToA);
if (originAProjection > -Geometry.Epsilon)
{
pointA = originA;
pointB = originB + directionB*originAProjection;
return;
}
}
pointA = originA;
pointB = originB;
return;
}
}
// Point intersection
pointA = pointB = originA + directionA*distanceA;
}
#endregion Ray-Ray
#region Ray-Segment
///
/// Finds closest points on the ray and the segment
///
public static void RaySegment(Ray2D ray, Segment2 segment, out Vector2 rayPoint, out Vector2 segmentPoint)
{
RaySegment(ray.origin, ray.direction, segment.a, segment.b, out rayPoint, out segmentPoint);
}
///
/// Finds closest points on the ray and the segment
///
public static void RaySegment(Vector2 rayOrigin, Vector2 rayDirection, Vector2 segmentA, Vector2 segmentB,
out Vector2 rayPoint, out Vector2 segmentPoint)
{
Vector2 segmentDirection = segmentB - segmentA;
Vector2 segmentAToOrigin = rayOrigin - segmentA;
float denominator = VectorE.PerpDot(rayDirection, segmentDirection);
float perpDotA = VectorE.PerpDot(rayDirection, segmentAToOrigin);
// Normalized direction gives more stable results
float perpDotB = VectorE.PerpDot(segmentDirection.normalized, segmentAToOrigin);
if (Mathf.Abs(denominator) < Geometry.Epsilon)
{
// Parallel
float segmentAProjection = -Vector2.Dot(rayDirection, segmentAToOrigin);
Vector2 rayOriginToSegmentB = segmentB - rayOrigin;
float segmentBProjection = Vector2.Dot(rayDirection, rayOriginToSegmentB);
if (Mathf.Abs(perpDotA) > Geometry.Epsilon || Mathf.Abs(perpDotB) > Geometry.Epsilon)
{
// Not collinear
if (segmentAProjection > -Geometry.Epsilon && segmentBProjection > -Geometry.Epsilon)
{
if (segmentAProjection < segmentBProjection)
{
rayPoint = rayOrigin + rayDirection*segmentAProjection;
segmentPoint = segmentA;
return;
}
else
{
rayPoint = rayOrigin + rayDirection*segmentBProjection;
segmentPoint = segmentB;
return;
}
}
if (segmentAProjection > -Geometry.Epsilon || segmentBProjection > -Geometry.Epsilon)
{
rayPoint = rayOrigin;
float sqrSegmentLength = segmentDirection.sqrMagnitude;
if (sqrSegmentLength > Geometry.Epsilon)
{
float rayOriginProjection = Vector2.Dot(segmentDirection, segmentAToOrigin)/sqrSegmentLength;
segmentPoint = segmentA + segmentDirection*rayOriginProjection;
}
else
{
segmentPoint = segmentA;
}
return;
}
rayPoint = rayOrigin;
segmentPoint = segmentAProjection > segmentBProjection ? segmentA : segmentB;
return;
}
// Collinear
if (segmentAProjection > -Geometry.Epsilon && segmentBProjection > -Geometry.Epsilon)
{
// Segment intersection
rayPoint = segmentPoint = segmentAProjection < segmentBProjection ? segmentA : segmentB;
return;
}
if (segmentAProjection > -Geometry.Epsilon || segmentBProjection > -Geometry.Epsilon)
{
// Point or segment intersection
rayPoint = segmentPoint = rayOrigin;
return;
}
// No intersection
rayPoint = rayOrigin;
segmentPoint = segmentAProjection > segmentBProjection ? segmentA : segmentB;
return;
}
// Not parallel
float rayDistance = perpDotB/denominator;
float segmentDistance = perpDotA/denominator;
if (rayDistance < -Geometry.Epsilon ||
segmentDistance < -Geometry.Epsilon || segmentDistance > 1 + Geometry.Epsilon)
{
// No intersection
bool codirected = Vector2.Dot(rayDirection, segmentDirection) > 0;
Vector2 segmentBToOrigin;
if (!codirected)
{
PTUtils.Swap(ref segmentA, ref segmentB);
segmentDirection = -segmentDirection;
segmentBToOrigin = segmentAToOrigin;
segmentAToOrigin = rayOrigin - segmentA;
segmentDistance = 1 - segmentDistance;
}
else
{
segmentBToOrigin = rayOrigin - segmentB;
}
float segmentAProjection = -Vector2.Dot(rayDirection, segmentAToOrigin);
float segmentBProjection = -Vector2.Dot(rayDirection, segmentBToOrigin);
bool segmentAOnRay = segmentAProjection > -Geometry.Epsilon;
bool segmentBOnRay = segmentBProjection > -Geometry.Epsilon;
if (segmentAOnRay && segmentBOnRay)
{
if (segmentDistance < 0)
{
rayPoint = rayOrigin + rayDirection*segmentAProjection;
segmentPoint = segmentA;
return;
}
else
{
rayPoint = rayOrigin + rayDirection*segmentBProjection;
segmentPoint = segmentB;
return;
}
}
else if (!segmentAOnRay && segmentBOnRay)
{
if (segmentDistance < 0)
{
rayPoint = rayOrigin;
segmentPoint = segmentA;
return;
}
else if (segmentDistance > 1 + Geometry.Epsilon)
{
rayPoint = rayOrigin + rayDirection*segmentBProjection;
segmentPoint = segmentB;
return;
}
else
{
rayPoint = rayOrigin;
float originProjection = Vector2.Dot(segmentDirection, segmentAToOrigin);
segmentPoint = segmentA + segmentDirection*originProjection/segmentDirection.sqrMagnitude;
return;
}
}
else
{
// Not on ray
rayPoint = rayOrigin;
float originProjection = Vector2.Dot(segmentDirection, segmentAToOrigin);
float sqrSegmentLength = segmentDirection.sqrMagnitude;
if (originProjection < 0)
{
segmentPoint = segmentA;
return;
}
else if (originProjection > sqrSegmentLength)
{
segmentPoint = segmentB;
return;
}
else
{
segmentPoint = segmentA + segmentDirection*originProjection/sqrSegmentLength;
return;
}
}
}
// Point intersection
rayPoint = segmentPoint = segmentA + segmentDirection*segmentDistance;
}
#endregion Ray-Segment
#region Ray-Circle
///
/// Finds closest points on the ray and the circle
///
public static void RayCircle(Ray2D ray, Circle2 circle, out Vector2 rayPoint, out Vector2 circlePoint)
{
RayCircle(ray.origin, ray.direction, circle.center, circle.radius, out rayPoint, out circlePoint);
}
///
/// Finds closest points on the ray and the circle
///
public static void RayCircle(Vector2 rayOrigin, Vector2 rayDirection, Vector2 circleCenter, float circleRadius,
out Vector2 rayPoint, out Vector2 circlePoint)
{
Vector2 originToCenter = circleCenter - rayOrigin;
float centerProjection = Vector2.Dot(rayDirection, originToCenter);
if (centerProjection + circleRadius < -Geometry.Epsilon)
{
// No intersection
rayPoint = rayOrigin;
circlePoint = circleCenter - originToCenter.normalized*circleRadius;
return;
}
float sqrDistanceToLine = originToCenter.sqrMagnitude - centerProjection*centerProjection;
float sqrDistanceToIntersection = circleRadius*circleRadius - sqrDistanceToLine;
if (sqrDistanceToIntersection < -Geometry.Epsilon)
{
// No intersection
if (centerProjection < -Geometry.Epsilon)
{
rayPoint = rayOrigin;
circlePoint = circleCenter - originToCenter.normalized*circleRadius;
return;
}
rayPoint = rayOrigin + rayDirection*centerProjection;
circlePoint = circleCenter + (rayPoint - circleCenter).normalized*circleRadius;
return;
}
if (sqrDistanceToIntersection < Geometry.Epsilon)
{
if (centerProjection < -Geometry.Epsilon)
{
// No intersection
rayPoint = rayOrigin;
circlePoint = circleCenter - originToCenter.normalized*circleRadius;
return;
}
// Point intersection
rayPoint = circlePoint = rayOrigin + rayDirection*centerProjection;
return;
}
// Line intersection
float distanceToIntersection = Mathf.Sqrt(sqrDistanceToIntersection);
float distanceA = centerProjection - distanceToIntersection;
if (distanceA < -Geometry.Epsilon)
{
float distanceB = centerProjection + distanceToIntersection;
if (distanceB < -Geometry.Epsilon)
{
// No intersection
rayPoint = rayOrigin;
circlePoint = circleCenter - originToCenter.normalized*circleRadius;
return;
}
// Point intersection
rayPoint = circlePoint = rayOrigin + rayDirection*distanceB;
return;
}
// Two points intersection
rayPoint = circlePoint = rayOrigin + rayDirection*distanceA;
}
#endregion Ray-Circle
#region Segment-Segment
///
/// Finds closest points on the segments
///
public static void SegmentSegment(Segment2 segment1, Segment2 segment2, out Vector2 segment1Point, out Vector2 segment2Point)
{
SegmentSegment(segment1.a, segment1.b, segment2.a, segment2.b, out segment1Point, out segment2Point);
}
///
/// Finds closest points on the segments
///
public static void SegmentSegment(Vector2 segment1A, Vector2 segment1B, Vector2 segment2A, Vector2 segment2B,
out Vector2 segment1Point, out Vector2 segment2Point)
{
Vector2 from2ATo1A = segment1A - segment2A;
Vector2 direction1 = segment1B - segment1A;
Vector2 direction2 = segment2B - segment2A;
float segment1Length = direction1.magnitude;
float segment2Length = direction2.magnitude;
bool segment1IsAPoint = segment1Length < Geometry.Epsilon;
bool segment2IsAPoint = segment2Length < Geometry.Epsilon;
if (segment1IsAPoint && segment2IsAPoint)
{
if (segment1A == segment2A)
{
segment1Point = segment2Point = segment1A;
return;
}
segment1Point = segment1A;
segment2Point = segment2A;
return;
}
if (segment1IsAPoint)
{
direction2.Normalize();
segment1Point = segment1A;
segment2Point = PointSegment(segment1A, segment2A, segment2B, direction2, segment2Length);
return;
}
if (segment2IsAPoint)
{
direction1.Normalize();
segment1Point = PointSegment(segment2A, segment1A, segment1B, direction1, segment1Length);
segment2Point = segment2A;
return;
}
direction1.Normalize();
direction2.Normalize();
float denominator = VectorE.PerpDot(direction1, direction2);
float perpDot1 = VectorE.PerpDot(direction1, from2ATo1A);
float perpDot2 = VectorE.PerpDot(direction2, from2ATo1A);
if (Mathf.Abs(denominator) < Geometry.Epsilon)
{
// Parallel
bool codirected = Vector2.Dot(direction1, direction2) > 0;
if (Mathf.Abs(perpDot1) > Geometry.Epsilon || Mathf.Abs(perpDot2) > Geometry.Epsilon)
{
// Not collinear
Vector2 from1ATo2B;
if (!codirected)
{
PTUtils.Swap(ref segment2A, ref segment2B);
direction2 = -direction2;
from1ATo2B = -from2ATo1A;
from2ATo1A = segment1A - segment2A;
}
else
{
from1ATo2B = segment2B - segment1A;
}
float segment2AProjection = -Vector2.Dot(direction1, from2ATo1A);
float segment2BProjection = Vector2.Dot(direction1, from1ATo2B);
bool segment2AIsAfter1A = segment2AProjection > -Geometry.Epsilon;
bool segment2BIsAfter1A = segment2BProjection > -Geometry.Epsilon;
if (!segment2AIsAfter1A && !segment2BIsAfter1A)
{
// 1A------1B
// 2A------2B
segment1Point = segment1A;
segment2Point = segment2B;
return;
}
bool segment2AIsBefore1B = segment2AProjection < segment1Length + Geometry.Epsilon;
bool segment2BIsBefore1B = segment2BProjection < segment1Length + Geometry.Epsilon;
if (!segment2AIsBefore1B && !segment2BIsBefore1B)
{
// 1A------1B
// 2A------2B
segment1Point = segment1B;
segment2Point = segment2A;
return;
}
if (segment2AIsAfter1A && segment2BIsBefore1B)
{
// 1A------1B
// 2A--2B
segment1Point = segment1A + direction1*segment2AProjection;
segment2Point = segment2A;
return;
}
if (segment2AIsAfter1A) // && segment2AIsBefore1B && !segment2BIsBefore1B)
{
// 1A------1B
// 2A------2B
segment1Point = segment1A + direction1*segment2AProjection;
segment2Point = segment2A;
return;
}
else
{
// 1A------1B
// 2A----2B
// 2A----------2B
segment1Point = segment1A;
float segment1AProjection = Vector2.Dot(direction2, from2ATo1A);
segment2Point = segment2A + direction2*segment1AProjection;
return;
}
}
// Collinear
if (codirected)
{
// Codirected
float segment2AProjection = -Vector2.Dot(direction1, from2ATo1A);
if (segment2AProjection > -Geometry.Epsilon)
{
// 1A------1B
// 2A------2B
SegmentSegmentCollinear(segment1A, segment1B, segment2A, out segment1Point, out segment2Point);
return;
}
else
{
// 1A------1B
// 2A------2B
SegmentSegmentCollinear(segment2A, segment2B, segment1A, out segment2Point, out segment1Point);
return;
}
}
else
{
// Contradirected
float segment2BProjection = Vector2.Dot(direction1, segment2B - segment1A);
if (segment2BProjection > -Geometry.Epsilon)
{
// 1A------1B
// 2B------2A
SegmentSegmentCollinear(segment1A, segment1B, segment2B, out segment1Point, out segment2Point);
return;
}
else
{
// 1A------1B
// 2B------2A
SegmentSegmentCollinear(segment2B, segment2A, segment1A, out segment2Point, out segment1Point);
return;
}
}
}
// Not parallel
float distance1 = perpDot2/denominator;
float distance2 = perpDot1/denominator;
if (distance1 < -Geometry.Epsilon || distance1 > segment1Length + Geometry.Epsilon ||
distance2 < -Geometry.Epsilon || distance2 > segment2Length + Geometry.Epsilon)
{
// No intersection
bool codirected = Vector2.Dot(direction1, direction2) > 0;
Vector2 from1ATo2B;
if (!codirected)
{
PTUtils.Swap(ref segment2A, ref segment2B);
direction2 = -direction2;
from1ATo2B = -from2ATo1A;
from2ATo1A = segment1A - segment2A;
distance2 = segment2Length - distance2;
}
else
{
from1ATo2B = segment2B - segment1A;
}
float segment2AProjection = -Vector2.Dot(direction1, from2ATo1A);
float segment2BProjection = Vector2.Dot(direction1, from1ATo2B);
bool segment2AIsAfter1A = segment2AProjection > -Geometry.Epsilon;
bool segment2BIsBefore1B = segment2BProjection < segment1Length + Geometry.Epsilon;
bool segment2AOnSegment1 = segment2AIsAfter1A && segment2AProjection < segment1Length + Geometry.Epsilon;
bool segment2BOnSegment1 = segment2BProjection > -Geometry.Epsilon && segment2BIsBefore1B;
if (segment2AOnSegment1 && segment2BOnSegment1)
{
if (distance2 < -Geometry.Epsilon)
{
segment1Point = segment1A + direction1*segment2AProjection;
segment2Point = segment2A;
}
else
{
segment1Point = segment1A + direction1*segment2BProjection;
segment2Point = segment2B;
}
}
else if (!segment2AOnSegment1 && !segment2BOnSegment1)
{
if (!segment2AIsAfter1A && !segment2BIsBefore1B)
{
segment1Point = distance1 < -Geometry.Epsilon ? segment1A : segment1B;
}
else
{
// Not on segment
segment1Point = segment2AIsAfter1A ? segment1B : segment1A;
}
float segment1PointProjection = Vector2.Dot(direction2, segment1Point - segment2A);
segment1PointProjection = Mathf.Clamp(segment1PointProjection, 0, segment2Length);
segment2Point = segment2A + direction2*segment1PointProjection;
}
else if (segment2AOnSegment1)
{
if (distance2 < -Geometry.Epsilon)
{
segment1Point = segment1A + direction1*segment2AProjection;
segment2Point = segment2A;
}
else
{
segment1Point = segment1B;
float segment1PointProjection = Vector2.Dot(direction2, segment1Point - segment2A);
segment1PointProjection = Mathf.Clamp(segment1PointProjection, 0, segment2Length);
segment2Point = segment2A + direction2*segment1PointProjection;
}
}
else
{
if (distance2 > segment2Length + Geometry.Epsilon)
{
segment1Point = segment1A + direction1*segment2BProjection;
segment2Point = segment2B;
}
else
{
segment1Point = segment1A;
float segment1PointProjection = Vector2.Dot(direction2, segment1Point - segment2A);
segment1PointProjection = Mathf.Clamp(segment1PointProjection, 0, segment2Length);
segment2Point = segment2A + direction2*segment1PointProjection;
}
}
return;
}
// Point intersection
segment1Point = segment2Point = segment1A + direction1*distance1;
}
private static void SegmentSegmentCollinear(Vector2 leftA, Vector2 leftB, Vector2 rightA, out Vector2 leftPoint, out Vector2 rightPoint)
{
Vector2 leftDirection = leftB - leftA;
float rightAProjection = Vector2.Dot(leftDirection.normalized, rightA - leftB);
if (Mathf.Abs(rightAProjection) < Geometry.Epsilon)
{
// LB == RA
// LA------LB
// RA------RB
// Point intersection
leftPoint = rightPoint = leftB;
return;
}
if (rightAProjection < 0)
{
// LB > RA
// LA------LB
// RARB
// RA--RB
// RA------RB
// Segment intersection
leftPoint = rightPoint = rightA;
return;
}
// LB < RA
// LA------LB
// RA------RB
// No intersection
leftPoint = leftB;
rightPoint = rightA;
}
#endregion Segment-Segment
#region Segment-Circle
///
/// Finds closest points on the segment and the circle
///
public static void SegmentCircle(Segment2 segment, Circle2 circle, out Vector2 segmentPoint, out Vector2 circlePoint)
{
SegmentCircle(segment.a, segment.b, circle.center, circle.radius, out segmentPoint, out circlePoint);
}
///
/// Finds closest points on the segment and the circle
///
public static void SegmentCircle(Vector2 segmentA, Vector2 segmentB, Vector2 circleCenter, float circleRadius,
out Vector2 segmentPoint, out Vector2 circlePoint)
{
Vector2 segmentAToCenter = circleCenter - segmentA;
Vector2 fromAtoB = segmentB - segmentA;
float segmentLength = fromAtoB.magnitude;
if (segmentLength < Geometry.Epsilon)
{
segmentPoint = segmentA;
float distanceToPoint = segmentAToCenter.magnitude;
if (distanceToPoint < circleRadius + Geometry.Epsilon)
{
if (distanceToPoint > circleRadius - Geometry.Epsilon)
{
circlePoint = segmentPoint;
return;
}
if (distanceToPoint < Geometry.Epsilon)
{
circlePoint = segmentPoint;
return;
}
}
Vector2 toPoint = -segmentAToCenter/distanceToPoint;
circlePoint = circleCenter + toPoint*circleRadius;
return;
}
Vector2 segmentDirection = fromAtoB.normalized;
float centerProjection = Vector2.Dot(segmentDirection, segmentAToCenter);
if (centerProjection + circleRadius < -Geometry.Epsilon ||
centerProjection - circleRadius > segmentLength + Geometry.Epsilon)
{
// No intersection
if (centerProjection < 0)
{
segmentPoint = segmentA;
circlePoint = circleCenter - segmentAToCenter.normalized*circleRadius;
return;
}
segmentPoint = segmentB;
circlePoint = circleCenter - (circleCenter - segmentB).normalized*circleRadius;
return;
}
float sqrDistanceToLine = segmentAToCenter.sqrMagnitude - centerProjection*centerProjection;
float sqrDistanceToIntersection = circleRadius*circleRadius - sqrDistanceToLine;
if (sqrDistanceToIntersection < -Geometry.Epsilon)
{
// No intersection
if (centerProjection < -Geometry.Epsilon)
{
segmentPoint = segmentA;
circlePoint = circleCenter - segmentAToCenter.normalized*circleRadius;
return;
}
if (centerProjection > segmentLength + Geometry.Epsilon)
{
segmentPoint = segmentB;
circlePoint = circleCenter - (circleCenter - segmentB).normalized*circleRadius;
return;
}
segmentPoint = segmentA + segmentDirection*centerProjection;
circlePoint = circleCenter + (segmentPoint - circleCenter).normalized*circleRadius;
return;
}
if (sqrDistanceToIntersection < Geometry.Epsilon)
{
if (centerProjection < -Geometry.Epsilon)
{
// No intersection
segmentPoint = segmentA;
circlePoint = circleCenter - segmentAToCenter.normalized*circleRadius;
return;
}
if (centerProjection > segmentLength + Geometry.Epsilon)
{
// No intersection
segmentPoint = segmentB;
circlePoint = circleCenter - (circleCenter - segmentB).normalized*circleRadius;
return;
}
// Point intersection
segmentPoint = circlePoint = segmentA + segmentDirection*centerProjection;
return;
}
// Line intersection
float distanceToIntersection = Mathf.Sqrt(sqrDistanceToIntersection);
float distanceA = centerProjection - distanceToIntersection;
float distanceB = centerProjection + distanceToIntersection;
bool pointAIsAfterSegmentA = distanceA > -Geometry.Epsilon;
bool pointBIsBeforeSegmentB = distanceB < segmentLength + Geometry.Epsilon;
if (pointAIsAfterSegmentA && pointBIsBeforeSegmentB)
{
segmentPoint = circlePoint = segmentA + segmentDirection*distanceA;
return;
}
if (!pointAIsAfterSegmentA && !pointBIsBeforeSegmentB)
{
// The segment is inside, but no intersection
if (distanceA > -(distanceB - segmentLength))
{
segmentPoint = segmentA;
circlePoint = segmentA + segmentDirection*distanceA;
return;
}
segmentPoint = segmentB;
circlePoint = segmentA + segmentDirection*distanceB;
return;
}
bool pointAIsBeforeSegmentB = distanceA < segmentLength + Geometry.Epsilon;
if (pointAIsAfterSegmentA && pointAIsBeforeSegmentB)
{
// Point A intersection
segmentPoint = circlePoint = segmentA + segmentDirection*distanceA;
return;
}
bool pointBIsAfterSegmentA = distanceB > -Geometry.Epsilon;
if (pointBIsAfterSegmentA && pointBIsBeforeSegmentB)
{
// Point B intersection
segmentPoint = circlePoint = segmentA + segmentDirection*distanceB;
return;
}
// No intersection
if (centerProjection < 0)
{
segmentPoint = segmentA;
circlePoint = circleCenter - segmentAToCenter.normalized*circleRadius;
return;
}
segmentPoint = segmentB;
circlePoint = circleCenter - (circleCenter - segmentB).normalized*circleRadius;
}
#endregion Segment-Circle
#region Circle-Circle
///
/// Finds closest points on the circles
///
public static void CircleCircle(Circle2 circleA, Circle2 circleB, out Vector2 pointA, out Vector2 pointB)
{
CircleCircle(circleA.center, circleA.radius, circleB.center, circleB.radius, out pointA, out pointB);
}
///
/// Finds closest points on the circles
///
public static void CircleCircle(Vector2 centerA, float radiusA, Vector2 centerB, float radiusB,
out Vector2 pointA, out Vector2 pointB)
{
Vector2 fromBtoA = (centerA - centerB).normalized;
pointA = centerA - fromBtoA*radiusA;
pointB = centerB + fromBtoA*radiusB;
}
#endregion Circle-Circle
}
}