HeavenStudioPlus/Assets/Plugins/NaughtyBezierCurves/Scripts/Core/BezierCurve3D.cs
2022-01-23 21:15:23 -05:00

393 lines
15 KiB
C#

using UnityEngine;
using System.Collections.Generic;
namespace NaughtyBezierCurves
{
public class BezierCurve3D : MonoBehaviour
{
// Serializable Fields
[SerializeField]
[Tooltip("The color used to render the curve")]
private Color curveColor = Color.green;
[SerializeField]
[Tooltip("The color used to render the start point of the curve")]
private Color startPointColor = Color.red;
[SerializeField]
[Tooltip("The color used to render the end point of the curve")]
private Color endPointColor = Color.blue;
[SerializeField]
[Tooltip("The number of segments that the curve has. Affects calculations and performance")]
private int sampling = 25;
[SerializeField]
[HideInInspector]
private List<BezierPoint3D> keyPoints = new List<BezierPoint3D>();
[SerializeField]
[Range(0f, 1f)]
float normalizedTime = 0.5f;
// Properties
public int Sampling
{
get
{
return this.sampling;
}
set
{
this.sampling = value;
}
}
public List<BezierPoint3D> KeyPoints
{
get
{
return this.keyPoints;
}
}
public int KeyPointsCount
{
get
{
return this.KeyPoints.Count;
}
}
// Public Methods
/// <summary>
/// Adds a key point at the end of the curve
/// </summary>
/// <returns>The new key point</returns>
public BezierPoint3D AddKeyPoint()
{
return this.AddKeyPointAt(this.KeyPointsCount);
}
/// <summary>
/// Add a key point at a specified index
/// </summary>
/// <param name="index">The index at which the key point will be added</param>
/// <returns>The new key point</returns>
public BezierPoint3D AddKeyPointAt(int index)
{
BezierPoint3D newPoint = new GameObject("Point " + this.KeyPoints.Count, typeof(BezierPoint3D)).GetComponent<BezierPoint3D>();
newPoint.Curve = this;
newPoint.transform.parent = this.transform;
newPoint.transform.localRotation = Quaternion.identity;
if (this.KeyPointsCount == 0 || this.KeyPointsCount == 1)
{
newPoint.LocalPosition = Vector3.zero;
}
else
{
if (index == 0)
{
newPoint.Position = (this.KeyPoints[0].Position - this.KeyPoints[1].Position).normalized + this.KeyPoints[0].Position;
}
else if (index == this.KeyPointsCount)
{
newPoint.Position = (this.KeyPoints[index - 1].Position - this.KeyPoints[index - 2].Position).normalized + this.KeyPoints[index - 1].Position;
}
else
{
newPoint.Position = BezierCurve3D.GetPointOnCubicCurve(0.5f, this.KeyPoints[index - 1], this.KeyPoints[index]);
}
}
this.KeyPoints.Insert(index, newPoint);
return newPoint;
}
/// <summary>
/// Removes a key point at a specified index
/// </summary>
/// <param name="index">The index of the key point that will be removed</param>
/// <returns>true - if the point was removed, false - otherwise</returns>
public bool RemoveKeyPointAt(int index)
{
if (this.KeyPointsCount < 2)
{
return false;
}
var point = this.KeyPoints[index];
this.KeyPoints.RemoveAt(index);
Destroy(point.gameObject);
return true;
}
/// <summary>
/// Evaluates a position along the curve at a specified normalized time [0, 1]
/// </summary>
/// <param name="time">The normalized length at which we want to get a position [0, 1]</param>
/// <returns>The evaluated Vector3 position</returns>
public Vector3 GetPoint(float time)
{
// The evaluated points is between these two points
BezierPoint3D startPoint;
BezierPoint3D endPoint;
float timeRelativeToSegment;
this.GetCubicSegment(time, out startPoint, out endPoint, out timeRelativeToSegment);
return BezierCurve3D.GetPointOnCubicCurve(timeRelativeToSegment, startPoint, endPoint);
}
public Quaternion GetRotation(float time, Vector3 up)
{
BezierPoint3D startPoint;
BezierPoint3D endPoint;
float timeRelativeToSegment;
this.GetCubicSegment(time, out startPoint, out endPoint, out timeRelativeToSegment);
return BezierCurve3D.GetRotationOnCubicCurve(timeRelativeToSegment, up, startPoint, endPoint);
}
public Vector3 GetTangent(float time)
{
BezierPoint3D startPoint;
BezierPoint3D endPoint;
float timeRelativeToSegment;
this.GetCubicSegment(time, out startPoint, out endPoint, out timeRelativeToSegment);
return BezierCurve3D.GetTangentOnCubicCurve(timeRelativeToSegment, startPoint, endPoint);
}
public Vector3 GetBinormal(float time, Vector3 up)
{
BezierPoint3D startPoint;
BezierPoint3D endPoint;
float timeRelativeToSegment;
this.GetCubicSegment(time, out startPoint, out endPoint, out timeRelativeToSegment);
return BezierCurve3D.GetBinormalOnCubicCurve(timeRelativeToSegment, up, startPoint, endPoint);
}
public Vector3 GetNormal(float time, Vector3 up)
{
BezierPoint3D startPoint;
BezierPoint3D endPoint;
float timeRelativeToSegment;
this.GetCubicSegment(time, out startPoint, out endPoint, out timeRelativeToSegment);
return BezierCurve3D.GetNormalOnCubicCurve(timeRelativeToSegment, up, startPoint, endPoint);
}
public float GetApproximateLength()
{
float length = 0;
int subCurveSampling = (this.Sampling / (this.KeyPointsCount - 1)) + 1;
for (int i = 0; i < this.KeyPointsCount - 1; i++)
{
length += BezierCurve3D.GetApproximateLengthOfCubicCurve(this.KeyPoints[i], this.KeyPoints[i + 1], subCurveSampling);
}
return length;
}
public void GetCubicSegment(float time, out BezierPoint3D startPoint, out BezierPoint3D endPoint, out float timeRelativeToSegment)
{
startPoint = null;
endPoint = null;
timeRelativeToSegment = 0f;
float subCurvePercent = 0f;
float totalPercent = 0f;
float approximateLength = this.GetApproximateLength();
int subCurveSampling = (this.Sampling / (this.KeyPointsCount - 1)) + 1;
for (int i = 0; i < this.KeyPointsCount - 1; i++)
{
subCurvePercent = BezierCurve3D.GetApproximateLengthOfCubicCurve(this.KeyPoints[i], this.KeyPoints[i + 1], subCurveSampling) / approximateLength;
if (subCurvePercent + totalPercent > time)
{
startPoint = this.KeyPoints[i];
endPoint = this.KeyPoints[i + 1];
break;
}
totalPercent += subCurvePercent;
}
if (endPoint == null)
{
// If the evaluated point is very near to the end of the curve we are in the last segment
startPoint = this.KeyPoints[this.KeyPointsCount - 2];
endPoint = this.KeyPoints[this.KeyPointsCount - 1];
// We remove the percentage of the last sub-curve
totalPercent -= subCurvePercent;
}
timeRelativeToSegment = (time - totalPercent) / subCurvePercent;
}
public static Vector3 GetPointOnCubicCurve(float time, BezierPoint3D startPoint, BezierPoint3D endPoint)
{
return GetPointOnCubicCurve(time, startPoint.Position, endPoint.Position, startPoint.RightHandlePosition, endPoint.LeftHandlePosition);
}
public static Vector3 GetPointOnCubicCurve(float time, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent)
{
float t = time;
float u = 1f - t;
float t2 = t * t;
float u2 = u * u;
float u3 = u2 * u;
float t3 = t2 * t;
Vector3 result =
(u3) * startPosition +
(3f * u2 * t) * startTangent +
(3f * u * t2) * endTangent +
(t3) * endPosition;
return result;
}
public static Quaternion GetRotationOnCubicCurve(float time, Vector3 up, BezierPoint3D startPoint, BezierPoint3D endPoint)
{
return GetRotationOnCubicCurve(time, up, startPoint.Position, endPoint.Position, startPoint.RightHandlePosition, endPoint.LeftHandlePosition);
}
public static Quaternion GetRotationOnCubicCurve(float time, Vector3 up, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent)
{
Vector3 tangent = GetTangentOnCubicCurve(time, startPosition, endPosition, startTangent, endTangent);
Vector3 normal = GetNormalOnCubicCurve(time, up, startPosition, endPosition, startTangent, endTangent);
return Quaternion.LookRotation(tangent, normal);
}
public static Vector3 GetTangentOnCubicCurve(float time, BezierPoint3D startPoint, BezierPoint3D endPoint)
{
return GetTangentOnCubicCurve(time, startPoint.Position, endPoint.Position, startPoint.RightHandlePosition, endPoint.LeftHandlePosition);
}
public static Vector3 GetTangentOnCubicCurve(float time, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent)
{
float t = time;
float u = 1f - t;
float u2 = u * u;
float t2 = t * t;
Vector3 tangent =
(-u2) * startPosition +
(u * (u - 2f * t)) * startTangent -
(t * (t - 2f * u)) * endTangent +
(t2) * endPosition;
return tangent.normalized;
}
public static Vector3 GetBinormalOnCubicCurve(float time, Vector3 up, BezierPoint3D startPoint, BezierPoint3D endPoint)
{
return GetBinormalOnCubicCurve(time, up, startPoint.Position, endPoint.Position, startPoint.RightHandlePosition, endPoint.LeftHandlePosition);
}
public static Vector3 GetBinormalOnCubicCurve(float time, Vector3 up, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent)
{
Vector3 tangent = GetTangentOnCubicCurve(time, startPosition, endPosition, startTangent, endTangent);
Vector3 binormal = Vector3.Cross(up, tangent);
return binormal.normalized;
}
public static Vector3 GetNormalOnCubicCurve(float time, Vector3 up, BezierPoint3D startPoint, BezierPoint3D endPoint)
{
return GetNormalOnCubicCurve(time, up, startPoint.Position, endPoint.Position, startPoint.RightHandlePosition, endPoint.LeftHandlePosition);
}
public static Vector3 GetNormalOnCubicCurve(float time, Vector3 up, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent)
{
Vector3 tangent = GetTangentOnCubicCurve(time, startPosition, endPosition, startTangent, endTangent);
Vector3 binormal = GetBinormalOnCubicCurve(time, up, startPosition, endPosition, startTangent, endTangent);
Vector3 normal = Vector3.Cross(tangent, binormal);
return normal.normalized;
}
public static float GetApproximateLengthOfCubicCurve(BezierPoint3D startPoint, BezierPoint3D endPoint, int sampling)
{
return GetApproximateLengthOfCubicCurve(startPoint.Position, endPoint.Position, startPoint.RightHandlePosition, endPoint.LeftHandlePosition, sampling);
}
public static float GetApproximateLengthOfCubicCurve(Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, int sampling)
{
float length = 0f;
Vector3 fromPoint = GetPointOnCubicCurve(0f, startPosition, endPosition, startTangent, endTangent);
for (int i = 0; i < sampling; i++)
{
float time = (i + 1) / (float)sampling;
Vector3 toPoint = GetPointOnCubicCurve(time, startPosition, endPosition, startTangent, endTangent);
length += Vector3.Distance(fromPoint, toPoint);
fromPoint = toPoint;
}
return length;
}
// Protected Methods
protected virtual void OnDrawGizmos()
{
if (this.KeyPointsCount > 1)
{
// Draw the curve
Vector3 fromPoint = this.GetPoint(0f);
for (int i = 0; i < this.Sampling; i++)
{
float time = (i + 1) / (float)this.Sampling;
Vector3 toPoint = this.GetPoint(time);
// Draw segment
Gizmos.color = this.curveColor;
Gizmos.DrawLine(fromPoint, toPoint);
fromPoint = toPoint;
}
// Draw the start and the end of the curve indicators
Gizmos.color = this.startPointColor;
Gizmos.DrawSphere(this.KeyPoints[0].Position, 0.05f);
Gizmos.color = this.endPointColor;
Gizmos.DrawSphere(this.KeyPoints[this.KeyPointsCount - 1].Position, 0.05f);
// Draw the point at the normalized time
Vector3 point = this.GetPoint(this.normalizedTime);
Gizmos.color = Color.yellow;
Gizmos.DrawSphere(point, 0.025f);
Vector3 tangent = this.GetTangent(this.normalizedTime);
Gizmos.color = Color.blue;
Gizmos.DrawLine(point, point + tangent / 2f);
Vector3 binormal = this.GetBinormal(this.normalizedTime, Vector3.up);
Gizmos.color = Color.red;
Gizmos.DrawLine(point, point + binormal / 2f);
Vector3 normal = this.GetNormal(this.normalizedTime, Vector3.up);
Gizmos.color = Color.green;
Gizmos.DrawLine(point, point + normal / 2f);
}
}
}
}