Search Unity

Moving Handle along Bezier Spline

Discussion in 'Immediate Mode GUI (IMGUI)' started by msklywenn, May 9, 2016.

  1. msklywenn

    msklywenn

    Joined:
    Nov 28, 2014
    Posts:
    67
    Hello,

    In our game, we have a big spline along which most of the game happens. To easily place objects along that spline, I'd like to have a position handle that stays constant along the spline. But there seems to be issues with PositionHandle when it's being moved externally in addition to the user input.

    I tried two methods.

    Method A "Handle Orientation"
    I use a position handle with the orientation of the spline corresponding to the projection of that object on the spline. When I move the Z (blue) component of the handle, the object indeed moves along the spline but the components on the X and Y axis locally on the spline also move which should not be the case. I'm not sure if my math is wrong here or if it's an issue with PositionHandle.

    Method B "Handle Matrix"
    Here, I set Handle.matrix to be oriented and offset so that all interactions with the handle are in "bezier spline space". The problem here is that everytime I move along Z (blue), it moves more than expected. It almost seems like the value returned by PositionHandle is relative to the initial position of the handle instead of being relative to the one I input to PositionHandle.

    In both cases, moving along X or Y works like a charm.

    I extracted a sample code from my bigger project which exhibits my troubles. To try it, just add the file to a project, and open the "Tools/Bezier Gizmo" window. It will move the selected object to the position 0 on an hardcoded spline and movement can then be controlled either by inputing value in the window or moving the buggy handle.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. public class SampleBezierGizmo : EditorWindow
    5. {
    6.     enum MoveMethod
    7.     {
    8.         HandleMatrix,
    9.         HandleOrientation,
    10.     }
    11.  
    12.     MoveMethod method;
    13.  
    14.     float time;
    15.  
    16.     Transform target;
    17.  
    18.     Vector3 bezierPosition;
    19.     Quaternion bezierOrientation;
    20.  
    21.     Vector2 localPosition;
    22.  
    23.     Vector3 p0 = new Vector3(0, 0, 0);
    24.     Vector3 p1 = new Vector3(10, 0, 0);
    25.     Vector3 p2 = new Vector3(10, 0, 10);
    26.     Vector3 p3 = new Vector3(0, 0, 10);
    27.  
    28.     Vector3 Bezier(float t)
    29.     {
    30.         float mt = 1.0f - t;
    31.         float t2 = t * t;
    32.         float mt2 = mt * mt;
    33.  
    34.         float c0 = mt2 * mt;
    35.         float c1 = 3.0f * t * mt2;
    36.         float c2 = 3.0f * t2 * mt;
    37.         float c3 = t2 * t;
    38.  
    39.         return c0 * p0 + c1 * p1 + c2 * p2 + c3 * p3;
    40.     }
    41.  
    42.     Vector3 BezierDerivative(float t)
    43.     {
    44.         float mt = 1.0f - t;
    45.  
    46.         float a = 3.0f * mt * mt;
    47.         float b = 6.0f * t * mt;
    48.         float c = 3.0f * t * t;
    49.  
    50.         float c0 = -a;
    51.         float c1 = a - b;
    52.         float c2 = b - c;
    53.         float c3 = c;
    54.  
    55.         return c0 * p0 + c1 * p1 + c2 * p2 + c3 * p3;
    56.     }
    57.  
    58.     [MenuItem("Tools/Bezier Gizmo")]
    59.     static void Open()
    60.     {
    61.         SampleBezierGizmo window = GetWindow<SampleBezierGizmo>("Playspace Gizmo");
    62.         window.minSize = new Vector2(250, 60);
    63.     }
    64.  
    65.     Tool prev;
    66.     void OnEnable()
    67.     {
    68.         SceneView.onSceneGUIDelegate += OnSceneGUI;
    69.         prev = Tools.current;
    70.         Tools.current = Tool.None;
    71.     }
    72.  
    73.     void OnDisable()
    74.     {
    75.         SceneView.onSceneGUIDelegate -= OnSceneGUI;
    76.         Tools.current = prev;
    77.     }
    78.  
    79.     void OnGUI()
    80.     {
    81.         method = (MoveMethod)EditorGUILayout.EnumPopup("Method", method);
    82.         EditorGUI.BeginChangeCheck();
    83.         Vector2 localMove = EditorGUILayout.Vector2Field("Position", localPosition) - localPosition;
    84.         float distanceMove = EditorGUILayout.FloatField("Time", time) - time;
    85.         if (EditorGUI.EndChangeCheck())
    86.             Move(localMove, distanceMove);
    87.     }
    88.  
    89.     void OnSceneGUI(SceneView sv)
    90.     {
    91.         if (target != Selection.activeTransform)
    92.             Initialize();
    93.  
    94.  
    95.         switch (method)
    96.         {
    97.             case MoveMethod.HandleMatrix:
    98.             {
    99.                 Handles.matrix = Matrix4x4.TRS(bezierPosition, bezierOrientation, Vector3.one);
    100.                 EditorGUI.BeginChangeCheck();
    101.                 Vector3 newLocalPosition = Handles.PositionHandle(localPosition, Quaternion.identity);
    102.                 if (EditorGUI.EndChangeCheck())
    103.                 {
    104.                     Vector3 ofs = newLocalPosition - new Vector3(localPosition.x, localPosition.y, 0);
    105.                     Move(ofs, ofs.z);
    106.  
    107.                     Debug.Log(localPosition.ToString() + ", " + newLocalPosition.ToString());
    108.                 }
    109.                 break;
    110.             }
    111.  
    112.             case MoveMethod.HandleOrientation:
    113.             {
    114.                 Vector3 worldPosition = bezierPosition + bezierOrientation * localPosition;
    115.                 EditorGUI.BeginChangeCheck();
    116.                 Vector3 newWorldPosition = Handles.PositionHandle(worldPosition, bezierOrientation);
    117.                 if (EditorGUI.EndChangeCheck())
    118.                 {
    119.                     Vector3 newLocalPosition = Quaternion.Inverse(bezierOrientation) * (newWorldPosition - bezierPosition);
    120.                     Move(new Vector2(newLocalPosition.x, newLocalPosition.y) - localPosition, newLocalPosition.z);
    121.                 }
    122.                 break;
    123.             }
    124.         }
    125.  
    126.         Handles.matrix = Matrix4x4.TRS(bezierPosition, bezierOrientation, Vector3.one);
    127.         if (localPosition.y >= 0)
    128.         {
    129.             Handles.DrawLine(Vector3.zero, new Vector3(localPosition.x, 0, 0));
    130.             Handles.DrawLine(new Vector3(localPosition.x, 0, 0), localPosition);
    131.         }
    132.         else
    133.         {
    134.             Handles.DrawLine(Vector3.zero, new Vector3(0, localPosition.y, 0));
    135.             Handles.DrawLine(new Vector3(0, localPosition.y, 0), localPosition);
    136.         }
    137.  
    138.         Handles.matrix = Matrix4x4.identity;
    139.         Handles.DrawBezier(p0, p3, p1, p2, Color.white, Texture2D.whiteTexture, 3);
    140.     }
    141.  
    142.     void Move(Vector2 localOffset, float distanceOffset)
    143.     {
    144.         bezierPosition = Bezier(time);
    145.         Vector3 derivative = BezierDerivative(time);
    146.         bezierOrientation = Quaternion.LookRotation(derivative.normalized);
    147.  
    148.         time += distanceOffset / derivative.magnitude;
    149.         localPosition += localOffset;
    150.  
    151.         Undo.RecordObject(Selection.activeTransform, "Moving selected transform");
    152.         Selection.activeTransform.position = bezierPosition + bezierOrientation * localPosition;
    153.         EditorUtility.SetDirty(Selection.activeTransform);
    154.         Repaint();
    155.     }
    156.  
    157.     void Initialize()
    158.     {
    159.         localPosition = Vector2.zero;
    160.         time = 0;
    161.  
    162.         bezierPosition = Bezier(time);
    163.         Vector3 derivative = BezierDerivative(time);
    164.         bezierOrientation = Quaternion.LookRotation(derivative.normalized);
    165.         target = Selection.activeTransform;
    166.         Repaint();
    167.     }
    168. }
    Any idea of what I am doing wrong ?
     
    Last edited: May 9, 2016
  2. crispybeans

    crispybeans

    Joined:
    Apr 13, 2015
    Posts:
    210
    What do you mean by "moved externally"? Moved by the user in the Sceneview ?
     
  3. msklywenn

    msklywenn

    Joined:
    Nov 28, 2014
    Posts:
    67
    Externally to the PositionHandle function. So when the user moves the handle, that's internal to PositionHandle and PositionHandle should return the new position with just the amount the user moved. But what I do in between frames is modify that position so that it is kept at the same XY offset along the curve. That seems to be of trouble to unity because then the PositionHandle returns a position that moved too far.

    In short : PositionHandle should be stateless and doesn't seem to be. It all happens like there was something weird being done under the hood.
     
    Last edited: May 20, 2016