12

我需要创建复杂的分段贝塞尔曲线,所以我想更新代码以创建类似 illustrator/photoshop 的钢笔工具贝塞尔曲线。该视频展示了pentool 的行为方式。

请注意,与其使用前两个锚点创建二次贝塞尔曲线(如视频所示),我更喜欢三次(如链接代码示例中所示)。

以下是我实现的 Illustrator/photoshop 钢笔工具的功能,这些功能是在 Unity 中复制所必需的。

  • 所有锚点/控制点都在第一次单击时创建在同一鼠标点(第一次单击时创建对象)

  • 当鼠标点从第一次单击(未按下)移动时,控制点与两个锚点落入以创建一条直线(三次曲线)

  • 当单击并拖动鼠标时(距第一次单击的任何距离),控制点会根据拖动的方向远离直线形成曲线,随着拖动距离第二次的增加,它们的长度也会增加点击。

  • 在创建曲线期间重新选择第一个锚点时,路径应关闭

我也不确定如何解决上述问题,但这是我迄今为止编写的代码:

B路径:

[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;
    }
    }
}

BPath创建者:

public class BPathCreator : MonoBehaviour
{

    [HideInInspector]
    public BPath path;


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

BPath编辑器:

[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;
    }
}
4

2 回答 2

0

你问了很多事情。如果不给出完整的实现,将很难回答。但它可能不会是你想要的那个。

您提到的一些要点(以及您展示的视频)表明 Hermite Spline 可能更合适。这就是我用来创建视频中的行为的方法。

于 2019-10-08T15:10:14.760 回答
0

使用Unity 的官方插件SpriteShape。它是开源的,支持在运行时修改。这将 Photoshop 行为用于切线(锚点)。

于 2019-10-29T18:53:07.007 回答