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); } } }