📜 ⬆️ ⬇️

Interpolation: draw smooth graphs using Bezier curves

Good day, harbilitator.

In this article I would like to tell you about a once thought-out algorithm (or, most likely, a re-invented bicycle) of building a smooth graph at given points using Bezier curves. The article was written under the influence of this article here and a very useful comment from comrade lany , for which a special thank you to them.

Formulation of the problem
There is an array of Y-cov points located evenly along the X-axis. It is necessary to get a smooth graph that passes through all given points. The example in the figure below:
')


All who are interested, please under the cat.

There are a number of standard solutions for carrying out a smooth curve through points (for this reason many interesting things are written in the article already mentioned ), such as, for example, interpolation by splines. When this algorithm was invented in the third year, the word “interpolation” terrified me, and googling at the request “smoothing of graphs” did not give a reasonable understanding of the results. But somehow I reached the Bezier curves and I really liked them. Draws fast, the algorithm is intuitive ... What else is needed for happiness. Well, somehow it started.

main idea


Split the idea into three subparagraphs to make it clearer and more readable.
  1. Bezier curves are well written on the wiki and javascript.ru . If you read carefully, you can pay attention that the Bezier curve goes out from the first point tangent to the straight start-point-first_-stop_point. Similarly, and at the end - the curve comes with respect to a straight line last_thatt-end-point. Thus, it turns out that if we have one curve end at point A and go tangently to line a , and the other curve leaves this point A tangently to that same line a , then this transition between two Bezier curves will turn out smooth.
  2. Proceeding from the first point, it turns out that our points of support on the left and right of point A must lie on one straight line. After thinking a bit, it was decided that this line should be such that ∠BAB 1 = ∠CAC 1 (figure below), where the points B 1 and C 1 are reference points.


  3. The distance from point A to point B 1 and C 1 was decided to be equal to half the step in X between points B and A , A and C , etc. It is difficult for me to somehow substantiate such a choice, but it is important that this distance be less than the X step between points A and B , otherwise it may result in something like the figure below. It is important to understand that the longer this distance is, the more winding the curve will be and vice versa. Half a step in X seems to be optimal, but options are already possible here.



Thus, it turns out that the task is reduced to finding a straight line ( B 1 C 1 ) and, in fact, reference points B 1 and C 1 , from which we will later build Bezier curves.

Search straight


As is known, a straight line on a plane is expressed by the formula y = kx + b , where k is the tangent of the angle of inclination of the straight line to the X axis, and b is the “height” of the intersection of the straight line and the Y axis.

K factor search

I will say in advance that k = tg (φ) = tg ((α-β) / 2) = (Sqrt (((Y A -Y B ) 2 + ΔX 2 ) * ((Y A- Y C ) 2 + ΔX 2 )) - ΔX 2 - (Y A -Y B ) * (Y A -Y C )) / (ΔX * (Y C -Y B )) , where ΔX is the distance along X between the points of the graph (I remind you that us points are evenly spaced on X). Below is a mathematical proof of the correctness of the formula, but if you are not in the mood, you can simply skip it.


