How to draw a curve in 2D?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Diet Estus

I know it’s possible to use Curve2D to describe a 2D curve.

But how would one go about drawing that curve?

I’ve been researching this for about a week. :wink:

You need to build add points to a Curve2D object, then adjust the control points of the curve, and then finally draw the curve. So here’s some simple, almost pseudo code:

var array_of_line_points # This already has the vectors which describe our line
for point in array_of_line_points:
  # The "get_perpendicular_vector()" function returns a vector that's a copy of the point, yet has been slid along a line parallel to two neighboring points. The "distance" variable is how far the control point should be from the originating point.
  var control_point1 = get_perpendicular_vector(point, distance)
  var control_point2 = get_perpendicular_vector(point, -distance)
  curve.add_point(point, control_point1, control_point2)
# ...In the draw function
func _draw():
  draw_polyline(curve.get_baked_points(), red, 2.0)

I got inspiration for creating the curves this way from this webpage on spline interpolation.

I hope this helps!

Ertain | 2018-09-01 18:01

Thanks, @Ertain! If you post this as an answer, I will select it.

Diet Estus | 2018-09-01 18:07

I’d create an answer, but the problem lies in defining the function get_perpendicular_vector(). I don’t know how to get a perpendicular vector.

Ertain | 2018-09-01 18:29

:bust_in_silhouette: Reply From: squished-01

It’s in the documentation:

func draw_circle_arc(center, radius, angle_from, angle_to, color):
    var nb_points = 32
    var points_arc = PoolVector2Array()

    for i in range(nb_points+1):
        var angle_point = deg2rad(angle_from + i * (angle_to-angle_from) / nb_points - 90)
        points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)

    for index_point in range(nb_points):
        draw_line(points_arc[index_point], points_arc[index_point + 1], color)
:bust_in_silhouette: Reply From: Ertain

May as well promote my comment to an answer.

You need to add points to a Curve2D object, then adjust the control points of the curve, and then finally draw the curve. So here’s some simple, almost pseudo code:

var array_of_line_points # This already has the vectors which describe our line
for point in array_of_line_points:
  # The "get_perpendicular_vector()" function returns a vector that's a copy of the point, yet has been slid along a line parallel to two neighboring points. The "distance" variable is how far the control point should be from the originating point.
  var control_point1 = get_perpendicular_vector(point, distance)
  var control_point2 = get_perpendicular_vector(point, -distance)
  curve.add_point(point, control_point1, control_point2)
# ...In the draw function
func _draw():
  draw_polyline(curve.get_baked_points(), red, 2.0)

I got the inspiration for this from Rob Spencer’s page on Spline Interpolation. I’d like to make this more thorough (i.e. defining the get_perpendicular_vector() function). But this is the best I can currently do.

:bust_in_silhouette: Reply From: Dlean Jeans

Hey I just wrote this node called SmoothPath based on Ertain’s answer.
First you create straight lines in the editor then press the Smooth button in the Inspector.

tool
class_name SmoothPath
extends Path2D

export(float) var spline_length = 100
export(bool) var _smooth setget smooth
export(bool) var _straighten setget straighten

func straighten(value):
	if not value: return
	for i in curve.get_point_count():
		curve.set_point_in(i, Vector2())
		curve.set_point_out(i, Vector2())

func smooth(value):
	if not value: return
	
	var point_count = curve.get_point_count()
	for i in point_count:
		var spline = _get_spline(i)
		curve.set_point_in(i, -spline)
		curve.set_point_out(i, spline)

func _get_spline(i):
	var last_point = _get_point(i - 1)
	var next_point = _get_point(i + 1)
	var spline = last_point.direction_to(next_point) * spline_length
	return spline

func _get_point(i):
	var point_count = curve.get_point_count()
	i = wrapi(i, 0, point_count - 1)
	return curve.get_point_position(i)

func _draw():
	var points = curve.get_baked_points()
	if points:
		draw_polyline(points, Color.black, 8, true)

Amazing thanks, works brilliantly.

harryfrook | 2020-01-08 00:12

Great job mate! I am also trying to visualize point_in and point_out handles by adding:

draw_circle(get_point_in(), 3, Color.white)
draw_circle(get_point_out(), 3, Color.white)

to the _draw() function. But it returns an error. Obviously, it needs to be done differently. I would highly appreciate if you have any idea how it can be done. I even tried this one below still without success:

for pc in curve.get_point_count():
    var spline = _get_spline(pc)
    var point_in = curve.get_point_in(-pc, _get_spline(pc))
    var point_out = curve.get_point_out(pc, _get_spline(pc))
    draw_circle(point_in, 3, Color.white)
    draw_circle(point_out, 3, Color.white)

It returns: Too many arguments for get_point_in/out()

Suleymanov | 2020-07-18 07:32

Never would have thought of that, Jeans. This may help for some game I’m designing.

Ertain | 2021-12-01 04:07

Hi thank you for this good piece of script, I wanted to ask you I know to reproduce your smooth curve elsewhere but I do not understand your logic how you do that I want to understand please help me I know what is the control point and how to retrieve the point but how do you do to manipulate so well the control point?

Alaiz | 2022-01-12 12:29

if you don’t know, this script has been referenced in a youtube tutorial, how to make water with dynamic waves. i’m not the creator but since they left your credit i just thought i’d drop in to say thanks :slight_smile:

quewon | 2022-04-20 09:45

2 Likes
:bust_in_silhouette: Reply From: camarones

Late to the game here, but here’s a simple function that takes an array of vector2D (e.g. the points of a Line2D) and returns a Curve2D.

func array_to_curve(input : Array, dist : float):
#dist determines length of controls, set dist = 0 for no smoothing
var curve = Curve2D.new()

#calculate first point
var start_dir = input[0].direction_to(input[1])
curve.add_point(input[0], - start_dir * dist, start_dir * dist)

#calculate middle points
for i in range(1, input.size() - 1):
	var dir = input[i-1].direction_to(input[i+1])
	curve.add_point(input[i], -dir * dist, dir * dist)

#calculate last point
var end_dir = input[-1].direction_to(input[-2])
curve.add_point(input[-1], - end_dir * dist, end_dir * dist)

return curve
:bust_in_silhouette: Reply From: Olaf007

The easiest way is to add a Line2D-Node as a child to your Path2D, give it a script, and provide it with the baked points of the path:

func _ready():
	points = get_parent().curve.get_baked_points() 

Now you can set up your Line2D with the attributes you want. (Color, Texture, etc.)