8
\$\begingroup\$

I found a very useful bezier curve creation code here, which I am using. I need to create complex segmented bezier curves so I want update the code to create illustrator/photoshop-like pen tool bezier curves. This video shows how the pentool behaves.

Please note that instead of creating a quadratic bezier curve with the first two anchor points (as shown in the video) I would rather prefer a cubic (as in the linked code example).

The following are features I've realised of the illustrator/photoshop pen tool that are necessary for replication in Unity.

  • all anchor/control points are created at the same mouse point on the first click (object is created on the first click)

  • as the mouse point is moved from the first click (not pressed) the control points fall in with the two anchor points to create a straight line (cubic curve)

  • when the mouse is clicked and dragged (any distance from the first click) a control points move away from the straight line to form a curve based on the direction of the drag, they also increase in length as the drag increases in distance from the second click.

  • the path should be closed when the first anchor point is re-selected during the curves creation

From the code below I created all my initial 4 points at the same point (I don't know how to click in the scene view to have game object created at the click point). I'm also not sure how to move the mouse point to drag the other anchor and click it to manipulate the control points.

Here is the code I've written so far:

BPath:

[System.Serializable]
public class BPath
{

    [SerializeField, HideInInspector]
    List<Vector2> points;

    [SerializeField, HideInInspector]
    public bool isContinuous;

    public BPath(Vector2 centre)
    {
        points = new List<Vector2>
    {
        centre+Vector2.left,
            centre+Vector2.left,
            centre+Vector2.left,
            centre+Vector2.left
    };
    }

    public Vector2 this[int i]
    {
        get
        {
            return points[i];
        }
    }

    public int NumPoints
    {
        get
        {
            return points.Count;
        }
    }

    public int NumSegments
    {
        get
        {
            return (points.Count - 4) / 3 + 1;
        }
    }

    public void AddSegment(Vector2 anchorPos)
    {
        points.Add(points[points.Count - 1] * 2 - points[points.Count - 2]);
        points.Add((points[points.Count - 1] + anchorPos) * .5f);
        points.Add(anchorPos);
    }

    public Vector2[] GetPointsInSegment(int i)
    {
        return new Vector2[] { points[i * 3], points[i * 3 + 1], points[i * 3 + 2], points[i * 3 + 3] };
    }

    public void MovePoint(int i, Vector2 pos)
    {

        if (isContinuous)
        {

            Vector2 deltaMove = pos - points[i];
            points[i] = pos;

            if (i % 3 == 0)
            {
                if (i + 1 < points.Count)
                {
                    points[i + 1] += deltaMove;
                }
                if (i - 1 >= 0)
                {
                    points[i - 1] += deltaMove;
                }
            }
            else
            {
                bool nextPointIsAnchor = (i + 1) % 3 == 0;
                int correspondingControlIndex = (nextPointIsAnchor) ? i + 2 : i - 2;
                int anchorIndex = (nextPointIsAnchor) ? i + 1 : i - 1;

                if (correspondingControlIndex >= 0 && correspondingControlIndex < points.Count)
                {
                    float dst = (points[anchorIndex] - points[correspondingControlIndex]).magnitude;
                    Vector2 dir = (points[anchorIndex] - pos).normalized;
                    points[correspondingControlIndex] = points[anchorIndex] + dir * dst;
                }
            }
        }


    else {
         points[i] = pos;
    }
    }
}

BPathCreator:

public class BPathCreator : MonoBehaviour
{

    [HideInInspector]
    public BPath path;


    public void CreatePath()
    {
        path = new BPath(transform.position);
    }
}

BPathEditor:

[CustomEditor(typeof(BPathCreator))]
public class BPathEditor : Editor
{

    BPathCreator creator;
    BPath path;

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        EditorGUI.BeginChangeCheck();

        bool continuousControlPoints = GUILayout.Toggle(path.isContinuous, "Set Continuous Control Points");
        if (continuousControlPoints != path.isContinuous)
        {
            Undo.RecordObject(creator, "Toggle set continuous controls");
            path.isContinuous = continuousControlPoints;
        }

        if (EditorGUI.EndChangeCheck())
        {
            SceneView.RepaintAll();
        }
    }

    void OnSceneGUI()
    {
        Input();
        Draw();
    }

    void Input()
    {
        Event guiEvent = Event.current;
        Vector2 mousePos = HandleUtility.GUIPointToWorldRay(guiEvent.mousePosition).origin;

        if (guiEvent.type == EventType.MouseDown && guiEvent.button == 0 && guiEvent.shift)
        {
            Undo.RecordObject(creator, "Add segment");
            path.AddSegment(mousePos);
        }
    }

    void Draw()
    {

        for (int i = 0; i < path.NumSegments; i++)
        {
            Vector2[] points = path.GetPointsInSegment(i);
            Handles.color = Color.black;
            Handles.DrawLine(points[1], points[0]);
            Handles.DrawLine(points[2], points[3]);
            Handles.DrawBezier(points[0], points[3], points[1], points[2], Color.green, null, 2);
        }

        Handles.color = Color.red;
        for (int i = 0; i < path.NumPoints; i++)
        {
            Vector2 newPos = Handles.FreeMoveHandle(path[i], Quaternion.identity, .1f, Vector2.zero, Handles.CylinderHandleCap);
            if (path[i] != newPos)
            {
                Undo.RecordObject(creator, "Move point");
                path.MovePoint(i, newPos);
            }
        }
    }

    void OnEnable()
    {
        creator = (BPathCreator)target;
        if (creator.path == null)
        {
            creator.CreatePath();
        }
        path = creator.path;
    }
}
\$\endgroup\$
7
  • \$\begingroup\$ @DMGregory I just updated the question with what I've been able to do so far. \$\endgroup\$
    – hutfelt
    Oct 29, 2018 at 19:12
  • \$\begingroup\$ @DMGregory I hope the update makes what I want to achieve more understandable \$\endgroup\$
    – hutfelt
    Oct 29, 2018 at 19:17
  • \$\begingroup\$ Is what I'm trying to achieve even possible? \$\endgroup\$
    – hutfelt
    Nov 2, 2018 at 19:09
  • \$\begingroup\$ What would render it impossible? \$\endgroup\$
    – DMGregory
    Nov 2, 2018 at 19:09
  • \$\begingroup\$ Not really sure, just want to make sure I'm not trying to work one something that leads to a dead end. \$\endgroup\$
    – hutfelt
    Nov 2, 2018 at 19:23

1 Answer 1

0
\$\begingroup\$

If you had some sort of generic spline object whose control points you could manipulate you could easily mirror the control points with small movable cubes. Implement a click and drag interface, use raycasts when needed and voila. The interface would be the fun part.

Here is a simple example of this in action without the modifiable control points

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .