1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-31 20:11:59 +02:00
This commit is contained in:
Pomax
2020-08-21 16:08:05 -07:00
parent c90565f493
commit 65173c10a2
17 changed files with 113 additions and 89 deletions

View File

@@ -1,4 +1,4 @@
# Canonical form (for cubic curves)
# The canonical form (for cubic curves)
While quadratic curves are relatively simple curves to analyze, the same cannot be said of the cubic curve. As a curvature is controlled by more than one control point, it exhibits all kinds of features like loops, cusps, odd colinear features, and as many as two inflection points because the curvature can change direction up to three times. Now, knowing what kind of curve we're dealing with means that some algorithms can be run more efficiently than if we have to implement them as generic solvers, so is there a way to determine the curve type without lots of work?
@@ -54,13 +54,13 @@ For the full details, head over to the paper and read through sections 3 and 4.
So now the question becomes: how do we manipulate our curve so that it fits this canonical form, with three fixed points, and one "free" point? Enter linear algebra. Don't worry, I'll be doing all the math for you, as well as show you what the effect is on our curves, but basically we're going to be using linear algebra, rather than calculus, because "it's way easier". Sometimes a calculus approach is very hard to work with, when the equivalent geometrical solution is super obvious.
The approach is going to start with a curve that doesn't have all-colinear points (so we need to make sure the points don't all fall on a straight line), and then applying four graphics operations that you will probably have heard of: translation (moving all points by some fixed x- and y-distance), scaling (multiplying all points by some x and y scale factor), and shearing (an operation that turns rectangles into parallelograms).
The approach is going to start with a curve that doesn't have all-colinear points (so we need to make sure the points don't all fall on a straight line), and then applying three graphics operations that you will probably have heard of: translation (moving all points by some fixed x- and y-distance), scaling (multiplying all points by some x and y scale factor), and shearing (an operation that turns rectangles into parallelograms).
Step 1: we translate any curve by -p1.x and -p1.y, so that the curve starts at (0,0). We're going to make use of an interesting trick here, by pretending our 2D coordinates are 3D, with the *z* coordinate simply always being 1. This is an old trick in graphics to overcome the limitations of 2D transformations: without it, we can only turn (x,y) coordinates into new coordinates of the form (ax + by, cx + dy), which means we can't do translation, since that requires we end up with some kind of (x + a, y + b). If we add a bogus *z* coordinate that is always 1, then we can suddenly add arbitrary values. For example:
\[
\left [ \begin{array}{ccc}
01 & 0 & a \\
1 & 0 & a \\
0 & 1 & b \\
0 & 0 & 1
\end{array} \right ]
@@ -103,7 +103,7 @@ Sweet! *z* stays 1, so we can effectively ignore it entirely, but we added some
\[
T_1 =
\left [ \begin{array}{ccc}
01 & 0 & -{P_1}_x \\
1 & 0 & -{P_1}_x \\
0 & 1 & -{P_1}_y \\
0 & 0 & 1
\end{array} \right ]
@@ -205,7 +205,11 @@ And this generates our final set of four coordinates. Of these, we already know
\[
mapped_4 = \left (
\begin{matrix}
x = \left (
x \\
y
\end{matrix}
\right ) = \left (
\begin{matrix}
\frac
{
-x_1 + x_4 - \frac{(-x_1+x_2)(-y_1+y_4)}{-y_1+y_2}
@@ -213,9 +217,7 @@ mapped_4 = \left (
{
-x_1+x_3-\frac{(-x_1+x_2)(-y_1+y_3)}{-y_1+y_2}
}
\right )
\\
y = \left (
\frac{(-y_1+y_4)}{-y_1+y_2}
+
\frac
@@ -226,26 +228,25 @@ mapped_4 = \left (
{
-x_1+x_3-\frac{(-x_1+x_2)(-y_1+y_3)}{-y_1+y_2}
}
\right )
\end{matrix}
\right )
\]
That looks very complex, but notice that every coordinate value is being offset by the initial translation, and a lot of terms in there repeat: it's pretty easy to calculate this fast, since there's so much we can cache and reuse while we compute this mapped coordinate!
Okay, well, that looks plain ridiculous, but: notice that every coordinate value is being offset by the initial translation, and also notice that _a lot_ of terms in that expression are repeated. Even though the maths looks crazy as a single expression, we can just pull this apart a little and end up with an easy-to-calculate bit of code!
First, let's just do that translation step as a "preprocessing" operation so we don't have to subtract the values all the time. What does that leave?
\[
... = \left (
\begin{matrix}
x = \left ( x_4 - \frac{x_2 \cdot y_4}{y_2} \middle/ x_3-\frac{x_2 \cdot y_3}{y_2} \right )
x_4 - \frac{x_2 \cdot y_4}{y_2} / x_3-\frac{x_2 \cdot y_3}{y_2}
\\
\\
y =
\frac{y_4}{y_2}
+
\left ( 1 - \frac{y_3}{y_2} \right )
\cdot
\left ( x_4 - \frac{x_2 \cdot y_4}{y_2} \middle/ x_3-\frac{x_2 \cdot y_3}{y_2} \right )
\left ( x_4 - \frac{x_2 \cdot y_4}{y_2} / x_3-\frac{x_2 \cdot y_3}{y_2} \right )
\end{matrix}
\right )
\]
@@ -255,16 +256,16 @@ Suddenly things look a lot simpler: the mapped x is fairly straight forward to c
\[
... = \left (
\begin{matrix}
x = (x_4 - x_2 \cdot f_{42}) / ( x_3- x_2 \cdot f_{32} )
\left ( x_4 - x_2 \cdot y_{42} \right ) / \left ( x_3- x_2 \cdot y_{32} \right )
\\
y =
f_{42}
\\
y_{42}
+
\left ( 1 - f_{32} \right )
\left ( 1 - y_{32} \right )
\cdot
x
\end{matrix}
\right ), f_{32} = \frac{y_3}{y_2}, f_{42} = \frac{y_4}{y_2}
\right ),\textit{ where } y_{32} = \frac{y_3}{y_2},\textit{ and } \ y_{42} = \frac{y_4}{y_2}
\]
That's kind of super-simple to write out in code, I think you'll agree. Coding math tends to be easier than the formulae initially make it look!

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -121,7 +121,9 @@
<li><a href="#aligning">Aligning curves</a></li>
<li><a href="#tightbounds">Tight bounding boxes</a></li>
<li><a href="#inflections">Curve inflections</a></li>
<li><a href="#canonical">Canonical form (for cubic curves)</a></li>
<li>
<a href="#canonical">The canonical form (for cubic curves)</a>
</li>
<li><a href="#yforx">Finding Y, given X</a></li>
<li><a href="#arclength">Arc length</a></li>
<li><a href="#arclengthapprox">Approximated arc length</a></li>
@@ -3824,7 +3826,9 @@ function getCubicRoots(pa, pb, pc, pd) {
>
</section>
<section id="canonical">
<h1><a href="#canonical">Canonical form (for cubic curves)</a></h1>
<h1>
<a href="#canonical">The canonical form (for cubic curves)</a>
</h1>
<p>
While quadratic curves are relatively simple curves to analyze, the
same cannot be said of the cubic curve. As a curvature is controlled
@@ -4006,11 +4010,11 @@ function getCubicRoots(pa, pb, pc, pd) {
<p>
The approach is going to start with a curve that doesn't have
all-colinear points (so we need to make sure the points don't all
fall on a straight line), and then applying four graphics operations
that you will probably have heard of: translation (moving all points
by some fixed x- and y-distance), scaling (multiplying all points by
some x and y scale factor), and shearing (an operation that turns
rectangles into parallelograms).
fall on a straight line), and then applying three graphics
operations that you will probably have heard of: translation (moving
all points by some fixed x- and y-distance), scaling (multiplying
all points by some x and y scale factor), and shearing (an operation
that turns rectangles into parallelograms).
</p>
<p>
Step 1: we translate any curve by -p1.x and -p1.y, so that the curve
@@ -4026,8 +4030,8 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/d089cc0687982a3302249bb82af3fc16.svg"
width="499px"
src="./images/chapters/canonical/2a411f175dcc987cdcc12e7df49ca272.svg"
width="464px"
height="55px"
loading="lazy"
/>
@@ -4038,8 +4042,8 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/058fa85ac31eb666857a860fdedd79df.svg"
width="477px"
src="./images/chapters/canonical/ba5f418452c3657f3c4dd4b319e59070.svg"
width="447px"
height="57px"
loading="lazy"
/>
@@ -4124,16 +4128,18 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/2f85d84f0e3dd14cc25e48583aed3822.svg"
width="460px"
height="80px"
src="./images/chapters/canonical/f039b4e7cf0203df9fac48dad820b2b7.svg"
width="455px"
height="91px"
loading="lazy"
/>
<p>
That looks very complex, but notice that every coordinate value is
being offset by the initial translation, and a lot of terms in there
repeat: it's pretty easy to calculate this fast, since there's so
much we can cache and reuse while we compute this mapped coordinate!
Okay, well, that looks plain ridiculous, but: notice that every
coordinate value is being offset by the initial translation, and
also notice that <em>a lot</em> of terms in that expression are
repeated. Even though the maths looks crazy as a single expression,
we can just pull this apart a little and end up with an
easy-to-calculate bit of code!
</p>
<p>
First, let's just do that translation step as a "preprocessing"
@@ -4142,9 +4148,9 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/83262761bb7fa9b832fe483ded436973.svg"
width="359px"
height="56px"
src="./images/chapters/canonical/16fad73cbbbd2202b08ebef05b1579c5.svg"
width="317px"
height="67px"
loading="lazy"
/>
<p>
@@ -4156,9 +4162,9 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/f3261ad2802d980ebe6e35b272375700.svg"
width="427px"
height="40px"
src="./images/chapters/canonical/5af3d1772ee07e634d04259a02f7201f.svg"
width="483px"
height="56px"
loading="lazy"
/>
<p>

View File

@@ -157,7 +157,7 @@
<li><a href="ja-JP/index.html#inflections">Curve inflections</a></li>
<li>
<a href="ja-JP/index.html#canonical"
>Canonical form (for cubic curves)</a
>The canonical form (for cubic curves)</a
>
</li>
<li><a href="ja-JP/index.html#yforx">Finding Y, given X</a></li>
@@ -3500,7 +3500,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<section id="canonical">
<h1>
<a href="ja-JP/index.html#canonical"
>Canonical form (for cubic curves)</a
>The canonical form (for cubic curves)</a
>
</h1>
<p>
@@ -3684,11 +3684,11 @@ function getCubicRoots(pa, pb, pc, pd) {
<p>
The approach is going to start with a curve that doesn't have
all-colinear points (so we need to make sure the points don't all
fall on a straight line), and then applying four graphics operations
that you will probably have heard of: translation (moving all points
by some fixed x- and y-distance), scaling (multiplying all points by
some x and y scale factor), and shearing (an operation that turns
rectangles into parallelograms).
fall on a straight line), and then applying three graphics
operations that you will probably have heard of: translation (moving
all points by some fixed x- and y-distance), scaling (multiplying
all points by some x and y scale factor), and shearing (an operation
that turns rectangles into parallelograms).
</p>
<p>
Step 1: we translate any curve by -p1.x and -p1.y, so that the curve
@@ -3704,8 +3704,8 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/d089cc0687982a3302249bb82af3fc16.svg"
width="499px"
src="./images/chapters/canonical/2a411f175dcc987cdcc12e7df49ca272.svg"
width="464px"
height="55px"
loading="lazy"
/>
@@ -3716,8 +3716,8 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/058fa85ac31eb666857a860fdedd79df.svg"
width="477px"
src="./images/chapters/canonical/ba5f418452c3657f3c4dd4b319e59070.svg"
width="447px"
height="57px"
loading="lazy"
/>
@@ -3802,16 +3802,18 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/2f85d84f0e3dd14cc25e48583aed3822.svg"
width="460px"
height="80px"
src="./images/chapters/canonical/f039b4e7cf0203df9fac48dad820b2b7.svg"
width="455px"
height="91px"
loading="lazy"
/>
<p>
That looks very complex, but notice that every coordinate value is
being offset by the initial translation, and a lot of terms in there
repeat: it's pretty easy to calculate this fast, since there's so
much we can cache and reuse while we compute this mapped coordinate!
Okay, well, that looks plain ridiculous, but: notice that every
coordinate value is being offset by the initial translation, and
also notice that <em>a lot</em> of terms in that expression are
repeated. Even though the maths looks crazy as a single expression,
we can just pull this apart a little and end up with an
easy-to-calculate bit of code!
</p>
<p>
First, let's just do that translation step as a "preprocessing"
@@ -3820,9 +3822,9 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/83262761bb7fa9b832fe483ded436973.svg"
width="359px"
height="56px"
src="./images/chapters/canonical/16fad73cbbbd2202b08ebef05b1579c5.svg"
width="317px"
height="67px"
loading="lazy"
/>
<p>
@@ -3834,9 +3836,9 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/f3261ad2802d980ebe6e35b272375700.svg"
width="427px"
height="40px"
src="./images/chapters/canonical/5af3d1772ee07e634d04259a02f7201f.svg"
width="483px"
height="56px"
loading="lazy"
/>
<p>

View File

@@ -151,7 +151,7 @@
<li><a href="zh-CN/index.html#inflections">Curve inflections</a></li>
<li>
<a href="zh-CN/index.html#canonical"
>Canonical form (for cubic curves)</a
>The canonical form (for cubic curves)</a
>
</li>
<li><a href="zh-CN/index.html#yforx">Finding Y, given X</a></li>
@@ -3510,7 +3510,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<section id="canonical">
<h1>
<a href="zh-CN/index.html#canonical"
>Canonical form (for cubic curves)</a
>The canonical form (for cubic curves)</a
>
</h1>
<p>
@@ -3694,11 +3694,11 @@ function getCubicRoots(pa, pb, pc, pd) {
<p>
The approach is going to start with a curve that doesn't have
all-colinear points (so we need to make sure the points don't all
fall on a straight line), and then applying four graphics operations
that you will probably have heard of: translation (moving all points
by some fixed x- and y-distance), scaling (multiplying all points by
some x and y scale factor), and shearing (an operation that turns
rectangles into parallelograms).
fall on a straight line), and then applying three graphics
operations that you will probably have heard of: translation (moving
all points by some fixed x- and y-distance), scaling (multiplying
all points by some x and y scale factor), and shearing (an operation
that turns rectangles into parallelograms).
</p>
<p>
Step 1: we translate any curve by -p1.x and -p1.y, so that the curve
@@ -3714,8 +3714,8 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/d089cc0687982a3302249bb82af3fc16.svg"
width="499px"
src="./images/chapters/canonical/2a411f175dcc987cdcc12e7df49ca272.svg"
width="464px"
height="55px"
loading="lazy"
/>
@@ -3726,8 +3726,8 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/058fa85ac31eb666857a860fdedd79df.svg"
width="477px"
src="./images/chapters/canonical/ba5f418452c3657f3c4dd4b319e59070.svg"
width="447px"
height="57px"
loading="lazy"
/>
@@ -3812,16 +3812,18 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/2f85d84f0e3dd14cc25e48583aed3822.svg"
width="460px"
height="80px"
src="./images/chapters/canonical/f039b4e7cf0203df9fac48dad820b2b7.svg"
width="455px"
height="91px"
loading="lazy"
/>
<p>
That looks very complex, but notice that every coordinate value is
being offset by the initial translation, and a lot of terms in there
repeat: it's pretty easy to calculate this fast, since there's so
much we can cache and reuse while we compute this mapped coordinate!
Okay, well, that looks plain ridiculous, but: notice that every
coordinate value is being offset by the initial translation, and
also notice that <em>a lot</em> of terms in that expression are
repeated. Even though the maths looks crazy as a single expression,
we can just pull this apart a little and end up with an
easy-to-calculate bit of code!
</p>
<p>
First, let's just do that translation step as a "preprocessing"
@@ -3830,9 +3832,9 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/83262761bb7fa9b832fe483ded436973.svg"
width="359px"
height="56px"
src="./images/chapters/canonical/16fad73cbbbd2202b08ebef05b1579c5.svg"
width="317px"
height="67px"
loading="lazy"
/>
<p>
@@ -3844,9 +3846,9 @@ function getCubicRoots(pa, pb, pc, pd) {
</p>
<img
class="LaTeX SVG"
src="./images/chapters/canonical/f3261ad2802d980ebe6e35b272375700.svg"
width="427px"
height="40px"
src="./images/chapters/canonical/5af3d1772ee07e634d04259a02f7201f.svg"
width="483px"
height="56px"
loading="lazy"
/>
<p>