{"id":616,"date":"2014-09-03T11:54:28","date_gmt":"2014-09-03T18:54:28","guid":{"rendered":"http:\/\/www.imaginary-institute.com\/blog\/?p=616"},"modified":"2017-06-28T20:25:46","modified_gmt":"2017-06-29T03:25:46","slug":"efficiently-finding-points-on-catmull-rom-and-bezier-curves","status":"publish","type":"post","link":"https:\/\/www.imaginary-institute.com\/blog\/2014\/09\/03\/efficiently-finding-points-on-catmull-rom-and-bezier-curves\/","title":{"rendered":"Efficiently Finding Points on Catmull-Rom and Bezier Curves"},"content":{"rendered":"<p>In my AU library, I offer some utilities related to curves. Although Processing offers functions for finding points on curves, I needed to write my own implementation that was fast and efficient. Here&#8217;s an easy, elegant way to compute points on Catmull-Rom and Bezier curves, along with a discussion of an idea for making it faster. <!--more--><\/p>\n<p>The most convenient way to generate points on these curves is to use a matrix form, as nicely summarized by <a href=\"http:\/\/alvyray.com\/Memos\/CG\/Pixar\/duff104.pdf\" target=\"_blank\" rel=\"noopener\">Tom Duff&#8217;s memo<\/a>. Suppose we have a curve with knot values k0, k1, k2, and k3. Form them into row vector, which I&#8217;ll call <strong>k<\/strong>. We&#8217;ll be using this in column form, so I&#8217;ll use this vector in its transposed form, written <strong>k<sup>T<\/sup><\/strong>. Now we want to find a point at the parameter value <em>t<\/em> along the curve. Form a row vector as [<em>t<\/em><sup>3<\/sup> <em>t<\/em><sup>2<\/sup> <em>t<\/em> 1], which I&#8217;ll call <strong>t<\/strong>.<\/p>\n<p>&nbsp;<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-657\" src=\"https:\/\/www.imaginary-institute.com\/blog\/wp-content\/uploads\/2014\/09\/curvetMk.gif\" alt=\"A curve in matrix form\" width=\"537\" height=\"190\" \/>To find the value of the curve at <em>t<\/em>, we need only one more thing: a 4-by-4 matrix, which I&#8217;ll call <strong>M<\/strong>. This matrix sits between the two vectors as in the tableau above. Multiplied together, these three elements give us the scalar <strong>t M k<sup>T<\/sup><\/strong>, which is the value of the curve of matrix type <strong>M<\/strong>, with knot data <strong>k<\/strong>, at time <em>t<\/em>. Each particular kind of curve has its own characteristic matrix <strong>M<\/strong> that combines <strong>t<\/strong> with <strong>k<\/strong> in the just the right way.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"attachment_620\" style=\"width: 316px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-620\" class=\"size-full wp-image-620\" src=\"https:\/\/www.imaginary-institute.com\/blog\/wp-content\/uploads\/2014\/09\/cardinalMatrix.gif\" alt=\"Cardinal Spline Matrix\" width=\"306\" height=\"123\" \/><p id=\"caption-attachment-620\" class=\"wp-caption-text\">Cardinal Spline Matrix<\/p><\/div>\n<div id=\"attachment_618\" style=\"width: 316px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-618\" class=\"size-full wp-image-618\" src=\"https:\/\/www.imaginary-institute.com\/blog\/wp-content\/uploads\/2014\/09\/CatmullRomMatrix.gif\" alt=\"Catmull-Rom Matrix\" width=\"306\" height=\"123\" \/><p id=\"caption-attachment-618\" class=\"wp-caption-text\">Catmull-Rom Matrix<\/p><\/div>\n<p>The interesting thing is that we can use this form for both Bezier curves and all the cardinal splines. The cardinal splines take a single parameter, which Duff calls <em>c<\/em>. When <em>c<\/em>=0 we get straight lines from knot to knot, and when <em>c<\/em>=.5 we get Catmull-Rom curves. Values of <em>c<\/em> between those values blend between the polygon and Catmull-Rom forms. Values of <em>c<\/em> outside that range produce very squiggly curves.<\/p>\n<p>A nice derivation of the matrix form of the Bezier curve is described by Ken Joy <a href=\"http:\/\/www.idav.ucdavis.edu\/education\/CAGDNotes\/Matrix-Cubic-Bezier-Curve\/Matrix-Cubic-Bezier-Curve.html\" target=\"_blank\" rel=\"noopener\">here<\/a> (note that Joy writes his <strong>t<\/strong> vector in the opposite order that I&#8217;ve written it, so I&#8217;ve flipped his matrix to make it match our notation).<\/p>\n<div id=\"attachment_619\" style=\"width: 245px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-619\" src=\"https:\/\/www.imaginary-institute.com\/blog\/wp-content\/uploads\/2014\/09\/bezierMatrix.gif\" alt=\"Bezier Matrix\" width=\"235\" height=\"123\" class=\"size-full wp-image-619\" \/><p id=\"caption-attachment-619\" class=\"wp-caption-text\">Bezier Matrix<\/p><\/div>\n<p>To evaluate a 1D curve at <em>t<\/em>, we just put our knot values into <strong>k<\/strong>, build <strong>t<\/strong>, and evaluate <strong>t M k<sup>T<\/sup><\/strong>. Because these curves are independent along each axis, we can blend as many values as we like for each value of <em>t<\/em> just by changing <strong>k<\/strong>. For example, to evaluate a 2D curve, with knot values for X and Y, we only need fill up <strong>k<\/strong> with the X values, and find <strong>t M k<sup>T<\/sup><\/strong>. To find the Y value, just put the Y values into <strong>k<\/strong> and find <strong>t M k<sup>T<\/sup><\/strong> again. We can attach other data to each knot, such as stroke thickness or rainfall that day, and interpolate it as well.<\/p>\n<p>This suggests saving some time by computing <strong>t M<\/strong> just once for a given value of <em>t<\/em>, and then applying it to each different set of <strong>k<\/strong>.<\/p>\n<p>But wait, why not pre-compute <strong>t M<\/strong> for all time? Then for any given value of <em>t<\/em>, we find the two closest pre-computed vectors, linearly interpolate them, and then apply them to <strong>k<\/strong>. For example, save a static table <code>tMtable[1000][4]<\/code>, loaded with the 4-element vector <strong>t M<\/strong> for 1000 choices of <em>t<\/em>. When a value of <em>t<\/em> arrives, find the two indices that bracket it, which I&#8217;ll call <code>i0<\/code> and <code>i1<\/code>. We&#8217;ll say that <code>b<\/code> is a float from 0 to 1 across that interval, telling us where <em>t<\/em> lands. Then we can find the value of <strong>t M<\/strong>, which I&#8217;ll save in a 4-element vector <code>tM<\/code>:<\/p>\n<pre>for (int i=0; i&lt;4; i++) tM[i] = lerp(tMtable[i0,i], tMtable[i1,i], b);\r\n<\/pre>\n<p>Great, now we have <code>tM<\/code> holding <strong>t M<\/strong>, and we can find the spline value by just dotting it with the vector <strong>k<\/strong> holding our knot data:<\/p>\n<pre>float val = 0;\r\nfor (int i=0; i&lt;4; i++) val += tM[i] * k[i];\r\n<\/pre>\n<p>I like this idea, but is it worth it? Writing out the full evaluation of <strong>t M k<sup>T<\/sup><\/strong>, we&#8217;d need 20 multiplies and 5 adds. In this lookup table form, we&#8217;d need a total of 8 multiplies and 12 adds (and that&#8217;s leaving out the overhead of the loops, and finding the values of <code>i0<\/code>, <code>i1<\/code>, and <code>b<\/code>). Unfortunately, this doesn&#8217;t seem at all worth it. Finding <strong>t M<\/strong> just isn&#8217;t that expensive.<\/p>\n<p>I think it&#8217;s always worth looking at these kinds of time\/space tradeoffs, because they often return impressive benefits. But it&#8217;s important to look at them closely, so we don&#8217;t waste time implementing &#8220;optimizations&#8221; that aren&#8217;t optimizing anything!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my AU library, I offer some utilities related to curves. Although Processing offers functions for finding points on curves, I needed to write my own implementation that was fast and efficient. Here&#8217;s an easy, elegant way to compute points on Catmull-Rom and Bezier curves, along with a discussion of an idea for making it [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-616","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/posts\/616","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/comments?post=616"}],"version-history":[{"count":51,"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/posts\/616\/revisions"}],"predecessor-version":[{"id":1277,"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/posts\/616\/revisions\/1277"}],"wp:attachment":[{"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/media?parent=616"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/categories?post=616"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.imaginary-institute.com\/blog\/wp-json\/wp\/v2\/tags?post=616"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}