517 lines
11 KiB
C#
517 lines
11 KiB
C#
using UnityEngine;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections;
|
|
|
|
public class BezierSpline : MonoBehaviour
|
|
{
|
|
public List<splinePointInfo> splinePointList = new List<splinePointInfo> ();
|
|
|
|
public List<Transform> lookDirectionList = new List<Transform> ();
|
|
|
|
public int selectedIndex = -1;
|
|
|
|
public float directionScale = 0.5f;
|
|
|
|
public float newPointOffset = 0.2f;
|
|
public float newPointBezierOffset = 0.2f;
|
|
|
|
public float handleSize = 0.04f;
|
|
public float pickSize = 0.06f;
|
|
|
|
public bool showGizmo;
|
|
public bool showLookDirectionGizmo;
|
|
public float lookDirectionGizmoRadius = 0.05f;
|
|
public float lookDirectionArrowLength = 1;
|
|
|
|
public enum BezierControlPointMode
|
|
{
|
|
Aligned,
|
|
Free,
|
|
Mirrored
|
|
}
|
|
|
|
[SerializeField]
|
|
public List<Vector3> points = new List<Vector3> ();
|
|
|
|
[SerializeField]
|
|
public List<BezierControlPointMode> modes = new List<BezierControlPointMode> ();
|
|
|
|
[SerializeField]
|
|
private bool loop;
|
|
|
|
public bool Loop {
|
|
get {
|
|
return loop;
|
|
}
|
|
set {
|
|
loop = value;
|
|
if (value == true) {
|
|
modes [modes.Count - 1] = modes [0];
|
|
SetControlPoint (0, points [0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public int ControlPointCount {
|
|
get {
|
|
return points.Count;
|
|
}
|
|
}
|
|
|
|
public Vector3 GetControlPoint (int index)
|
|
{
|
|
return points [index];
|
|
}
|
|
|
|
public void SetControlPoint (int index, Vector3 point)
|
|
{
|
|
if (index % 3 == 0) {
|
|
Vector3 delta = point - points [index];
|
|
if (loop) {
|
|
if (index == 0) {
|
|
points [1] += delta;
|
|
points [points.Count - 2] += delta;
|
|
points [points.Count - 1] = point;
|
|
} else if (index == points.Count - 1) {
|
|
points [0] = point;
|
|
points [1] += delta;
|
|
points [index - 1] += delta;
|
|
} else {
|
|
points [index - 1] += delta;
|
|
points [index + 1] += delta;
|
|
}
|
|
} else {
|
|
if (index > 0) {
|
|
points [index - 1] += delta;
|
|
}
|
|
if (index + 1 < points.Count) {
|
|
points [index + 1] += delta;
|
|
}
|
|
}
|
|
}
|
|
points [index] = point;
|
|
EnforceMode (index);
|
|
}
|
|
|
|
public BezierControlPointMode GetControlPointMode (int index)
|
|
{
|
|
return modes [(index + 1) / 3];
|
|
}
|
|
|
|
public void SetControlPointMode (int index, BezierControlPointMode mode)
|
|
{
|
|
int modeIndex = (index + 1) / 3;
|
|
modes [modeIndex] = mode;
|
|
if (loop) {
|
|
if (modeIndex == 0) {
|
|
modes [modes.Count - 1] = mode;
|
|
} else if (modeIndex == modes.Count - 1) {
|
|
modes [0] = mode;
|
|
}
|
|
}
|
|
EnforceMode (index);
|
|
}
|
|
|
|
private void EnforceMode (int index)
|
|
{
|
|
int modeIndex = (index + 1) / 3;
|
|
BezierControlPointMode mode = modes [modeIndex];
|
|
if (mode == BezierControlPointMode.Free || !loop && (modeIndex == 0 || modeIndex == modes.Count - 1)) {
|
|
return;
|
|
}
|
|
|
|
int middleIndex = modeIndex * 3;
|
|
int fixedIndex, enforcedIndex;
|
|
if (index <= middleIndex) {
|
|
fixedIndex = middleIndex - 1;
|
|
if (fixedIndex < 0) {
|
|
fixedIndex = points.Count - 2;
|
|
}
|
|
enforcedIndex = middleIndex + 1;
|
|
if (enforcedIndex >= points.Count) {
|
|
enforcedIndex = 1;
|
|
}
|
|
} else {
|
|
fixedIndex = middleIndex + 1;
|
|
if (fixedIndex >= points.Count) {
|
|
fixedIndex = 1;
|
|
}
|
|
enforcedIndex = middleIndex - 1;
|
|
if (enforcedIndex < 0) {
|
|
enforcedIndex = points.Count - 2;
|
|
}
|
|
}
|
|
|
|
Vector3 middle = points [middleIndex];
|
|
Vector3 enforcedTangent = middle - points [fixedIndex];
|
|
if (mode == BezierControlPointMode.Aligned) {
|
|
enforcedTangent = enforcedTangent.normalized * GKC_Utils.distance (middle, points [enforcedIndex]);
|
|
}
|
|
points [enforcedIndex] = middle + enforcedTangent;
|
|
}
|
|
|
|
public int CurveCount {
|
|
get {
|
|
return (points.Count - 1) / 3;
|
|
}
|
|
}
|
|
|
|
public Vector3 GetPoint (float t)
|
|
{
|
|
int i;
|
|
if (t >= 1f) {
|
|
t = 1f;
|
|
i = points.Count - 4;
|
|
} else {
|
|
t = Mathf.Clamp01 (t) * CurveCount;
|
|
i = (int)t;
|
|
t -= i;
|
|
i *= 3;
|
|
}
|
|
|
|
return transform.TransformPoint (GetPoint (points [i], points [i + 1], points [i + 2], points [i + 3], t));
|
|
}
|
|
|
|
public Vector3 GetLookDirection (float t)
|
|
{
|
|
t = Mathf.Clamp01 (t) * CurveCount;
|
|
|
|
int i = (int)t;
|
|
|
|
//print (i);
|
|
|
|
if (i >= lookDirectionList.Count) {
|
|
i = lookDirectionList.Count - 1;
|
|
}
|
|
|
|
return lookDirectionList [i].localEulerAngles;
|
|
}
|
|
|
|
public Transform GetLookTransform (float t)
|
|
{
|
|
t = Mathf.Clamp01 (t) * CurveCount;
|
|
int i = (int)t;
|
|
|
|
//print (i);
|
|
|
|
if (i >= lookDirectionList.Count) {
|
|
i = lookDirectionList.Count - 1;
|
|
}
|
|
|
|
return lookDirectionList [i];
|
|
}
|
|
|
|
public int getPointIndex (float t)
|
|
{
|
|
t = Mathf.Clamp01 (t) * CurveCount;
|
|
int i = (int)t;
|
|
|
|
return i;
|
|
}
|
|
|
|
public Vector3 GetVelocity (float t)
|
|
{
|
|
int i;
|
|
if (t >= 1f) {
|
|
t = 1f;
|
|
i = points.Count - 4;
|
|
} else {
|
|
t = Mathf.Clamp01 (t) * CurveCount;
|
|
i = (int)t;
|
|
t -= i;
|
|
i *= 3;
|
|
}
|
|
return transform.TransformPoint (GetFirstDerivative (points [i], points [i + 1], points [i + 2], points [i + 3], t)) - transform.position;
|
|
}
|
|
|
|
public Vector3 GetDirection (float t)
|
|
{
|
|
return GetVelocity (t).normalized;
|
|
}
|
|
|
|
public void setInitialSplinePoint (Vector3 position)
|
|
{
|
|
points [0] = transform.InverseTransformPoint (position);
|
|
}
|
|
|
|
public void setFinalSplinePoint (Vector3 position)
|
|
{
|
|
points [points.Count - 1] = transform.InverseTransformPoint (position);
|
|
}
|
|
|
|
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t)
|
|
{
|
|
t = Mathf.Clamp01 (t);
|
|
float oneMinusT = 1f - t;
|
|
return oneMinusT * oneMinusT * p0 + 2f * oneMinusT * t * p1 + t * t * p2;
|
|
}
|
|
|
|
public static Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2, float t)
|
|
{
|
|
return 2f * (1f - t) * (p1 - p0) + 2f * t * (p2 - p1);
|
|
}
|
|
|
|
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
|
|
{
|
|
t = Mathf.Clamp01 (t);
|
|
float OneMinusT = 1f - t;
|
|
return OneMinusT * OneMinusT * OneMinusT * p0 + 3f * OneMinusT * OneMinusT * t * p1 + 3f * OneMinusT * t * t * p2 + t * t * t * p3;
|
|
}
|
|
|
|
public static Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
|
|
{
|
|
t = Mathf.Clamp01 (t);
|
|
float oneMinusT = 1f - t;
|
|
return 3f * oneMinusT * oneMinusT * (p1 - p0) + 6f * oneMinusT * t * (p2 - p1) + 3f * t * t * (p3 - p2);
|
|
}
|
|
|
|
public float AccuracyToStepSize (float accuracy)
|
|
{
|
|
if (accuracy <= 0f) {
|
|
return 0.2f;
|
|
}
|
|
|
|
return Mathf.Clamp (1f / accuracy, 0.001f, 0.2f);
|
|
}
|
|
|
|
public Vector3 FindNearestPointTo (Vector3 worldPos, float accuracy = 100f, int secondPassIterations = 7, float secondPassExtents = 0.025f)
|
|
{
|
|
float normalizedT;
|
|
|
|
return FindNearestPointTo (worldPos, out normalizedT, accuracy, secondPassIterations, secondPassExtents);
|
|
}
|
|
|
|
public Vector3 FindNearestPointTo (Vector3 worldPos, out float normalizedT, float accuracy = 100f, int secondPassIterations = 7, float secondPassExtents = 0.025f)
|
|
{
|
|
Vector3 result = Vector3.zero;
|
|
|
|
normalizedT = -1f;
|
|
|
|
float step = AccuracyToStepSize (accuracy);
|
|
|
|
float minDistance = Mathf.Infinity;
|
|
|
|
for (float i = 0f; i < 1f; i += step) {
|
|
Vector3 thisPoint = GetPoint (i);
|
|
float thisDistance = (worldPos - thisPoint).sqrMagnitude;
|
|
|
|
if (thisDistance < minDistance) {
|
|
minDistance = thisDistance;
|
|
result = thisPoint;
|
|
normalizedT = i;
|
|
}
|
|
}
|
|
|
|
if (secondPassIterations > 0) {
|
|
float minT = normalizedT - secondPassExtents;
|
|
float maxT = normalizedT + secondPassExtents;
|
|
|
|
for (int i = 0; i < secondPassIterations; i++) {
|
|
float leftT = (minT + normalizedT) * 0.5f;
|
|
float rightT = (maxT + normalizedT) * 0.5f;
|
|
|
|
Vector3 leftPoint = GetPoint (leftT);
|
|
Vector3 rightPoint = GetPoint (rightT);
|
|
|
|
float leftDistance = (worldPos - leftPoint).sqrMagnitude;
|
|
float rightDistance = (worldPos - rightPoint).sqrMagnitude;
|
|
|
|
if (leftDistance < minDistance && leftDistance < rightDistance) {
|
|
minDistance = leftDistance;
|
|
result = leftPoint;
|
|
maxT = normalizedT;
|
|
normalizedT = leftT;
|
|
} else if (rightDistance < minDistance && rightDistance < leftDistance) {
|
|
minDistance = rightDistance;
|
|
result = rightPoint;
|
|
minT = normalizedT;
|
|
normalizedT = rightT;
|
|
} else {
|
|
minT = leftT;
|
|
maxT = rightT;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
//EDITOR FUNCTIONS
|
|
public void setSelectedIndex (int index)
|
|
{
|
|
selectedIndex = index;
|
|
|
|
updateComponent ();
|
|
}
|
|
|
|
public void AddCurve (int index, bool addingAtTheEnd)
|
|
{
|
|
Vector3 point = points [index - 1];
|
|
|
|
float currentOffset = newPointOffset;
|
|
|
|
point.z += currentOffset;
|
|
points.Insert (index, point);
|
|
|
|
point.z += currentOffset;
|
|
points.Insert (index + 1, point);
|
|
|
|
point.z += currentOffset;
|
|
points.Insert (index + 2, point);
|
|
|
|
|
|
modes.Add (modes [modes.Count - 1]);
|
|
EnforceMode (index - 4);
|
|
|
|
if (loop) {
|
|
points [index - 1] = points [0];
|
|
modes [modes.Count - 1] = modes [0];
|
|
EnforceMode (0);
|
|
}
|
|
|
|
if (addingAtTheEnd) {
|
|
selectedIndex = points.Count - 1;
|
|
} else {
|
|
selectedIndex = index + 2;
|
|
}
|
|
|
|
updateComponent ();
|
|
}
|
|
|
|
public void removeCurve (int index)
|
|
{
|
|
if (index % 3 == 0) {
|
|
if (points.Count > 4) {
|
|
if (index == points.Count - 1) {
|
|
points.RemoveAt (index);
|
|
points.RemoveAt (index - 1);
|
|
points.RemoveAt (index - 2);
|
|
|
|
if (index == 3) {
|
|
selectedIndex = 0;
|
|
} else {
|
|
selectedIndex = index - 3;
|
|
}
|
|
} else {
|
|
if (index == 0) {
|
|
points.RemoveAt (index + 2);
|
|
points.RemoveAt (index + 1);
|
|
points.RemoveAt (index);
|
|
|
|
selectedIndex = 0;
|
|
} else {
|
|
points.RemoveAt (index + 1);
|
|
points.RemoveAt (index);
|
|
points.RemoveAt (index - 1);
|
|
|
|
selectedIndex = index;
|
|
}
|
|
}
|
|
|
|
modes.RemoveAt (index / 3);
|
|
|
|
if (points.Count == 4) {
|
|
Loop = false;
|
|
}
|
|
} else {
|
|
print ("At least two points must be configured for the spline");
|
|
}
|
|
} else {
|
|
print ("This point can't be removed");
|
|
}
|
|
|
|
updateComponent ();
|
|
}
|
|
|
|
public void Reset ()
|
|
{
|
|
points.Clear ();
|
|
|
|
float currentOffset = newPointOffset;
|
|
|
|
points.Add (new Vector3 (0f, 0f, currentOffset));
|
|
points.Add (new Vector3 (0f, 0f, currentOffset * 2));
|
|
points.Add (new Vector3 (0f, 0f, currentOffset * 3));
|
|
points.Add (new Vector3 (0f, 0f, currentOffset * 4));
|
|
|
|
modes.Clear ();
|
|
|
|
modes.Add (BezierControlPointMode.Aligned);
|
|
modes.Add (BezierControlPointMode.Aligned);
|
|
|
|
selectedIndex = 0;
|
|
|
|
updateComponent ();
|
|
}
|
|
|
|
public void alignLookDirection (bool adjustPositionAndRotation)
|
|
{
|
|
int pointIndex = 0;
|
|
|
|
for (int j = 0; j < lookDirectionList.Count; j++) {
|
|
Vector3 newPosition = Vector3.zero;
|
|
|
|
if (pointIndex <= points.Count - 1) {
|
|
newPosition = points [pointIndex];
|
|
}
|
|
|
|
lookDirectionList [j].localPosition = newPosition;
|
|
|
|
if (adjustPositionAndRotation) {
|
|
lookDirectionList [j].localRotation = Quaternion.identity;
|
|
}
|
|
|
|
pointIndex += 3;
|
|
}
|
|
|
|
updateComponent ();
|
|
}
|
|
|
|
public void updateComponent ()
|
|
{
|
|
GKC_Utils.updateComponent (this);
|
|
}
|
|
|
|
void OnDrawGizmos ()
|
|
{
|
|
if (!showGizmo) {
|
|
return;
|
|
}
|
|
|
|
if (GKC_Utils.isCurrentSelectionActiveGameObject (gameObject)) {
|
|
DrawGizmos ();
|
|
}
|
|
}
|
|
|
|
void OnDrawGizmosSelected ()
|
|
{
|
|
DrawGizmos ();
|
|
}
|
|
|
|
void DrawGizmos ()
|
|
{
|
|
if (showGizmo) {
|
|
if (showLookDirectionGizmo) {
|
|
|
|
for (int i = 0; i < lookDirectionList.Count; i++) {
|
|
if (lookDirectionList [i] != null) {
|
|
Gizmos.color = Color.yellow;
|
|
Gizmos.DrawSphere (lookDirectionList [i].position, lookDirectionGizmoRadius);
|
|
|
|
GKC_Utils.drawGizmoArrow (lookDirectionList [i].position, lookDirectionList [i].forward * lookDirectionArrowLength, Color.yellow, 0.2f, 20);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class splinePointInfo
|
|
{
|
|
public string Name;
|
|
public Vector3 point;
|
|
public BezierControlPointMode mode;
|
|
}
|
|
} |