diff options
Diffstat (limited to 'Assets/ThirdParty/BezierCurves/Scripts/BezierCurve.cs')
-rw-r--r-- | Assets/ThirdParty/BezierCurves/Scripts/BezierCurve.cs | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/Assets/ThirdParty/BezierCurves/Scripts/BezierCurve.cs b/Assets/ThirdParty/BezierCurves/Scripts/BezierCurve.cs new file mode 100644 index 00000000..6e28fa14 --- /dev/null +++ b/Assets/ThirdParty/BezierCurves/Scripts/BezierCurve.cs @@ -0,0 +1,557 @@ +#region UsingStatements + +using UnityEngine; +using System; +using System.Collections; +using System.Collections.Generic; + +#endregion + +/// <summary> +/// - Class for describing and drawing Bezier Curves +/// - Efficiently handles approximate length calculation through 'dirty' system +/// - Has static functions for getting points on curves constructed by Vector3 parameters (GetPoint, GetCubicPoint, GetQuadraticPoint, and GetLinearPoint) +/// </summary> +[ExecuteInEditMode] +[Serializable] +public class BezierCurve : MonoBehaviour { + + #region PublicVariables + + /// <summary> + /// - the number of mid-points calculated for each pair of bezier points + /// - used for drawing the curve in the editor + /// - used for calculating the "length" variable + /// </summary> + public int resolution = 30; + + /// <summary> + /// Gets or sets a value indicating whether this <see cref="BezierCurve"/> is dirty. + /// </summary> + /// <value> + /// <c>true</c> if dirty; otherwise, <c>false</c>. + /// </value> + public bool dirty { get; private set; } + + /// <summary> + /// - color this curve will be drawn with in the editor + /// - set in the editor + /// </summary> + public Color drawColor = Color.white; + + #endregion + + #region PublicProperties + + /// <summary> + /// - set in the editor + /// - used to determine if the curve should be drawn as "closed" in the editor + /// - used to determine if the curve's length should include the curve between the first and the last points in "points" array + /// - setting this value will cause the curve to become dirty + /// </summary> + [SerializeField] private bool _close; + public bool close + { + get { return _close; } + set + { + if(_close == value) return; + _close = value; + dirty = true; + } + } + + /// <summary> + /// - set internally + /// - gets point corresponding to "index" in "points" array + /// - does not allow direct set + /// </summary> + /// <param name='index'> + /// - the index + /// </param> + public BezierPoint this[int index] + { + get { return points[index]; } + } + + /// <summary> + /// - number of points stored in 'points' variable + /// - set internally + /// - does not include "handles" + /// </summary> + /// <value> + /// - The point count + /// </value> + public int pointCount + { + get { return points.Length; } + } + + /// <summary> + /// - The approximate length of the curve + /// - recalculates if the curve is "dirty" + /// </summary> + private float _length; + public float length + { + get + { + if(dirty) + { + _length = 0; + for(int i = 0; i < points.Length - 1; i++){ + _length += ApproximateLength(points[i], points[i + 1], resolution); + } + + if(close) _length += ApproximateLength(points[points.Length - 1], points[0], resolution); + + dirty = false; + } + + return _length; + } + } + + #endregion + + #region PrivateVariables + + /// <summary> + /// - Array of point objects that make up this curve + /// - Populated through editor + /// </summary> + [SerializeField] private BezierPoint[] points = new BezierPoint[0]; + + #endregion + + #region UnityFunctions + + void OnDrawGizmos () { + Gizmos.color = drawColor; + + if(points.Length > 1){ + for(int i = 0; i < points.Length - 1; i++){ + DrawCurve(points[i], points[i+1], resolution); + } + + if (close) DrawCurve(points[points.Length - 1], points[0], resolution); + } + } + + void Awake(){ + dirty = true; + } + + #endregion + + #region PublicFunctions + + /// <summary> + /// - Adds the given point to the end of the curve ("points" array) + /// </summary> + /// <param name='point'> + /// - The point to add. + /// </param> + public void AddPoint(BezierPoint point) + { + List<BezierPoint> tempArray = new List<BezierPoint>(points); + tempArray.Add(point); + points = tempArray.ToArray(); + dirty = true; + } + + /// <summary> + /// - Adds a point at position + /// </summary> + /// <returns> + /// - The point object + /// </returns> + /// <param name='position'> + /// - Where to add the point + /// </param> + public BezierPoint AddPointAt(Vector3 position) + { + GameObject pointObject = new GameObject("Point "+pointCount); + + pointObject.transform.parent = transform; + pointObject.transform.position = position; + + BezierPoint newPoint = pointObject.AddComponent<BezierPoint>(); + newPoint.curve = this; + + return newPoint; + } + + /// <summary> + /// - Removes the given point from the curve ("points" array) + /// </summary> + /// <param name='point'> + /// - The point to remove + /// </param> + public void RemovePoint(BezierPoint point) + { + List<BezierPoint> tempArray = new List<BezierPoint>(points); + tempArray.Remove(point); + points = tempArray.ToArray(); + dirty = false; + } + + /// <summary> + /// - Gets a copy of the bezier point array used to define this curve + /// </summary> + /// <returns> + /// - The cloned array of points + /// </returns> + public BezierPoint[] GetAnchorPoints() + { + return (BezierPoint[])points.Clone(); + } + + /// <summary> + /// - Gets the point at 't' percent along this curve + /// </summary> + /// <returns> + /// - Returns the point at 't' percent + /// </returns> + /// <param name='t'> + /// - Value between 0 and 1 representing the percent along the curve (0 = 0%, 1 = 100%) + /// </param> + public Vector3 GetPointAt(float t) + { + if(t <= 0) return points[0].position; + else if (t >= 1) return points[points.Length - 1].position; + + float totalPercent = 0; + float curvePercent = 0; + + BezierPoint p1 = null; + BezierPoint p2 = null; + + for(int i = 0; i < points.Length - 1; i++) + { + curvePercent = ApproximateLength(points[i], points[i + 1], 10) / length; + if(totalPercent + curvePercent > t) + { + p1 = points[i]; + p2 = points[i + 1]; + break; + } + + else totalPercent += curvePercent; + } + + if(close && p1 == null) + { + p1 = points[points.Length - 1]; + p2 = points[0]; + } + + t -= totalPercent; + + return GetPoint(p1, p2, t / curvePercent); + } + + /// <summary> + /// - Get the index of the given point in this curve + /// </summary> + /// <returns> + /// - The index, or -1 if the point is not found + /// </returns> + /// <param name='point'> + /// - Point to search for + /// </param> + public int GetPointIndex(BezierPoint point) + { + int result = -1; + for(int i = 0; i < points.Length; i++) + { + if(points[i] == point) + { + result = i; + break; + } + } + + return result; + } + + /// <summary> + /// - Sets this curve to 'dirty' + /// - Forces the curve to recalculate its length + /// </summary> + public void SetDirty() + { + dirty = true; + } + + #endregion + + #region PublicStaticFunctions + + /// <summary> + /// - Draws the curve in the Editor + /// </summary> + /// <param name='p1'> + /// - The bezier point at the beginning of the curve + /// </param> + /// <param name='p2'> + /// - The bezier point at the end of the curve + /// </param> + /// <param name='resolution'> + /// - The number of segments along the curve to draw + /// </param> + public static void DrawCurve(BezierPoint p1, BezierPoint p2, int resolution) + { + int limit = resolution+1; + float _res = resolution; + Vector3 lastPoint = p1.position; + Vector3 currentPoint = Vector3.zero; + + for(int i = 1; i < limit; i++){ + currentPoint = GetPoint(p1, p2, i/_res); + Gizmos.DrawLine(lastPoint, currentPoint); + lastPoint = currentPoint; + } + } + + /// <summary> + /// - Gets the point 't' percent along a curve + /// - Automatically calculates for the number of relevant points + /// </summary> + /// <returns> + /// - The point 't' percent along the curve + /// </returns> + /// <param name='p1'> + /// - The bezier point at the beginning of the curve + /// </param> + /// <param name='p2'> + /// - The bezier point at the end of the curve + /// </param> + /// <param name='t'> + /// - Value between 0 and 1 representing the percent along the curve (0 = 0%, 1 = 100%) + /// </param> + public static Vector3 GetPoint(BezierPoint p1, BezierPoint p2, float t) + { + if(p1.handle2 != Vector3.zero) + { + if(p2.handle1 != Vector3.zero) return GetCubicCurvePoint(p1.position, p1.globalHandle2, p2.globalHandle1, p2.position, t); + else return GetQuadraticCurvePoint(p1.position, p1.globalHandle2, p2.position, t); + } + + else + { + if(p2.handle1 != Vector3.zero) return GetQuadraticCurvePoint(p1.position, p2.globalHandle1, p2.position, t); + else return GetLinearPoint(p1.position, p2.position, t); + } + } + + /// <summary> + /// - Gets the point 't' percent along a third-order curve + /// </summary> + /// <returns> + /// - The point 't' percent along the curve + /// </returns> + /// <param name='p1'> + /// - The point at the beginning of the curve + /// </param> + /// <param name='p2'> + /// - The second point along the curve + /// </param> + /// <param name='p3'> + /// - The third point along the curve + /// </param> + /// <param name='p4'> + /// - The point at the end of the curve + /// </param> + /// <param name='t'> + /// - Value between 0 and 1 representing the percent along the curve (0 = 0%, 1 = 100%) + /// </param> + public static Vector3 GetCubicCurvePoint(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float t) + { + t = Mathf.Clamp01(t); + + Vector3 part1 = Mathf.Pow(1 - t, 3) * p1; + Vector3 part2 = 3 * Mathf.Pow(1 - t, 2) * t * p2; + Vector3 part3 = 3 * (1 - t) * Mathf.Pow(t, 2) * p3; + Vector3 part4 = Mathf.Pow(t, 3) * p4; + + return part1 + part2 + part3 + part4; + } + + /// <summary> + /// - Gets the point 't' percent along a second-order curve + /// </summary> + /// <returns> + /// - The point 't' percent along the curve + /// </returns> + /// <param name='p1'> + /// - The point at the beginning of the curve + /// </param> + /// <param name='p2'> + /// - The second point along the curve + /// </param> + /// <param name='p3'> + /// - The point at the end of the curve + /// </param> + /// <param name='t'> + /// - Value between 0 and 1 representing the percent along the curve (0 = 0%, 1 = 100%) + /// </param> + public static Vector3 GetQuadraticCurvePoint(Vector3 p1, Vector3 p2, Vector3 p3, float t) + { + t = Mathf.Clamp01(t); + + Vector3 part1 = Mathf.Pow(1 - t, 2) * p1; + Vector3 part2 = 2 * (1 - t) * t * p2; + Vector3 part3 = Mathf.Pow(t, 2) * p3; + + return part1 + part2 + part3; + } + + /// <summary> + /// - Gets point 't' percent along a linear "curve" (line) + /// - This is exactly equivalent to Vector3.Lerp + /// </summary> + /// <returns> + /// - The point 't' percent along the curve + /// </returns> + /// <param name='p1'> + /// - The point at the beginning of the line + /// </param> + /// <param name='p2'> + /// - The point at the end of the line + /// </param> + /// <param name='t'> + /// - Value between 0 and 1 representing the percent along the line (0 = 0%, 1 = 100%) + /// </param> + public static Vector3 GetLinearPoint(Vector3 p1, Vector3 p2, float t) + { + return p1 + ((p2 - p1) * t); + } + + /// <summary> + /// - Gets point 't' percent along n-order curve + /// </summary> + /// <returns> + /// - The point 't' percent along the curve + /// </returns> + /// <param name='t'> + /// - Value between 0 and 1 representing the percent along the curve (0 = 0%, 1 = 100%) + /// </param> + /// <param name='points'> + /// - The points used to define the curve + /// </param> + public static Vector3 GetPoint(float t, params Vector3[] points){ + t = Mathf.Clamp01(t); + + int order = points.Length-1; + Vector3 point = Vector3.zero; + Vector3 vectorToAdd; + + for(int i = 0; i < points.Length; i++){ + vectorToAdd = points[points.Length-i-1] * (BinomialCoefficient(i, order) * Mathf.Pow(t, order-i) * Mathf.Pow((1-t), i)); + point += vectorToAdd; + } + + return point; + } + + /// <summary> + /// - Approximates the length + /// </summary> + /// <returns> + /// - The approximate length + /// </returns> + /// <param name='p1'> + /// - The bezier point at the start of the curve + /// </param> + /// <param name='p2'> + /// - The bezier point at the end of the curve + /// </param> + /// <param name='resolution'> + /// - The number of points along the curve used to create measurable segments + /// </param> + public static float ApproximateLength(BezierPoint p1, BezierPoint p2, int resolution = 10) + { + float _res = resolution; + float total = 0; + Vector3 lastPosition = p1.position; + Vector3 currentPosition; + + for(int i = 0; i < resolution + 1; i++) + { + currentPosition = GetPoint(p1, p2, i / _res); + total += (currentPosition - lastPosition).magnitude; + lastPosition = currentPosition; + } + + return total; + } + + #endregion + + #region UtilityFunctions + + private static int BinomialCoefficient(int i, int n){ + return Factoral(n)/(Factoral(i)*Factoral(n-i)); + } + + private static int Factoral(int i){ + if(i == 0) return 1; + + int total = 1; + + while(i-1 >= 0){ + total *= i; + i--; + } + + return total; + } + + #endregion + + /* needs testing + public Vector3 GetPointAtDistance(float distance) + { + if(close) + { + if(distance < 0) while(distance < 0) { distance += length; } + else if(distance > length) while(distance > length) { distance -= length; } + } + + else + { + if(distance <= 0) return points[0].position; + else if(distance >= length) return points[points.Length - 1].position; + } + + float totalLength = 0; + float curveLength = 0; + + BezierPoint firstPoint = null; + BezierPoint secondPoint = null; + + for(int i = 0; i < points.Length - 1; i++) + { + curveLength = ApproximateLength(points[i], points[i + 1], resolution); + if(totalLength + curveLength >= distance) + { + firstPoint = points[i]; + secondPoint = points[i+1]; + break; + } + else totalLength += curveLength; + } + + if(firstPoint == null) + { + firstPoint = points[points.Length - 1]; + secondPoint = points[0]; + curveLength = ApproximateLength(firstPoint, secondPoint, resolution); + } + + distance -= totalLength; + return GetPoint(distance / curveLength, firstPoint, secondPoint); + } + */ +}
\ No newline at end of file |