mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-08-30 03:30:34 +02:00
catmull-rom
This commit is contained in:
257
docs/index.html
257
docs/index.html
@@ -197,13 +197,15 @@
|
||||
<div class="figure">
|
||||
<graphics-element title="A quadratic Bézier curve" width="275" height="275" src="./chapters/introduction/quadratic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\introduction\54e9ec0600ac436b0e6f0c6b5005cf03.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>A quadratic Bézier curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="A cubic Bézier curve" width="275" height="275" src="./chapters/introduction/cubic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>A cubic Bézier curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
|
||||
@@ -219,8 +221,9 @@
|
||||
<p>So let's look at that in action: the following graphic is interactive in that you can use your up and down arrow keys to increase or decrease the interpolation ratio, to see what happens. We start with three points, which gives us two lines. Linear interpolation over those lines gives us two points, between which we can again perform linear interpolation, yielding a single point. And that point —and all points we can form in this way for all ratios taken together— form our Bézier curve:</p>
|
||||
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" height="275" src="./chapters/whatis/interpolation.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\whatis\9b3633889c38325c24c19ce18ab94ad6.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="10" max="90" step="1" value="25" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -246,8 +249,9 @@
|
||||
<p>So, parametric curves don't define a <i>y</i> coordinate in terms of an <i>x</i> coordinate, like normal functions do, but they instead link the values to a "control" variable. If we vary the value of <i>t</i>, then with every change we get <strong>two</strong> values, which we can use as (<i>x</i>,<i>y</i>) coordinates in a graph. The above set of functions, for instance, generates points on a circle: We can range <i>t</i> from negative to positive infinity, and the resulting (<i>x</i>,<i>y</i>) coordinates will always lie on a circle with radius 1 around the origin (0,0). If we plot it for <i>t</i> from 0 to 5, we get this:</p>
|
||||
<graphics-element title="A (partial) circle: x=sin(t), y=cos(t)" width="275" height="275" src="./chapters/explanation/circle.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\explanation\6d2f915735ebdc05e42c0ea7adc85343.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>A (partial) circle: x=sin(t), y=cos(t)</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="10" step="0.1" value="5" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -328,24 +332,27 @@ function Bezier(3,t):
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Quadratic interpolations</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="Cubic interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Cubic interpolations</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="15th degree interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>15th degree interpolations</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -359,8 +366,9 @@ function Bezier(3,t):
|
||||
<p>Which gives us the curve we saw at the top of the article:</p>
|
||||
<graphics-element title="Our cubic Bézier curve" width="275" height="275" src="./chapters/control/../introduction/cubic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Our cubic Bézier curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>What else can we do with Bézier curves? Quite a lot, actually. The rest of this article covers a multitude of possible operations and algorithms that we can apply, and the tasks they achieve.</p>
|
||||
@@ -404,8 +412,9 @@ function Bezier(3,t,w[]):
|
||||
<p>But the best way to show what this does is to do literally that: let's look at the effect of "rationalising" our Bézier curves using an interactive graphic for a rationalised curves. The following graphic shows the Bézier curve from the previous section, "enriched" with ratio factors for each coordinate. The closer to zero we set one or more terms, the less relative influence the associated coordinate exerts on the curve (and of course the higher we set them, the more influence they have). Try to change the values and see how it affects what gets drawn:</p>
|
||||
<graphics-element title="Our rational cubic Bézier curve" width="275" height="275" src="./chapters/weightcontrol/rational.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Our rational cubic Bézier curve</label>
|
||||
</fallback-image>
|
||||
ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 2 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
@@ -462,13 +471,15 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic infinite interval Bézier curve" width="275" height="275" src="./chapters/extended/extended.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\extended\391a61142c56b79260680aefb08cd9c4.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Quadratic infinite interval Bézier curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Cubic infinite interval Bézier curve" width="275" height="275" src="./chapters/extended/extended.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\extended\baeceec6e1587794b8b275a90d5d85e9.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Cubic infinite interval Bézier curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
|
||||
@@ -519,8 +530,9 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<p>To see this in action, mouse-over the following sketch. Moving the mouse changes which curve point is explicitly evaluated using de Casteljau's algorithm, moving the cursor left-to-right (or, of course, right-to-left), shows you how a curve is generated using this approach.</p>
|
||||
<graphics-element title="Traversing a curve using de Casteljau's algorithm" width="275" height="275" src="./chapters/decasteljau/decasteljau.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\decasteljau\715d1d2eecc762d6bc1470954b145018.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Traversing a curve using de Casteljau's algorithm</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -560,16 +572,18 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<div class="figure">
|
||||
<graphics-element title="Flattening a quadratic curve" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\flattening\3deec756c96e53127cd1d615c61043ae.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Flattening a quadratic curve</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="Flattening a cubic curve" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\flattening\e2bb7113d5cda2e3fd29bbc54fbe8841.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Flattening a cubic curve</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -604,8 +618,9 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<p>Using de Casteljau's algorithm, we can also find all the points we need to split up a Bézier curve into two, smaller curves, which taken together form the original curve. When we construct de Casteljau's skeleton for some value <code>t</code>, the procedure gives us all the points we need to split a curve at that <code>t</code> value: one curve is defined by all the inside skeleton points found prior to our on-curve point, with the other curve being defined by all the inside skeleton points after our on-curve point.</p>
|
||||
<graphics-element title="Splitting a curve" width="825" height="275" src="./chapters/splitting/splitting.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\splitting\bef2f09698c0d3d2b7c4c031be17ff69.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -727,8 +742,9 @@ function drawCurve(points[], t):
|
||||
<p>And we're done: we now have an expression that lets us approximate an <code>n+1</code><sup>th</sup> order curve with a lower <code>n</code><sup>th</sup> order curve. It won't be an exact fit, but it's definitely a best approximation. So, let's implement these rules for raising and lowering curve order to a (semi) random curve, using the following graphic. Select the sketch, which has movable control points, and press your up and down arrow keys to raise or lower the curve order.</p>
|
||||
<graphics-element title="A variable-order Bézier curve" width="275" height="275" src="./chapters/reordering/reorder.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>A variable-order Bézier curve</label>
|
||||
</fallback-image>
|
||||
<button class="raise">raise</button>
|
||||
<button class="lower">lower</button>
|
||||
@@ -802,13 +818,15 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Quadratic Bézier tangents and normals</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Cubic Bézier tangents and normals</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
|
||||
@@ -836,8 +854,9 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
<p>And then we're done, we found "the" normal vector for a 3D curve. Let's see what that looks like for a sample curve, shall we? You can move your cursor across the graphic from left to right, to show the normal at a point with a t value that is based on your cursor position: all the way on the left is 0, all the way on the right = 1, midway is t=0.5, etc:</p>
|
||||
<graphics-element title="Some known and unknown vectors" width="350" height="300" src="./chapters/pointvectors3d/frenet.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="350px" height="300px" src="images\chapters\pointvectors3d\cbadec403b99dec015ab084ee10e1671.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -910,8 +929,9 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
<p>Speaking of better looking, what does this actually look like? Let's revisit that earlier curve, but this time use rotation minimising frames rather than Frenet frames:</p>
|
||||
<graphics-element title="Some known and unknown vectors" width="350" height="300" src="./chapters/pointvectors3d/rotation-minimizing.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="350px" height="300px" src="images\chapters\pointvectors3d\9983328e50c2156c124bb1ea4ad5c05b.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -928,14 +948,16 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
<p>If you move points in a curve sideways, you should only see the middle graph change; likewise, moving points vertically should only show a change in the right graph.</p>
|
||||
<graphics-element title="Quadratic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\components\008604bc4c53bd7e0d97c99a67812ad1.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<graphics-element title="Cubic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\components\5214256129e6396e7ac1f1713fa9c88d.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
</section>
|
||||
@@ -1070,15 +1092,17 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<p>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. For the quadratic curve, that means just the first derivative, in red:</p>
|
||||
<graphics-element title="Quadratic Bézier curve extremities" width="825" height="275" src="./chapters/extremities/extremities.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\extremities\9850eec01924deae7fda4400ce44270d.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>And for cubic curves, that means first and second derivatives, in red and purple respectively:</p>
|
||||
<graphics-element title="Cubic Bézier curve extremities" width="825" height="275" src="./chapters/extremities/extremities.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\extremities\890406c7bc96904224f8f14940bf3e56.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
</section>
|
||||
@@ -1095,13 +1119,15 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\boundingbox\e2c621442e98e2cd20af7efe1cfb041f.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Quadratic Bézier bounding box</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Cubic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\boundingbox\f8989a62ebec9d6f123291c146caab5b.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Cubic Bézier bounding box</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
|
||||
@@ -1123,13 +1149,15 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<div class="figure">
|
||||
<graphics-element title="Aligning a quadratic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="550px" height="275px" src="images\chapters\aligning\b3ccd45a72c815388aee6515fe37a486.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Aligning a cubic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="550px" height="275px" src="images\chapters\aligning\31655f24b7dd8b8871687b6610d9ac0e.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
|
||||
@@ -1141,13 +1169,15 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<div class="figure">
|
||||
<graphics-element title="Aligning a quadratic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\tightbounds\ccc77ae1f57d7dd7ce4d5397fe1b140b.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Aligning a quadratic curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Aligning a cubic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\tightbounds\419415bee6ffd7598c035c42de09a94f.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Aligning a cubic curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
|
||||
@@ -1186,8 +1216,9 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<p>Taking that into account, we compute <em>t</em>, we disregard any <em>t</em> value that isn't in the Bézier interval [0,1], and we now know at which <em>t</em> value(s) our curve will inflect.</p>
|
||||
<graphics-element title="Finding cubic Bézier curve inflections" width="275" height="275" src="./chapters/inflections/inflection.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\inflections\9e1ce3975100600d4979370851929b73.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Finding cubic Bézier curve inflections</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
</section>
|
||||
@@ -1198,8 +1229,9 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<p>The first observation that makes things work is that if we have a cubic curve with four points, we can apply a linear transformation to these points such that three of the points end up on (0,0), (0,1) and (1,1), with the last point then being "somewhere". After applying that transformation, the location of that last point can then tell us what kind of curve we're dealing with. Specifically, we see the following breakdown:</p>
|
||||
<graphics-element title="The canonical curve map" width="400" height="400" src="./chapters/canonical/canonical.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\canonical\675217e6df3d75c86503dc2af623e14e.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>This is a fairly funky image, so let's see what the various parts of it mean...</p>
|
||||
@@ -1264,8 +1296,9 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<p>So, let's write up a sketch that'll show us the canonical form for any curve drawn in blue, overlaid on our canonical map, so that we can immediately tell which features our curve must have, based on where the fourth coordinate is located on the map:</p>
|
||||
<graphics-element title="A cubic curve mapped to canonical form" width="800" height="400" src="./chapters/canonical/interactive.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="800px" height="400px" src="images\chapters\canonical\344346f09b6d5d7e3ddf91084ad50d46.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
</section>
|
||||
@@ -1275,8 +1308,9 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<p>We'll be tackling this problem in two stages: the first, which is the hard part, is figuring out which "t" value belongs to any given "x" value. For instance, have a look at the following graphic. On the left we have a Bézier curve that looks for all intents and purposes like it fits our criteria: every "x" has one and only one associated "y" value. On the right we see the function for just the "x" values: that's a cubic curve, but not a really crazy cubic curve. If you move the graphic's slider, you will see a red line drawn that corresponds to the <code>x</code> coordinate: this is a vertical line in the left graphic, and a horizontal line on the right.</p>
|
||||
<graphics-element title="Finding t, given x=x(t). Left: our curve, right: the x=x(t) function" width="550" height="275" src="./chapters/yforx/basics.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="550px" height="275px" src="images\chapters\yforx\dd28d64458d22f4fe89c98568258efcb.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -1302,8 +1336,9 @@ y = curve.get(t).y</code></pre>
|
||||
<p>So the procedure is fairly straight forward: pick an <code>x</code>, find the associted <code>t</code> value, evaluate our curve <em>for</em> that <code>t</code> value, which gives us the curve's {x,y} coordinate, which means we know <code>y</code> for this <code>x</code>. Move the slider for the following graphic to see this in action:</p>
|
||||
<graphics-element title="Finding By(t), by finding t for a given x" width="275" height="275" src="./chapters/yforx/yforx.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\yforx\efcfe9b48ca4e65eef3d4bf3e4c97bc3.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Finding By(t), by finding t for a given x</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -1324,18 +1359,21 @@ y = curve.get(t).y</code></pre>
|
||||
<div class="figure">
|
||||
<graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>A function's approximated integral</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>A better approximation</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>An even better approximation</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
|
||||
@@ -1357,8 +1395,9 @@ y = curve.get(t).y</code></pre>
|
||||
<p>If we use the Legendre-Gauss values for our <em>C</em> values (thickness for each strip) and <em>t</em> values (location of each strip), we can determine the approximate length of a Bézier curve by computing the Legendre-Gauss sum. The following graphic shows a cubic curve, with its computed lengths; Go ahead and change the curve, to see how its length changes. One thing worth trying is to see if you can make a straight line, and see if the length matches what you'd expect. What if you form a line with the control points on the outside, and the start/end points on the inside?</p>
|
||||
<graphics-element title="Arc length for a Bézier curve" width="275" height="275" src="./chapters/arclength/arclength.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\195f9a3c60f8dfe977c6450d21968f69.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Arc length for a Bézier curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
</section>
|
||||
@@ -1370,16 +1409,18 @@ y = curve.get(t).y</code></pre>
|
||||
|
||||
<graphics-element title="Approximate quadratic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Approximate quadratic curve arc length</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="24" step="1" value="4" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Approximate cubic curve arc length</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="32" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -1425,8 +1466,9 @@ y = curve.get(t).y</code></pre>
|
||||
<p>With all of that covered, let's line up some curves! The following graphic gives you two curves that look identical, but use quadratic and cubic functions, respectively. As you can see, despite their derivatives being necessarily different, their curvature (thanks to being derived based on maths that "ignores" specific function derivative, and instead gives a formulat that smooths out any differences) is exactly the same. And because of that, we can put them together such that the point where they overlap has the same curvature for both curves, giving us the smoothest transition.</p>
|
||||
<graphics-element title="Matching curvatures for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\curvature\5fcfb0572cae06717506c84768aa568c.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>One thing you may have noticed in this sketch is that sometimes the curvature looks fine, but seems to be pointing in the wrong direction, making it hard to line up the curves properly. A way around that, of course, is to show the curvature on both sides of the curve, so let's just do that. But let's take it one step further: we can also compute the associated "radius of curvature", which gives us the implicit circle that "fits" the curve's curvature at any point, using what is possibly the simplest bit of maths found in this entire primer:</p>
|
||||
@@ -1434,8 +1476,9 @@ y = curve.get(t).y</code></pre>
|
||||
<p>So let's revisit the previous graphic with the curvature visualised on both sides of our curves, as well as showing the circle that "fits" our curve at some point that we can control by using a slider:</p>
|
||||
<graphics-element title="(Easier) curvature matching for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" data-omni="true">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\curvature\876d7b2750d7c29068ac6181c3634d25.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -1449,16 +1492,18 @@ y = curve.get(t).y</code></pre>
|
||||
<p>The following graphic shows a particularly illustrative curve, and it's distance-for-t plot. For linear traversal, this line needs to be straight, running from (0,0) to (length,1). That is, it's safe to say, not what we'll see: we'll see something very wobbly, instead. To make matters even worse, the distance-for-t function is also of a much higher order than our curve is: while the curve we're using for this exercise is a cubic curve, which can switch concave/convex form twice at best, the distance function is our old friend the arc length function, which can have more inflection points.</p>
|
||||
<graphics-element title="The t-for-distance function" width="550" height="275" src="./chapters/tracing/distance-function.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="550px" height="275px" src="images\chapters\tracing\52f815cefe99dabc47ca83d0b97b61fc.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>So, how do we "cut up" the arc length function at regular intervals, when we can't really work with it? We basically cheat: we run through the curve using <code>t</code> values, determine the distance-for-this-<code>t</code>-value at each point we generate during the run, and then we find "the closest <code>t</code> value that matches some required distance" using those values instead. If we have a low number of points sampled, we can then even refine which <code>t</code> value "should" work for our desired distance by interpolating between two points, but if we have a high enough number of samples, we don't even need to bother.</p>
|
||||
<p>So let's do exactly that: the following graph is similar to the previous one, showing how we would have to "chop up" our distance-for-t curve in order to get regularly spaced points on the curve. It also shows what using those <code>t</code> values on the real curve looks like, by coloring each section of curve between two distance markers differently:</p>
|
||||
<graphics-element title="Fixed-interval coloring a curve" width="825" height="275" src="./chapters/tracing/tracing.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\tracing\133bf9d02801a3149c9ddb8b313e6797.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="2" max="24" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -1476,8 +1521,9 @@ y = curve.get(t).y</code></pre>
|
||||
<p>The following graphic implements this intersection detection, showing a red point for an intersection on the lines our segments lie on (thus being a virtual intersection point), and a green point for an intersection that lies on both segments (being a real intersection point).</p>
|
||||
<graphics-element title="Line/line intersections" width="275" height="275" src="./chapters/intersections/line-line.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\intersections\61876a2bd727df377619c5ad34ce86be.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Line/line intersections</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
@@ -1508,13 +1554,15 @@ lli = function(line1, line2):
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\intersections\594c2df534a1736c03cd3a96ff4a9913.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Quadratic curve/line intersections</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Cubic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\intersections\dc26a6063dadc31d242f1c1c8f38bb5e.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Cubic curve/line intersections</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
|
||||
@@ -1540,8 +1588,9 @@ lli = function(line1, line2):
|
||||
<p>(can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p>
|
||||
<graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
|
||||
<button class="next">Advance one step</button>
|
||||
@@ -1561,15 +1610,17 @@ lli = function(line1, line2):
|
||||
|
||||
<graphics-element inline={true} title="Projections in a quadratic Bézier curve" width="275" height="275" src="./chapters/abc/abc.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\abc\7a69dd4350ddda5701712e1d3b46b863.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Projections in a quadratic Bézier curve</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
|
||||
</graphics-element>
|
||||
<graphics-element inline={true} title="Projections in a cubic Bézier curve" width="275" height="275" src="./chapters/abc/abc.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\abc\eeec7cf16fb22c666e0143a3a030731f.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Projections in a cubic Bézier curve</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -1615,16 +1666,18 @@ lli = function(line1, line2):
|
||||
<p>With this code in place, creating a quadratic curve from three points is literally just computing the ABC values, and using <code>A</code> as our curve's control point:</p>
|
||||
<graphics-element title="Fitting a quadratic Bézier curve" width="275" height="275" src="./chapters/pointcurves/quadratic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointcurves\067a3df30e32708fc0d13f8eb78c0b05.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Fitting a quadratic Bézier curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>For cubic curves we need to do a little more work, but really only just a little. We're first going to assume that a decent curve through the three points should approximate a circular arc, which first requires knowing how to fit a circle to three points. You may remember (if you ever learned it!) that a line between two points on a circle is called a <a href="https://en.wikipedia.org/wiki/Chord_%28geometry%29">chord</a>, and that one property of chords is that the line from the center of any chord, perpendicular to that chord, passes through the center of the circle.</p>
|
||||
<p>That means that if we have have three points on a circle, we have three (different) chords, and consequently, three (different) lines that go from those chords through the center of the circle: if we find two of those lines, then their intersection will be our circle's center, and the circle's radius will—by definition!—be the distance from the center to any of our three points:</p>
|
||||
<graphics-element title="Finding a circle through three points" width="275" height="275" src="./chapters/pointcurves/circle.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointcurves\173ea31517a72a927d561f121f0677db.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Finding a circle through three points</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>With that covered, we now also know the tangent line to our point <code>B</code>, because the tangent to any point on the circle is a line through that point, perpendicular to the line from that point to the center. That just leaves marking appropriate points <code>e1</code> and <code>e2</code> on that tangent, so that we can construct a new cubic curve hull. We use the approach as we did for quadratic curves to automatically determine a reasonable <code>t</code> value, and then our <code>e1</code> and <code>e2</code> coordinates must obey the standard de Casteljau rule for linear interpolation:</p>
|
||||
@@ -1636,15 +1689,17 @@ lli = function(line1, line2):
|
||||
<p>The result of this approach looks as follows:</p>
|
||||
<graphics-element title="Finding the cubic e₁ and e₂ given three points " width="275" height="275" src="./chapters/pointcurves/circle.js" data-show-curve="true">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointcurves\8d045d352f5017b65e60620b92d7ae29.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Finding the cubic e₁ and e₂ given three points </label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>It is important to remember that even though we're using a circular arc to come up with decent <code>e1</code> and <code>e2</code> terms, we're <em>not</em> trying to perfectly create a circular arc with a cubic curve (which is good, because we can't; <a href="#arcapproximation">more on that later</a>), we're <em>only</em> trying to come up with some reasonable <code>e1</code> and <code>e2</code> points so we can construct a new cubic curve... so now that we have those: let's see what kind of cubic curve that gives us:</p>
|
||||
<graphics-element title="Fitting a quadratic Bézier curve" width="275" height="275" src="./chapters/pointcurves/cubic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointcurves\eab6ea46fa93030e03ec0ef7deb571dc.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label>Fitting a quadratic Bézier curve</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>That looks perfectly servicable!</p>
|
||||
@@ -1672,8 +1727,9 @@ for (coordinate, index) in LUT:
|
||||
<p>So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.</p>
|
||||
<graphics-element title="Projecting a point onto a Bézier curve" width="320" height="320" src="./chapters/projections/project.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
</section>
|
||||
@@ -1687,24 +1743,27 @@ for (coordinate, index) in LUT:
|
||||
<p>And we're done, because that's our new quadratic control point!</p>
|
||||
<graphics-element title="Molding a quadratic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\molding\6b91671c2962530b863ae0da5789a9cc.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>As before, cubic curves are a bit more work, because while it's easy to find our initial <code>t</code> value and ABC values, getting those all-important <code>e1</code> and <code>e2</code> coordinates is going to pose a bit of a problem... in the section on curve creation, we were free to pick an appropriate <code>t</code> value ourselves, which allowed us to find appropriate <code>e1</code> and <code>e2</code> coordinates. That's great, but when we're curve molding we don't have that luxury: whatever point we decide to start moving around already has its own <code>t</code> value, and its own <code>e1</code> and <code>e2</code> values, and those may not make sense for the rest of the curve.</p>
|
||||
<p>For example, let's see what happens if we just "go with what we get" when we pick a point and start moving it around, preserving its <code>t</code> value and <code>e1</code>/<code>e2</code> coordinates:</p>
|
||||
<graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\molding\522f1edd37163772b81acb86d3a4f423.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>That looks reasonable, close to the original point, but the further we drag our point, the less "useful" things become. Especially if we drag our point across the baseline, rather than turning into a nice curve.</p>
|
||||
<p>One way to combat this might be to combine the above approach with the approach from the <a href="#pointcurves">creating curves</a> section: generate both the "unchanged <code>t</code>/<code>e1</code>/<code>e2</code>" curve, as well as the "idealised" curve through the start/cursor/end points, with idealised <code>t</code> value, and then interpolating between those two curves:</p>
|
||||
<graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic" data-interpolated="true">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\molding\ea1656c068a60631135ad499e8a29453.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="10" max="200" step="1" value="100" class="slide-control">
|
||||
</graphics-element>
|
||||
@@ -1788,8 +1847,9 @@ for (coordinate, index) in LUT:
|
||||
<p>So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once there's enough points for compound <a href="https://en.wikipedia.org/wiki/Round-off_error#Floating-point_number_system">floating point rounding errors</a> to start making a difference (which is around 10~11 points).</p>
|
||||
<graphics-element title="Fitting a Bézier curve" width="550" height="275" src="./chapters/curvefitting/curve-fitting.js" >
|
||||
<fallback-image>
|
||||
<img width="550px" height="275px" src="images\chapters\curvefitting\c2c92587a184efa6c4fee45e4a3e32ed.png">
|
||||
Scripts are disabled. Showing fallback image.
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="550px" height="275px" src="images\chapters\curvefitting\c6c8442e24793ce72a872ce29b2b4125.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<button class="toggle">toggle</button>
|
||||
<div class="sliders"><!-- this will contain range inputs, created by the graphic --></div>
|
||||
@@ -1800,83 +1860,92 @@ for (coordinate, index) in LUT:
|
||||
</section>
|
||||
<section id="catmullconv">
|
||||
<h1><a href="#catmullconv">Bézier curves and Catmull-Rom curves</a></h1>
|
||||
<p>Taking an excursion to different splines, the other common design curve is the <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom spline</a>. Now, a Catmull-Rom spline is a form of <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline">cubic Hermite spline</a>, and as it so happens the cubic Bézier curve is <em>also</em> a cubic Hermite spline, so maybe... maybe we can convert one into the other, and back, with some simple substitutions?</p>
|
||||
<p>Unlike Bézier curves, Catmull-Rom splines pass through each point used to define the curve, except the first and last, which makes sense if you read the "natural language" description for how a Catmull-Rom spline works: a Catmull-Rom spline is a curve that, at each point P<sub>x</sub>, has a tangent along the line P<sub>x-1</sub> to P<sub>x+1</sub>. The curve runs from points P<sub>2</sub> to P<sub>n-1</sub>, and has a "tension" that determines how fast the curve passes through each point. The lower the tension, the faster the curve goes through each point, and the bigger its local tangent is.</p>
|
||||
<p>I'll be showing the conversion to and from Catmull-Rom curves for the tension that the Processing language uses for its Catmull-Rom algorithm.</p>
|
||||
<p>We start with showing the Catmull-Rom matrix form, which looks similar to the Bézier matrix form, with slightly different values in the matrix:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/9215d05705c8e8a7ebd718ae6f690371.svg" width="409px" height="75px" loading="lazy">
|
||||
<p>However, there's something funny going on here: the coordinate column matrix looks weird. The reason is that Catmull-Rom curves are actually curve segments that are described by two coordinate points, and two tangents; the curve starts at coordinate V1, and ends at coordinate V2, with the curve "departing" V1 with a tangent vector V'1 and "arriving" at V2 with tangent vector V'2.</p>
|
||||
<p>This is not particularly useful if we want to draw Catmull-Rom curves in the same way we draw Bézier curves, i.e. by providing four points. However, we can fairly easily go from the former to the latter, but it's going to require some linear algebra, so if you just want to know how to convert between the two coordinate systems: skip the following bit.</p>
|
||||
<p>But... if you want to know <em>why</em> that conversion works, let's do some maths!</p>
|
||||
<p>Taking an excursion to different splines, the other common design curve is the <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom spline</a>, which unlike Bézier curves pass <em>through</em> the points you define. In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, or you can click/tap somewhere to extend the curve.</p>
|
||||
<graphics-element title="A Catmull-Rom curve" width="275" height="275" src="./chapters/catmullconv/catmull-rom.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\catmullconv\b0cb0cccdbea86dcdd610a871e86183f.png">
|
||||
<label>A Catmull-Rom curve</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0.1" max="1" step="0.01" value="0.5" class="slide-control tension">
|
||||
</graphics-element>
|
||||
|
||||
<p>You may have noticed the slider that seems to control the "tension" of the curve: that's a feature of Catmull-Rom curves; because Catmull-Rom curves pass through points, the curve tightness is controlled by a tension factor, rather than by moving control points around.</p>
|
||||
<p>What you may <em>also</em> have noticed is that the Catmull-Rom curve seems to just go on forever: add as many points as you like, same curve, rigth? Well, sort of. Catmull-Rom curves are splines, a type of curve with arbitrary number of points, technically consisting of "segments between sets of points", but with maths that makes all the segments line up neatly. As such, at its core a Catmull-Rom consists of two points, and draws a curve between them based on the tangents at those points.</p>
|
||||
<p>Now, a Catmull-Rom spline is a form of <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline">cubic Hermite spline</a>, and as it so happens, the cubic Bézier curve is <em>also</em> a cubic Hermite spline, so in an interesting bit of programming maths: we can losslessly convert between the two, and the maths (well, the final maths) is surprisingly simple!</p>
|
||||
<p>The main difference between Catmull-Rom curves and Bezier curves is "what the points mean". A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point. A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point. Those are <em>very</em> similar, so let's see exactly <em>how</em> similar they are.</p>
|
||||
<p>We start with the matrix form of thee Catmull-Rom curve, which looks similar to the Bézier matrix form, with slightly different values in the matrix, and a slightly different coordinate vector:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/9215d05705c8e8a7ebd718ae6f690371.svg" width="423px" height="75px" loading="lazy">
|
||||
<p>So the question is: how can we convert that expression with Catmull-Rom matrix and vector into an expression that uses Bézier matrix and vector? The short answer is of course "by using linear algebra", but the real answer is a bit longer, and involves some maths that you may not even care for: just skip over the next bit to see the incredibly simple conversions between the formats, but if you want to know <em>why</em>... let's go!</p>
|
||||
<div class="note">
|
||||
|
||||
<h2>Deriving the conversion formulae</h2>
|
||||
<p>In order to convert between Catmull-Rom curves and Bézier curves, we need to know two things. Firstly, how to express the Catmull-Rom curve using a "set of four coordinates", rather than a mix of coordinates and tangents, and secondly, how to convert those Catmull-Rom coordinates to and from Bézier form.</p>
|
||||
<p>So, let's start with the first, where we want to satisfy the following equality:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/1811b59c5ab9233f08590396e5d03303.svg" width="187px" height="83px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/1811b59c5ab9233f08590396e5d03303.svg" width="196px" height="79px" loading="lazy">
|
||||
<p>This mapping says that in order to map a Catmull-Rom "point + tangent" vector to something based on an "all coordinates" vector, we need to determine the mapping matrix such that applying <em>T</em> yields P2 as start point, P3 as end point, and two tangents based on the lines between P1 and P3, and P2 nd P4, respectively.</p>
|
||||
<p>Computing <em>T</em> is really more "arranging the numbers":</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/4d524810417b4caffedd13af23135f5b.svg" width="591px" height="83px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/4d524810417b4caffedd13af23135f5b.svg" width="621px" height="79px" loading="lazy">
|
||||
<p>Thus:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f41487aff3e34fafd5d4ee5979f133f1.svg" width="143px" height="81px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f41487aff3e34fafd5d4ee5979f133f1.svg" width="145px" height="76px" loading="lazy">
|
||||
<p>However, we're not <em>quite</em> done, because Catmull-Rom curves have a parameter called "tension", written as τ ("tau"), which is a scaling factor for the tangent vectors: the bigger the tension, the smaller the tangents, and the smaller the tension, the bigger the tangents. As such, the tension factor goes in the denominator for the tangents, and before we continue, let's add that tension factor into both our coordinate vector representation, and mapping matrix <em>T</em>:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/06ae1e3fdc660e59d618e0760e8e9ab5.svg" width="285px" height="84px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/06ae1e3fdc660e59d618e0760e8e9ab5.svg" width="291px" height="79px" loading="lazy">
|
||||
<p>With the mapping matrix properly done, let's rewrite the "point + tangent" Catmull-Rom matrix form to a matrix form in terms of four coordinates, and see what we end up with:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/cc1e2ff43350c32f0ae9ba9a7652b8fb.svg" width="409px" height="75px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/cc1e2ff43350c32f0ae9ba9a7652b8fb.svg" width="423px" height="75px" loading="lazy">
|
||||
<p>Replace point/tangent vector with the expression for all-coordinates:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f08e34395ce2812276fd70548f805041.svg" width="549px" height="81px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f08e34395ce2812276fd70548f805041.svg" width="564px" height="76px" loading="lazy">
|
||||
<p>and merge the matrices:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f2b2a16a41d134ce0dfd544ab77ff25e.svg" width="455px" height="84px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f2b2a16a41d134ce0dfd544ab77ff25e.svg" width="465px" height="76px" loading="lazy">
|
||||
<p>This looks a lot like the Bézier matrix form, which as we saw in the chapter on Bézier curves, should look like this:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/8f56909fcb62b8eef18b9b9559575c13.svg" width="353px" height="73px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/8f56909fcb62b8eef18b9b9559575c13.svg" width="359px" height="75px" loading="lazy">
|
||||
<p>So, if we want to express a Catmull-Rom curve using a Bézier curve, we'll need to turn this Catmull-Rom bit:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/b21386f86bef8894f108c5441dad10de.svg" width="227px" height="84px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/b21386f86bef8894f108c5441dad10de.svg" width="227px" height="76px" loading="lazy">
|
||||
<p>Into something that looks like this:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/78ac9df086ec19147414359369b563fc.svg" width="167px" height="73px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/78ac9df086ec19147414359369b563fc.svg" width="171px" height="75px" loading="lazy">
|
||||
<p>And the way we do that is with a fairly straight forward bit of matrix rewriting. We start with the equality we need to ensure:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/a47b072a325812ac4f0ff52c22792588.svg" width="440px" height="84px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/a47b072a325812ac4f0ff52c22792588.svg" width="452px" height="76px" loading="lazy">
|
||||
<p>Then we remove the coordinate vector from both sides without affecting the equality:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/841fb6a2a035c9bcf5a2d46f2a67709b.svg" width="353px" height="84px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/841fb6a2a035c9bcf5a2d46f2a67709b.svg" width="356px" height="76px" loading="lazy">
|
||||
<p>Then we can "get rid of" the Bézier matrix on the right by left-multiply both with the inverse of the Bézier matrix:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/cbdd46d5e2e1a6202ef46fb03711ebe4.svg" width="657px" height="88px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/cbdd46d5e2e1a6202ef46fb03711ebe4.svg" width="672px" height="84px" loading="lazy">
|
||||
<p>A matrix times its inverse is the matrix equivalent of 1, and because "something times 1" is the same as "something", so we can just outright remove any matrix/inverse pair:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/3ea54fe939d076f8db605c5b480e7db0.svg" width="369px" height="88px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/3ea54fe939d076f8db605c5b480e7db0.svg" width="372px" height="84px" loading="lazy">
|
||||
<p>And now we're <em>basically</em> done. We just multiply those two matrices and we know what <em>V</em> is:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/169fd85a95e4d16fe289a75583017a11.svg" width="161px" height="77px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/169fd85a95e4d16fe289a75583017a11.svg" width="160px" height="73px" loading="lazy">
|
||||
<p>We now have the final piece of our function puzzle. Let's run through each step.</p>
|
||||
<ol>
|
||||
<li>Start with the Catmull-Rom function:</li>
|
||||
</ol>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/cc1e2ff43350c32f0ae9ba9a7652b8fb.svg" width="409px" height="75px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/cc1e2ff43350c32f0ae9ba9a7652b8fb.svg" width="423px" height="75px" loading="lazy">
|
||||
<ol start="2">
|
||||
<li>rewrite to pure coordinate form:</li>
|
||||
</ol>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f814bb8d627f9c8f33b347c1cf13d4c7.svg" width="324px" height="84px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f814bb8d627f9c8f33b347c1cf13d4c7.svg" width="329px" height="79px" loading="lazy">
|
||||
<ol start="3">
|
||||
<li>rewrite for "normal" coordinate vector:</li>
|
||||
</ol>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/5f2750de827497375d9a915f96686885.svg" width="441px" height="81px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/5f2750de827497375d9a915f96686885.svg" width="448px" height="76px" loading="lazy">
|
||||
<ol start="4">
|
||||
<li>merge the inner matrices:</li>
|
||||
</ol>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/79e333cd0c569657eea033b04fb5e61b.svg" width="348px" height="84px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/79e333cd0c569657eea033b04fb5e61b.svg" width="348px" height="76px" loading="lazy">
|
||||
<ol start="5">
|
||||
<li>rewrite for Bézier matrix form:</li>
|
||||
</ol>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/1b8a782f7540503d38067317e4cd00b0.svg" width="431px" height="77px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/1b8a782f7540503d38067317e4cd00b0.svg" width="436px" height="75px" loading="lazy">
|
||||
<ol start="6">
|
||||
<li>and transform the coordinates so we have a "pure" Bézier expression:</li>
|
||||
</ol>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/e3d30ab368dcead1411532ce3814d3f3.svg" width="348px" height="81px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/e3d30ab368dcead1411532ce3814d3f3.svg" width="353px" height="77px" loading="lazy">
|
||||
<p>And we're done: we finally know how to convert these two curves!</p>
|
||||
</div>
|
||||
|
||||
<p>If we have a Catmull-Rom curve defined by four coordinates P<sub>1</sub> through P<sub>4</sub>, then we can draw that curve using a Bézier curve that has the vector:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/ba31c32eba62f1e3b15066cd5ddda597.svg" width="249px" height="85px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/ba31c32eba62f1e3b15066cd5ddda597.svg" width="268px" height="81px" loading="lazy">
|
||||
<p>Similarly, if we have a Bézier curve defined by four coordinates P<sub>1</sub> through P<sub>4</sub>, we can draw that using a standard tension Catmull-Rom curve with the following coordinate values:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/eae7f01976e511ee38b08b6edc8765d2.svg" width="284px" height="77px" loading="lazy">
|
||||
<p>or, if your API requires specifying Catmull-Rom curves using "point + tangent" form:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/26363fc09f8cf2d41ea5b4256656bb6d.svg" width="284px" height="77px" loading="lazy">
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/26363fc09f8cf2d41ea5b4256656bb6d.svg" width="300px" height="79px" loading="lazy">
|
||||
<p>Or, if your API allows you to specify Catmull-Rom curves using plain coordinates:</p>
|
||||
<img class="LaTeX SVG" src="./images/chapters/catmullconv/eae7f01976e511ee38b08b6edc8765d2.svg" width="300px" height="80px" loading="lazy">
|
||||
|
||||
</section>
|
||||
<section id="catmullmolding">
|
||||
|
Reference in New Issue
Block a user