using System.Collections.Generic;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
///
/// Methods to create spline shapes.
///
public static class SplineFactory
{
///
/// Create a from a list of positions.
///
/// A collection of knot positions.
/// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).
/// A new Spline.
public static Spline CreateLinear(IList positions, bool closed = false)
{
return CreateLinear(positions, null, closed);
}
///
/// Create a from a list of positions.
///
/// A collection of knot positions.
/// A collection of knot rotations. Must be equal in length to the positions array.
/// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).
/// A new Spline.
public static Spline CreateLinear(IList positions, IList rotations, bool closed = false)
{
var knotCount = positions.Count;
var spline = new Spline(knotCount, closed);
for (int i = 0; i < knotCount; ++i)
{
var position = positions[i];
var rotation = rotations?[i] ?? quaternion.identity;
var tangentIn = float3.zero;
var tangentOut = float3.zero;
spline.Add(new BezierKnot(position, tangentIn, tangentOut, rotation), TangentMode.Linear);
}
return spline;
}
///
/// Create a from a list of positions and place tangents to create Catmull Rom curves.
///
/// A collection of knot positions.
/// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).
/// A new Spline.
public static Spline CreateCatmullRom(IList positions, bool closed = false)
{
return CreateCatmullRom(positions, null, closed);
}
///
/// Create a from a list of positions and place tangents to create Catmull Rom curves.
///
/// A collection of knot positions.
/// A collection of knot rotations. Must be equal in length to the positions array.
/// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).
/// A new Spline.
internal static Spline CreateCatmullRom(IList positions, IList rotations, bool closed = false)
{
var knotCount = positions.Count;
var spline = new Spline(knotCount, closed);
for (int i = 0; i < knotCount; ++i)
{
var position = positions[i];
var rotation = rotations?[i] ?? quaternion.identity;
var n = SplineUtility.NextIndex(i, knotCount, closed);
var p = SplineUtility.PreviousIndex(i, knotCount, closed);
var tangentOut = math.rotate(
math.inverse(rotation),
SplineUtility.GetAutoSmoothTangent(positions[p], positions[i], positions[n], SplineUtility.CatmullRomTension));
var tangentIn = -tangentOut;
spline.Add(new BezierKnot(position, tangentIn, tangentOut, rotation), TangentMode.AutoSmooth);
}
return spline;
}
///
/// Create a in a square shape with rounding at the edges.
///
/// The distance from center to outermost edge.
/// The amount of rounding to apply to corners.
/// A new Spline.
public static Spline CreateRoundedSquare(float radius, float rounding)
{
float3 p0 = new float3(-.5f, 0f, -.5f);
float3 p1 = new float3(-.5f, 0f, .5f);
float3 p2 = new float3( .5f, 0f, .5f);
float3 p3 = new float3( .5f, 0f, -.5f);
float3 tanIn = new float3(0f, 0f, -1f);
float3 tanOut = new float3(0f, 0f, 1f);
var spline = new Spline(new BezierKnot[]
{
new BezierKnot(p0 * radius, tanIn * rounding, tanOut * rounding, Quaternion.Euler(0f, -45f, 0f)),
new BezierKnot(p1 * radius, tanIn * rounding, tanOut * rounding, Quaternion.Euler(0f, 45f, 0f)),
new BezierKnot(p2 * radius, tanIn * rounding, tanOut * rounding, Quaternion.Euler(0f, 135f, 0f)),
new BezierKnot(p3 * radius, tanIn * rounding, tanOut * rounding, Quaternion.Euler(0f, -135f, 0f))
}, true);
for (int i = 0; i < spline.Count; ++i)
spline.SetTangentMode(i, TangentMode.Mirrored);
return spline;
}
///
/// Creates a in the shape of a helix with a single revolution.
///
/// The distance from the center to the helix's curve.
/// The height of the helix shape.
/// The number of revolutions the helix should have.
/// A new Spline.
public static Spline CreateHelix(float radius, float height, int revolutions)
{
revolutions = math.max(1, revolutions);
var revHeight = height / revolutions;
var alpha = 0.5f * math.PI;
var p = revHeight / (2f * math.PI);
var ax = radius * math.cos(alpha);
var az = radius * math.sin(alpha);
var b = p * alpha * (radius - ax) * (3f * radius - ax) / (az * (4f * radius - ax) * math.tan(alpha));
var yOffset = revHeight * 0.25f;
var p0 = new float3(ax, -alpha * p + yOffset, -az);
var p1 = new float3((4f * radius - ax) / 3f, -b + yOffset, -(radius - ax) * (3f * radius - ax) / (3f * az));
var p2 = new float3((4f * radius - ax) / 3f, b + yOffset, (radius - ax) * (3f * radius - ax) / (3f * az));
var p3 = new float3(ax, alpha * p + yOffset, az);
Spline spline = new Spline();
// Create the first two points and tangents forming the first half of the helix.
var tangent = p1 - p0;
var tangentLength = math.length(tangent);
var tangentNorm = math.normalize(tangent);
var normal = math.cross(math.cross(tangentNorm, math.up()), tangentNorm);
spline.Add(new BezierKnot(p0, new float3(0f, 0f, -tangentLength), new float3(0f, 0f, tangentLength), quaternion.LookRotation(tangentNorm, normal)));
tangent = p3 - p2;
tangentNorm = math.normalize(tangent);
normal = math.cross(math.cross(tangentNorm, math.up()), tangentNorm);
spline.Add(new BezierKnot(p3, new float3(0f, 0f, -tangentLength), new float3(0f, 0f, tangentLength), quaternion.LookRotation(tangentNorm, normal)));
// Rotate and offset the first half to form a full single revolution helix.
var rotation = quaternion.AxisAngle(math.up(), math.radians(180f));
yOffset = revHeight * 0.5f;
p3 = math.rotate(rotation, p3);
p3.y += yOffset;
tangent = p1 - p0;
tangentNorm = math.normalize(tangent);
normal = math.cross(math.cross(tangentNorm, math.up()), tangentNorm);
spline.Add(new BezierKnot(p3, new float3(0f, 0f, -tangentLength), new float3(0f, 0f, tangentLength), quaternion.LookRotation(tangentNorm, normal)));
// Create knots for remaining revolutions
var revYOffset = new float3(0f, revHeight, 0f);
for (int i = 1; i < revolutions; ++i)
{
var knotA = spline[^1];
knotA.Position += revYOffset;
var knotB = spline[^2];
knotB.Position += revYOffset;
spline.Add(knotB);
spline.Add(knotA);
}
return spline;
}
///
/// Creates a in the shape of a square with circular arcs at its corners.
///
/// The size of the square's edges.
/// The radius of the circular arcs at the corners of the shape.
/// A value of 0 creates a square with no rounding. A value that is half of creates a circle.
/// The range for is 0 and half of .
/// A new Spline.
public static Spline CreateRoundedCornerSquare(float size, float cornerRadius)
{
float radius = size * 0.5f;
cornerRadius = math.clamp(cornerRadius, 0f, radius);
if (cornerRadius == 0f)
return CreateSquare(size);
float3 cornerP0 = new float3(-radius, 0f, radius - cornerRadius);
float3 cornerP1 = new float3(-radius + cornerRadius, 0f, radius);
float cornerTangentLen = SplineMath.GetUnitCircleTangentLength() * cornerRadius;
float cornerAngle = 0f;
var spline = new Spline();
for (int i = 0; i < 4; i++)
{
var rotation = Quaternion.Euler(0f, cornerAngle, 0f);
if (cornerRadius < 1f)
{
spline.Add(new BezierKnot(rotation * cornerP0, new float3(0f, 0f, -math.min(cornerTangentLen, 0f)), new float3(0f, 0f, cornerTangentLen), Quaternion.identity * rotation));
spline.Add(new BezierKnot(rotation * cornerP1, new float3(0f, 0f, -cornerTangentLen), new float3(0f, 0f, math.min(cornerTangentLen, 0f)), Quaternion.Euler(0f, 90f, 0f) * rotation));
}
else
spline.Add(new BezierKnot(rotation * cornerP0, new float3(0f, 0f, -cornerTangentLen), new float3(0f, 0f, cornerTangentLen), Quaternion.identity * rotation));
cornerAngle += 90f;
}
spline.Closed = true;
return spline;
}
///
/// Creates a in the shape of a square with sharp corners.
///
/// The size of the square's edges.
/// A new Spline.
public static Spline CreateSquare(float size)
{
float3 p0 = new float3(-.5f, 0f, -.5f) * size;
float3 p1 = new float3(-.5f, 0f, .5f) * size;
float3 p2 = new float3( .5f, 0f, .5f) * size;
float3 p3 = new float3( .5f, 0f, -.5f) * size;
return CreateLinear(new float3[] { p0, p1, p2, p3 }, true);
}
///
/// Creates a in the shape of a circle.
///
/// The radius of the circle.
/// A new Spline.
public static Spline CreateCircle(float radius)
{
float3 point = new float3(-radius, 0f, 0);
float3 tangent = new float3(0f, 0f, SplineMath.GetUnitCircleTangentLength() * radius);
var spline = new Spline();
quaternion rotation = quaternion.identity;
for (int i = 0; i < 4; i++)
{
spline.Add(new BezierKnot(math.rotate(rotation, point), -tangent, tangent, rotation));
rotation = math.mul(rotation, quaternion.AxisAngle(math.up(), math.PI * 0.5f));
}
spline.Closed = true;
return spline;
}
///
/// Creates a in the shape of a polygon with a specific number of sides.
///
/// The size of the polygon's edges.
/// The amount of sides the polygon has.
/// A new Spline.
public static Spline CreatePolygon(float edgeSize, int sides)
{
sides = math.max(3, sides);
var points = new float3[sides];
var angleStep = 2f * math.PI / sides;
var radius = edgeSize * 0.5f / math.sin(angleStep * 0.5f);
var point = new float3(0f, 0f, radius);
var rotation = quaternion.identity;
for (int i = 0; i < sides; ++i)
{
points[i] = math.rotate(rotation, point);
rotation = math.mul(rotation, quaternion.AxisAngle(math.up(), angleStep));
}
return CreateLinear(points, true);
}
///
/// Creates a in in the shape of a star with a specified number of corners.
///
/// The distance between the corners of the star.
/// The amount of corners the star has.
/// The sharpness of the corners. The range is 0 through 1.
/// A new Spline.
public static Spline CreateStarPolygon(float edgeSize, int corners, float concavity)
{
concavity = math.clamp(concavity, 0f, 1f);
if (concavity == 0f)
CreatePolygon(edgeSize, corners);
corners = math.max(3, corners);
var sidesDouble = corners * 2;
var points = new float3[sidesDouble];
var angleStep = 2f * math.PI / corners;
var radius = edgeSize * 0.5f / math.sin(angleStep * 0.5f);
var point = new float3(0f, 0f, radius);
var rotation = quaternion.identity;
for (int i = 0; i < sidesDouble; i+= 2)
{
points[i] = math.rotate(rotation, point);
rotation = math.mul(rotation, quaternion.AxisAngle(math.up(), angleStep));
if (i != 0)
points[i - 1] = (points[i - 2] + points[i]) * 0.5f * (1f - concavity);
if (i == sidesDouble - 2)
points[i + 1] = (points[0] + points[i]) * 0.5f * (1f - concavity);
}
return CreateLinear(points, true);
}
}
}