+3 votes

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

But how would one go about drawing that curve?

in Engine by (1,604 points)

I've been researching this for about a week. ;)

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!

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

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.

5 Answers

+4 votes
Best answer

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.

by (3,162 points)
selected by
0 votes

It's in the documentation:

http://docs.godotengine.org/en/3.0/tutorials/2d/custom_drawing_in_2d.html

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)
by (21 points)
+5 votes

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.

SmoothPath Preview

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)
by (4,237 points)

Amazing thanks, works brilliantly.

Great job mate! I am also trying to visualize pointin and pointout 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 getpointin/out()

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

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?

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 :)

+1 vote

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
by (113 points)
+3 votes

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.)

by (54 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.