To make use of the Bezier class from the last post, here is a Spline class. It is not the most grabage friendly class, so I might revisit it at some time, but it does what I wanted it to do when I wrote it. As I said it uses the Bezier class for smooth curves, but there is much automatisation, so no user-input is needed/possible to define the typical Bezier handles. This has the drawback that there is a risk of overshooting, if the distance between spline-vertices varies too much. Splines can be smooth or segmented, closed or open, and are visualized via a LineRenderer.
As usual, the most current code is found on my Bitbucket repository.
<pre>using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Utilities { public class Spline { public Vector3[] vertices { get; private set; } private bool _closed; private bool _smooth; private float _thickness; private bool _isInitialized; private GameObject _visualizationObject; private LineRenderer _visualizationRenderer; public struct Segment { public Vector3 start; public Vector3 end; public Vector3 A, B; public float length; public float startLength; }; private Segment[] _segments; public float length { get; private set; } public bool visible { get { return _visualizationRenderer.enabled; } set { _visualizationRenderer.enabled = value; } } private Spline() {} public static Spline Create(Vector3[] points, Material material, float thickness = 0.1f, bool smooth = true, bool closed = true) { if (points.Length < 2) return null; Spline spline = new Spline { vertices = points, _smooth = smooth, _closed = closed, _thickness = thickness, _visualizationObject = new GameObject("Spline Vertex") {layer = GeneralUtilities.GIZMOS_LAYER} }; spline._visualizationRenderer = spline._visualizationObject.AddComponent<LineRenderer>(); spline._visualizationRenderer.material = material; spline.Setup(); return spline; } public void Update(Vector3[] points) { vertices = points; Setup(); } void Setup() { if (vertices.Length < 2) return; _segments = new Segment[_closed ? vertices.Length : vertices.Length - 1]; length = 0; if (!_smooth || vertices.Length == 2) { for (int i = 0; i < vertices.Length - 1; ++i) { float l = (vertices[i + 1] - vertices[i]).magnitude; _segments[i] = new Segment { start = vertices[i], end = vertices[i + 1], length = l, startLength = length }; length += l; } if (_closed) { float l = (vertices[0] - vertices[vertices.Length - 1]).magnitude; _segments[_segments.Length - 1] = new Segment { start = vertices[vertices.Length - 1], end = vertices[0], length = l, startLength = length }; length += l; } } else if (_closed) { for (int i = 0; i < vertices.Length; ++i) { Vector3 start = vertices[i]; Vector3 end = vertices[(i + 1) % vertices.Length]; Vector3 controlPointA = start + (end - vertices[(i - 1 + vertices.Length) % vertices.Length]) / 4; Vector3 controlPointB = end + (start - vertices[(i + 2) % vertices.Length]) / 4; float l = Bezier.Length(start, end, controlPointA, controlPointB, 10); _segments[i] = new Segment { start = start, end = end, length = l, startLength = length, A = controlPointA, B = controlPointB }; length += l; } } else { Vector3 start = vertices[0]; Vector3 end = vertices[1]; Vector3 controlPointB = end + (start - vertices[2]) / 4; Vector3 controlPointA = (controlPointB + 2 * start) / 3; float l = Bezier.Length(start, end, controlPointA, controlPointB, 10); _segments[0] = new Segment { start = start, end = end, length = l, startLength = 0, A = controlPointA, B = controlPointB }; length += l; for (int i = 1; i < vertices.Length - 2; ++i) { start = vertices[i]; end = vertices[i + 1]; controlPointA = start + (end - vertices[i - 1]) / 4; controlPointB = end + (start - vertices[i + 2]) / 4; l = Bezier.Length(start, end, controlPointA, controlPointB, 10); _segments[i] = new Segment { start = start, end = end, length = l, startLength = length, A = controlPointA, B = controlPointB }; length += l; } start = vertices[vertices.Length - 2]; end = vertices[vertices.Length - 1]; controlPointA = start + (end - vertices[vertices.Length - 3]) / 4; controlPointB = (controlPointA + 2 * end) / 3; l = Bezier.Length(start, end, controlPointA, controlPointB, 10); _segments[vertices.Length - 2] = new Segment { start = start, end = end, length = l, startLength = length, A = controlPointA, B = controlPointB }; length += l; } _isInitialized = true; } public void SetMaterial(Material material) { _visualizationRenderer.material = material; } public void SetColor(Color color) { _visualizationRenderer.material.color = color; } public void Draw() { if (vertices.Length < 2) { _visualizationRenderer.SetVertexCount(0); return; } if (!_isInitialized) Setup(); List<Vector3> segmentPoints; if (!_smooth || vertices.Length == 2) { segmentPoints = _segments.Select(s => s.start).ToList(); } else { segmentPoints = _segments.SelectMany(s => GetCurvePoints(s)).ToList(); } segmentPoints.Add(_segments[_segments.Length - 1].end); _visualizationRenderer.SetVertexCount(segmentPoints.Count); _visualizationRenderer.SetPositions(segmentPoints.ToArray()); _visualizationRenderer.SetWidth(_thickness, _thickness); visible = true; } public void Hide() { visible = false; } public void Clear() { vertices = null; Hide(); } private static Vector3[] GetCurvePoints(Segment segment, int segments = 10) { Vector3[] positions = new Vector3[segments]; for (int i = 0; i < segments; ++i) { positions[i] = Bezier.Interpolate(segment.start, segment.end, segment.A, segment.B, (float) i / segments); } return positions; } public Vector3 GetPoint(float alongPath) { if (!_isInitialized) { Setup(); } if (alongPath >= 1) { return vertices[vertices.Length - 1]; } if (alongPath <= 0) { return vertices[0]; } if (vertices.Length == 2) { return Vector3.Lerp(vertices[0], vertices[1], alongPath); } float alongLength = alongPath * length; int segIndex = 1; while (segIndex < _segments.Length && _segments[segIndex].startLength < alongLength) { segIndex++; } Segment seg = _segments[segIndex - 1]; float localPercentage = (alongLength - seg.startLength) / (seg.length); return !_smooth ? Vector3.Lerp(seg.start, seg.end, localPercentage) : Bezier.Interpolate(seg.start, seg.end, seg.A, seg.B, localPercentage); } } }
Comments