beziers_and_curves.rst 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. .. _doc_beziers_and_curves:
  2. Beziers, curves and paths
  3. =========================
  4. Bezier curves are a mathematical approximation of natural geometric shapes. We
  5. use them to represent a curve with as little information as possible and with a
  6. high level of flexibility.
  7. Unlike more abstract mathematical concepts, Bezier curves were created for
  8. industrial design. They are a popular tool in the graphics software industry.
  9. They rely on :ref:`interpolation<doc_interpolation>`, which we saw in the
  10. previous article, combining multiple steps to create smooth curves. To better
  11. understand how Bezier curves work, let's start from its simplest form: Quadratic
  12. Bezier.
  13. Quadratic Bezier
  14. ----------------
  15. Take three points, the minimum required for Quadratic Bezier to work:
  16. .. image:: img/bezier_quadratic_points.png
  17. To draw a curve between them, we first interpolate gradually over the two
  18. vertices of each of the two segments formed by the three points, using values
  19. ranging from 0 to 1. This gives us two points that move along the segments as we
  20. change the value of ``t`` from 0 to 1.
  21. .. tabs::
  22. .. code-tab:: gdscript GDScript
  23. func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float):
  24. var q0 = p0.lerp(p1, t)
  25. var q1 = p1.lerp(p2, t)
  26. .. code-tab:: csharp
  27. private Vector2 QuadraticBezier(Vector2 p0, Vector2 p1, Vector2 p2, float t)
  28. {
  29. Vector2 q0 = p0.Lerp(p1, t);
  30. Vector2 q1 = p1.Lerp(p2, t);
  31. }
  32. We then interpolate ``q0`` and ``q1`` to obtain a single point ``r`` that moves
  33. along a curve.
  34. .. tabs::
  35. .. code-tab:: gdscript GDScript
  36. var r = q0.lerp(q1, t)
  37. return r
  38. .. code-tab:: csharp
  39. Vector2 r = q0.Lerp(q1, t);
  40. return r;
  41. This type of curve is called a *Quadratic Bezier* curve.
  42. .. image:: img/bezier_quadratic_points2.gif
  43. *(Image credit: Wikipedia)*
  44. Cubic Bezier
  45. ------------
  46. Building upon the previous example, we can get more control by interpolating
  47. between four points.
  48. .. image:: img/bezier_cubic_points.png
  49. We first use a function with four parameters to take four points as an input,
  50. ``p0``, ``p1``, ``p2`` and ``p3``:
  51. .. tabs::
  52. .. code-tab:: gdscript GDScript
  53. func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
  54. .. code-tab:: csharp
  55. public Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
  56. {
  57. }
  58. We apply a linear interpolation to each couple of points to reduce them to
  59. three:
  60. .. tabs::
  61. .. code-tab:: gdscript GDScript
  62. var q0 = p0.lerp(p1, t)
  63. var q1 = p1.lerp(p2, t)
  64. var q2 = p2.lerp(p3, t)
  65. .. code-tab:: csharp
  66. Vector2 q0 = p0.Lerp(p1, t);
  67. Vector2 q1 = p1.Lerp(p2, t);
  68. Vector2 q2 = p2.Lerp(p3, t);
  69. We then take our three points and reduce them to two:
  70. .. tabs::
  71. .. code-tab:: gdscript GDScript
  72. var r0 = q0.lerp(q1, t)
  73. var r1 = q1.lerp(q2, t)
  74. .. code-tab:: csharp
  75. Vector2 r0 = q0.Lerp(q1, t);
  76. Vector2 r1 = q1.Lerp(q2, t);
  77. And to one:
  78. .. tabs::
  79. .. code-tab:: gdscript GDScript
  80. var s = r0.lerp(r1, t)
  81. return s
  82. .. code-tab:: csharp
  83. Vector2 s = r0.Lerp(r1, t);
  84. return s;
  85. Here is the full function:
  86. .. tabs::
  87. .. code-tab:: gdscript GDScript
  88. func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
  89. var q0 = p0.lerp(p1, t)
  90. var q1 = p1.lerp(p2, t)
  91. var q2 = p2.lerp(p3, t)
  92. var r0 = q0.lerp(q1, t)
  93. var r1 = q1.lerp(q2, t)
  94. var s = r0.lerp(r1, t)
  95. return s
  96. .. code-tab:: csharp
  97. private Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
  98. {
  99. Vector2 q0 = p0.Lerp(p1, t);
  100. Vector2 q1 = p1.Lerp(p2, t);
  101. Vector2 q2 = p2.Lerp(p3, t);
  102. Vector2 r0 = q0.Lerp(q1, t);
  103. Vector2 r1 = q1.Lerp(q2, t);
  104. Vector2 s = r0.Lerp(r1, t);
  105. return s;
  106. }
  107. The result will be a smooth curve interpolating between all four points:
  108. .. image:: img/bezier_cubic_points.gif
  109. *(Image credit: Wikipedia)*
  110. .. note:: Cubic Bezier interpolation works the same in 3D, just use ``Vector3``
  111. instead of ``Vector2``.
  112. Adding control points
  113. ---------------------
  114. Building upon Cubic Bezier, we can change the way two of the points work to
  115. control the shape of our curve freely. Instead of having ``p0``, ``p1``, ``p2``
  116. and ``p3``, we will store them as:
  117. * ``point0 = p0``: Is the first point, the source
  118. * ``control0 = p1 - p0``: Is a vector relative to the first control point
  119. * ``control1 = p3 - p2``: Is a vector relative to the second control point
  120. * ``point1 = p3``: Is the second point, the destination
  121. This way, we have two points and two control points which are relative vectors
  122. to the respective points. If you've used graphics or animation software before,
  123. this might look familiar:
  124. .. image:: img/bezier_cubic_handles.png
  125. This is how graphics software presents Bezier curves to the users, and how they
  126. work and look in Godot.
  127. Curve2D, Curve3D, Path and Path2D
  128. ---------------------------------
  129. There are two objects that contain curves: :ref:`Curve3D <class_Curve3D>` and :ref:`Curve2D <class_Curve2D>` (for 3D and 2D respectively).
  130. They can contain several points, allowing for longer paths. It is also possible to set them to nodes: :ref:`Path3D <class_Path3D>` and :ref:`Path2D <class_Path2D>` (also for 3D and 2D respectively):
  131. .. image:: img/bezier_path_2d.png
  132. Using them, however, may not be completely obvious, so following is a description of the most common use cases for Bezier curves.
  133. Evaluating
  134. ----------
  135. Only evaluating them may be an option, but in most cases it's not very useful. The big drawback with Bezier curves is that if you traverse them at constant speed, from ``t = 0`` to ``t = 1``, the actual interpolation will *not* move at constant speed. The speed is also an interpolation between the distances between points ``p0``, ``p1``, ``p2`` and ``p3`` and there is not a mathematically simple way to traverse the curve at constant speed.
  136. Let's do an example with the following pseudocode:
  137. .. tabs::
  138. .. code-tab:: gdscript GDScript
  139. var t = 0.0
  140. func _process(delta):
  141. t += delta
  142. position = _cubic_bezier(p0, p1, p2, p3, t)
  143. .. code-tab:: csharp
  144. private float _t = 0.0f;
  145. public override void _Process(double delta)
  146. {
  147. _t += (float)delta;
  148. Position = CubicBezier(p0, p1, p2, p3, _t);
  149. }
  150. .. image:: img/bezier_interpolation_speed.gif
  151. As you can see, the speed (in pixels per second) of the circle varies, even though ``t`` is increased at constant speed. This makes beziers difficult to use for anything practical out of the box.
  152. Drawing
  153. -------
  154. Drawing beziers (or objects based on the curve) is a very common use case, but it's also not easy. For pretty much any case, Bezier curves need to be converted to some sort of segments. This is normally difficult, however, without creating a very high amount of them.
  155. The reason is that some sections of a curve (specifically, corners) may require considerable amounts of points, while other sections may not:
  156. .. image:: img/bezier_point_amount.png
  157. Additionally, if both control points were ``0, 0`` (remember they are relative vectors), the Bezier curve would just be a straight line (so drawing a high amount of points would be wasteful).
  158. Before drawing Bezier curves, *tessellation* is required. This is often done with a recursive or divide and conquer function that splits the curve until the curvature amount becomes less than a certain threshold.
  159. The *Curve* classes provide this via the
  160. :ref:`Curve2D.tessellate() <class_Curve2D_method_tessellate>` function (which receives optional ``stages`` of recursion and angle ``tolerance`` arguments). This way, drawing something based on a curve is easier.
  161. Traversal
  162. ---------
  163. The last common use case for the curves is to traverse them. Because of what was mentioned before regarding constant speed, this is also difficult.
  164. To make this easier, the curves need to be *baked* into equidistant points. This way, they can be approximated with regular interpolation (which can be improved further with a cubic option). To do this, just use the :ref:`Curve3D.sample_baked()<class_Curve3D_method_sample_baked>` method together with
  165. :ref:`Curve2D.get_baked_length()<class_Curve2D_method_get_baked_length>`. The first call to either of them will bake the curve internally.
  166. Traversal at constant speed, then, can be done with the following pseudo-code:
  167. .. tabs::
  168. .. code-tab:: gdscript GDScript
  169. var t = 0.0
  170. func _process(delta):
  171. t += delta
  172. position = curve.sample_baked(t * curve.get_baked_length(), true)
  173. .. code-tab:: csharp
  174. private float _t = 0.0f;
  175. public override void _Process(double delta)
  176. {
  177. _t += (float)delta;
  178. Position = curve.SampleBaked(_t * curve.GetBakedLength(), true);
  179. }
  180. And the output will, then, move at constant speed:
  181. .. image:: img/bezier_interpolation_baked.gif