mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-02-24 17:42:43 +01:00
72 lines
27 KiB
HTML
72 lines
27 KiB
HTML
<!doctype html>
|
||
<html>
|
||
<head>
|
||
<meta charset='utf-8'>
|
||
<title>
|
||
Finding extremities: root finding
|
||
</title>
|
||
<base href='..'>
|
||
</head>
|
||
<body>
|
||
<div data-reactid=".fw3jf46xog" data-react-checksum="-640624315"><div class="ribbon" data-reactid=".fw3jf46xog.0"><img src="images/ribbon.png" alt="This page on GitHub" usemap="#githubmap" width="200px" height="149px" data-reactid=".fw3jf46xog.0.0"/><map name="githubmap" data-reactid=".fw3jf46xog.0.1"><area shape="poly" coords="30,0, 200,0, 200,114" href="http://github.com/pomax/BezierInfo-2" alt="This page on GitHub" data-reactid=".fw3jf46xog.0.1.0"/></map></div><header data-reactid=".fw3jf46xog.1"><h1 data-reactid=".fw3jf46xog.1.0">A Primer on Bézier Curves</h1><h2 data-reactid=".fw3jf46xog.1.1">A free, online book for when you really need to know how to do Bézier things.</h2></header><div data-reactid=".fw3jf46xog.2"><table class="relatives before" data-reactid=".fw3jf46xog.2.0"><tbody data-reactid=".fw3jf46xog.2.0.0"><tr data-reactid=".fw3jf46xog.2.0.0.0"><td data-reactid=".fw3jf46xog.2.0.0.0.0"><a class="prev" href="components" data-reactid=".fw3jf46xog.2.0.0.0.0.0">13. Component functions</a></td><td class="toc" data-reactid=".fw3jf46xog.2.0.0.0.1"><a class="" href="/" data-reactid=".fw3jf46xog.2.0.0.0.1.0">ToC</a></td><td data-reactid=".fw3jf46xog.2.0.0.0.2"><a class="next" href="boundingbox" data-reactid=".fw3jf46xog.2.0.0.0.2.0">15. Bounding boxes</a></td></tr></tbody></table><section data-reactid=".fw3jf46xog.2.$extremities"><h2 data-num="14" data-reactid=".fw3jf46xog.2.$extremities.0"><a href="#extremities" data-reactid=".fw3jf46xog.2.$extremities.0.0">Finding extremities: root finding</a></h2><p data-reactid=".fw3jf46xog.2.$extremities.1">Now that we understand (well, superficially anyway) the component functions, we can find the extremities of our Bézier curve by finding maxima and minima on the component functions, by solving the equations B'(t) = 0 and B''(t) = 0. Although, in the case of quadratic curves there is no B''(t), so we only need to compute B'(t) = 0. So, how do we compute the first and second derivatives? Fairly easily, actually, until our derivatives are 4th order or higher... then things get really hard. But let's start simple:</p><h3 data-reactid=".fw3jf46xog.2.$extremities.2">Quadratic curves: linear derivatives.</h3><p data-reactid=".fw3jf46xog.2.$extremities.3">Finding the solution for "where is this line 0" should be trivial:</p><p data-reactid=".fw3jf46xog.2.$extremities.4"><img class="LaTeX SVG" src="images/latex/9929fd19d54366db382b7d453491d90f894352a7.svg" style="width:9.450000000000001rem;height:6.37515rem;" data-reactid=".fw3jf46xog.2.$extremities.4.0"/></p><p data-reactid=".fw3jf46xog.2.$extremities.5"><span data-reactid=".fw3jf46xog.2.$extremities.5.0">Done. And quadratic curves have no meaningful second derivative, so we're </span><em data-reactid=".fw3jf46xog.2.$extremities.5.1">really</em><span data-reactid=".fw3jf46xog.2.$extremities.5.2"> done.</span></p><h3 data-reactid=".fw3jf46xog.2.$extremities.6">Cubic curves: the quadratic formula.</h3><p data-reactid=".fw3jf46xog.2.$extremities.7"><span data-reactid=".fw3jf46xog.2.$extremities.7.0">The derivative of a cubic curve is a quadratic curve, and finding the roots for a quadratic Bézier curve means we can apply the </span><a href="https://en.wikipedia.org/wiki/Quadratic_formula" data-reactid=".fw3jf46xog.2.$extremities.7.1">Quadratic formulat</a><span data-reactid=".fw3jf46xog.2.$extremities.7.2">. If you've seen it before, you'll remember it, and if you haven't, it looks like this:</span></p><p data-reactid=".fw3jf46xog.2.$extremities.8"><img class="LaTeX SVG" src="images/latex/d5882cc83b002196c8e701ad273ced103e2b4484.svg" style="width:28.72485rem;height:2.475rem;" data-reactid=".fw3jf46xog.2.$extremities.8.0"/></p><p data-reactid=".fw3jf46xog.2.$extremities.9">So, if we can express a Bézier component function as a plain polynomial, we're done: we just plug in the values into the quadratic formula, check if that square root is negative or not (if it is, there are no roots) and then just compute the two values that come out (because of that plus/minus sign we get two). Any value between 0 and 1 is a root that matters for Bézier curves, anything below or above that is irrelevant (because Bézier curves are only defined over the interval [0,1]). So, how do we convert?</p><p data-reactid=".fw3jf46xog.2.$extremities.a"><span data-reactid=".fw3jf46xog.2.$extremities.a.0">First we turn our cubic Bézier function into a quadratic one, by following the rule mentioned at the end of the </span><a href="#derivatives" data-reactid=".fw3jf46xog.2.$extremities.a.1">derivatives section</a><span data-reactid=".fw3jf46xog.2.$extremities.a.2">:</span></p><p data-reactid=".fw3jf46xog.2.$extremities.b"><img class="LaTeX SVG" src="images/latex/d904e86a3967e7e5bdba8a5f6b943a8fde3ad458.svg" style="width:45rem;height:2.77515rem;" data-reactid=".fw3jf46xog.2.$extremities.b.0"/></p><p data-reactid=".fw3jf46xog.2.$extremities.c"><span data-reactid=".fw3jf46xog.2.$extremities.c.0">And then, using these </span><em data-reactid=".fw3jf46xog.2.$extremities.c.1">v</em><span data-reactid=".fw3jf46xog.2.$extremities.c.2"> values, we can find out what our </span><em data-reactid=".fw3jf46xog.2.$extremities.c.3">a</em><span data-reactid=".fw3jf46xog.2.$extremities.c.4">, </span><em data-reactid=".fw3jf46xog.2.$extremities.c.5">b</em><span data-reactid=".fw3jf46xog.2.$extremities.c.6">, and </span><em data-reactid=".fw3jf46xog.2.$extremities.c.7">c</em><span data-reactid=".fw3jf46xog.2.$extremities.c.8"> should be:</span></p><p data-reactid=".fw3jf46xog.2.$extremities.d"><img class="LaTeX SVG" src="images/latex/c638a85a950ffb535fbf2056958bed5f44be5067.svg" style="width:21.375rem;height:7.2rem;" data-reactid=".fw3jf46xog.2.$extremities.d.0"/></p><p data-reactid=".fw3jf46xog.2.$extremities.e">So we can find the roots by using:</p><p data-reactid=".fw3jf46xog.2.$extremities.f"><img class="LaTeX SVG" src="images/latex/076b74a0f2bcb43a3b2d39fdc52c58c6f89ce33a.svg" style="width:20.84985rem;height:3.97485rem;" data-reactid=".fw3jf46xog.2.$extremities.f.0"/></p><p data-reactid=".fw3jf46xog.2.$extremities.g">Easy peasy. We also note that the second derivative of a cubic curve means computing the first derivative of a quadratic curve, and we just saw how to do that in the section above.</p><h3 data-reactid=".fw3jf46xog.2.$extremities.h">Quartic curves: Cardano's algorithm.</h3><p data-reactid=".fw3jf46xog.2.$extremities.i"><span data-reactid=".fw3jf46xog.2.$extremities.i.0">Quartic—fourth degree—curves have a cubic function as derivative. Now, cubic functions are a bit of a problem because they're really hard to solve. But, way back in the 16</span><sup data-reactid=".fw3jf46xog.2.$extremities.i.1">th</sup><span data-reactid=".fw3jf46xog.2.$extremities.i.2"> century, </span><a href="https://en.wikipedia.org/wiki/Gerolamo_Cardano" data-reactid=".fw3jf46xog.2.$extremities.i.3">Gerolamo Cardano</a><span data-reactid=".fw3jf46xog.2.$extremities.i.4"> figured out that even if the general cubic function is really hard to solve, it can be rewritten to a form for which finding the roots is "easy", and then the only hard part is figuring out how to go from that form to the generic form. So:</span></p><p data-reactid=".fw3jf46xog.2.$extremities.j"><img class="LaTeX SVG" src="images/latex/a16a0da87e138b1307973397275c296eb475b1b1.svg" style="width:45rem;height:2.77515rem;" data-reactid=".fw3jf46xog.2.$extremities.j.0"/></p><p data-reactid=".fw3jf46xog.2.$extremities.k"><span data-reactid=".fw3jf46xog.2.$extremities.k.0">This is easier because for the "easier formula" we can use </span><a href="http://www.wolframalpha.com/input/?i=t^3+%2B+pt+%2B+q" data-reactid=".fw3jf46xog.2.$extremities.k.1">regular calculus</a><span data-reactid=".fw3jf46xog.2.$extremities.k.2"> to find the roots (as a cubic function, however, it can have up to three roots, but two of those can be complex. For the purpose of Bézier curve extremities, we can completely ignore those complex roots, since our </span><em data-reactid=".fw3jf46xog.2.$extremities.k.3">t</em><span data-reactid=".fw3jf46xog.2.$extremities.k.4"> is a plain real number from 0 to 1).</span></p><p data-reactid=".fw3jf46xog.2.$extremities.l"><span data-reactid=".fw3jf46xog.2.$extremities.l.0">So, the trick is to figure out how to turn the first formula into the second formula, and to then work out the maths that gives us the roots. This is explained in detail over at </span><a href="http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm" data-reactid=".fw3jf46xog.2.$extremities.l.1">Ken J. Ward's page</a><span data-reactid=".fw3jf46xog.2.$extremities.l.2"> for solving the cubic equation, so instead of showing the maths, I'm simply going to show the programming code for solving the cubic equation, with the complex roots getting totally ignored.</span></p><div class="howtocode" data-reactid=".fw3jf46xog.2.$extremities.m"><h3 data-reactid=".fw3jf46xog.2.$extremities.m.0">Implementing Cardano's algorithm for finding all real roots</h3><p data-reactid=".fw3jf46xog.2.$extremities.m.1"><span data-reactid=".fw3jf46xog.2.$extremities.m.1.0">The "real roots" part is fairly important, because while you cannot take a square, cube, etc. root of a negative number in the "real" number space (denoted with ℝ), this is perfectly fine in the </span><a href="https://en.wikipedia.org/wiki/Complex_number" data-reactid=".fw3jf46xog.2.$extremities.m.1.1">"complex" number</a><span data-reactid=".fw3jf46xog.2.$extremities.m.1.2"> space (denoted with ℂ). And, as it so happens, Cardano is also attributed as the first mathematician in history to have made use of complex numbers in his calculations. For this very algorithm!</span></p><pre data-reactid=".fw3jf46xog.2.$extremities.m.2"><span data-reactid=".fw3jf46xog.2.$extremities.m.2.0">// A helper function to filter for values in the [0,1] interval:</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2">function accept(t) </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.3">{</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.4">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.5"> return 0<=t && t <=1;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.6">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.7">}</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.8">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.9">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.a">// A real-cuberoots-only function:</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.b">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.c">function crt(v) </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.d">{</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.e">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.f"> if(v<0) return -Math.pow(-v,1/3);</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.g">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.h"> return Math.pow(v,1/3);</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.i">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.j">}</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.k">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.l">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.m">// Now then: given cubic coordinates </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.n">{</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.o">pa, pb, pc, pd</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.p">}</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.q"> find all roots.</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.r">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.s">function getCubicRoots(pa, pb, pc, pd) </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.t">{</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.u">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.v"> var d = (-pa + 3*pb - 3*pc + pd),</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.w">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.x"> a = (3*pa - 6*pb + 3*pc) / d,</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.y">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.z"> b = (-3*pa + 3*pb) / d,</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.10">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.11"> c = pa / d;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.12">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.13">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.14"> var p = (3*b - a*a)/3,</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.15">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.16"> p3 = p/3,</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.17">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.18"> q = (2*a*a*a - 9*a*b + 27*c)/27,</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.19">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1a"> q2 = q/2,</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1b">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1c"> discriminant = q2*q2 + p3*p3*p3;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1d">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1e">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1f"> // and some variables we're going to use later on:</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1g">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1h"> var u1,v1,root1,root2,root3;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1i">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1j">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1k"> // three possible real roots:</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1l">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1m"> if (discriminant < 0) </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1n">{</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1o">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1p"> var mp3 = -p/3,</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1q">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1r"> mp33 = mp3*mp3*mp3,</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1s">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1t"> r = sqrt( mp33 ),</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1u">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1v"> t = -q / (2*r),</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1w">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1x"> cosphi = t<-1 ? -1 : t>1 ? 1 : t,</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1y">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.1z"> phi = acos(cosphi),</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.20">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.21"> crtr = cuberoot(r),</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.22">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.23"> t1 = 2*crtr;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.24">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.25"> root1 = t1 * cos(phi/3) - a/3;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.26">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.27"> root2 = t1 * cos((phi+2*pi)/3) - a/3;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.28">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.29"> root3 = t1 * cos((phi+4*pi)/3) - a/3;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2a">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2b"> return [root1, root2, root3].filter(accept);</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2c">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2d"> </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2e">}</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2f">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2g">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2h"> // three real roots, but two of them are equal:</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2i">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2j"> else if(discriminant === 0) </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2k">{</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2l">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2m"> u1 = q2 < 0 ? cuberoot(-q2) : -cuberoot(q2);</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2n">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2o"> root1 = 2*u1 - a/3;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2p">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2q"> root2 = -u1 - a/3;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2r">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2s"> return [root1, root2].filter(accept);</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2t">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2u"> </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2v">}</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2w">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2x">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2y"> // one real root, two complex roots</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.2z">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.30"> else </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.31">{</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.32">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.33"> var sd = sqrt(discriminant);</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.34">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.35"> u1 = cuberoot(sd - q2);</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.36">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.37"> v1 = cuberoot(sd + q2);</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.38">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.39"> root1 = u1 - v1 - a/3;</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.3a">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.3b"> return [root1].filter(accept);</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.3c">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.3d"> </span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.3e">}</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.3f">
|
||
</span><span data-reactid=".fw3jf46xog.2.$extremities.m.2.3g">}</span></pre></div><p data-reactid=".fw3jf46xog.2.$extremities.n">And that's it. The maths is complicated, but the code is pretty much just "follow the maths, while caching as many values as we can to reduce recomputing things as much as possible" and now we have a way to find all roots for a cubic function and can just move on with using that to find extremities of our curves.</p><h3 data-reactid=".fw3jf46xog.2.$extremities.o">Quintic and higher order curves: finding numerical solutions</h3><p data-reactid=".fw3jf46xog.2.$extremities.p">The problem with this is that as the order of the curve goes up, we can't actually solve those equations the normal way. We can't take the function, and then work out what the solutions are. Not to mention that even solving a third order derivative (for a fourth order curve) is already a royal pain in the backside. We need a better solution. We need numerical approaches.</p><p data-reactid=".fw3jf46xog.2.$extremities.q"><span data-reactid=".fw3jf46xog.2.$extremities.q.0">That's a fancy word for saying "rather than solve the function, treat the problem as a sequence of identical operations, the performing of which gets us closer and closer to the real answer". As it turns out, there is a really nice numerical root finding algorithm, called the </span><a href="http://en.wikipedia.org/wiki/Newton-Raphson" data-reactid=".fw3jf46xog.2.$extremities.q.1">Newton-Raphson</a><span data-reactid=".fw3jf46xog.2.$extremities.q.2"> root finding method (yes, after </span><em data-reactid=".fw3jf46xog.2.$extremities.q.3"><a href="https://en.wikipedia.org/wiki/Isaac_Newton" data-reactid=".fw3jf46xog.2.$extremities.q.3.0">that</a></em><span data-reactid=".fw3jf46xog.2.$extremities.q.4"> Newton), which we can make use of.</span></p><p data-reactid=".fw3jf46xog.2.$extremities.r"><span data-reactid=".fw3jf46xog.2.$extremities.r.0">The Newton-Raphson approach consists of picking a value </span><i data-reactid=".fw3jf46xog.2.$extremities.r.1">t</i><span data-reactid=".fw3jf46xog.2.$extremities.r.2"> (any will do), and getting the corresponding value at that </span><i data-reactid=".fw3jf46xog.2.$extremities.r.3">t</i><span data-reactid=".fw3jf46xog.2.$extremities.r.4"> value. For normal functions, we can treat that value as a height. If the height is zero, we're done, we have found a root. If it's not, we take the tangent of the curve at that point, and extend it until it passes the x-axis, which will be at some new point </span><i data-reactid=".fw3jf46xog.2.$extremities.r.5">t</i><span data-reactid=".fw3jf46xog.2.$extremities.r.6">. We then repeat the procedure with this new value, and we keep doing this until we find our root.</span></p><p data-reactid=".fw3jf46xog.2.$extremities.s"><span data-reactid=".fw3jf46xog.2.$extremities.s.0">Mathematically, this means that for some </span><i data-reactid=".fw3jf46xog.2.$extremities.s.1">t</i><span data-reactid=".fw3jf46xog.2.$extremities.s.2">, at step </span><i data-reactid=".fw3jf46xog.2.$extremities.s.3">n=1</i><span data-reactid=".fw3jf46xog.2.$extremities.s.4">, we perform the following calculation until </span><i data-reactid=".fw3jf46xog.2.$extremities.s.5"><span data-reactid=".fw3jf46xog.2.$extremities.s.5.0">f</span><sub data-reactid=".fw3jf46xog.2.$extremities.s.5.1">y</sub></i><span data-reactid=".fw3jf46xog.2.$extremities.s.6">(</span><i data-reactid=".fw3jf46xog.2.$extremities.s.7">t</i><span data-reactid=".fw3jf46xog.2.$extremities.s.8">) is zero, so that the next </span><i data-reactid=".fw3jf46xog.2.$extremities.s.9">t</i><span data-reactid=".fw3jf46xog.2.$extremities.s.a"> is the same as the one we already have:</span></p><p data-reactid=".fw3jf46xog.2.$extremities.t"><img class="LaTeX SVG" src="images/latex/b563256be7016370365935944308cf878cdbc29c.svg" style="width:8.625150000000001rem;height:2.9250000000000003rem;" data-reactid=".fw3jf46xog.2.$extremities.t.0"/></p><p data-reactid=".fw3jf46xog.2.$extremities.u">(The wikipedia article has a decent animation for this process, so I'm not adding a sketch for that here)</p><p data-reactid=".fw3jf46xog.2.$extremities.v">Now, this works well only if we can pick good starting points, and our curve is continuously differentiable and doesn't have oscillations. Glossing over the exact meaning of those terms, the curves we're dealing with conform to those constraints, so as long as we pick good starting points, this will work. So the question is: which starting points do we pick?</p><p data-reactid=".fw3jf46xog.2.$extremities.w"><span data-reactid=".fw3jf46xog.2.$extremities.w.0">As it turns out, Newton-Raphson is so blindingly fast, so we could get away with just not picking: we simply run the algorithm from </span><i data-reactid=".fw3jf46xog.2.$extremities.w.1">t=0</i><span data-reactid=".fw3jf46xog.2.$extremities.w.2"> to </span><i data-reactid=".fw3jf46xog.2.$extremities.w.3">t=1</i><span data-reactid=".fw3jf46xog.2.$extremities.w.4"> at small steps (say, 1/200</span><sup data-reactid=".fw3jf46xog.2.$extremities.w.5">th</sup><span data-reactid=".fw3jf46xog.2.$extremities.w.6">) and the result will be all the roots we want. Of course, this may pose problems for high order Bézier curves: 200 steps for a 200</span><sup data-reactid=".fw3jf46xog.2.$extremities.w.7">th</sup><span data-reactid=".fw3jf46xog.2.$extremities.w.8"> order Bézier curve is going to go wrong, but that's okay: there is no reason, ever, to use Bézier curves of crazy high orders. You might use a fifth order curve to get the "nicest still remotely workable" approximation of a full circle with a single Bézier curve, that's pretty much as high as you'll ever need to go.</span></p><h3 data-reactid=".fw3jf46xog.2.$extremities.x">In conclusion:</h3><p data-reactid=".fw3jf46xog.2.$extremities.y">So now that we know how to do root finding, we can determine the first and second derivative roots for our Bézier curves, and show those roots overlaid on the previous graphics:</p><figure class="false" data-reactid=".fw3jf46xog.2.$extremities.z"><canvas tabindex="0" data-reactid=".fw3jf46xog.2.$extremities.z.0"></canvas><figcaption data-reactid=".fw3jf46xog.2.$extremities.z.1"><span data-reactid=".fw3jf46xog.2.$extremities.z.1.0">Quadratic Bézier curve extremities</span><span data-reactid=".fw3jf46xog.2.$extremities.z.1.1"> </span></figcaption></figure><figure class="false" data-reactid=".fw3jf46xog.2.$extremities.10"><canvas tabindex="0" data-reactid=".fw3jf46xog.2.$extremities.10.0"></canvas><figcaption data-reactid=".fw3jf46xog.2.$extremities.10.1"><span data-reactid=".fw3jf46xog.2.$extremities.10.1.0">Cubic Bézier curve extremities</span><span data-reactid=".fw3jf46xog.2.$extremities.10.1.1"> </span></figcaption></figure></section><table class="relatives after" data-reactid=".fw3jf46xog.2.2"><tbody data-reactid=".fw3jf46xog.2.2.0"><tr data-reactid=".fw3jf46xog.2.2.0.0"><td data-reactid=".fw3jf46xog.2.2.0.0.0"><a class="prev" href="components" data-reactid=".fw3jf46xog.2.2.0.0.0.0">13. Component functions</a></td><td class="toc" data-reactid=".fw3jf46xog.2.2.0.0.1"><a class="" href="/" data-reactid=".fw3jf46xog.2.2.0.0.1.0">ToC</a></td><td data-reactid=".fw3jf46xog.2.2.0.0.2"><a class="next" href="boundingbox" data-reactid=".fw3jf46xog.2.2.0.0.2.0">15. Bounding boxes</a></td></tr></tbody></table></div><footer class="copyright" data-reactid=".fw3jf46xog.3"><span data-reactid=".fw3jf46xog.3.0">This article is © 2011-2016 to me, Mike "Pomax" Kamermans, but the text, code, and images are </span><a href="https://github.com/Pomax/bezierinfo/blob/gh-pages/LICENSE.md" data-reactid=".fw3jf46xog.3.1">almost no rights reserved</a><span data-reactid=".fw3jf46xog.3.2">. Go do something cool with it!</span></footer></div>
|
||
</body>
|
||
</html> |