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 Vector3 PointLine(Vector3 point, Line3 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 Vector3 PointLine(Vector3 point, Line3 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 Vector3 PointLine(Vector3 point, Vector3 lineOrigin, Vector3 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 Vector3 PointLine(Vector3 point, Vector3 lineOrigin, Vector3 lineDirection, out float projectedX)
{
// In theory, sqrMagnitude should be 1, but in practice this division helps with numerical stability
projectedX = Vector3.Dot(lineDirection, point - lineOrigin)/lineDirection.sqrMagnitude;
return lineOrigin + lineDirection*projectedX;
}
#endregion Point-Line
#region Point-Ray
///
/// Projects the point onto the ray
///
public static Vector3 PointRay(Vector3 point, Ray 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 Vector3 PointRay(Vector3 point, Ray 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 Vector3 PointRay(Vector3 point, Vector3 rayOrigin, Vector3 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 Vector3 PointRay(Vector3 point, Vector3 rayOrigin, Vector3 rayDirection, out float projectedX)
{
Vector3 toPoint = point - rayOrigin;
float pointProjection = Vector3.Dot(rayDirection, toPoint);
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 Vector3 PointSegment(Vector3 point, Segment3 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 Vector3 PointSegment(Vector3 point, Segment3 segment, out float projectedX)
{
return PointSegment(point, segment.a, segment.b, out projectedX);
}
///
/// Projects the point onto the segment
///
public static Vector3 PointSegment(Vector3 point, Vector3 segmentA, Vector3 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 Vector3 PointSegment(Vector3 point, Vector3 segmentA, Vector3 segmentB, out float projectedX)
{
Vector3 segmentDirection = segmentB - segmentA;
float sqrSegmentLength = segmentDirection.sqrMagnitude;
if (sqrSegmentLength < Geometry.Epsilon)
{
// The segment is a point
projectedX = 0;
return segmentA;
}
float pointProjection = Vector3.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;
}
#endregion Point-Segment
#region Point-Sphere
///
/// Projects the point onto the sphere
///
public static Vector3 PointSphere(Vector3 point, Sphere sphere)
{
return PointSphere(point, sphere.center, sphere.radius);
}
///
/// Projects the point onto the sphere
///
public static Vector3 PointSphere(Vector3 point, Vector3 sphereCenter, float sphereRadius)
{
return sphereCenter + (point - sphereCenter).normalized*sphereRadius;
}
#endregion Point-Sphere
#region Line-Sphere
///
/// Finds closest points on the line and the sphere
///
public static void LineSphere(Line3 line, Sphere sphere, out Vector3 linePoint, out Vector3 spherePoint)
{
LineSphere(line.origin, line.direction, sphere.center, sphere.radius, out linePoint, out spherePoint);
}
///
/// Finds closest points on the line and the sphere
///
public static void LineSphere(Vector3 lineOrigin, Vector3 lineDirection, Vector3 sphereCenter, float sphereRadius,
out Vector3 linePoint, out Vector3 spherePoint)
{
Vector3 originToCenter = sphereCenter - lineOrigin;
float centerProjection = Vector3.Dot(lineDirection, originToCenter);
float sqrDistanceToLine = originToCenter.sqrMagnitude - centerProjection*centerProjection;
float sqrDistanceToIntersection = sphereRadius*sphereRadius - sqrDistanceToLine;
if (sqrDistanceToIntersection < -Geometry.Epsilon)
{
// No intersection
linePoint = lineOrigin + lineDirection*centerProjection;
spherePoint = sphereCenter + (linePoint - sphereCenter).normalized*sphereRadius;
return;
}
if (sqrDistanceToIntersection < Geometry.Epsilon)
{
// Point intersection
linePoint = spherePoint = lineOrigin + lineDirection*centerProjection;
return;
}
// Two points intersection
float distanceToIntersection = Mathf.Sqrt(sqrDistanceToIntersection);
float distanceA = centerProjection - distanceToIntersection;
linePoint = spherePoint = lineOrigin + lineDirection*distanceA;
}
#endregion Line-Sphere
#region Ray-Sphere
///
/// Finds closest points on the ray and the sphere
///
public static void RaySphere(Ray ray, Sphere sphere, out Vector3 rayPoint, out Vector3 spherePoint)
{
RaySphere(ray.origin, ray.direction, sphere.center, sphere.radius, out rayPoint, out spherePoint);
}
///
/// Finds closest points on the ray and the sphere
///
public static void RaySphere(Vector3 rayOrigin, Vector3 rayDirection, Vector3 sphereCenter, float sphereRadius,
out Vector3 rayPoint, out Vector3 spherePoint)
{
Vector3 originToCenter = sphereCenter - rayOrigin;
float centerProjection = Vector3.Dot(rayDirection, originToCenter);
if (centerProjection + sphereRadius < -Geometry.Epsilon)
{
// No intersection
rayPoint = rayOrigin;
spherePoint = sphereCenter - originToCenter.normalized*sphereRadius;
return;
}
float sqrDistanceToLine = originToCenter.sqrMagnitude - centerProjection*centerProjection;
float sqrDistanceToIntersection = sphereRadius*sphereRadius - sqrDistanceToLine;
if (sqrDistanceToIntersection < -Geometry.Epsilon)
{
// No intersection
if (centerProjection < -Geometry.Epsilon)
{
rayPoint = rayOrigin;
spherePoint = sphereCenter - originToCenter.normalized*sphereRadius;
return;
}
rayPoint = rayOrigin + rayDirection*centerProjection;
spherePoint = sphereCenter + (rayPoint - sphereCenter).normalized*sphereRadius;
return;
}
if (sqrDistanceToIntersection < Geometry.Epsilon)
{
if (centerProjection < -Geometry.Epsilon)
{
// No intersection
rayPoint = rayOrigin;
spherePoint = sphereCenter - originToCenter.normalized*sphereRadius;
return;
}
// Point intersection
rayPoint = spherePoint = 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;
spherePoint = sphereCenter - originToCenter.normalized*sphereRadius;
return;
}
// Point intersection
rayPoint = spherePoint = rayOrigin + rayDirection*distanceB;
return;
}
// Two points intersection
rayPoint = spherePoint = rayOrigin + rayDirection*distanceA;
}
#endregion Ray-Sphere
#region Segment-Sphere
///
/// Finds closest points on the segment and the sphere
///
public static void SegmentSphere(Segment3 segment, Sphere sphere, out Vector3 segmentPoint, out Vector3 spherePoint)
{
SegmentSphere(segment.a, segment.b, sphere.center, sphere.radius, out segmentPoint, out spherePoint);
}
///
/// Finds closest points on the segment and the sphere
///
public static void SegmentSphere(Vector3 segmentA, Vector3 segmentB, Vector3 sphereCenter, float sphereRadius,
out Vector3 segmentPoint, out Vector3 spherePoint)
{
Vector3 segmentAToCenter = sphereCenter - segmentA;
Vector3 fromAtoB = segmentB - segmentA;
float segmentLength = fromAtoB.magnitude;
if (segmentLength < Geometry.Epsilon)
{
segmentPoint = segmentA;
float distanceToPoint = segmentAToCenter.magnitude;
if (distanceToPoint < sphereRadius + Geometry.Epsilon)
{
if (distanceToPoint > sphereRadius - Geometry.Epsilon)
{
spherePoint = segmentPoint;
return;
}
if (distanceToPoint < Geometry.Epsilon)
{
spherePoint = segmentPoint;
return;
}
}
Vector3 toPoint = -segmentAToCenter/distanceToPoint;
spherePoint = sphereCenter + toPoint*sphereRadius;
return;
}
Vector3 segmentDirection = fromAtoB.normalized;
float centerProjection = Vector3.Dot(segmentDirection, segmentAToCenter);
if (centerProjection + sphereRadius < -Geometry.Epsilon ||
centerProjection - sphereRadius > segmentLength + Geometry.Epsilon)
{
// No intersection
if (centerProjection < 0)
{
segmentPoint = segmentA;
spherePoint = sphereCenter - segmentAToCenter.normalized*sphereRadius;
return;
}
segmentPoint = segmentB;
spherePoint = sphereCenter - (sphereCenter - segmentB).normalized*sphereRadius;
return;
}
float sqrDistanceToLine = segmentAToCenter.sqrMagnitude - centerProjection*centerProjection;
float sqrDistanceToIntersection = sphereRadius*sphereRadius - sqrDistanceToLine;
if (sqrDistanceToIntersection < -Geometry.Epsilon)
{
// No intersection
if (centerProjection < -Geometry.Epsilon)
{
segmentPoint = segmentA;
spherePoint = sphereCenter - segmentAToCenter.normalized*sphereRadius;
return;
}
if (centerProjection > segmentLength + Geometry.Epsilon)
{
segmentPoint = segmentB;
spherePoint = sphereCenter - (sphereCenter - segmentB).normalized*sphereRadius;
return;
}
segmentPoint = segmentA + segmentDirection*centerProjection;
spherePoint = sphereCenter + (segmentPoint - sphereCenter).normalized*sphereRadius;
return;
}
if (sqrDistanceToIntersection < Geometry.Epsilon)
{
if (centerProjection < -Geometry.Epsilon)
{
// No intersection
segmentPoint = segmentA;
spherePoint = sphereCenter - segmentAToCenter.normalized*sphereRadius;
return;
}
if (centerProjection > segmentLength + Geometry.Epsilon)
{
// No intersection
segmentPoint = segmentB;
spherePoint = sphereCenter - (sphereCenter - segmentB).normalized*sphereRadius;
return;
}
// Point intersection
segmentPoint = spherePoint = 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 = spherePoint = segmentA + segmentDirection*distanceA;
return;
}
if (!pointAIsAfterSegmentA && !pointBIsBeforeSegmentB)
{
// The segment is inside, but no intersection
if (distanceA > -(distanceB - segmentLength))
{
segmentPoint = segmentA;
spherePoint = segmentA + segmentDirection*distanceA;
return;
}
segmentPoint = segmentB;
spherePoint = segmentA + segmentDirection*distanceB;
return;
}
bool pointAIsBeforeSegmentB = distanceA < segmentLength + Geometry.Epsilon;
if (pointAIsAfterSegmentA && pointAIsBeforeSegmentB)
{
// Point A intersection
segmentPoint = spherePoint = segmentA + segmentDirection*distanceA;
return;
}
bool pointBIsAfterSegmentA = distanceB > -Geometry.Epsilon;
if (pointBIsAfterSegmentA && pointBIsBeforeSegmentB)
{
// Point B intersection
segmentPoint = spherePoint = segmentA + segmentDirection*distanceB;
return;
}
// No intersection
if (centerProjection < 0)
{
segmentPoint = segmentA;
spherePoint = sphereCenter - segmentAToCenter.normalized*sphereRadius;
return;
}
segmentPoint = segmentB;
spherePoint = sphereCenter - (sphereCenter - segmentB).normalized*sphereRadius;
}
#endregion Segment-Sphere
#region Sphere-Sphere
///
/// Finds closest points on the spheres
///
public static void SphereSphere(Sphere sphereA, Sphere sphereB, out Vector3 pointA, out Vector3 pointB)
{
SphereSphere(sphereA.center, sphereA.radius, sphereB.center, sphereB.radius, out pointA, out pointB);
}
///
/// Finds closest points on the spheres
///
public static void SphereSphere(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB,
out Vector3 pointA, out Vector3 pointB)
{
Vector3 fromBtoA = (centerA - centerB).normalized;
pointA = centerA - fromBtoA*radiusA;
pointB = centerB + fromBtoA*radiusB;
}
#endregion Sphere-Sphere
}
}