Monday, May 13, 2019

How to create an illustrator/photoshop-like pentool for creating bezier curves in Unity


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 points;

[SerializeField, HideInInspector]
public bool isContinuous;

public BPath(Vector2 centre)

{
points = new List
{
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;
}
}



No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...