using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Assertions;
namespace ProceduralToolkit
{
///
/// Utility class for computational geometry algorithms
///
public static class Geometry
{
///
/// A tiny floating point value used in comparisons
///
public const float Epsilon = 0.00001f;
#region Point samplers 2D
///
/// Returns a point on a segment at the given normalized position
///
/// Start of the segment
/// End of the segment
/// Normalized position
public static Vector2 PointOnSegment2(Vector2 segmentA, Vector2 segmentB, float position)
{
return Vector2.Lerp(segmentA, segmentB, position);
}
///
/// Returns a list of evenly distributed points on a segment
///
/// Start of the segment
/// End of the segment
/// Number of points
public static List PointsOnSegment2(Vector2 segmentA, Vector2 segmentB, int count)
{
var points = new List(count);
if (count <= 0)
{
return points;
}
if (count == 1)
{
points.Add(segmentA);
return points;
}
for (int i = 0; i < count; i++)
{
points.Add(PointOnSegment2(segmentA, segmentB, i/(float) (count - 1)));
}
return points;
}
#region PointOnCircle2
///
/// Returns a point on a circle in the XY plane
///
/// Circle radius
/// Angle in degrees
public static Vector2 PointOnCircle2(float radius, float angle)
{
float angleInRadians = angle*Mathf.Deg2Rad;
return new Vector2(radius*Mathf.Sin(angleInRadians), radius*Mathf.Cos(angleInRadians));
}
///
/// Returns a point on a circle in the XY plane
///
/// Center of the circle
/// Circle radius
/// Angle in degrees
public static Vector2 PointOnCircle2(Vector2 center, float radius, float angle)
{
return center + PointOnCircle2(radius, angle);
}
#endregion PointOnCircle2
#region PointsOnCircle2
///
/// Returns a list of evenly distributed points on a circle in the XY plane
///
/// Circle radius
/// Number of points
public static List PointsOnCircle2(float radius, int count)
{
float segmentAngle = 360f/count;
float currentAngle = 0;
var points = new List(count);
for (var i = 0; i < count; i++)
{
points.Add(PointOnCircle2(radius, currentAngle));
currentAngle += segmentAngle;
}
return points;
}
///
/// Returns a list of evenly distributed points on a circle in the XY plane
///
/// Center of the circle
/// Circle radius
/// Number of points
public static List PointsOnCircle2(Vector2 center, float radius, int count)
{
float segmentAngle = 360f/count;
float currentAngle = 0;
var points = new List(count);
for (var i = 0; i < count; i++)
{
points.Add(PointOnCircle2(center, radius, currentAngle));
currentAngle += segmentAngle;
}
return points;
}
#endregion PointsOnCircle2
#region PointsInCircle2
///
/// Returns a list of evenly distributed points inside a circle in the XY plane
///
/// Circle radius
/// Number of points
public static List PointsInCircle2(float radius, int count)
{
float currentAngle = 0;
var points = new List(count);
for (int i = 0; i < count; i++)
{
// The 0.5 offset improves the position of the first point
float r = Mathf.Sqrt((i + 0.5f)/count);
points.Add(new Vector2(radius*Mathf.Sin(currentAngle)*r, radius*Mathf.Cos(currentAngle)*r));
currentAngle += PTUtils.GoldenAngle;
}
return points;
}
///
/// Returns a list of evenly distributed points inside a circle in the XY plane
///
/// Center of the circle
/// Circle radius
/// Number of points
public static List PointsInCircle2(Vector2 center, float radius, int count)
{
float currentAngle = 0;
var points = new List(count);
for (int i = 0; i < count; i++)
{
// The 0.5 offset improves the position of the first point
float r = Mathf.Sqrt((i + 0.5f)/count);
points.Add(center + new Vector2(radius*Mathf.Sin(currentAngle)*r, radius*Mathf.Cos(currentAngle)*r));
currentAngle += PTUtils.GoldenAngle;
}
return points;
}
#endregion PointsInCircle2
#endregion Point samplers 2D
#region Point samplers 3D
///
/// Returns a point on a segment at the given normalized position
///
/// Start of the segment
/// End of the segment
/// Normalized position
public static Vector3 PointOnSegment3(Vector3 segmentA, Vector3 segmentB, float position)
{
return Vector3.Lerp(segmentA, segmentB, position);
}
///
/// Returns a list of evenly distributed points on a segment
///
/// Start of the segment
/// End of the segment
/// Number of points
public static List PointsOnSegment3(Vector3 segmentA, Vector3 segmentB, int count)
{
var points = new List(count);
if (count <= 0)
{
return points;
}
if (count == 1)
{
points.Add(segmentA);
return points;
}
for (int i = 0; i < count; i++)
{
points.Add(PointOnSegment3(segmentA, segmentB, i/(float) (count - 1)));
}
return points;
}
#region PointOnCircle3
///
/// Returns a point on a circle in the XY plane
///
/// Circle radius
/// Angle in degrees
public static Vector3 PointOnCircle3XY(float radius, float angle)
{
return PointOnCircle3(0, 1, radius, angle);
}
///
/// Returns a point on a circle in the XY plane
///
/// Center of the circle
/// Circle radius
/// Angle in degrees
public static Vector3 PointOnCircle3XY(Vector3 center, float radius, float angle)
{
return PointOnCircle3(0, 1, center, radius, angle);
}
///
/// Returns a point on a circle in the XZ plane
///
/// Circle radius
/// Angle in degrees
public static Vector3 PointOnCircle3XZ(float radius, float angle)
{
return PointOnCircle3(0, 2, radius, angle);
}
///
/// Returns a point on a circle in the XZ plane
///
/// Center of the circle
/// Circle radius
/// Angle in degrees
public static Vector3 PointOnCircle3XZ(Vector3 center, float radius, float angle)
{
return PointOnCircle3(0, 2, center, radius, angle);
}
///
/// Returns a point on a circle in the YZ plane
///
/// Circle radius
/// Angle in degrees
public static Vector3 PointOnCircle3YZ(float radius, float angle)
{
return PointOnCircle3(1, 2, radius, angle);
}
///
/// Returns a point on a circle in the YZ plane
///
/// Center of the circle
/// Circle radius
/// Angle in degrees
public static Vector3 PointOnCircle3YZ(Vector3 center, float radius, float angle)
{
return PointOnCircle3(1, 2, center, radius, angle);
}
private static Vector3 PointOnCircle3(int xIndex, int yIndex, float radius, float angle)
{
float angleInRadians = angle*Mathf.Deg2Rad;
var point = new Vector3();
point[xIndex] = radius*Mathf.Sin(angleInRadians);
point[yIndex] = radius*Mathf.Cos(angleInRadians);
return point;
}
private static Vector3 PointOnCircle3(int xIndex, int yIndex, Vector3 center, float radius, float angle)
{
return center + PointOnCircle3(xIndex, yIndex, radius, angle);
}
#endregion PointOnCircle3
#region PointsOnCircle3
///
/// Returns a list of evenly distributed points on a circle in the XY plane
///
/// Circle radius
/// Number of points
public static List PointsOnCircle3XY(float radius, int count)
{
return PointsOnCircle3(0, 1, radius, count);
}
///
/// Returns a list of evenly distributed points on a circle in the XY plane
///
/// Center of the circle
/// Circle radius
/// Number of points
public static List PointsOnCircle3XY(Vector3 center, float radius, int count)
{
return PointsOnCircle3(0, 1, center, radius, count);
}
///
/// Returns a list of evenly distributed points on a circle in the XZ plane
///
/// Circle radius
/// Number of points
public static List PointsOnCircle3XZ(float radius, int count)
{
return PointsOnCircle3(0, 2, radius, count);
}
///
/// Returns a list of evenly distributed points on a circle in the XZ plane
///
/// Center of the circle
/// Circle radius
/// Number of points
public static List PointsOnCircle3XZ(Vector3 center, float radius, int count)
{
return PointsOnCircle3(0, 2, center, radius, count);
}
///
/// Returns a list of evenly distributed points on a circle in the YZ plane
///
/// Circle radius
/// Number of points
public static List PointsOnCircle3YZ(float radius, int count)
{
return PointsOnCircle3(1, 2, radius, count);
}
///
/// Returns a list of evenly distributed points on a circle in the YZ plane
///
/// Center of the circle
/// Circle radius
/// Number of points
public static List PointsOnCircle3YZ(Vector3 center, float radius, int count)
{
return PointsOnCircle3(1, 2, center, radius, count);
}
private static List PointsOnCircle3(int xIndex, int yIndex, float radius, int count)
{
float segmentAngle = 360f/count;
float currentAngle = 0;
var points = new List(count);
for (var i = 0; i < count; i++)
{
points.Add(PointOnCircle3(xIndex, yIndex, radius, currentAngle));
currentAngle += segmentAngle;
}
return points;
}
private static List PointsOnCircle3(int xIndex, int yIndex, Vector3 center, float radius, int count)
{
float segmentAngle = 360f/count;
float currentAngle = 0;
var points = new List(count);
for (var i = 0; i < count; i++)
{
points.Add(PointOnCircle3(xIndex, yIndex, center, radius, currentAngle));
currentAngle += segmentAngle;
}
return points;
}
#endregion PointsOnCircle3
#region PointsInCircle3
///
/// Returns a list of evenly distributed points inside a circle in the XY plane
///
/// Circle radius
/// Number of points
public static List PointsInCircle3XY(float radius, int count)
{
return PointsInCircle3(0, 1, radius, count);
}
///
/// Returns a list of evenly distributed points inside a circle in the XY plane
///
/// Circle radius
/// Number of points
public static List PointsInCircle3XY(Vector3 center, float radius, int count)
{
return PointsInCircle3(0, 1, center, radius, count);
}
///
/// Returns a list of evenly distributed points inside a circle in the XZ plane
///
/// Circle radius
/// Number of points
public static List PointsInCircle3XZ(float radius, int count)
{
return PointsInCircle3(0, 2, radius, count);
}
///
/// Returns a list of evenly distributed points inside a circle in the XZ plane
///
/// Circle radius
/// Number of points
public static List PointsInCircle3XZ(Vector3 center, float radius, int count)
{
return PointsInCircle3(0, 2, center, radius, count);
}
///
/// Returns a list of evenly distributed points inside a circle in the YZ plane
///
/// Circle radius
/// Number of points
public static List PointsInCircle3YZ(float radius, int count)
{
return PointsInCircle3(1, 2, radius, count);
}
///
/// Returns a list of evenly distributed points inside a circle in the YZ plane
///
/// Circle radius
/// Number of points
public static List PointsInCircle3YZ(Vector3 center, float radius, int count)
{
return PointsInCircle3(1, 2, center, radius, count);
}
private static List PointsInCircle3(int xIndex, int yIndex, float radius, int count)
{
float currentAngle = 0;
var points = new List(count);
for (int i = 0; i < count; i++)
{
// The 0.5 offset improves the position of the first point
float r = Mathf.Sqrt((i + 0.5f)/count);
var point = new Vector3();
point[xIndex] = radius*Mathf.Sin(currentAngle)*r;
point[yIndex] = radius*Mathf.Cos(currentAngle)*r;
points.Add(point);
currentAngle += PTUtils.GoldenAngle;
}
return points;
}
private static List PointsInCircle3(int xIndex, int yIndex, Vector3 center, float radius, int count)
{
float currentAngle = 0;
var points = new List(count);
for (int i = 0; i < count; i++)
{
// The 0.5 offset improves the position of the first point
float r = Mathf.Sqrt((i + 0.5f)/count);
var point = new Vector3();
point[xIndex] = radius*Mathf.Sin(currentAngle)*r;
point[yIndex] = radius*Mathf.Cos(currentAngle)*r;
points.Add(center + point);
currentAngle += PTUtils.GoldenAngle;
}
return points;
}
#endregion PointsInCircle3
///
/// Returns a point on a sphere in geographic coordinate system
///
/// Sphere radius
/// Horizontal angle in degrees [0, 360]
/// Vertical angle in degrees [-90, 90]
public static Vector3 PointOnSphere(float radius, float horizontalAngle, float verticalAngle)
{
return PointOnSpheroid(radius, radius, horizontalAngle, verticalAngle);
}
///
/// Returns a point on a spheroid in geographic coordinate system
///
/// Spheroid radius
/// Spheroid height
/// Horizontal angle in degrees [0, 360]
/// Vertical angle in degrees [-90, 90]
public static Vector3 PointOnSpheroid(float radius, float height, float horizontalAngle, float verticalAngle)
{
float horizontalRadians = horizontalAngle*Mathf.Deg2Rad;
float verticalRadians = verticalAngle*Mathf.Deg2Rad;
float cosVertical = Mathf.Cos(verticalRadians);
return new Vector3(
x: radius*Mathf.Sin(horizontalRadians)*cosVertical,
y: height*Mathf.Sin(verticalRadians),
z: radius*Mathf.Cos(horizontalRadians)*cosVertical);
}
///
/// Returns a point on a teardrop surface in geographic coordinate system
///
/// Teardrop radius
/// Teardrop height
/// Horizontal angle in degrees [0, 360]
/// Vertical angle in degrees [-90, 90]
public static Vector3 PointOnTeardrop(float radius, float height, float horizontalAngle, float verticalAngle)
{
float horizontalRadians = horizontalAngle*Mathf.Deg2Rad;
float verticalRadians = verticalAngle*Mathf.Deg2Rad;
float sinVertical = Mathf.Sin(verticalRadians);
float teardrop = (1 - sinVertical)*Mathf.Cos(verticalRadians)/2;
return new Vector3(
x: radius*Mathf.Sin(horizontalRadians)*teardrop,
y: height*sinVertical,
z: radius*Mathf.Cos(horizontalRadians)*teardrop);
}
///
/// Returns a list of evenly distributed points on a sphere
///
/// Sphere radius
/// Number of points
public static List PointsOnSphere(float radius, int count)
{
var points = new List(count);
float deltaY = -2f/count;
float y = 1 + deltaY/2;
float currentAngle = 0;
for (int i = 0; i < count; i++)
{
float r = Mathf.Sqrt(1 - y*y);
points.Add(new Vector3(
x: radius*Mathf.Sin(currentAngle)*r,
y: radius*y,
z: radius*Mathf.Cos(currentAngle)*r));
y += deltaY;
currentAngle += PTUtils.GoldenAngle;
}
return points;
}
#endregion Point samplers 3D
///
/// Returns a list of points representing a polygon in the XY plane
///
/// Radius of the circle passing through the vertices
/// Number of polygon vertices
public static List Polygon2(int vertices, float radius)
{
return PointsOnCircle2(radius, vertices);
}
///
/// Returns a list of points representing a star polygon in the XY plane
///
/// Radius of the circle passing through the outer vertices
/// Radius of the circle passing through the inner vertices
/// Number of polygon vertices
public static List StarPolygon2(int vertices, float innerRadius, float outerRadius)
{
float segmentAngle = 360f/vertices;
float halfSegmentAngle = segmentAngle/2;
float currentAngle = 0;
var polygon = new List(vertices);
for (var i = 0; i < vertices; i++)
{
polygon.Add(PointOnCircle2(outerRadius, currentAngle));
polygon.Add(PointOnCircle2(innerRadius, currentAngle + halfSegmentAngle));
currentAngle += segmentAngle;
}
return polygon;
}
///
/// Returns the value of an angle. Assumes clockwise order of the polygon.
///
/// Previous vertex
/// Current vertex
/// Next vertex
public static float GetAngle(Vector2 previous, Vector2 current, Vector2 next)
{
Vector2 toPrevious = (previous - current).normalized;
Vector2 toNext = (next - current).normalized;
return VectorE.Angle360(toNext, toPrevious);
}
///
/// Returns the bisector of an angle. Assumes clockwise order of the polygon.
///
/// Previous vertex
/// Current vertex
/// Next vertex
/// Value of the angle in degrees. Always positive.
public static Vector2 GetAngleBisector(Vector2 previous, Vector2 current, Vector2 next, out float degrees)
{
Vector2 toPrevious = (previous - current).normalized;
Vector2 toNext = (next - current).normalized;
degrees = VectorE.Angle360(toNext, toPrevious);
Assert.IsFalse(float.IsNaN(degrees));
return toNext.RotateCW(degrees/2);
}
///
/// Creates a new offset polygon from the input polygon. Assumes clockwise order of the polygon.
/// Does not handle intersections.
///
/// Vertices of the polygon in clockwise order.
/// Offset distance. Positive values offset outside, negative inside.
public static List OffsetPolygon(IReadOnlyList polygon, float distance)
{
var newPolygon = new List(polygon.Count);
for (int i = 0; i < polygon.Count; i++)
{
Vector2 previous = polygon.GetLooped(i - 1);
Vector2 current = polygon[i];
Vector2 next = polygon.GetLooped(i + 1);
Vector2 bisector = GetAngleBisector(previous, current, next, out float angle);
float angleOffset = GetAngleOffset(distance, angle);
newPolygon.Add(current - bisector*angleOffset);
}
return newPolygon;
}
///
/// Offsets the input polygon. Assumes clockwise order of the polygon.
/// Does not handle intersections.
///
/// Vertices of the polygon in clockwise order.
/// Offset distance. Positive values offset outside, negative inside.
public static void OffsetPolygon(ref List polygon, float distance)
{
var offsets = new Vector2[polygon.Count];
for (int i = 0; i < polygon.Count; i++)
{
Vector2 previous = polygon.GetLooped(i - 1);
Vector2 current = polygon[i];
Vector2 next = polygon.GetLooped(i + 1);
Vector2 bisector = GetAngleBisector(previous, current, next, out float angle);
float angleOffset = GetAngleOffset(distance, angle);
offsets[i] = -bisector*angleOffset;
}
for (int i = 0; i < polygon.Count; i++)
{
polygon[i] += offsets[i];
}
}
///
/// Offsets the input polygon. Assumes clockwise order of the polygon.
/// Does not handle intersections.
///
/// Vertices of the polygon in clockwise order.
/// Offset distance. Positive values offset outside, negative inside.
public static void OffsetPolygon(ref Vector2[] polygon, float distance)
{
var offsets = new Vector2[polygon.Length];
for (int i = 0; i < polygon.Length; i++)
{
Vector2 previous = polygon.GetLooped(i - 1);
Vector2 current = polygon[i];
Vector2 next = polygon.GetLooped(i + 1);
Vector2 bisector = GetAngleBisector(previous, current, next, out float angle);
float angleOffset = GetAngleOffset(distance, angle);
offsets[i] = -bisector*angleOffset;
}
for (int i = 0; i < polygon.Length; i++)
{
polygon[i] += offsets[i];
}
}
public static float GetAngleOffset(float edgeOffset, float angle)
{
return edgeOffset/GetAngleBisectorSin(angle);
}
public static float GetAngleBisectorSin(float angle)
{
return Mathf.Sin(angle*Mathf.Deg2Rad/2);
}
///
/// Calculates the area of the input polygon.
///
/// Vertices of the polygon.
public static float GetArea(IReadOnlyList polygon)
{
return Mathf.Abs(GetSignedArea(polygon));
}
///
/// Calculates the signed area of the input polygon.
///
/// Vertices of the polygon.
public static float GetSignedArea(IReadOnlyList polygon)
{
if (polygon.Count < 3) return 0;
float a = 0;
for (int i = 0; i < polygon.Count; i++)
{
a += VectorE.PerpDot(polygon.GetLooped(i - 1), polygon[i]);
}
return a/2;
}
///
/// Calculates the orientation of the vertices of the input polygon.
///
/// Vertices of the polygon.
public static Orientation GetOrientation(IReadOnlyList polygon)
{
if (polygon.Count < 3) return Orientation.NonOrientable;
float signedArea = GetSignedArea(polygon);
if (signedArea < -Epsilon) return Orientation.Clockwise;
if (signedArea > Epsilon) return Orientation.CounterClockwise;
return Orientation.NonOrientable;
}
///
/// Calculates the perimeter of the input polygon.
///
/// Vertices of the polygon.
public static float GetPerimeter(IReadOnlyList polygon)
{
if (polygon.Count < 2) return 0;
float perimeter = 0;
for (var i = 0; i < polygon.Count; i++)
{
perimeter += Vector2.Distance(polygon.GetLooped(i - 1), polygon[i]);
}
return perimeter;
}
///
/// Calculates a bounding rect for a set of vertices.
///
public static Rect GetRect(IReadOnlyList vertices)
{
Vector2 min = vertices[0];
Vector2 max = vertices[0];
for (var i = 1; i < vertices.Count; i++)
{
var vertex = vertices[i];
min = Vector2.Min(min, vertex);
max = Vector2.Max(max, vertex);
}
return Rect.MinMaxRect(min.x, min.y, max.x, max.y);
}
///
/// Calculates a circumradius for a rectangle.
///
public static float GetCircumradius(Rect rect)
{
return GetCircumradius(rect.width, rect.height);
}
///
/// Calculates a circumradius for a rectangle.
///
public static float GetCircumradius(float width, float height)
{
return Mathf.Sqrt(width/2*width/2 + height/2*height/2);
}
}
}