Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I was facing the same problem: Every animation package out there seems to use Bézier curves to control values over time, but there is no information out there on how to implement a Bézier curve as a y(x) function. So here is what I came up with.</p> <p>A standard cubic Bézier curve in 2D space can be defined by the four points <strong>P<sub>0</sub>=(x<sub>0</sub>, y<sub>0</sub>) .. P<sub>3</sub>=(x<sub>3</sub>, y<sub>3</sub>)</strong>.<br> P<sub>0</sub> and P<sub>3</sub> are the end points of the curve, while P<sub>1</sub> and P<sub>2</sub> are the handles affecting its shape. Using a parameter t ϵ [0, 1], the x and y coordinates for any given point along the curve can then be determined using the equations<br> A) <strong>x = (1-t)<sup>3</sup>x<sub>0</sub> + 3t(1-t)<sup>2</sup>x<sub>1</sub> + 3t<sup>2</sup>(1-t)x<sub>2</sub> + t<sup>3</sup>x<sub>3</sub></strong> and<br> B) <strong>y = (1-t)<sup>3</sup>y<sub>0</sub> + 3t(1-t)<sup>2</sup>y<sub>1</sub> + 3t<sup>2</sup>(1-t)y<sub>2</sub> + t<sup>3</sup>y<sub>3</sub></strong>.</p> <p>What we want is a function y(x) that, given an x coordinate, will return the corresponding y coordinate of the curve. For this to work, the curve must move monotonically from left to right, so that it doesn't occupy the same x coordinate more than once on different y positions. The easiest way to ensure this is to restrict the input points so that <strong>x<sub>0</sub> &lt; x<sub>3</sub></strong> and <strong>x<sub>1</sub>, x<sub>2</sub> ϵ [x<sub>0</sub>, x<sub>3</sub>]</strong>. In other words, P<sub>0</sub> must be to the left of P<sub>3</sub> with the two handles between them.</p> <p>In order to calculate y for a given x, we must first determine t from x. Getting y from t is then a simple matter of applying t to equation B.</p> <p>I see two ways of determining t for a given y.</p> <p>First, you might try a binary search for t. Start with a lower bound of 0 and an upper bound of 1 and calculate x for these values for t via equation A. Keep bisecting the interval until you get a reasonably close approximation. While this should work fine, it will neither be particularly fast nor very precise (at least not both at once).</p> <p>The second approach is to actually solve equation A for t. That's a bit tough to implement because the equation is cubic. On the other hand, calculation becomes really fast and yields precise results.</p> <p>Equation A can be rewritten as<br> <strong>(-x<sub>0</sub>+3x<sub>1</sub>-3x<sub>2</sub>+x<sub>3</sub>)t<sup>3</sup> + (3x<sub>0</sub>-6x<sub>1</sub>+3x<sub>2</sub>)t<sup>2</sup> + (-3x<sub>0</sub>+3x<sub>1</sub>)t + (x<sub>0</sub>-x) = 0</strong>.<br> Inserting your actual values for x<sub>0</sub>..x<sub>3</sub>, we get a cubic equation of the form <strong>a<em>t<sup>3</sup> + b</em>t<sup>2</sup> + c*t + d = 0</strong> for which we know there is only one solution within [0, 1]. We can now solve this equation using an algorithm like the one posted in <a href="https://stackoverflow.com/a/13328773/52041">this Stack Overflow answer</a>.</p> <p>The following is a little C# class demonstrating this approach. It should be simple enough to convert it to a language of your choice.</p> <pre class="lang-cs prettyprint-override"><code>using System; public class Point { public Point(double x, double y) { X = x; Y = y; } public double X { get; private set; } public double Y { get; private set; } } public class BezierCurve { public BezierCurve(Point p0, Point p1, Point p2, Point p3) { P0 = p0; P1 = p1; P2 = p2; P3 = p3; } public Point P0 { get; private set; } public Point P1 { get; private set; } public Point P2 { get; private set; } public Point P3 { get; private set; } public double? GetY(double x) { // Determine t double t; if (x == P0.X) { // Handle corner cases explicitly to prevent rounding errors t = 0; } else if (x == P3.X) { t = 1; } else { // Calculate t double a = -P0.X + 3 * P1.X - 3 * P2.X + P3.X; double b = 3 * P0.X - 6 * P1.X + 3 * P2.X; double c = -3 * P0.X + 3 * P1.X; double d = P0.X - x; double? tTemp = SolveCubic(a, b, c, d); if (tTemp == null) return null; t = tTemp.Value; } // Calculate y from t return Cubed(1 - t) * P0.Y + 3 * t * Squared(1 - t) * P1.Y + 3 * Squared(t) * (1 - t) * P2.Y + Cubed(t) * P3.Y; } // Solves the equation ax³+bx²+cx+d = 0 for x ϵ ℝ // and returns the first result in [0, 1] or null. private static double? SolveCubic(double a, double b, double c, double d) { if (a == 0) return SolveQuadratic(b, c, d); if (d == 0) return 0; b /= a; c /= a; d /= a; double q = (3.0 * c - Squared(b)) / 9.0; double r = (-27.0 * d + b * (9.0 * c - 2.0 * Squared(b))) / 54.0; double disc = Cubed(q) + Squared(r); double term1 = b / 3.0; if (disc &gt; 0) { double s = r + Math.Sqrt(disc); s = (s &lt; 0) ? -CubicRoot(-s) : CubicRoot(s); double t = r - Math.Sqrt(disc); t = (t &lt; 0) ? -CubicRoot(-t) : CubicRoot(t); double result = -term1 + s + t; if (result &gt;= 0 &amp;&amp; result &lt;= 1) return result; } else if (disc == 0) { double r13 = (r &lt; 0) ? -CubicRoot(-r) : CubicRoot(r); double result = -term1 + 2.0 * r13; if (result &gt;= 0 &amp;&amp; result &lt;= 1) return result; result = -(r13 + term1); if (result &gt;= 0 &amp;&amp; result &lt;= 1) return result; } else { q = -q; double dum1 = q * q * q; dum1 = Math.Acos(r / Math.Sqrt(dum1)); double r13 = 2.0 * Math.Sqrt(q); double result = -term1 + r13 * Math.Cos(dum1 / 3.0); if (result &gt;= 0 &amp;&amp; result &lt;= 1) return result; result = -term1 + r13 * Math.Cos((dum1 + 2.0 * Math.PI) / 3.0); if (result &gt;= 0 &amp;&amp; result &lt;= 1) return result; result = -term1 + r13 * Math.Cos((dum1 + 4.0 * Math.PI) / 3.0); if (result &gt;= 0 &amp;&amp; result &lt;= 1) return result; } return null; } // Solves the equation ax² + bx + c = 0 for x ϵ ℝ // and returns the first result in [0, 1] or null. private static double? SolveQuadratic(double a, double b, double c) { double result = (-b + Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a); if (result &gt;= 0 &amp;&amp; result &lt;= 1) return result; result = (-b - Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a); if (result &gt;= 0 &amp;&amp; result &lt;= 1) return result; return null; } private static double Squared(double f) { return f * f; } private static double Cubed(double f) { return f * f * f; } private static double CubicRoot(double f) { return Math.Pow(f, 1.0 / 3.0); } } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload