Enfisk

Bezier Splines in Unity (17 apr 2015)

Apr 17th, 2015
22
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 11.92 KB | None | 0 0
  1. //Taken from http://catlikecoding.com/unity/tutorials/curves-and-splines/
  2. /*************
  3. * Bezier.cs  *
  4. *************/
  5.  
  6. using UnityEngine;
  7. using System.Collections;
  8.  
  9. public static class Bezier {
  10.     public static Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, float t) {
  11.         t = Mathf.Clamp01(t);
  12.         float oneMinusT = 1f - t;
  13.         return (oneMinusT * oneMinusT * p0) + (2f * oneMinusT * t * p1) + (t * t * p2);
  14.     }
  15.  
  16.     public static Vector3 GetFirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, float t) {
  17.         return 2f * (1f - t) * (p1 - p0) + 2f * t * (p2 - p1);
  18.     }
  19.  
  20.     public static Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
  21.         t = Mathf.Clamp01(t);
  22.         float oneMinusT = 1f - t;
  23.  
  24.         return oneMinusT * oneMinusT * oneMinusT * p0 +
  25.                3f * oneMinusT * oneMinusT * t * p1 +
  26.                3f * oneMinusT * t * t * p2 +
  27.                t * t * t * p3;
  28.     }
  29.  
  30.     public static Vector3 GetFirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
  31.         t = Mathf.Clamp01(t);
  32.         float oneMinusT = 1f - t;
  33.  
  34.         return 3f * oneMinusT * oneMinusT * (p1 - p0) +
  35.                6f * oneMinusT * t * (p2 - p1) +
  36.                3f * t * t * (p3 - p2);
  37.     }
  38. }
  39.  
  40. /*****************
  41. *BezierSpline.cs *
  42. *****************/
  43.  
  44. using UnityEngine;
  45. using System.Collections;
  46. using System;
  47.  
  48. public class BezierSpline : MonoBehaviour {
  49.     public enum BezierControlPointMode {
  50.         Free,
  51.         Aligned,
  52.         Mirrored
  53.     }
  54.  
  55.     [SerializeField]
  56.     private BezierControlPointMode[] modes;
  57.  
  58.     [SerializeField]
  59.     private Vector3[] points;
  60.  
  61.     [SerializeField]
  62.     private bool loop;
  63.     public int CurveCount {
  64.         get {
  65.             return (points.Length - 1) / 3;
  66.         }
  67.     }
  68.  
  69.     public int NumPoints {
  70.         get {
  71.             return points.Length;
  72.         }
  73.     }
  74.  
  75.     public int ControlPointCount {
  76.         get {
  77.             return points.Length;
  78.         }
  79.     }
  80.  
  81.     public void Reset() {
  82.         points = new Vector3[] {
  83.             new Vector3(1f, 0f, 0f),
  84.             new Vector3(2f, 0f, 0f),
  85.             new Vector3(3f, 0f, 0f),
  86.             new Vector3(4f, 0f, 0f)
  87.         };
  88.  
  89.         modes = new BezierControlPointMode[] {
  90.             BezierControlPointMode.Free,
  91.             BezierControlPointMode.Free
  92.         };
  93.     }
  94.  
  95.     public Vector3 GetControlPoint(int index) {
  96.         return points[index];
  97.     }
  98.  
  99.     public void SetControlPoint(int index, Vector3 point) {
  100.         if (index % 3 == 0) {
  101.             Vector3 delta = point - points[index];
  102.             if (loop) {
  103.                 if (index == 0) {
  104.                     points[1] += delta;
  105.                     points[points.Length - 2] += delta;
  106.                     points[points.Length - 1] = point;
  107.                 }
  108.                 else if (index == points.Length - 1) {
  109.                     points[0] = point;
  110.                     points[1] += delta;
  111.                     points[index - 1] += delta;
  112.                 }
  113.                 else {
  114.                     if (index > 0) {
  115.                         points[index - 1] += delta;
  116.                     }
  117.                     if (index + 1 < points.Length) {
  118.                         points[index + 1] += delta;
  119.                     }
  120.                 }
  121.             }
  122.  
  123.         }
  124.  
  125.         points[index] = point;
  126.         EnforceMode(index);
  127.     }
  128.  
  129.     public Vector3 GetPoint(float t) {
  130.         int i;
  131.         if (t >= 1f) {
  132.             t = 1f;
  133.             i = points.Length - 4;
  134.         }
  135.         else {
  136.             t = Mathf.Clamp01(t) * CurveCount;
  137.             i = (int)t;
  138.             t -= i;
  139.             i *= 3;
  140.         }
  141.         return transform.TransformPoint(Bezier.GetPoint(points[i], points[i+1], points[i+2], points[i+3], t));
  142.     }
  143.  
  144.     public Vector3 GetVelocity(float t) {
  145.         int i;
  146.         if (t >= 1f) {
  147.             t = 1f;
  148.             i = points.Length - 4;
  149.         }
  150.         else {
  151.             t = Mathf.Clamp01(t) * CurveCount;
  152.             i = (int)t;
  153.             t -= i;
  154.             i *= 3;
  155.         }
  156.         return transform.TransformPoint(Bezier.GetFirstDerivative(points[i], points[i+1], points[i+2], points[i+3], t)) - transform.position;
  157.     }
  158.  
  159.     public Vector3 GetDirection(float t) {
  160.         return GetVelocity(t).normalized;
  161.     }
  162.  
  163.     public void AddCurve() {
  164.         Vector3 point = points[points.Length - 1];
  165.         Array.Resize(ref points, points.Length + 3);
  166.  
  167.         for (int i = 3; i > 0; --i) {
  168.             point.x += 1f;
  169.             points[points.Length - i] = point;
  170.         }
  171.  
  172.         Array.Resize(ref modes, modes.Length + 1);
  173.         modes[modes.Length - 1] = modes[modes.Length - 2];
  174.         EnforceMode(points.Length - 4);
  175.  
  176.         if (loop) {
  177.             points[points.Length - 1] = points[0];
  178.             modes[modes.Length - 1] = modes[0];
  179.             EnforceMode(0);
  180.         }
  181.     }
  182.  
  183.     public void RemoveCurve() {
  184.         Array.Resize(ref points, points.Length - 3);
  185.         Array.Resize(ref modes, modes.Length - 1);
  186.     }
  187.  
  188.     public BezierControlPointMode GetControlPointMode(int index) {
  189.         return modes[(index + 1) / 3];
  190.     }
  191.  
  192.     public void SetControlPointMode(int index, BezierControlPointMode mode) {
  193.         int modeIndex = (index + 1) / 3;
  194.         modes[modeIndex] = mode;
  195.         if (loop) {
  196.             if (modeIndex == 0) {
  197.                 modes[modes.Length - 1] = mode;
  198.             }
  199.             else if (modeIndex == modes.Length - 1) {
  200.                 modes[0] = mode;
  201.             }
  202.         }
  203.         EnforceMode(index);
  204.     }
  205.  
  206.     private void EnforceMode(int index) {
  207.         int modeIndex = (index + 1) / 3;
  208.         BezierControlPointMode mode = modes[modeIndex];
  209.         if (mode == BezierControlPointMode.Free || !loop && (modeIndex == 0 || modeIndex == modes.Length - 1)) {
  210.             return;
  211.         }
  212.  
  213.         int middleIndex = modeIndex * 3;
  214.         int fixedIndex, enforcedIndex;
  215.         if (index <= middleIndex) {
  216.             fixedIndex = middleIndex - 1;
  217.             if (fixedIndex < 0) {
  218.                 fixedIndex = points.Length - 2;
  219.             }
  220.             enforcedIndex = middleIndex + 1;
  221.             if (enforcedIndex >= points.Length) {
  222.                 enforcedIndex = 1;
  223.             }
  224.         }
  225.         else {
  226.             fixedIndex = middleIndex + 1;
  227.             if (fixedIndex >= points.Length) {
  228.                 fixedIndex = 1;
  229.             }
  230.             enforcedIndex = middleIndex - 1;
  231.             if (enforcedIndex < 0) {
  232.                 enforcedIndex = points.Length - 2;
  233.             }
  234.         }
  235.  
  236.         Vector3 middle = points[middleIndex];
  237.         Vector3 enforcedTangent = middle - points[fixedIndex];
  238.         if (mode == BezierControlPointMode.Aligned) {
  239.             enforcedTangent = enforcedTangent.normalized * Vector3.Distance(middle, points[enforcedIndex]);
  240.         }
  241.         points[enforcedIndex] = middle + enforcedTangent;
  242.     }
  243.  
  244.     public bool Loop {
  245.         get {
  246.             return loop;
  247.         }
  248.         set {
  249.             loop = value;
  250.             if (value == true) {
  251.                 modes[modes.Length - 1] = modes[0];
  252.                 SetControlPoint(0, points[0]);
  253.             }
  254.         }
  255.     }
  256. }
  257.  
  258. /***************************
  259. * BezierSplineInspector.cs *
  260. ***************************/
  261.  
  262. using UnityEngine;
  263. using UnityEditor;
  264. using System.Collections;
  265.  
  266. [CustomEditor(typeof(BezierSpline))]
  267. public class BezierSplineInspector : Editor {
  268.     private static Color[] modeColors = {
  269.         Color.white,
  270.         Color.yellow,
  271.         Color.cyan
  272.     };
  273.  
  274.  
  275.     private BezierSpline spline;
  276.     private Transform handleTransform;
  277.     private Quaternion handleRotation;
  278.     private const int lineSteps = 10;
  279.     private const float directionScale = 0.5f;
  280.     private const int stepsPerCurve = 10;
  281.     private const float handleSize = 0.04f;
  282.     private const float pickSize = 0.06f;
  283.     private int selectedIndex = -1;
  284.  
  285.     public override void OnInspectorGUI() {
  286.         spline = target as BezierSpline;
  287.         EditorGUI.BeginChangeCheck();
  288.         bool loop = EditorGUILayout.Toggle("Loop", spline.Loop);
  289.         if (EditorGUI.EndChangeCheck()) {
  290.             Undo.RecordObject(spline, "Toggle Loop");
  291.             EditorUtility.SetDirty(spline);
  292.             spline.Loop = loop;
  293.         }
  294.  
  295.         if (selectedIndex >= 0 && selectedIndex < spline.ControlPointCount) {
  296.             DrawSelectedPointInspector();
  297.         }
  298.         if (GUILayout.Button("Add Curve")) {
  299.             Undo.RecordObject(spline, "Add Curve");
  300.             spline.AddCurve();
  301.             EditorUtility.SetDirty(spline);
  302.         }
  303.         if (GUILayout.Button("Remove Curve")) {
  304.             if (spline.CurveCount > 1) {
  305.                 Undo.RecordObject(spline, "Remove Curve");
  306.                 spline.RemoveCurve();
  307.                 EditorUtility.SetDirty(spline);
  308.             }
  309.         }
  310.     }
  311.  
  312.     private void DrawSelectedPointInspector() {
  313.         GUILayout.Label("Selected Point");
  314.         EditorGUI.BeginChangeCheck();
  315.         Vector3 point = EditorGUILayout.Vector3Field("Position", spline.GetControlPoint(selectedIndex));
  316.  
  317.         if (EditorGUI.EndChangeCheck()) {
  318.             Undo.RecordObject(spline, "Move Point");
  319.             EditorUtility.SetDirty(spline);
  320.             spline.SetControlPoint(selectedIndex, point);
  321.         }
  322.  
  323.         EditorGUI.BeginChangeCheck();
  324.         BezierSpline.BezierControlPointMode mode = (BezierSpline.BezierControlPointMode)EditorGUILayout.EnumPopup("Mode", spline.GetControlPointMode(selectedIndex));
  325.         if (EditorGUI.EndChangeCheck()) {
  326.             Undo.RecordObject(spline, "Change Point Mode");
  327.             spline.SetControlPointMode(selectedIndex, mode);
  328.             EditorUtility.SetDirty(spline);
  329.         }
  330.     }
  331.  
  332.     private void OnSceneGUI() {
  333.         spline = target as BezierSpline;
  334.         handleTransform = spline.transform;
  335.         handleRotation = Tools.pivotRotation == PivotRotation.Local ? handleTransform.rotation : Quaternion.identity;
  336.  
  337.         Vector3 p0 = ShowPoint(0);
  338.         for (int i = 1; i < spline.ControlPointCount; i += 3) {
  339.             Vector3 p1 = ShowPoint(i);
  340.             Vector3 p2 = ShowPoint(i + 1);
  341.             Vector3 p3 = ShowPoint(i + 2);
  342.  
  343.             Handles.color = Color.grey;
  344.             Handles.DrawLine(p0, p1);
  345.             Handles.DrawLine(p2, p3);
  346.  
  347.             Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);
  348.             p0 = p3;
  349.         }
  350.  
  351.         ShowDirections();
  352.     }
  353.  
  354.     private Vector3 ShowPoint(int index) {
  355.         Vector3 point = handleTransform.TransformPoint(spline.GetControlPoint(index));
  356.         float size = HandleUtility.GetHandleSize(point);
  357.         if (index == 0) {
  358.             size *= 2f;
  359.         }
  360.         Handles.color = modeColors[(int)spline.GetControlPointMode(index)];
  361.         if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
  362.             selectedIndex = index;
  363.             Repaint();
  364.         }
  365.         if (selectedIndex == index) {
  366.             EditorGUI.BeginChangeCheck();
  367.             point = Handles.DoPositionHandle(point, handleRotation);
  368.  
  369.             if (EditorGUI.EndChangeCheck()) {
  370.                 Undo.RecordObject(spline, "Move Point");
  371.                 EditorUtility.SetDirty(spline);
  372.                 spline.SetControlPoint(index, handleTransform.InverseTransformPoint(point));
  373.             }
  374.         }
  375.  
  376.         return point;
  377.     }
  378.  
  379.     private void ShowDirections() {
  380.         Handles.color = Color.green;
  381.         Vector3 point = spline.GetPoint(0f);
  382.         Handles.DrawLine(point, point + spline.GetDirection(0f) * directionScale);
  383.         int steps = stepsPerCurve * spline.CurveCount;
  384.         for (int i = 1; i <= steps; ++i) {
  385.             point = spline.GetPoint(i / (float)steps);
  386.             Handles.DrawLine(point, point + spline.GetDirection(i / (float)steps) * directionScale);
  387.         }
  388.     }
  389. }
Advertisement
Add Comment
Please, Sign In to add comment