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
I'm also not sure how to solve the above stated points but 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;
}
}