Mathematical derivation of the coefficient k
  1. Let the angle ∠O 1 BA = α , and the angle ∠O 2 CA = β .
    Then ∠BAO 1 = 90 o ; ∠CAO 2 = 90 o , since △ ABO 1 and △ ACO 2 are rectangular.

  2. (1) ∠B 1 AC 1 = ∠B 1 AB + ∠BAO 1 + O 1 AC + ∠CA 1 = 180 o
    (2) ∠B 1 AB = ∠C 1 - by condition
    (3) ∠BAO 1 = 90 o
    (4) ∠O 1 A = ∠CAO 2 = 90 o

    From (1), (2), (3) and (4) it turns out that:
    2 * ∠C 1 AC + (90 o -α) + (90 o -β) = 180 o
    2 * ∠C 1 AC + 180 o -α-β = 180 o
    (5) ∠C 1 A = (α + β) / 2

  3. (6) ∠C 1 A = ∠C 1 AD + ∠DAC = φ + ∠DAC
    (7) ∠DAC = ∠O 2 CA = β - as internal miscellaneous angles formed by two parallel straight lines (AD) and (O 2 C) and secant (AC)

    From (5), (6) and (7) it turns out that:
    ∠C 1 AC = φ + ∠DAC
    (α + β) / 2 = φ + β
    φ + β = (α + β) / 2
    (8) φ = (α-β) / 2

  4. k = tg (φ) = tg ((α-β) / 2)

    From △ ABO 1 :
    sin (α) = [AO 1 ] / [AB]
    cos (α) = [BO 1 ] / [AB]

    From △ ACO 2 :
    sin (β) = [AO 2 ] / [AC]
    cos (β) = [CO 2 ] / [AC]

    The square brackets imply the length of the segment (I did not want to use vertical straight lines - I hope that the reader will forgive me)

  5. From the previous subparagraph should:


  6. Knowing that:
    [BO 1 ] = [CO 2 ] = ΔX
    [AO 1 ] = Y A -Y B
    [AO 2 ] = Y A –Y C
    [AB] = Sqrt ([AO 1 ] 2 + [BO 1 ] 2 ) = Sqrt ((Y A -Y B ) 2 + ΔX 2 )
    [AC] = Sqrt ([AO 2 ] 2 + [CO 2 ] 2 ) = Sqrt ((Y A -Y C ) 2 + ΔX 2 )

    We get that:

And so, k we found. Looking ahead to say that b is not useful to us in the calculations. Let's start the search for reference points.

Looking for pivot points


Immediately I will say that: ΔX '= ΔX / 2 * Sqrt (1 / (1 + k 2 )) , the coordinates of the reference point on the right: X C1 = X A + ΔX'; Y C1 = Y A + k * ΔX ' , and on the left: X B1 = X A -ΔX'; Y B1 = Y A -k * ΔX ' .



A bit of mathematics, which proves it.
From trigonometry, we remember that:


From △ AC 1 O :
ΔX '= [AO] = [AC 1 ] * cos (φ).
[C 1 O] = [AO] * tg (φ) = k * ΔX '

If we accept [AC 1 ] equal to half the step in X between the main points of the graph (points B and A , A and C , etc.), then:


And here, actually:
X C1 = X A + ΔX '
X B1 = X A -ΔX '
Y C1 = Y A + k * ΔX '
Y B1 = Y A -k * ΔX '


To enjoy!


  1. From comrade lany (a huge thank you to him and plus him to karma), I learned that html5 using the quadraticCurveTo () and bezierCurveTo () functions can draw bezier curves on the canvas. Accordingly, the algorithm can be applied with JavaScript.
  2. A nice feature of the algorithm is that the graph predictably “protrudes” beyond the boundaries of the space of points through which we actually plot. If we take the distance to the reference points equal to half of the step in X (the [AC 1 ] segment in the last figure), then a gap of a quarter of the step in X above and below the canvas boundaries will suffice.

JSFiddle implementation example


UPDATE:
  1. Attempts to make the pivot points have to be counted once, and then, when scaling the graph, simply using their coordinates, failed. It all comes down to the fact that the calculation of the coefficient k includes the current scale in X, and the current scale in Y. And to pull them out of the formula does not go. Comrade quverty warned me about it and turned out to be right.
  2. I would like to note that the curvature of the plotted graph can be adjusted. To do this, change the distance to the reference points - change the coefficient ΔX '= ΔX / 2 * Sqrt (1 / (1 + k 2 )) , namely - the denominator of this piece of it: ΔX / 2 . The denominator less than 1 should not be taken (read the third subparagraph of the paragraph "Basic idea"). You can experiment on JSFiddle (line 129 of JavaScript).
  3. Pay attention to the implementation of the spline Catmulla-Roma comrade IIvana . And thank him for this comment.

Source: https://habr.com/ru/post/247235/


All Articles