1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-21 07:51:49 +02:00

finished molding

This commit is contained in:
Pomax
2020-09-01 15:54:50 -07:00
parent fbec463127
commit fc13f64451
84 changed files with 789 additions and 795 deletions

View File

@@ -45,10 +45,10 @@ draw() {
line(p1.x, p1.y, p2.x, p2.y); line(p1.x, p1.y, p2.x, p2.y);
} }
this.drawABCdata(t, A, B, C); this.drawABCdata(t, A, B, C, hull);
} }
drawABCdata(t, A, B, C) { drawABCdata(t, A, B, C, hull) {
// show the lines between the A/B/C values // show the lines between the A/B/C values
setStroke(`#00FF00`); setStroke(`#00FF00`);
line(A.x, A.y, B.x, B.y); line(A.x, A.y, B.x, B.y);
@@ -59,10 +59,23 @@ drawABCdata(t, A, B, C) {
// with their associated labels // with their associated labels
setFill(`black`); setFill(`black`);
text(`Using t = ${t.toFixed(2)}`, this.width/2, 10, CENTER);
setTextStroke(`white`, 4);
text(`A`, 10 + A.x, A.y); text(`A`, 10 + A.x, A.y);
text(`B (t = ${t.toFixed(2)})`, 10 + B.x, B.y); text(`B`, 10 + B.x, B.y);
text(`C`, 10 + C.x, C.y); text(`C`, 10 + C.x, C.y);
if(curve.order === 2) {
text(`e1`, hull[3].x, hull[3].y+3, CENTER);
text(`e2`, hull[4].x, hull[4].y+3, CENTER);
} else {
text(`e1`, hull[7].x, hull[7].y+3, CENTER);
text(`e2`, hull[8].x, hull[8].y+3, CENTER);
text(`v1`, hull[4].x, hull[4].y+3, CENTER);
text(`v2`, hull[6].x, hull[6].y+3, CENTER);
}
// and show the distance ratio, which we see does not change irrespective of whether A/B/C change. // and show the distance ratio, which we see does not change irrespective of whether A/B/C change.
const d1 = dist(A.x, A.y, B.x, B.y); const d1 = dist(A.x, A.y, B.x, B.y);
const d2 = dist(B.x, B.y, C.x, C.y); const d2 = dist(B.x, B.y, C.x, C.y);

View File

@@ -1,6 +1,6 @@
# The projection identity # The projection identity
De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mould" a curve, by picking it up at some point, and dragging that point around to change the curve's shape. De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mold" a curve, by picking it up at some point, and dragging that point around to change the curve's shape.
How does that work? Succinctly: we run de Casteljau's algorithm in reverse! How does that work? Succinctly: we run de Casteljau's algorithm in reverse!
@@ -24,6 +24,8 @@ So these graphics show us several things:
1. a point at the tip of the curve construction's "hat": let's call that `A`, as well as 1. a point at the tip of the curve construction's "hat": let's call that `A`, as well as
2. our on-curve point give our chosen `t` value: let's call that `B`, and finally, 2. our on-curve point give our chosen `t` value: let's call that `B`, and finally,
3. a point that we get by projecting A, through B, onto the line between the curve's start and end points: let's call that `C`. 3. a point that we get by projecting A, through B, onto the line between the curve's start and end points: let's call that `C`.
4. for both qudratic and cubic curves, two points `e1` and `e2`, which represent the single-to-last step in de Casteljau's algorithm: in the last step, we find `B` at `(1-t) * e1 + t * e2`.
4. for cubic curves, also the points `v1` and `v2`, which together with `A` represent the first step in de Casteljau's algorithm: in the next step, we find `e1` and `e2`.
These three values A, B, and C allow us to derive an important identity formula for quadratic and cubic Bézier curves: for any point on the curve with some `t` value, the ratio of distances from A to B and B to C is fixed: if some `t` value sets up a C that is 20% away from the start and 80% away from the end, then _it doesn't matter where the start, end, or control points are_; for that `t` value, `C` will *always* lie at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic and then move all the other points around: if you only move the control points, start and end won't move, and so neither will C, and if you move either start or end point, C will move but its relative position will not change. These three values A, B, and C allow us to derive an important identity formula for quadratic and cubic Bézier curves: for any point on the curve with some `t` value, the ratio of distances from A to B and B to C is fixed: if some `t` value sets up a C that is 20% away from the start and 80% away from the end, then _it doesn't matter where the start, end, or control points are_; for that `t` value, `C` will *always* lie at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic and then move all the other points around: if you only move the control points, start and end won't move, and so neither will C, and if you move either start or end point, C will move but its relative position will not change.
@@ -71,4 +73,22 @@ Which now leaves us with some powerful tools: given thee points (start, end, and
A = B - \frac{C - B}{ratio(t)} = B + \frac{B - C}{ratio(t)} A = B - \frac{C - B}{ratio(t)} = B + \frac{B - C}{ratio(t)}
\] \]
So: if we have a curve's start and end point, then for any `t` value, we implicitly know all the ABC values, which gives us the necessary information to reconstruct a curve's "de Casteljau skeleton". Which means that we can now do several things: we can "fit" curves using only three points, which means we can also "mould" curves by moving an on-curve point but leaving its start and end point, and then reconstructing the curve based on where we moved the on-curve point to. These are very useful things, and we'll look at both in the next sections. With `A` found, finding `e1` and `e2` for quadratic curves is a matter of running the linear interpolation with `t` between start and `A` to yield `e1`, and between `A` and end to yield `e2`. For cubic curves, there is no single pair of points that can act as `e1` and `e2`: as long as the distance ratio between `e1` to `B` and `B` to `e2` is the Bézier ratio `(1-t):t`, we can reverse engineer `v1` and `v2`:
\[
\left \{ \begin{aligned}
v_1 &= A' - \frac{A' - e_1}{1 - t} \\
v_2 &= A' - \frac{A' - e_2}{t}
\end{aligned} \right .
\]
And then reverse engineer the curve's control control points:
\[
\left \{ \begin{aligned}
C_1' &= start + \frac{v_1 - start}{t} \\
C_2' &= end + \frac{v_2 - end}{1 - t}
\end{aligned} \right .
\]
So: if we have a curve's start and end point, then for any `t` value we implicitly know all the ABC values, which (combined with an educated guess on appropriate `e1` and `e2` coordinates for cubic curves) gives us the necessary information to reconstruct a curve's "de Casteljau skeleton". Which means that we can now do several things: we can "fit" curves using only three points, which means we can also "mold" curves by moving an on-curve point but leaving its start and end point, and then reconstructing the curve based on where we moved the on-curve point to. These are very useful things, and we'll look at both in the next few sections.

View File

@@ -0,0 +1,37 @@
# Molding a curve
Armed with knowledge of the "ABC" relation, point-on-curve projection, and guestimating reasonable looking helper values for cubic curve construction, we can finally cover curve molding: updating a curve's shape interactively, by dragging points on the curve around.
For quadratic curve, this is a really simple trick: we project our cursor onto the curve, which gives us a `t` value and initial `B` coordinate. We don't even need the latter: with our `t` value and "whever the cursor is" as target `B`, we can compute the associated `C`:
\[
C = u(t)_{q} \cdot Start + \left ( 1-u(t)_{q} \right ) \cdot End
\]
And then the associated `A`:
\[
A = B - \frac{C - B}{ratio(t)_{q}} = B + \frac{B - C}{ratio(t)_{q}}
\]
And we're done, because that's our new quadratic control point!
<graphics-element title="Molding a quadratic Bézier curve" width="825" src="./molding.js" data-type="quadratic"></graphics-element>
As before, cubic curves are a bit more work, because while it's easy to find our initial `t` value and ABC values, getting those all-important `e1` and `e2` coordinates is going to pose a bit of a problem... in the section on curve creation, we were free to pick an appropriate `t` value ourselves, which allowed us to find appropriate `e1` and `e2` 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 `t` value, and its own `e1` and `e2` values, and those may not make sense for the rest of the curve.
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 `t` value and `e1`/`e2` coordinates:
<graphics-element title="Molding a cubic Bézier curve" width="825" src="./molding.js" data-type="cubic"></graphics-element>
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.
One way to combat this might be to combine the above approach with the approach from the [creating curves](#pointcurves) section: generate both the "unchanged `t`/`e1`/`e2`" curve, as well as the "idealised" curve through the start/cursor/end points, with idealised `t` value, and then interpolating between those two curves:
<graphics-element title="Molding a cubic Bézier curve" width="825" src="./molding.js" data-type="cubic" data-interpolated="true">
<input type="range" min="10" max="200" step="1" value="100" class="slide-control">
</graphics-element>
The slide controls the "falloff distance" relative to where the original point on the curve is, so that as we drag our point around, it interpolates with a bias towards "preserving `t`/`e1`/`e2`" closer to the original point, and bias towards "idealised" form the further away we move our point, with anything that's further than our falloff distance simply _being_ the idealised curve. We don't even try to interpolate at that point.
A more advanced way to try to smooth things out is to implement _continuous_ molding, where we constantly update the curve as we move around, and constantly change what our `B` point is, based on constantly projecting the cursor on the curve _as we're updating it_ - this is, you won't be surprised to learn, tricky, and beyond the scope of this section: interpolation (with a reasonable distance) will do for now!

View File

@@ -0,0 +1,262 @@
let curve, utils = Bezier.getUtils();
setup() {
setPanelCount(3);
const type = this.type = this.parameters.type ?? `quadratic`;
curve = type === `quadratic` ? Bezier.defaultQuadratic(this) : Bezier.defaultCubic(this);
this.position = {x:0,y:0};
setMovable(curve.points, [this.position]);
if (this.parameters.interpolated) {
setSlider(`.slide-control`, `falloff`, 100);
}
}
draw() {
clear();
curve.drawSkeleton();
curve.drawCurve();
curve.drawPoints();
this.drawPosition();
nextPanel();
curve.drawSkeleton(`lightblue`);
curve.drawCurve(`lightblue`);
curve.points.forEach(p => circle(p.x, p.y, 2));
this.drawMark();
nextPanel();
this.drawResult();
}
drawPosition() {
if (!this.position) return;
setColor(`blue`);
let p = this.position.projection;
if (!this.mark) {
p = this.position.projection = curve.project(
this.position.x,
this.position.y
)
this.position.x = p.x;
this.position.y = p.y;
}
circle(p.x, p.y, 3);
}
drawMark() {
if (!this.mark) return;
if (this.type === `quadratic`) {
this.drawQuadraticMark();
} else {
this.drawCubicMark();
}
}
drawQuadraticMark() {
let {B, t} = this.mark;
setFill(`black`);
text(`t: ${t.toFixed(5)}`, this.panelWidth/2, 15, CENTER);
let {A, C, S, E} = curve.getABC(t, B);
setColor(`lightblue`);
line(S.x, S.y, E.x, E.y);
line(A.x, A.y, C.x, C.y);
const lbl = [`A`, `B`, `C`];
[A,B,C].forEach((p,i) => {
circle(p.x, p.y, 3);
text(lbl[i], p.x + 10, p.y);
});
if (this.currentPoint) {
let {A,B,C,S,E} = curve.getABC(t, this.position);
setColor(`purple`);
line(A.x, A.y, C.x, C.y);
line(S.x, S.y, A.x, A.y);
line(E.x, E.y, A.x, A.y);
[A,B,C].forEach(p => circle(p.x, p.y, 3));
noFill();
circle(B.x, B.y, 5);
this.molded = new Bezier(this, [S,A,E]);
}
}
drawCubicMark() {
const S = curve.points[0],
E = curve.points[curve.order],
{B, t, e1, e2} = this.mark,
org = curve.getABC(t, B),
nB = this.position,
d1 = { x: e1.x - B.x, y: e1.y - B.y },
d2 = { x: e2.x - B.x, y: e2.y - B.y },
ne1 = { x: nB.x + d1.x, y: nB.y + d1.y },
ne2 = { x: nB.x + d2.x, y: nB.y + d2.y },
{A, C} = curve.getABC(t, nB),
{v1, v2, C1, C2} = this.deriveControlPoints(S, A, E, ne1, ne2, t);
if (this.parameters.interpolated) {
const ideal = this.getIdealisedCurve(S, nB, E);
this.ideal = new Bezier(this, [ideal.S, ideal.C1, ideal.C2, ideal.E]);
}
setColor(`black`);
text(`t: ${t}`, this.panelWidth/2, 20, CENTER);
setColor(`lightblue`);
line(S.x,S.y,E.x,E.y);
line(org.C.x,org.C.y,org.A.x,org.A.y);
circle(org.A.x, org.A.y, 3);
circle(org.B.x, org.B.y, 3);
circle(org.C.x, org.C.y, 3);
text(`A`, org.A.x + 5, org.A.y);
text(`B`, org.B.x + 5, org.B.y);
text(`C`, org.C.x + 5, org.C.y);
setColor(`purple`);
circle(A.x, A.y, 3);
circle(nB.x, nB.y, 3);
circle(C.x, C.y, 3);
circle(ne1.x, ne1.y, 2);
circle(ne2.x, ne2.y, 2);
line(v1.x, v1.y, A.x, A.y);
line(v2.x, v2.y, A.x, A.y);
line(S.x,S.y,C1.x,C1.y);
line(E.x,E.y,C2.x,C2.y);
line(C2.x,C2.y,C1.x,C1.y);
line(A.x,A.y,C.x,C.y);
line(ne1.x, ne1.y, ne2.x, ne2.y);
noFill();
circle(nB.x, nB.y, 5);
this.molded = new Bezier(this, [S,C1,C2,E]);
}
deriveControlPoints(S, A, E, e1, e2, t) {
// And then use those to derive the correct v1/v2/C1/C2 coordinates
const v1 = {
x: A.x - (A.x - e1.x)/(1-t),
y: A.y - (A.y - e1.y)/(1-t)
};
const v2 = {
x: A.x - (A.x - e2.x)/t,
y: A.y - (A.y - e2.y)/t
};
const C1 = {
x: S.x + (v1.x - S.x) / t,
y: S.y + (v1.y - S.y) / t
};
const C2 = {
x: E.x + (v2.x - E.x) / (1-t),
y: E.y + (v2.y - E.y) / (1-t)
};
return {v1, v2, C1, C2};
}
getIdealisedCurve(p1, p2, p3) {
const c = utils.getccenter(p1, p2, p3),
d1 = dist(p1.x, p1.y, p2.x, p2.y),
d2 = dist(p3.x, p3.y, p2.x, p2.y),
t = d1 / (d1 + d2),
{ A, B, C, S, E } = Bezier.getABC(3, p1, p2, p3, t),
angle = ( atan2(E.y-S.y, E.x-S.x) - atan2(B.y-S.y, B.x-S.x) + TAU ) % TAU,
bc = (angle < 0 || angle > PI ? -1 : 1) * dist(S.x, S.y, E.x, E.y)/3,
de1 = t * bc,
de2 = (1-t) * bc,
tangent = [
{ x: B.x - 10 * (B.y-c.y), y: B.y + 10 * (B.x-c.x) },
{ x: B.x + 10 * (B.y-c.y), y: B.y - 10 * (B.x-c.x) }
],
tlength = dist(tangent[0].x, tangent[0].y, tangent[1].x, tangent[1].y),
dx = (tangent[1].x - tangent[0].x)/tlength,
dy = (tangent[1].y - tangent[0].y)/tlength,
e1 = { x: B.x + de1 * dx, y: B.y + de1 * dy},
e2 = { x: B.x - de2 * dx, y: B.y - de2 * dy },
{v1, v2, C1, C2} = this.deriveControlPoints(S, A, E, e1, e2, t);
return {A,B,C,S,E,e1,e2,v1,v2,C1,C2};
}
drawResult() {
let last = curve;
if (this.molded) last = this.molded;
last.drawSkeleton(`lightblue`);
last.drawCurve(this.parameters.interpolated ? `lightblue` : `black`);
last.points.forEach(p => circle(p.x, p.y, 2));
if (this.mark) {
let t = this.mark.t;
let B = last.get(t);
circle(B.x, B.y, 3);
if (this.ideal) {
let d = dist(this.mark.B.x, this.mark.B.y, this.position.x, this.position.y);
let t = min(this.falloff, d) / this.falloff;
this.ideal.drawCurve(`lightblue`);
let iC1 = {
x: (1-t) * last.points[1].x + t * this.ideal.points[1].x,
y: (1-t) * last.points[1].y + t * this.ideal.points[1].y
};
let iC2 = {
x: (1-t) * last.points[2].x + t * this.ideal.points[2].x,
y: (1-t) * last.points[2].y + t * this.ideal.points[2].y
};
this.interpolated = new Bezier(this, [last.points[0], iC1, iC2, last.points[3]]);
this.interpolated.drawCurve();
}
}
}
onMouseDown() {
if (this.currentPoint !== this.position) {
this.mark = false;
this.position.projection = false;
}
else if (this.position.projection) {
let t = this.position.projection.t;
if (this.type === `quadratic`) {
this.mark = {
t, B: this.position.projection,
};
} else {
let struts = curve.getStrutPoints(t);
let m = this.mark = {
t, B: this.position.projection,
e1: struts[7],
e2: struts[8]
};
m.d1 = { x: m.e1.x - m.B.x, y: m.e1.y - m.B.y};
m.d2 = { x: m.e2.x - m.B.x, y: m.e2.y - m.B.y};
}
}
redraw();
}
onMouseMove() {
if (!this.currentPoint && !this.mark) {
this.position.x = this.cursor.x;
this.position.y = this.cursor.y;
}
redraw();
}
onMouseUp() {
this.mark = false;
if (this.molded) {
curve = this.interpolated ?? this.molded;
this.interpolated = false;
this.molded = false;
resetMovable(curve.points, [this.position]);
}
redraw();
}

View File

@@ -1,83 +0,0 @@
# Manipulating a curve
Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the <em>t</em>-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.
<graphics-element title="Moulding a quadratic Bézier curve" width="825" src="./moulding.js" data-type="quadratic"></graphics-element>
Click-dragging a point on the curve shows what we're using to compute the new coordinates: while dragging you will see the original point `B` and its corresponding <i>t</i>-value, and the original points `A` and `C` for that <i>t</i>-value, in light coloring, as well as the new `A'`, `B'`, and `C'` (although of course the `C` coordinates are the same ones, because that's the defining feature of point `C`) based on where you're dragging point `B` to, in purple.
Since we know the new point `B'`, and the "new" point `C'` as well as the `t` value, we know our new point A' has to be:
\[
A' = B' - \frac{C - B'}{ratio(t)} = B' + \frac{B' - C}{ratio(t)}
\]
For quadratic curves, this means we're done, since the new point `A'` is equivalent to the new quadratic control point.
For cubic curves, we need to do a little more work, because while computing a new `A'` is exactly the same as before, we're not quite done once we've done so. For cubic curves, `B` has not just an associated `t` value, but also two associated "side" values. Let's revisit the graphic from the chapter on de Casteljau's algorithm, to see what we mean:
<graphics-element title="The information necessary to manipulate cubic curves" src="./decasteljau.js">
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
</graphics-element>
In addition to the `A`, `B`, and `C` values, we also see the points `e1` and `e2`, without which constructing our de Casteljau "strut lines" becomes very difficult indeed; as well as the points `v1` and `v2`, which we can construct when we know our ABC values enriched with `e1` and `e2`:
\[
\left \{ \begin{aligned}
v_1 &= A' - \frac{A' - e_1}{1 - t} \\
v_2 &= A' - \frac{A' - e_2}{t}
\end{aligned} \right .
\]
After which computing the new control points is straight-forward:
\[
\left \{ \begin{aligned}
C_1' &= start + \frac{v_1 - start}{t} \\
C_2' &= end + \frac{v_2 - end}{1 - t}
\end{aligned} \right .
\]
So let's put that into practice:
<graphics-element title="Moulding a cubic Bézier curve" width="825" src="./moulding.js" data-type="cubic"></graphics-element>
So that looks pretty good, but you may not like having `e1` and `e2` stay the same distances away from `B'` while moving the point around, and want to rearrange those to lead to "cleaner looking" curve manipulation. Unfortunately, there are so many differen ways in which we can do this that figuring out "good looking" alternatives, given what the curve is being manipulated for, could be an entire book on its own... so we're only going to look at one way that you might effect alternative `e1` and `e2` points, based on the idea of rotating a vector.
If we treat point `B` as a "a vector originating at `C`" then we can treat the points `e1` and `e2` as offets (let's call these `d1` and `d2`) of that vector, where:
\[
\left \{ \begin{aligned}
e_1 &= B + d_1 \\
e_2 &= B + d_2
\end{aligned} \right .
\]
Which means that:
\[
\left \{ \begin{aligned}
d_1 &= e_1 - B\\
d_2 &= e_2 - B
\end{aligned} \right .
\]
Now, if we now `B` to some new coordinate `B'` we can treat that "moving of the coordinate" as a rotation and scaling of the vector for `B` instead. If the new point `B'` is the same distance away from `C` as `B` was, this is a pure rotation, but otherwise the length of the vector has decreased or increased by some factor.
We can use both those values to change where `e1` and `e2` end up, and thus how our curve moulding "feels", by placing new `e1'` and `e2'` where:
\[
\left \{ \begin{aligned}
angle &= atan2(B_y-C_y,B_x-C_x) - atan2(B_y\prime-C.y, B_x\prime-C.x) \\
e_1' &= B' + scale \cdot rotate(d_1, B', angle) \\
e_2' &= B' + scale \cdot rotate(d_2, B', angle)
\end{aligned} \right .
\]
Here, the `rotate()` function rotates a vector (in this case `d1` or `d2`) around some point (in this case, `B'`), by some angle (in this case, the angle by which we rotated our original `B` to become `B'`). So what does _that_ look like?
<graphics-element title="Moulding a cubic Bézier curve" width="825" src="./moulding.js" data-type="cubic" data-alternative="true"></graphics-element>
As you can see, this is both better, and worse, depending on what you're trying to do with the curve, and there are many different ways in which you can try to change `e1` and `e2` such that they behave "as users would expect them to" based on the context in which you're implementing curve moulding. You might want to add reflections when `B'` crosses the baseline, or even some kind of weight-swapping when `B'` crosses the midline (perpendicular to the baseline, at its mid point), and instead of scaling both points with respects to `C`, you might want to scale them to coordinates 1/2rd and 2/3rd along the baseline, etc. etc.
There are too many options to go over here, so: the best behaviour is, of course, the behaviour _you_ think is best, and it might be a lot of work to find that and/or implement that!

View File

@@ -1,217 +0,0 @@
let curve, utils = Bezier.getUtils();
setup() {
setPanelCount(3);
const type = this.type = this.parameters.type ?? `quadratic`;
curve = type === `quadratic` ? Bezier.defaultQuadratic(this) : Bezier.defaultCubic(this);
this.position = {x:0,y:0};
setMovable(curve.points, [this.position]);
}
draw() {
clear();
curve.drawSkeleton();
curve.drawCurve();
curve.drawPoints();
this.drawPosition();
nextPanel();
curve.drawSkeleton(`lightblue`);
curve.drawCurve(`lightblue`);
curve.points.forEach(p => circle(p.x, p.y, 2));
this.drawMark();
nextPanel();
this.drawResult();
}
drawPosition() {
if (!this.position) return;
setColor(`blue`);
let p = this.position.projection;
if (!this.mark) {
p = this.position.projection = curve.project(
this.position.x,
this.position.y
)
this.position.x = p.x;
this.position.y = p.y;
}
circle(p.x, p.y, 3);
}
drawMark() {
if (!this.mark) return;
if (this.type === `quadratic`) {
this.drawQuadraticMark();
} else {
this.drawCubicMark();
}
}
drawQuadraticMark() {
let {B, t} = this.mark;
setFill(`black`);
text(`t = ${t.toFixed(2)}`, B.x + 5, B.y + 10);
let {A, C, S, E} = curve.getABC(t, B);
setColor(`lightblue`);
line(S.x, S.y, E.x, E.y);
line(A.x, A.y, C.x, C.y);
const lbl = [`A`, `B`, `C`];
[A,B,C].forEach((p,i) => {
circle(p.x, p.y, 3);
text(lbl[i], p.x + 10, p.y);
});
if (this.currentPoint) {
let {A,B,C,S,E} = curve.getABC(t, this.position);
setColor(`purple`);
line(A.x, A.y, C.x, C.y);
line(S.x, S.y, A.x, A.y);
line(E.x, E.y, A.x, A.y);
[A,B,C].forEach(p => circle(p.x, p.y, 3));
noFill();
circle(B.x, B.y, 5);
this.moulded = new Bezier(this, [S,A,E]);
}
}
drawCubicMark() {
let {B, t, e1, e2, d1, d2} = this.mark;
let oB = B;
setFill(`black`);
text(`t = ${t.toFixed(2)}`, B.x + 5, B.y + 10);
let {A, C, S, E} = curve.getABC(this.mark.t, B);
let olen = dist(B.x, B.y, C.x, C.y);
setColor(`lightblue`);
line(S.x, S.y, E.x, E.y);
line(A.x, A.y, C.x, C.y);
const lbl = [`A`, `B`, `C`, `e1`, `e2`];
[A,B,C,e1,e2].forEach((p,i) => {
circle(p.x, p.y, 3);
text(lbl[i], p.x + 10, p.y);
});
if (this.currentPoint) {
let {A,B,C,S,E} = curve.getABC(this.mark.t, this.position);
let st1 = { x: B.x + d1.x, y: B.y + d1.y };
let st2 = { x: B.x + d2.x, y: B.y + d2.y };
if (this.parameters.alternative) {
let nlen = dist(B.x, B.y, C.x, C.y);
let scale = nlen/olen;
let angle = atan2(B.y-C.y, B.x-C.x) - atan2(oB.y-C.y, oB.x-C.x);
st1 = {
x: B.x + scale * d1.x * cos(angle) - scale * d1.y * sin(angle),
y: B.y + scale * d1.x * sin(angle) + scale * d1.y * cos(angle)
};
st2 = {
x: B.x + scale * d2.x * cos(angle) - scale * d2.y * sin(angle),
y: B.y + scale * d2.x * sin(angle) + scale * d2.y * cos(angle)
};
}
e1 = st1;
e2 = st2;
setColor(`purple`);
line(A.x, A.y, C.x, C.y);
line(e1.x, e1.y, e2.x, e2.y);
let v1 = {
x: A.x - (A.x - e1.x)/(1-t),
y: A.y - (A.y - e1.y)/(1-t)
};
let v2 = {
x: A.x - (A.x - e2.x)/t,
y: A.y - (A.y - e2.y)/t
};
let C1 = {
x: S.x + (v1.x - S.x) / t,
y: S.y + (v1.y - S.y) / t
};
let C2 = {
x: E.x + (v2.x - E.x) / (1-t),
y: E.y + (v2.y - E.y) / (1-t)
};
[A,B,C,e1,e2,v1,v2,C1,C2].forEach(p => circle(p.x, p.y, 3));
noFill();
circle(B.x, B.y, 5);
this.moulded = new Bezier(this, [S,C1,C2,E]);
}
}
drawResult() {
let last = curve;
if (this.moulded) last = this.moulded;
last.drawSkeleton(`lightblue`);
last.drawCurve(`black`);
last.points.forEach(p => circle(p.x, p.y, 2));
if (this.mark) {
let t = this.mark.t;
let B = last.get(t);
circle(B.x, B.y, 3);
setFill(`black`);
text(`t = ${this.mark.t.toFixed(2)}`, B.x + 5, B.y + 10);
}
}
onMouseDown() {
if (this.currentPoint !== this.position) {
this.mark = false;
this.position.projection = false;
}
else if (this.position.projection) {
let t = this.position.projection.t;
if (this.type === `quadratic`) {
this.mark = {
t, B: this.position.projection,
};
} else {
let struts = curve.getStrutPoints(t);
let m = this.mark = {
t, B: this.position.projection,
e1: struts[7],
e2: struts[8]
};
m.d1 = { x: m.e1.x - m.B.x, y: m.e1.y - m.B.y};
m.d2 = { x: m.e2.x - m.B.x, y: m.e2.y - m.B.y};
}
}
redraw();
}
onMouseMove() {
if (!this.currentPoint && !this.mark) {
this.position.x = this.cursor.x;
this.position.y = this.cursor.y;
}
redraw();
}
onMouseUp() {
this.mark = false;
if (this.moulded) {
curve = this.moulded;
resetMovable(curve.points, [this.position]);
}
redraw();
}

View File

@@ -131,7 +131,7 @@ showCurve(p1, p2, p3, c) {
// Check which length we need to use for our e1-e2 segment, // Check which length we need to use for our e1-e2 segment,
// corrected for whether B is "above" or "below" the baseline: // corrected for whether B is "above" or "below" the baseline:
const angle = atan2(E.y-S.y, E.x-S.x) - atan2(B.y-S.y, B.x-S.x), const angle = ( atan2(E.y-S.y, E.x-S.x) - atan2(B.y-S.y, B.x-S.x) + TAU ) % TAU,
bc = (angle < 0 || angle > PI ? -1 : 1) * dist(S.x, S.y, E.x, E.y)/3, bc = (angle < 0 || angle > PI ? -1 : 1) * dist(S.x, S.y, E.x, E.y)/3,
de1 = t * bc, de1 = t * bc,
de2 = (1-t) * bc; de2 = (1-t) * bc;

View File

@@ -1,8 +1,8 @@
# Creating a curve from three points # Creating a curve from three points
Given the preceding section on curve manipulation, we can also generate quadratic and cubic curves from any three points, although Given the preceding section, you might be wondering if we can use that knowledge to just "create" curves by placing some points and having the computer do the rest, to which the answer is: that's exactly what we can now do!
For quadratic curves, things are pretty easy: technically we need a `t` value in order to compute the ratio function used in computing the ABC coordinates, but we can just as easily approximate one by treating the distance between the start and `B` point, and `B` and end point as a ratio, using For quadratic curves, things are pretty easy. Technically, we'll need a `t` value in order to compute the ratio function used in computing the ABC coordinates, but we can just as easily approximate one by treating the distance between the start and `B` point, and `B` and end point as a ratio, using
\[ \[
\left \{ \begin{aligned} \left \{ \begin{aligned}
@@ -34,7 +34,7 @@ With that covered, we now also know the tangent line to our point `B`, because t
Where `d` is the total length of the line segment from `e1` to `e2`. So how long do we make that? There are again all kinds of approaches we can take, and a simple-but-effective one is to set the length of that segment to "one third the length of the baseline". This forces `e1` and `e2` to always be the "linear curve" distance apart, which means if we place our three points on a line, it will actually _look_ like a line. Nice! The last thing we'll need to do is make sure to flip the sign of `d` depending on which side of the baseline our `B` is located, so we don't up creating a funky curve with a loop in it. To do this, we can use the [atan2](https://en.wikipedia.org/wiki/Atan2) function: Where `d` is the total length of the line segment from `e1` to `e2`. So how long do we make that? There are again all kinds of approaches we can take, and a simple-but-effective one is to set the length of that segment to "one third the length of the baseline". This forces `e1` and `e2` to always be the "linear curve" distance apart, which means if we place our three points on a line, it will actually _look_ like a line. Nice! The last thing we'll need to do is make sure to flip the sign of `d` depending on which side of the baseline our `B` is located, so we don't up creating a funky curve with a loop in it. To do this, we can use the [atan2](https://en.wikipedia.org/wiki/Atan2) function:
\[ \[
\phi = atan2(E_y-S_y, E_x-S_x) - atan2(B_y-S_y, B_x-S_x) \phi = \left ( atan2(E_y-S_y, E_x-S_x) - atan2(B_y-S_y, B_x-S_x) + 2 \pi \right ) \textit{ mod } 2 \pi
\] \]
This angle φ will be between 0 and π if `B` is "above" the baseline (rotating all three points so that the start is on the left and the end is the right), so we can use a relatively straight forward check to make sure we're using the correct sign for our value `d`: This angle φ will be between 0 and π if `B` is "above" the baseline (rotating all three points so that the start is on the left and the end is the right), so we can use a relatively straight forward check to make sure we're using the correct sign for our value `d`:
@@ -46,7 +46,6 @@ This angle φ will be between 0 and π if `B` is "above" the baseline (rotating
\end{aligned} \right . \end{aligned} \right .
\] \]
The result of this approach looks as follows: The result of this approach looks as follows:
<graphics-element title="Finding the cubic e₁ and e₂ given three points " src="./circle.js" data-show-curve="true"></graphics-element> <graphics-element title="Finding the cubic e₁ and e₂ given three points " src="./circle.js" data-show-curve="true"></graphics-element>
@@ -56,3 +55,5 @@ It is important to remember that even though we're using a circular arc to come
<graphics-element title="Fitting a quadratic Bézier curve" src="./cubic.js"></graphics-element> <graphics-element title="Fitting a quadratic Bézier curve" src="./cubic.js"></graphics-element>
That looks perfectly servicable! That looks perfectly servicable!
Of course, we can take this one step further: we can't just "create" curves, we also have (almost!) all the tools available to "mold" curves, where we can reshape a curve by dragging a point on the curve around while leaving the start and end fixed, effectively molding the shape as if it were clay or the like. We'll see the last tool we need to do that in the next section, and then we'll look at implementing curve molding in the section after that, so read on!

View File

@@ -1,6 +1,6 @@
# Projecting a point onto a Bézier curve # Projecting a point onto a Bézier curve
Before we can move on to actual curve moulding, it'll be good if know how to actually be able to find "some point on the curve" that we're trying to click on. After all, if all we have is our Bézier coordinates, that is not in itself enough to figure out which point on the curve our cursor will be closest to. So, how do we project points onto a curve? Before we can move on to actual curve molding, it'll be good if know how to actually be able to find "some point on the curve" that we're trying to click on. After all, if all we have is our Bézier coordinates, that is not in itself enough to figure out which point on the curve our cursor will be closest to. So, how do we project points onto a curve?
If the Bézier curve is of low enough order, we might be able to [work out the maths for how to do this](https://web.archive.org/web/20140713004709/http://jazzros.blogspot.com/2011/03/projecting-point-on-bezier-curve.html), and get a perfect `t` value back, but in general this is an incredibly hard problem and the easiest solution is, really, a numerical approach again. We'll be finding our ideal `t` value using a [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm). First, we do a coarse distance-check based on `t` values associated with the curve's "to draw" coordinates (using a lookup table, or LUT). This is pretty fast: If the Bézier curve is of low enough order, we might be able to [work out the maths for how to do this](https://web.archive.org/web/20140713004709/http://jazzros.blogspot.com/2011/03/projecting-point-on-bezier-curve.html), and get a perfect `t` value back, but in general this is an incredibly hard problem and the easiest solution is, really, a numerical approach again. We'll be finding our ideal `t` value using a [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm). First, we do a coarse distance-check based on `t` values associated with the curve's "to draw" coordinates (using a lookup table, or LUT). This is pretty fast:

View File

@@ -45,14 +45,14 @@ export default [
// curve manipulation // curve manipulation
'abc', 'abc',
'projections',
'moulding',
'pointcurves', 'pointcurves',
'projections',
'molding',
'curvefitting', 'curvefitting',
// A quick foray into Catmull-Rom splines // A quick foray into Catmull-Rom splines
'catmullconv', 'catmullconv',
'catmullmoulding', 'catmullmolding',
// "things made of more than on curve" // "things made of more than on curve"
'polybezier', 'polybezier',

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="96" height="49px" viewBox="0 0 72 37"><defs><symbol overflow="visible" id="a"><path d="M9.219 14.5c0-.125-.281-.39-.39-.422-1.657-.61-2.642-2.281-2.642-3.75V1.36c0-1.75-1.484-3.671-3.03-4.484 1.546-.797 3.03-2.719 3.03-4.484v-8.954c0-1.468.985-3.14 2.641-3.75.11-.046.39-.296.39-.421 0-.172-.343-.453-.5-.453-.046 0-.077.015-.109.03-2.015.735-4.03 2.782-4.03 4.595v8.953c0 1.64-.954 3.468-2.626 4.078-.11.031-.406.281-.406.406 0 .14.297.39.406.422 1.672.61 2.625 2.422 2.625 4.062v8.97c0 1.796 2.016 3.858 4.031 4.593.032 0 .063.015.11.015.156 0 .5-.265.5-.437zm0 0"/></symbol><symbol overflow="visible" id="b"><path d="M6.297-8.203s-.125-.36-.281-.36c-.188 0-1.313.11-1.516.141-.094 0-.375.203-.375.36 0 .14.313.265.484.265.579 0 .407-.047.407.078l-.047.235-.719 2.828h.406c-.218-.438-.765-.89-1.297-.89-1.406 0-3.078 1.89-3.078 3.64 0 1.125.86 2.031 1.781 2.031.25 0 .97-.078 1.422-.61.032.094.657.61 1.22.61.421 0 .827-.297 1.015-.688l.062-.093c.203-.438.297-1.094.297-1.094l.063-.094c0-.11-.297-.25-.329-.25-.125 0-.343.188-.375.344-.203.781-.218 1.36-.703 1.36-.328 0-.171-.188-.171-.422 0-.282.03-.375.078-.579l1.718-6.906zm-2.188 4.11c0 .062-.015.14-.03.202l-.595 2.344c-.062.203 0 .14-.187.344-.531.656-.875.812-1.203.812-.594 0-.578-.53-.578-1 0-.593.328-1.968.593-2.515.375-.703.782-1.11 1.266-1.11.766 0 .734.844.734.922zm0 0"/></symbol><symbol overflow="visible" id="d"><path d="M8.828-4.281c0-.125-.312-.375-.437-.375H.906c-.125 0-.437.25-.437.375 0 .14.312.375.437.375h7.485c.125 0 .437-.235.437-.375zm0 2.328c0-.14-.312-.375-.437-.375H.906c-.125 0-.437.234-.437.375 0 .125.312.36.437.36h7.485c.125 0 .437-.235.437-.36zm0 0"/></symbol><symbol overflow="visible" id="e"><path d="M5.328-1.406c0-.078-.266-.297-.328-.297-.063 0-.234.062-.297.156C3.75-.359 2.593-.39 2.453-.39c-.937 0-.844-.875-.844-1.265 0-.14 0-.5.141-1.11h.484c.344 0 1.235-.015 1.829-.265.843-.36 1.093-1.203 1.093-1.375 0-.516-.656-1.14-1.468-1.14-1.329 0-3.344 1.296-3.344 3.39C.344-.937 1.25.125 2.422.125c1.719 0 2.906-1.39 2.906-1.531zm-.969-3c0 1.25-1.703 1.125-2.203 1.125h-.25c.422-1.688 1.469-1.735 1.782-1.735.562 0 .671.204.671.61zm0 0"/></symbol><symbol overflow="visible" id="f"><path d="M8.828-3.125c0-.125-.312-.36-.437-.36H.906c-.125 0-.437.235-.437.36 0 .14.312.375.437.375h7.485c.125 0 .437-.234.437-.375zm0 0"/></symbol><symbol overflow="visible" id="g"><path d="M9.234-6.64c0-.891-.984-1.782-2.421-1.782H2.796c-.235 0-.547.125-.547.36 0 .14.313.265.531.265l.14-.031s.11.031.313.062c.22.016.125-.093.125.063 0 .047-.015.078-.046.219l-1.61 6.421c-.11.47.063.422-.875.422-.203 0-.531.141-.531.375 0 .141.312.266.531.266h4.266c1.89 0 3.484-1.547 3.484-2.719 0-.86-.875-1.687-2.047-1.812v.265c1.25-.234 2.703-1.25 2.703-2.375zM7.75-6.689c0 1.047-.828 2.047-2.281 2.047H3.937l.72-2.828c.093-.422-.063-.328.452-.328h1.532c1.062 0 1.109.578 1.109 1.11zm-.672 3.844c0 1.188-.86 2.203-2.266 2.203h-1.89c-.125-.015.031.11.031 0 0-.03 0-.046.063-.265l.78-3.203h2.11c1.14 0 1.172.75 1.172 1.265zm0 0"/></symbol><symbol overflow="visible" id="c"><path d="M4.172-.156v-.469H3.64c-.829 0-.625.063-.625-.219v-4.64c0-.235-.235-.391-.563-.391-.578.578-1.203.531-1.75.531v.625c.406 0 1.11-.047 1.219-.11v3.985c0 .281.203.219-.625.219H.766v.64L2.469-.03l1.703.047zm0 0"/></symbol><symbol overflow="visible" id="h"><path d="M4.234-1.844H3.75c-.047.344-.063.735-.172.813-.062.047-.562 0-.687 0H1.812c.563-.5.86-.735 1.36-1.125.625-.5 1.265-1.14 1.265-1.938 0-1-1.093-1.781-2.171-1.781-1.032 0-1.954.89-1.954 1.656 0 .422.579.625.657.625.203 0 .656-.297.656-.61 0-.14-.266-.593-.422-.593.188-.437.547-.469.938-.469.843 0 1.062.5 1.062 1.172 0 .735-.453 1.203-.719 1.5l-2.03 2C.374-.516.311-.39.311 0h3.844l.313-1.844zm0 0"/></symbol></defs><use xlink:href="#a" x="-1.109" y="21.554"/><use xlink:href="#b" x="9.675" y="12.737"/><use xlink:href="#c" x="15.891" y="15.69"/><use xlink:href="#d" x="24.472" y="12.737"/><use xlink:href="#e" x="37.085" y="12.737"/><use xlink:href="#c" x="42.666" y="15.69"/><use xlink:href="#f" x="50.583" y="12.737"/><use xlink:href="#g" x="62.538" y="12.737"/><use xlink:href="#b" x="9.675" y="30.172"/><use xlink:href="#h" x="15.891" y="33.125"/><use xlink:href="#d" x="24.472" y="30.172"/><use xlink:href="#e" x="37.085" y="30.172"/><use xlink:href="#h" x="42.666" y="33.125"/><use xlink:href="#f" x="50.583" y="30.172"/><use xlink:href="#g" x="62.538" y="30.172"/></svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="95px" height="49px" viewBox="0 0 71 37"><defs><symbol overflow="visible" id="a"><path d="M9.219 14.5c0-.125-.281-.39-.39-.422-1.657-.61-2.642-2.281-2.642-3.75V1.36c0-1.75-1.484-3.671-3.03-4.484 1.546-.797 3.03-2.719 3.03-4.484v-8.954c0-1.468.985-3.14 2.641-3.75.11-.046.39-.296.39-.421 0-.172-.343-.453-.5-.453-.046 0-.077.015-.109.03-2.015.735-4.03 2.782-4.03 4.595v8.953c0 1.64-.954 3.468-2.626 4.078-.11.031-.406.281-.406.406 0 .14.297.39.406.422 1.672.61 2.625 2.422 2.625 4.062v8.97c0 1.796 2.016 3.858 4.031 4.593.032 0 .063.015.11.015.156 0 .5-.265.5-.437zm0 0"/></symbol><symbol overflow="visible" id="b"><path d="M5.328-1.406c0-.078-.266-.297-.328-.297-.063 0-.234.062-.297.156C3.75-.359 2.593-.39 2.453-.39c-.937 0-.844-.875-.844-1.265 0-.14 0-.5.141-1.11h.484c.344 0 1.235-.015 1.829-.265.843-.36 1.093-1.203 1.093-1.375 0-.516-.656-1.14-1.468-1.14-1.329 0-3.344 1.296-3.344 3.39C.344-.937 1.25.125 2.422.125c1.719 0 2.906-1.39 2.906-1.531zm-.969-3c0 1.25-1.703 1.125-2.203 1.125h-.25c.422-1.688 1.469-1.735 1.782-1.735.562 0 .671.204.671.61zm0 0"/></symbol><symbol overflow="visible" id="d"><path d="M8.828-4.281c0-.125-.312-.375-.437-.375H.906c-.125 0-.437.25-.437.375 0 .14.312.375.437.375h7.485c.125 0 .437-.235.437-.375zm0 2.328c0-.14-.312-.375-.437-.375H.906c-.125 0-.437.234-.437.375 0 .125.312.36.437.36h7.485c.125 0 .437-.235.437-.36zm0 0"/></symbol><symbol overflow="visible" id="e"><path d="M9.234-6.64c0-.891-.984-1.782-2.421-1.782H2.796c-.235 0-.547.125-.547.36 0 .14.313.265.531.265l.14-.031s.11.031.313.062c.22.016.125-.093.125.063 0 .047-.015.078-.046.219l-1.61 6.421c-.11.47.063.422-.875.422-.203 0-.531.141-.531.375 0 .141.312.266.531.266h4.266c1.89 0 3.484-1.547 3.484-2.719 0-.86-.875-1.687-2.047-1.812v.265c1.25-.234 2.703-1.25 2.703-2.375zM7.75-6.689c0 1.047-.828 2.047-2.281 2.047H3.937l.72-2.828c.093-.422-.063-.328.452-.328h1.532c1.062 0 1.109.578 1.109 1.11zm-.672 3.844c0 1.188-.86 2.203-2.266 2.203h-1.89c-.125-.015.031.11.031 0 0-.03 0-.046.063-.265l.78-3.203h2.11c1.14 0 1.172.75 1.172 1.265zm0 0"/></symbol><symbol overflow="visible" id="f"><path d="M8.828-3.125c0-.125-.312-.36-.437-.36H5.078v-3.374c0-.141-.297-.375-.422-.375-.14 0-.453.234-.453.375v3.375H.906c-.125 0-.437.234-.437.359 0 .14.312.375.437.375h3.297V.625c0 .125.313.36.453.36.125 0 .422-.235.422-.36V-2.75h3.313c.125 0 .437-.234.437-.375zm0 0"/></symbol><symbol overflow="visible" id="g"><path d="M6.297-8.203s-.125-.36-.281-.36c-.188 0-1.313.11-1.516.141-.094 0-.375.203-.375.36 0 .14.313.265.484.265.579 0 .407-.047.407.078l-.047.235-.719 2.828h.406c-.218-.438-.765-.89-1.297-.89-1.406 0-3.078 1.89-3.078 3.64 0 1.125.86 2.031 1.781 2.031.25 0 .97-.078 1.422-.61.032.094.657.61 1.22.61.421 0 .827-.297 1.015-.688l.062-.093c.203-.438.297-1.094.297-1.094l.063-.094c0-.11-.297-.25-.329-.25-.125 0-.343.188-.375.344-.203.781-.218 1.36-.703 1.36-.328 0-.171-.188-.171-.422 0-.282.03-.375.078-.579l1.718-6.906zm-2.188 4.11c0 .062-.015.14-.03.202l-.595 2.344c-.062.203 0 .14-.187.344-.531.656-.875.812-1.203.812-.594 0-.578-.53-.578-1 0-.593.328-1.968.593-2.515.375-.703.782-1.11 1.266-1.11.766 0 .734.844.734.922zm0 0"/></symbol><symbol overflow="visible" id="c"><path d="M4.172-.156v-.469H3.64c-.829 0-.625.063-.625-.219v-4.64c0-.235-.235-.391-.563-.391-.578.578-1.203.531-1.75.531v.625c.406 0 1.11-.047 1.219-.11v3.985c0 .281.203.219-.625.219H.766v.64L2.469-.03l1.703.047zm0 0"/></symbol><symbol overflow="visible" id="h"><path d="M4.234-1.844H3.75c-.047.344-.063.735-.172.813-.062.047-.562 0-.687 0H1.812c.563-.5.86-.735 1.36-1.125.625-.5 1.265-1.14 1.265-1.938 0-1-1.093-1.781-2.171-1.781-1.032 0-1.954.89-1.954 1.656 0 .422.579.625.657.625.203 0 .656-.297.656-.61 0-.14-.266-.593-.422-.593.188-.437.547-.469.938-.469.843 0 1.062.5 1.062 1.172 0 .735-.453 1.203-.719 1.5l-2.03 2C.374-.516.311-.39.311 0h3.844l.313-1.844zm0 0"/></symbol></defs><use xlink:href="#a" x="-1.109" y="21.554"/><use xlink:href="#b" x="9.675" y="12.737"/><use xlink:href="#c" x="15.246" y="15.69"/><use xlink:href="#d" x="23.827" y="12.737"/><use xlink:href="#e" x="36.44" y="12.737"/><use xlink:href="#f" x="48.479" y="12.737"/><use xlink:href="#g" x="60.434" y="12.737"/><use xlink:href="#c" x="66.654" y="15.69"/><use xlink:href="#b" x="9.675" y="30.172"/><use xlink:href="#h" x="15.246" y="33.125"/><use xlink:href="#d" x="23.827" y="30.172"/><use xlink:href="#e" x="36.44" y="30.172"/><use xlink:href="#f" x="48.479" y="30.172"/><use xlink:href="#g" x="60.434" y="30.172"/><use xlink:href="#h" x="66.654" y="33.125"/></svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 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

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -95,12 +95,12 @@
<li><a href="#intersections">Intersections</a></li> <li><a href="#intersections">Intersections</a></li>
<li><a href="#curveintersection">Curve/curve intersection</a></li> <li><a href="#curveintersection">Curve/curve intersection</a></li>
<li><a href="#abc">The projection identity</a></li> <li><a href="#abc">The projection identity</a></li>
<li><a href="#projections">Projecting a point onto a Bézier curve</a></li>
<li><a href="#moulding">Manipulating a curve</a></li>
<li><a href="#pointcurves">Creating a curve from three points</a></li> <li><a href="#pointcurves">Creating a curve from three points</a></li>
<li><a href="#projections">Projecting a point onto a Bézier curve</a></li>
<li><a href="#molding">Molding a curve</a></li>
<li><a href="#curvefitting">Curve fitting</a></li> <li><a href="#curvefitting">Curve fitting</a></li>
<li><a href="#catmullconv">Bézier curves and Catmull-Rom curves</a></li> <li><a href="#catmullconv">Bézier curves and Catmull-Rom curves</a></li>
<li><a href="#catmullmoulding">Creating a Catmull-Rom curve from three points</a></li> <li><a href="#catmullmolding">Creating a Catmull-Rom curve from three points</a></li>
<li><a href="#polybezier">Forming poly-Bézier curves</a></li> <li><a href="#polybezier">Forming poly-Bézier curves</a></li>
<li><a href="#shapes">Boolean shape operations</a></li> <li><a href="#shapes">Boolean shape operations</a></li>
<li><a href="#offsetting">Curve offsetting</a></li> <li><a href="#offsetting">Curve offsetting</a></li>
@@ -197,12 +197,12 @@
<div class="figure"> <div class="figure">
<graphics-element title="A quadratic Bézier curve" width="275" height="275" src="./chapters/introduction/quadratic.js" > <graphics-element title="A quadratic Bézier curve" width="275" height="275" src="./chapters/introduction/quadratic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\introduction\54e9ec0600ac436b0e6f0c6b5005cf03.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\introduction\54e9ec0600ac436b0e6f0c6b5005cf03.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="A cubic Bézier curve" width="275" height="275" src="./chapters/introduction/cubic.js" > <graphics-element title="A cubic Bézier curve" width="275" height="275" src="./chapters/introduction/cubic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -219,7 +219,7 @@
<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> <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" > <graphics-element title="Linear Interpolation leading to Bézier curves" width="825" height="275" src="./chapters/whatis/interpolation.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\whatis\9b3633889c38325c24c19ce18ab94ad6.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\whatis\9b3633889c38325c24c19ce18ab94ad6.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="10" max="90" step="1" value="25" class="slide-control"> <input type="range" min="10" max="90" step="1" value="25" class="slide-control">
@@ -246,7 +246,7 @@
<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> <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" > <graphics-element title="A (partial) circle: x=sin(t), y=cos(t)" width="275" height="275" src="./chapters/explanation/circle.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\explanation\6d2f915735ebdc05e42c0ea7adc85343.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\explanation\6d2f915735ebdc05e42c0ea7adc85343.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="10" step="0.1" value="5" class="slide-control"> <input type="range" min="0" max="10" step="0.1" value="5" class="slide-control">
@@ -328,7 +328,7 @@ function Bezier(3,t):
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3"> <graphics-element title="Quadratic interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -336,7 +336,7 @@ function Bezier(3,t):
<graphics-element title="Cubic interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4"> <graphics-element title="Cubic interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -344,7 +344,7 @@ function Bezier(3,t):
<graphics-element title="15th degree interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15"> <graphics-element title="15th degree interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -353,13 +353,13 @@ function Bezier(3,t):
<p>Also shown is the interpolation function for a 15<sup>th</sup> order Bézier function. As you can see, the start and end point contribute considerably more to the curve's shape than any other point in the control point set.</p> <p>Also shown is the interpolation function for a 15<sup>th</sup> order Bézier function. As you can see, the start and end point contribute considerably more to the curve's shape than any other point in the control point set.</p>
<p>If we want to change the curve, we need to change the weights of each point, effectively changing the interpolations. The way to do this is about as straightforward as possible: just multiply each point with a value that changes its strength. These values are conventionally called "weights", and we can add them to our original Bézier function:</p> <p>If we want to change the curve, we need to change the weights of each point, effectively changing the interpolations. The way to do this is about as straightforward as possible: just multiply each point with a value that changes its strength. These values are conventionally called "weights", and we can add them to our original Bézier function:</p>
<img class="LaTeX SVG" src="./images/chapters/control/14cb9fbbaae9e7d87ae6bef3ea7a782e.svg" width="379px" height="56px" loading="lazy"> <script>console.log("LaTeX for 14cb9fbbaae9e7d87ae6bef3ea7a782e failed!");</script>
<p>That looks complicated, but as it so happens, the "weights" are actually just the coordinate values we want our curve to have: for an <i>n<sup>th</sup></i> order curve, w<sub>0</sub> is our start coordinate, w<sub>n</sub> is our last coordinate, and everything in between is a controlling coordinate. Say we want a cubic curve that starts at (110,150), is controlled by (25,190) and (210,250) and ends at (210,30), we use this Bézier curve:</p> <p>That looks complicated, but as it so happens, the "weights" are actually just the coordinate values we want our curve to have: for an <i>n<sup>th</sup></i> order curve, w<sub>0</sub> is our start coordinate, w<sub>n</sub> is our last coordinate, and everything in between is a controlling coordinate. Say we want a cubic curve that starts at (110,150), is controlled by (25,190) and (210,250) and ends at (210,30), we use this Bézier curve:</p>
<img class="LaTeX SVG" src="./images/chapters/control/c0d4dbc07b8ec7c0a18ea43c8a386935.svg" width="476px" height="40px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/control/c0d4dbc07b8ec7c0a18ea43c8a386935.svg" width="476px" height="40px" loading="lazy">
<p>Which gives us the curve we saw at the top of the article:</p> <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" > <graphics-element title="Our cubic Bézier curve" width="275" height="275" src="./chapters/control/../introduction/cubic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -404,7 +404,7 @@ 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> <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" > <graphics-element title="Our rational cubic Bézier curve" width="275" height="275" src="./chapters/weightcontrol/rational.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br> ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
@@ -462,12 +462,12 @@ function RationalBezier(3,t,w[],r[]):
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic infinite interval Bézier curve" width="275" height="275" src="./chapters/extended/extended.js" data-type="quadratic"> <graphics-element title="Quadratic infinite interval Bézier curve" width="275" height="275" src="./chapters/extended/extended.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\extended\391a61142c56b79260680aefb08cd9c4.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\extended\391a61142c56b79260680aefb08cd9c4.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </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"> <graphics-element title="Cubic infinite interval Bézier curve" width="275" height="275" src="./chapters/extended/extended.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\extended\baeceec6e1587794b8b275a90d5d85e9.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\extended\baeceec6e1587794b8b275a90d5d85e9.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -519,7 +519,7 @@ 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> <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" > <graphics-element title="Traversing a curve using de Casteljau's algorithm" width="275" height="275" src="./chapters/decasteljau/decasteljau.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\decasteljau\715d1d2eecc762d6bc1470954b145018.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\decasteljau\715d1d2eecc762d6bc1470954b145018.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -560,7 +560,7 @@ function RationalBezier(3,t,w[],r[]):
<div class="figure"> <div class="figure">
<graphics-element title="Flattening a quadratic curve" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="quadratic"> <graphics-element title="Flattening a quadratic curve" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\flattening\3deec756c96e53127cd1d615c61043ae.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\flattening\3deec756c96e53127cd1d615c61043ae.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="16" step="1" value="4" class="slide-control"> <input type="range" min="1" max="16" step="1" value="4" class="slide-control">
@@ -568,7 +568,7 @@ function RationalBezier(3,t,w[],r[]):
<graphics-element title="Flattening a cubic curve" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="cubic"> <graphics-element title="Flattening a cubic curve" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\flattening\e2bb7113d5cda2e3fd29bbc54fbe8841.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\flattening\e2bb7113d5cda2e3fd29bbc54fbe8841.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="24" step="1" value="8" class="slide-control"> <input type="range" min="1" max="24" step="1" value="8" class="slide-control">
@@ -604,7 +604,7 @@ 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> <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" > <graphics-element title="Splitting a curve" width="825" height="275" src="./chapters/splitting/splitting.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\splitting\bef2f09698c0d3d2b7c4c031be17ff69.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\splitting\bef2f09698c0d3d2b7c4c031be17ff69.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
@@ -637,7 +637,7 @@ function drawCurve(points[], t):
<section id="matrixsplit"> <section id="matrixsplit">
<h1><a href="#matrixsplit">Splitting curves using matrices</a></h1> <h1><a href="#matrixsplit">Splitting curves using matrices</a></h1>
<p>Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a>, we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic and cubic curves respectively: (we'll reverse the Bézier coefficients vector for legibility)</p> <p>Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a>, we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic and cubic curves respectively: (we'll reverse the Bézier coefficients vector for legibility)</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/77a11d65d7cffc4b84a85c4bec837792.svg" width="263px" height="55px" loading="lazy"> <script>console.log("LaTeX for 77a11d65d7cffc4b84a85c4bec837792 failed!");</script>
<p>and</p> <p>and</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy">
<p>Let's say we want to split the curve at some point <code>t = z</code>, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we separate out the actual "point on the curve" information into a new matrix multiplication:</p> <p>Let's say we want to split the curve at some point <code>t = z</code>, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we separate out the actual "point on the curve" information into a new matrix multiplication:</p>
@@ -727,7 +727,7 @@ 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> <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" > <graphics-element title="A variable-order Bézier curve" width="275" height="275" src="./chapters/reordering/reorder.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<button class="raise">raise</button> <button class="raise">raise</button>
@@ -802,12 +802,12 @@ treated as a sequence of three (elementary) shear operations. When we combine th
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" > <graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" > <graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -836,7 +836,7 @@ 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> <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" > <graphics-element title="Some known and unknown vectors" width="350" height="300" src="./chapters/pointvectors3d/frenet.js" >
<fallback-image> <fallback-image>
<img width="350px" height="300px" src="images\chapters\pointvectors3d\cbadec403b99dec015ab084ee10e1671.png" loading="lazy"> <img width="350px" height="300px" src="images\chapters\pointvectors3d\cbadec403b99dec015ab084ee10e1671.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -910,7 +910,7 @@ 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> <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" > <graphics-element title="Some known and unknown vectors" width="350" height="300" src="./chapters/pointvectors3d/rotation-minimizing.js" >
<fallback-image> <fallback-image>
<img width="350px" height="300px" src="images\chapters\pointvectors3d\9983328e50c2156c124bb1ea4ad5c05b.png" loading="lazy"> <img width="350px" height="300px" src="images\chapters\pointvectors3d\9983328e50c2156c124bb1ea4ad5c05b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -928,13 +928,13 @@ 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> <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"> <graphics-element title="Quadratic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\components\008604bc4c53bd7e0d97c99a67812ad1.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\components\008604bc4c53bd7e0d97c99a67812ad1.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="cubic"> <graphics-element title="Cubic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\components\5214256129e6396e7ac1f1713fa9c88d.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\components\5214256129e6396e7ac1f1713fa9c88d.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1070,14 +1070,14 @@ 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> <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"> <graphics-element title="Quadratic Bézier curve extremities" width="825" height="275" src="./chapters/extremities/extremities.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\extremities\9850eec01924deae7fda4400ce44270d.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\extremities\9850eec01924deae7fda4400ce44270d.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<p>And for cubic curves, that means first and second derivatives, in red and purple respectively:</p> <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"> <graphics-element title="Cubic Bézier curve extremities" width="825" height="275" src="./chapters/extremities/extremities.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\extremities\890406c7bc96904224f8f14940bf3e56.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\extremities\890406c7bc96904224f8f14940bf3e56.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1095,12 +1095,12 @@ function getCubicRoots(pa, pb, pc, pd) {
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="quadratic"> <graphics-element title="Quadratic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\boundingbox\e2c621442e98e2cd20af7efe1cfb041f.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\boundingbox\e2c621442e98e2cd20af7efe1cfb041f.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="cubic"> <graphics-element title="Cubic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\boundingbox\f8989a62ebec9d6f123291c146caab5b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\boundingbox\f8989a62ebec9d6f123291c146caab5b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1123,12 +1123,12 @@ function getCubicRoots(pa, pb, pc, pd) {
<div class="figure"> <div class="figure">
<graphics-element title="Aligning a quadratic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="quadratic"> <graphics-element title="Aligning a quadratic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="550px" height="275px" src="images\chapters\aligning\b3ccd45a72c815388aee6515fe37a486.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\aligning\b3ccd45a72c815388aee6515fe37a486.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Aligning a cubic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="cubic"> <graphics-element title="Aligning a cubic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="550px" height="275px" src="images\chapters\aligning\31655f24b7dd8b8871687b6610d9ac0e.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\aligning\31655f24b7dd8b8871687b6610d9ac0e.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1141,12 +1141,12 @@ function getCubicRoots(pa, pb, pc, pd) {
<div class="figure"> <div class="figure">
<graphics-element title="Aligning a quadratic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="quadratic"> <graphics-element title="Aligning a quadratic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\tightbounds\ccc77ae1f57d7dd7ce4d5397fe1b140b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\tightbounds\ccc77ae1f57d7dd7ce4d5397fe1b140b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Aligning a cubic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="cubic"> <graphics-element title="Aligning a cubic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\tightbounds\419415bee6ffd7598c035c42de09a94f.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\tightbounds\419415bee6ffd7598c035c42de09a94f.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1186,7 +1186,7 @@ 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> <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" > <graphics-element title="Finding cubic Bézier curve inflections" width="275" height="275" src="./chapters/inflections/inflection.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\inflections\9e1ce3975100600d4979370851929b73.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\inflections\9e1ce3975100600d4979370851929b73.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1198,7 +1198,7 @@ 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> <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" > <graphics-element title="The canonical curve map" width="400" height="400" src="./chapters/canonical/canonical.js" >
<fallback-image> <fallback-image>
<img width="400px" height="400px" src="images\chapters\canonical\675217e6df3d75c86503dc2af623e14e.png" loading="lazy"> <img width="400px" height="400px" src="images\chapters\canonical\675217e6df3d75c86503dc2af623e14e.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1264,7 +1264,7 @@ 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> <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" > <graphics-element title="A cubic curve mapped to canonical form" width="800" height="400" src="./chapters/canonical/interactive.js" >
<fallback-image> <fallback-image>
<img width="800px" height="400px" src="images\chapters\canonical\344346f09b6d5d7e3ddf91084ad50d46.png" loading="lazy"> <img width="800px" height="400px" src="images\chapters\canonical\344346f09b6d5d7e3ddf91084ad50d46.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1275,7 +1275,7 @@ 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> <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" > <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> <fallback-image>
<img width="550px" height="275px" src="images\chapters\yforx\dd28d64458d22f4fe89c98568258efcb.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\yforx\dd28d64458d22f4fe89c98568258efcb.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" class="slide-control"> <input type="range" min="0" max="1" step="0.01" class="slide-control">
@@ -1302,7 +1302,7 @@ 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> <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" > <graphics-element title="Finding By(t), by finding t for a given x" width="275" height="275" src="./chapters/yforx/yforx.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\yforx\efcfe9b48ca4e65eef3d4bf3e4c97bc3.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\yforx\efcfe9b48ca4e65eef3d4bf3e4c97bc3.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" class="slide-control"> <input type="range" min="0" max="1" step="0.01" class="slide-control">
@@ -1324,17 +1324,17 @@ y = curve.get(t).y</code></pre>
<div class="figure"> <div class="figure">
<graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10"> <graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24"> <graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99"> <graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1357,7 +1357,7 @@ 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> <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" > <graphics-element title="Arc length for a Bézier curve" width="275" height="275" src="./chapters/arclength/arclength.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\195f9a3c60f8dfe977c6450d21968f69.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\195f9a3c60f8dfe977c6450d21968f69.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1370,7 +1370,7 @@ 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"> <graphics-element title="Approximate quadratic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="24" step="1" value="4" class="slide-control"> <input type="range" min="1" max="24" step="1" value="4" class="slide-control">
@@ -1378,7 +1378,7 @@ y = curve.get(t).y</code></pre>
<graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic"> <graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="32" step="1" value="8" class="slide-control"> <input type="range" min="1" max="32" step="1" value="8" class="slide-control">
@@ -1425,7 +1425,7 @@ 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> <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" > <graphics-element title="Matching curvatures for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\5fcfb0572cae06717506c84768aa568c.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\curvature\5fcfb0572cae06717506c84768aa568c.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1434,7 +1434,7 @@ 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> <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"> <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> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\876d7b2750d7c29068ac6181c3634d25.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\curvature\876d7b2750d7c29068ac6181c3634d25.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control"> <input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control">
@@ -1449,7 +1449,7 @@ 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> <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" > <graphics-element title="The t-for-distance function" width="550" height="275" src="./chapters/tracing/distance-function.js" >
<fallback-image> <fallback-image>
<img width="550px" height="275px" src="images\chapters\tracing\52f815cefe99dabc47ca83d0b97b61fc.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\tracing\52f815cefe99dabc47ca83d0b97b61fc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1457,7 +1457,7 @@ y = curve.get(t).y</code></pre>
<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> <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" > <graphics-element title="Fixed-interval coloring a curve" width="825" height="275" src="./chapters/tracing/tracing.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\tracing\133bf9d02801a3149c9ddb8b313e6797.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\tracing\133bf9d02801a3149c9ddb8b313e6797.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="2" max="24" step="1" value="8" class="slide-control"> <input type="range" min="2" max="24" step="1" value="8" class="slide-control">
@@ -1476,7 +1476,7 @@ 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> <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" > <graphics-element title="Line/line intersections" width="275" height="275" src="./chapters/intersections/line-line.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\intersections\61876a2bd727df377619c5ad34ce86be.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\intersections\61876a2bd727df377619c5ad34ce86be.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1508,12 +1508,12 @@ lli = function(line1, line2):
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="quadratic"> <graphics-element title="Quadratic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\intersections\594c2df534a1736c03cd3a96ff4a9913.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\intersections\594c2df534a1736c03cd3a96ff4a9913.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="cubic"> <graphics-element title="Cubic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\intersections\dc26a6063dadc31d242f1c1c8f38bb5e.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\intersections\dc26a6063dadc31d242f1c1c8f38bb5e.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1540,7 +1540,7 @@ lli = function(line1, line2):
<p>(can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p> <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" > <graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control"> <input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
@@ -1553,7 +1553,7 @@ lli = function(line1, line2):
</section> </section>
<section id="abc"> <section id="abc">
<h1><a href="#abc">The projection identity</a></h1> <h1><a href="#abc">The projection identity</a></h1>
<p>De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mould" a curve, by picking it up at some point, and dragging that point around to change the curve's shape.</p> <p>De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mold" a curve, by picking it up at some point, and dragging that point around to change the curve's shape.</p>
<p>How does that work? Succinctly: we run de Casteljau's algorithm in reverse!</p> <p>How does that work? Succinctly: we run de Casteljau's algorithm in reverse!</p>
<p>In order to run de Casteljau's algorithm in reverse, we need a few basic things: a start and end point, a point on the curve that want to be moving around, which has an associated <em>t</em> value, and a point we've not explicitly talked about before, and as far as I know has no explicit name, but lives one iteration higher in the de Casteljau process then our on-curve point does. I like to call it "A" for reasons that will become obvious.</p> <p>In order to run de Casteljau's algorithm in reverse, we need a few basic things: a start and end point, a point on the curve that want to be moving around, which has an associated <em>t</em> value, and a point we've not explicitly talked about before, and as far as I know has no explicit name, but lives one iteration higher in the de Casteljau process then our on-curve point does. I like to call it "A" for reasons that will become obvious.</p>
<p>So let's use graphics instead of text to see where this "A" is, because text only gets us so far: move the sliders for the following graphics to see what, given specific <code>t</code> value, our <code>A</code> coordinate is. As well as some other coordinates, which taken together let us derive a value that the graphics call "ratio": if you move the curve's points around, A, B, and C will move, what happens to that value?</p> <p>So let's use graphics instead of text to see where this "A" is, because text only gets us so far: move the sliders for the following graphics to see what, given specific <code>t</code> value, our <code>A</code> coordinate is. As well as some other coordinates, which taken together let us derive a value that the graphics call "ratio": if you move the curve's points around, A, B, and C will move, what happens to that value?</p>
@@ -1561,14 +1561,14 @@ 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"> <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> <fallback-image>
<img width="275px" height="275px" src="images\chapters\abc\6e40975c21e70b73954a4dce02b9ba75.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\abc\7a69dd4350ddda5701712e1d3b46b863.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
</graphics-element> </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"> <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> <fallback-image>
<img width="275px" height="275px" src="images\chapters\abc\d744a4955a3ff4e2d85760887ea923d4.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\abc\eeec7cf16fb22c666e0143a3a030731f.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
@@ -1581,6 +1581,8 @@ lli = function(line1, line2):
<li>a point at the tip of the curve construction's "hat": let's call that <code>A</code>, as well as</li> <li>a point at the tip of the curve construction's "hat": let's call that <code>A</code>, as well as</li>
<li>our on-curve point give our chosen <code>t</code> value: let's call that <code>B</code>, and finally,</li> <li>our on-curve point give our chosen <code>t</code> value: let's call that <code>B</code>, and finally,</li>
<li>a point that we get by projecting A, through B, onto the line between the curve's start and end points: let's call that <code>C</code>.</li> <li>a point that we get by projecting A, through B, onto the line between the curve's start and end points: let's call that <code>C</code>.</li>
<li>for both qudratic and cubic curves, two points <code>e1</code> and <code>e2</code>, which represent the single-to-last step in de Casteljau's algorithm: in the last step, we find <code>B</code> at <code>(1-t) * e1 + t * e2</code>.</li>
<li>for cubic curves, also the points <code>v1</code> and <code>v2</code>, which together with <code>A</code> represent the first step in de Casteljau's algorithm: in the next step, we find <code>e1</code> and <code>e2</code>.</li>
</ol> </ol>
<p>These three values A, B, and C allow us to derive an important identity formula for quadratic and cubic Bézier curves: for any point on the curve with some <code>t</code> value, the ratio of distances from A to B and B to C is fixed: if some <code>t</code> value sets up a C that is 20% away from the start and 80% away from the end, then <em>it doesn't matter where the start, end, or control points are</em>; for that <code>t</code> value, <code>C</code> will <em>always</em> lie at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic and then move all the other points around: if you only move the control points, start and end won't move, and so neither will C, and if you move either start or end point, C will move but its relative position will not change.</p> <p>These three values A, B, and C allow us to derive an important identity formula for quadratic and cubic Bézier curves: for any point on the curve with some <code>t</code> value, the ratio of distances from A to B and B to C is fixed: if some <code>t</code> value sets up a C that is 20% away from the start and 80% away from the end, then <em>it doesn't matter where the start, end, or control points are</em>; for that <code>t</code> value, <code>C</code> will <em>always</em> lie at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic and then move all the other points around: if you only move the control points, start and end won't move, and so neither will C, and if you move either start or end point, C will move but its relative position will not change.</p>
<p>So, how can we compute <code>C</code>? We start with our observation that <code>C</code> always lies somewhere between the start and ends points, so logically <code>C</code> will have a function that interpolates between those two coordinates:</p> <p>So, how can we compute <code>C</code>? We start with our observation that <code>C</code> always lies somewhere between the start and ends points, so logically <code>C</code> will have a function that interpolates between those two coordinates:</p>
@@ -1598,12 +1600,60 @@ lli = function(line1, line2):
<img class="LaTeX SVG" src="./images/chapters/abc/b4987e9b77b0df604238b88596c5f7c3.svg" width="228px" height="41px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/abc/b4987e9b77b0df604238b88596c5f7c3.svg" width="228px" height="41px" loading="lazy">
<p>Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a <code>t</code> value, we can <em>contruct</em> curves: we can compute <code>C</code> using the start and end points, and our <code>u(t)</code> function, and once we have <code>C</code>, we can use our on-curve point (<code>B</code>) and the <code>ratio(t)</code> function to find <code>A</code>:</p> <p>Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a <code>t</code> value, we can <em>contruct</em> curves: we can compute <code>C</code> using the start and end points, and our <code>u(t)</code> function, and once we have <code>C</code>, we can use our on-curve point (<code>B</code>) and the <code>ratio(t)</code> function to find <code>A</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/abc/12aaf0d7fd20b3c551a0ec76b18bd7d2.svg" width="231px" height="39px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/abc/12aaf0d7fd20b3c551a0ec76b18bd7d2.svg" width="231px" height="39px" loading="lazy">
<p>So: if we have a curve's start and end point, then for any <code>t</code> value, we implicitly know all the ABC values, which gives us the necessary information to reconstruct a curve's "de Casteljau skeleton". Which means that we can now do several things: we can "fit" curves using only three points, which means we can also "mould" curves by moving an on-curve point but leaving its start and end point, and then reconstructing the curve based on where we moved the on-curve point to. These are very useful things, and we'll look at both in the next sections.</p> <p>With <code>A</code> found, finding <code>e1</code> and <code>e2</code> for quadratic curves is a matter of running the linear interpolation with <code>t</code> between start and <code>A</code> to yield <code>e1</code>, and between <code>A</code> and end to yield <code>e2</code>. For cubic curves, there is no single pair of points that can act as <code>e1</code> and <code>e2</code>: as long as the distance ratio between <code>e1</code> to <code>B</code> and <code>B</code> to <code>e2</code> is the Bézier ratio <code>(1-t):t</code>, we can reverse engineer <code>v1</code> and <code>v2</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/abc/bc245327e0b011712168bad1c48dfec4.svg" width="139px" height="75px" loading="lazy">
<p>And then reverse engineer the curve's control control points:</p>
<img class="LaTeX SVG" src="./images/chapters/abc/3c696e0364d61b1391695342707d6ccc.svg" width="177px" height="72px" loading="lazy">
<p>So: if we have a curve's start and end point, then for any <code>t</code> value we implicitly know all the ABC values, which (combined with an educated guess on appropriate <code>e1</code> and <code>e2</code> coordinates for cubic curves) gives us the necessary information to reconstruct a curve's "de Casteljau skeleton". Which means that we can now do several things: we can "fit" curves using only three points, which means we can also "mold" curves by moving an on-curve point but leaving its start and end point, and then reconstructing the curve based on where we moved the on-curve point to. These are very useful things, and we'll look at both in the next few sections.</p>
</section>
<section id="pointcurves">
<h1><a href="#pointcurves">Creating a curve from three points</a></h1>
<p>Given the preceding section, you might be wondering if we can use that knowledge to just "create" curves by placing some points and having the computer do the rest, to which the answer is: that's exactly what we can now do!</p>
<p>For quadratic curves, things are pretty easy. Technically, we'll need a <code>t</code> value in order to compute the ratio function used in computing the ABC coordinates, but we can just as easily approximate one by treating the distance between the start and <code>B</code> point, and <code>B</code> and end point as a ratio, using</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/3c7516c16a5dea95df741f4263cecd1c.svg" width="132px" height="85px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\067a3df30e32708fc0d13f8eb78c0b05.png">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\173ea31517a72a927d561f121f0677db.png">
Scripts are disabled. Showing fallback image.
</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>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/55d4f7ed095dfea8f9772208abc83b51.svg" width="147px" height="49px" loading="lazy">
<p>Where <code>d</code> is the total length of the line segment from <code>e1</code> to <code>e2</code>. So how long do we make that? There are again all kinds of approaches we can take, and a simple-but-effective one is to set the length of that segment to "one third the length of the baseline". This forces <code>e1</code> and <code>e2</code> to always be the "linear curve" distance apart, which means if we place our three points on a line, it will actually <em>look</em> like a line. Nice! The last thing we'll need to do is make sure to flip the sign of <code>d</code> depending on which side of the baseline our <code>B</code> is located, so we don't up creating a funky curve with a loop in it. To do this, we can use the <a href="https://en.wikipedia.org/wiki/Atan2">atan2</a> function:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/9203537b7dca98ebb2d7017c76100fde.svg" width="507px" height="21px" loading="lazy">
<p>This angle φ will be between 0 and π if <code>B</code> is "above" the baseline (rotating all three points so that the start is on the left and the end is the right), so we can use a relatively straight forward check to make sure we're using the correct sign for our value <code>d</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/6f0e2b6494d7dae2ea79a46a499d7ed4.svg" width="183px" height="49px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\8d045d352f5017b65e60620b92d7ae29.png">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\eab6ea46fa93030e03ec0ef7deb571dc.png">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>That looks perfectly servicable!</p>
<p>Of course, we can take this one step further: we can't just "create" curves, we also have (almost!) all the tools available to "mold" curves, where we can reshape a curve by dragging a point on the curve around while leaving the start and end fixed, effectively molding the shape as if it were clay or the like. We'll see the last tool we need to do that in the next section, and then we'll look at implementing curve molding in the section after that, so read on!</p>
</section> </section>
<section id="projections"> <section id="projections">
<h1><a href="#projections">Projecting a point onto a Bézier curve</a></h1> <h1><a href="#projections">Projecting a point onto a Bézier curve</a></h1>
<p>Before we can move on to actual curve moulding, it'll be good if know how to actually be able to find "some point on the curve" that we're trying to click on. After all, if all we have is our Bézier coordinates, that is not in itself enough to figure out which point on the curve our cursor will be closest to. So, how do we project points onto a curve?</p> <p>Before we can move on to actual curve molding, it'll be good if know how to actually be able to find "some point on the curve" that we're trying to click on. After all, if all we have is our Bézier coordinates, that is not in itself enough to figure out which point on the curve our cursor will be closest to. So, how do we project points onto a curve?</p>
<p>If the Bézier curve is of low enough order, we might be able to <a href="https://web.archive.org/web/20140713004709/http://jazzros.blogspot.com/2011/03/projecting-point-on-bezier-curve.html">work out the maths for how to do this</a>, and get a perfect <code>t</code> value back, but in general this is an incredibly hard problem and the easiest solution is, really, a numerical approach again. We'll be finding our ideal <code>t</code> value using a <a href="https://en.wikipedia.org/wiki/Binary_search_algorithm">binary search</a>. First, we do a coarse distance-check based on <code>t</code> values associated with the curve's "to draw" coordinates (using a lookup table, or LUT). This is pretty fast:</p> <p>If the Bézier curve is of low enough order, we might be able to <a href="https://web.archive.org/web/20140713004709/http://jazzros.blogspot.com/2011/03/projecting-point-on-bezier-curve.html">work out the maths for how to do this</a>, and get a perfect <code>t</code> value back, but in general this is an incredibly hard problem and the easiest solution is, really, a numerical approach again. We'll be finding our ideal <code>t</code> value using a <a href="https://en.wikipedia.org/wiki/Binary_search_algorithm">binary search</a>. First, we do a coarse distance-check based on <code>t</code> values associated with the curve's "to draw" coordinates (using a lookup table, or LUT). This is pretty fast:</p>
<pre><code>p = some point to project onto the curve <pre><code>p = some point to project onto the curve
d = some initially huge value d = some initially huge value
@@ -1622,104 +1672,45 @@ 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> <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" > <graphics-element title="Projecting a point onto a Bézier curve" width="320" height="320" src="./chapters/projections/project.js" >
<fallback-image> <fallback-image>
<img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png" loading="lazy"> <img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</section> </section>
<section id="moulding"> <section id="molding">
<h1><a href="#moulding">Manipulating a curve</a></h1> <h1><a href="#molding">Molding a curve</a></h1>
<p>Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the <em>t</em>-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.</p> <p>Armed with knowledge of the "ABC" relation, point-on-curve projection, and guestimating reasonable looking helper values for cubic curve construction, we can finally cover curve molding: updating a curve's shape interactively, by dragging points on the curve around.</p>
<graphics-element title="Moulding a quadratic Bézier curve" width="825" height="275" src="./chapters/moulding/moulding.js" data-type="quadratic"> <p>For quadratic curve, this is a really simple trick: we project our cursor onto the curve, which gives us a <code>t</code> value and initial <code>B</code> coordinate. We don't even need the latter: with our <code>t</code> value and "whever the cursor is" as target <code>B</code>, we can compute the associated <code>C</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/molding/079d318ad693b6b17413a91f5de06be8.svg" width="256px" height="21px" loading="lazy">
<p>And then the associated <code>A</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/molding/82a99caec5f84fb26dce28277377c041.svg" width="244px" height="40px" loading="lazy">
<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> <fallback-image>
<img width="825px" height="275px" src="images\chapters\moulding\adc7e0785dade356b62fadcd903e73d9.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\molding\6b91671c2962530b863ae0da5789a9cc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<p>Click-dragging a point on the curve shows what we're using to compute the new coordinates: while dragging you will see the original point <code>B</code> and its corresponding <i>t</i>-value, and the original points <code>A</code> and <code>C</code> for that <i>t</i>-value, in light coloring, as well as the new <code>A'</code>, <code>B'</code>, and <code>C'</code> (although of course the <code>C</code> coordinates are the same ones, because that's the defining feature of point <code>C</code>) based on where you're dragging point <code>B</code> to, in purple.</p> <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>Since we know the new point <code>B'</code>, and the "new" point <code>C'</code> as well as the <code>t</code> value, we know our new point A' has to be:</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>
<img class="LaTeX SVG" src="./images/chapters/moulding/7159e1e6eeab38c5aba225fe7553dbe6.svg" width="247px" height="39px" loading="lazy"> <graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic">
<p>For quadratic curves, this means we're done, since the new point <code>A'</code> is equivalent to the new quadratic control point.</p>
<p>For cubic curves, we need to do a little more work, because while computing a new <code>A'</code> is exactly the same as before, we're not quite done once we've done so. For cubic curves, <code>B</code> has not just an associated <code>t</code> value, but also two associated "side" values. Let's revisit the graphic from the chapter on de Casteljau's algorithm, to see what we mean:</p>
<graphics-element title="The information necessary to manipulate cubic curves" width="275" height="275" src="./chapters/moulding/decasteljau.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\moulding\fbc7d78dd768eec4314eb4b5320f2ce5.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\molding\522f1edd37163772b81acb86d3a4f423.png">
Scripts are disabled. Showing fallback image.
</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>
<img width="825px" height="275px" src="images\chapters\molding\ea1656c068a60631135ad499e8a29453.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="10" max="200" step="1" value="100" class="slide-control">
</graphics-element> </graphics-element>
<p>In addition to the <code>A</code>, <code>B</code>, and <code>C</code> values, we also see the points <code>e1</code> and <code>e2</code>, without which constructing our de Casteljau "strut lines" becomes very difficult indeed; as well as the points <code>v1</code> and <code>v2</code>, which we can construct when we know our ABC values enriched with <code>e1</code> and <code>e2</code>:</p> <p>The slide controls the "falloff distance" relative to where the original point on the curve is, so that as we drag our point around, it interpolates with a bias towards "preserving <code>t</code>/<code>e1</code>/<code>e2</code>" closer to the original point, and bias towards "idealised" form the further away we move our point, with anything that's further than our falloff distance simply <em>being</em> the idealised curve. We don't even try to interpolate at that point.</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/bc245327e0b011712168bad1c48dfec4.svg" width="139px" height="75px" loading="lazy"> <p>A more advanced way to try to smooth things out is to implement <em>continuous</em> molding, where we constantly update the curve as we move around, and constantly change what our <code>B</code> point is, based on constantly projecting the cursor on the curve <em>as we're updating it</em> - this is, you won't be surprised to learn, tricky, and beyond the scope of this section: interpolation (with a reasonable distance) will do for now!</p>
<p>After which computing the new control points is straight-forward:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/3c696e0364d61b1391695342707d6ccc.svg" width="177px" height="72px" loading="lazy">
<p>So let's put that into practice:</p>
<graphics-element title="Moulding a cubic Bézier curve" width="825" height="275" src="./chapters/moulding/moulding.js" data-type="cubic">
<fallback-image>
<img width="825px" height="275px" src="images\chapters\moulding\b9928928e1f622e66b59e4a59cfac925.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>So that looks pretty good, but you may not like having <code>e1</code> and <code>e2</code> stay the same distances away from <code>B'</code> while moving the point around, and want to rearrange those to lead to "cleaner looking" curve manipulation. Unfortunately, there are so many differen ways in which we can do this that figuring out "good looking" alternatives, given what the curve is being manipulated for, could be an entire book on its own... so we're only going to look at one way that you might effect alternative <code>e1</code> and <code>e2</code> points, based on the idea of rotating a vector.</p>
<p>If we treat point <code>B</code> as a "a vector originating at <code>C</code>" then we can treat the points <code>e1</code> and <code>e2</code> as offets (let's call these <code>d1</code> and <code>d2</code>) of that vector, where:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/65c9a6f8a210d41b18f20e4da1ba1403.svg" width="95px" height="49px" loading="lazy">
<p>Which means that:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/28e90ffa101453e4c030174d36d185a5.svg" width="96px" height="49px" loading="lazy">
<p>Now, if we now <code>B</code> to some new coordinate <code>B'</code> we can treat that "moving of the coordinate" as a rotation and scaling of the vector for <code>B</code> instead. If the new point <code>B'</code> is the same distance away from <code>C</code> as <code>B</code> was, this is a pure rotation, but otherwise the length of the vector has decreased or increased by some factor.</p>
<p>We can use both those values to change where <code>e1</code> and <code>e2</code> end up, and thus how our curve moulding "feels", by placing new <code>e1'</code> and <code>e2'</code> where:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/54e423d1eb61157abd3acffc5271c3ac.svg" width="465px" height="67px" loading="lazy">
<p>Here, the <code>rotate()</code> function rotates a vector (in this case <code>d1</code> or <code>d2</code>) around some point (in this case, <code>B'</code>), by some angle (in this case, the angle by which we rotated our original <code>B</code> to become <code>B'</code>). So what does <em>that</em> look like?</p>
<graphics-element title="Moulding a cubic Bézier curve" width="825" height="275" src="./chapters/moulding/moulding.js" data-type="cubic" data-alternative="true">
<fallback-image>
<img width="825px" height="275px" src="images\chapters\moulding\db817c3899e954da6c882e4699a49353.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>As you can see, this is both better, and worse, depending on what you're trying to do with the curve, and there are many different ways in which you can try to change <code>e1</code> and <code>e2</code> such that they behave "as users would expect them to" based on the context in which you're implementing curve moulding. You might want to add reflections when <code>B'</code> crosses the baseline, or even some kind of weight-swapping when <code>B'</code> crosses the midline (perpendicular to the baseline, at its mid point), and instead of scaling both points with respects to <code>C</code>, you might want to scale them to coordinates 1/2rd and 2/3rd along the baseline, etc. etc.</p>
<p>There are too many options to go over here, so: the best behaviour is, of course, the behaviour <em>you</em> think is best, and it might be a lot of work to find that and/or implement that!</p>
</section>
<section id="pointcurves">
<h1><a href="#pointcurves">Creating a curve from three points</a></h1>
<p>Given the preceding section on curve manipulation, we can also generate quadratic and cubic curves from any three points, although</p>
<p>For quadratic curves, things are pretty easy: technically we need a <code>t</code> value in order to compute the ratio function used in computing the ABC coordinates, but we can just as easily approximate one by treating the distance between the start and <code>B</code> point, and <code>B</code> and end point as a ratio, using</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/3c7516c16a5dea95df741f4263cecd1c.svg" width="132px" height="85px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\067a3df30e32708fc0d13f8eb78c0b05.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\93ee12566f93f241ad0970acd5505367.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</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>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/55d4f7ed095dfea8f9772208abc83b51.svg" width="147px" height="49px" loading="lazy">
<p>Where <code>d</code> is the total length of the line segment from <code>e1</code> to <code>e2</code>. So how long do we make that? There are again all kinds of approaches we can take, and a simple-but-effective one is to set the length of that segment to "one third the length of the baseline". This forces <code>e1</code> and <code>e2</code> to always be the "linear curve" distance apart, which means if we place our three points on a line, it will actually <em>look</em> like a line. Nice! The last thing we'll need to do is make sure to flip the sign of <code>d</code> depending on which side of the baseline our <code>B</code> is located, so we don't up creating a funky curve with a loop in it. To do this, we can use the <a href="https://en.wikipedia.org/wiki/Atan2">atan2</a> function:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/b4464f73fb2f79027d8e971fc66813f6.svg" width="393px" height="19px" loading="lazy">
<p>This angle φ will be between 0 and π if <code>B</code> is "above" the baseline (rotating all three points so that the start is on the left and the end is the right), so we can use a relatively straight forward check to make sure we're using the correct sign for our value <code>d</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/6f0e2b6494d7dae2ea79a46a499d7ed4.svg" width="183px" height="49px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\8b0e8e79ba09916a9af27b33f8a1919f.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\eab6ea46fa93030e03ec0ef7deb571dc.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>That looks perfectly servicable!</p>
</section> </section>
<section id="curvefitting"> <section id="curvefitting">
@@ -1886,8 +1877,8 @@ for (coordinate, index) in LUT:
<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="284px" height="77px" loading="lazy">
</section> </section>
<section id="catmullmoulding"> <section id="catmullmolding">
<h1><a href="#catmullmoulding">Creating a Catmull-Rom curve from three points</a></h1> <h1><a href="#catmullmolding">Creating a Catmull-Rom curve from three points</a></h1>
<p>Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead?</p> <p>Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead?</p>
<p>As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve.</p> <p>As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve.</p>
<p>Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> points (and that's without double-counting points that are shared by consecutive cubic curves).</p> <p>Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> points (and that's without double-counting points that are shared by consecutive cubic curves).</p>
@@ -2051,7 +2042,7 @@ for (coordinate, index) in LUT:
<p>which we can then substitute in the expression for <em>a</em>:</p> <p>which we can then substitute in the expression for <em>a</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy">
<p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p> <p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/fe32474b4616ee9478e1308308f1b6bf.svg" width="188px" height="32px" loading="lazy"> <script>console.log("LaTeX for fe32474b4616ee9478e1308308f1b6bf failed!");</script>
<p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p> <p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy">
<p>Which, worked out for the x and y components, gives:</p> <p>Which, worked out for the x and y components, gives:</p>

View File

@@ -95,12 +95,12 @@
<li><a href="ja-JP/index.html#intersections">Intersections</a></li> <li><a href="ja-JP/index.html#intersections">Intersections</a></li>
<li><a href="ja-JP/index.html#curveintersection">Curve/curve intersection</a></li> <li><a href="ja-JP/index.html#curveintersection">Curve/curve intersection</a></li>
<li><a href="ja-JP/index.html#abc">The projection identity</a></li> <li><a href="ja-JP/index.html#abc">The projection identity</a></li>
<li><a href="ja-JP/index.html#projections">Projecting a point onto a Bézier curve</a></li>
<li><a href="ja-JP/index.html#moulding">Manipulating a curve</a></li>
<li><a href="ja-JP/index.html#pointcurves">Creating a curve from three points</a></li> <li><a href="ja-JP/index.html#pointcurves">Creating a curve from three points</a></li>
<li><a href="ja-JP/index.html#projections">Projecting a point onto a Bézier curve</a></li>
<li><a href="ja-JP/index.html#molding">Molding a curve</a></li>
<li><a href="ja-JP/index.html#curvefitting">Curve fitting</a></li> <li><a href="ja-JP/index.html#curvefitting">Curve fitting</a></li>
<li><a href="ja-JP/index.html#catmullconv">Bézier curves and Catmull-Rom curves</a></li> <li><a href="ja-JP/index.html#catmullconv">Bézier curves and Catmull-Rom curves</a></li>
<li><a href="ja-JP/index.html#catmullmoulding">Creating a Catmull-Rom curve from three points</a></li> <li><a href="ja-JP/index.html#catmullmolding">Creating a Catmull-Rom curve from three points</a></li>
<li><a href="ja-JP/index.html#polybezier">Forming poly-Bézier curves</a></li> <li><a href="ja-JP/index.html#polybezier">Forming poly-Bézier curves</a></li>
<li><a href="ja-JP/index.html#shapes">Boolean shape operations</a></li> <li><a href="ja-JP/index.html#shapes">Boolean shape operations</a></li>
<li><a href="ja-JP/index.html#offsetting">Curve offsetting</a></li> <li><a href="ja-JP/index.html#offsetting">Curve offsetting</a></li>
@@ -200,12 +200,12 @@
<div class="figure"> <div class="figure">
<graphics-element title="2次のベジエ曲線" width="275" height="275" src="./chapters/introduction/quadratic.js" > <graphics-element title="2次のベジエ曲線" width="275" height="275" src="./chapters/introduction/quadratic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\introduction\54e9ec0600ac436b0e6f0c6b5005cf03.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\introduction\54e9ec0600ac436b0e6f0c6b5005cf03.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="3次のベジエ曲線" width="275" height="275" src="./chapters/introduction/cubic.js" > <graphics-element title="3次のベジエ曲線" width="275" height="275" src="./chapters/introduction/cubic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -222,7 +222,7 @@
<p>では、実際に見てみましょう。下の図はインタラクティブになっています。上下キーで補間の比率が増減しますので、どうなるか確かめてみましょう。最初に3点があり、それを結んで2本の直線が引かれています。この直線の上でそれぞれ線形補間を行うと、2つの点が得られます。この2点の間でさらに線形補間を行うと、1つの点を得ることができます。そして、あらゆる比率に対して同様に点を求め、それをすべて集めると、このようにベジエ曲線ができるのです。</p> <p>では、実際に見てみましょう。下の図はインタラクティブになっています。上下キーで補間の比率が増減しますので、どうなるか確かめてみましょう。最初に3点があり、それを結んで2本の直線が引かれています。この直線の上でそれぞれ線形補間を行うと、2つの点が得られます。この2点の間でさらに線形補間を行うと、1つの点を得ることができます。そして、あらゆる比率に対して同様に点を求め、それをすべて集めると、このようにベジエ曲線ができるのです。</p>
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" height="275" src="./chapters/whatis/interpolation.js" > <graphics-element title="Linear Interpolation leading to Bézier curves" width="825" height="275" src="./chapters/whatis/interpolation.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\whatis\9b3633889c38325c24c19ce18ab94ad6.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\whatis\9b3633889c38325c24c19ce18ab94ad6.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="10" max="90" step="1" value="25" class="slide-control"> <input type="range" min="10" max="90" step="1" value="25" class="slide-control">
@@ -248,7 +248,7 @@
<p>というわけで、普通の関数では<i>y</i>座標を<i>x</i>座標によって定義しますが、パラメトリック曲線ではそうではなく、座標の値を「制御」変数と結びつけます。<i>t</i>の値を変化させるたびに<strong>2つ</strong>の値が変化するので、これをグラフ上の座標 (<i>x</i>,<i>y</i>)として使うことができます。例えば、先ほどの関数の組は円周上の点を生成します。負の無限大から正の無限大へと<i>t</i>を動かすと、得られる座標(<i>x</i>,<i>y</i>)は常に中心(0,0)・半径1の円の上に乗ります。<i>t</i>を0から5まで変化させてプロットした場合は、このようになります。</p> <p>というわけで、普通の関数では<i>y</i>座標を<i>x</i>座標によって定義しますが、パラメトリック曲線ではそうではなく、座標の値を「制御」変数と結びつけます。<i>t</i>の値を変化させるたびに<strong>2つ</strong>の値が変化するので、これをグラフ上の座標 (<i>x</i>,<i>y</i>)として使うことができます。例えば、先ほどの関数の組は円周上の点を生成します。負の無限大から正の無限大へと<i>t</i>を動かすと、得られる座標(<i>x</i>,<i>y</i>)は常に中心(0,0)・半径1の円の上に乗ります。<i>t</i>を0から5まで変化させてプロットした場合は、このようになります。</p>
<graphics-element title="(部分)円 x=sin(t), y=cos(t)" width="275" height="275" src="./chapters/explanation/circle.js" > <graphics-element title="(部分)円 x=sin(t), y=cos(t)" width="275" height="275" src="./chapters/explanation/circle.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\explanation\6d2f915735ebdc05e42c0ea7adc85343.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\explanation\6d2f915735ebdc05e42c0ea7adc85343.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="10" step="0.1" value="5" class="slide-control"> <input type="range" min="0" max="10" step="0.1" value="5" class="slide-control">
@@ -330,7 +330,7 @@ function Bezier(3,t):
<div class="figure"> <div class="figure">
<graphics-element title="2次の補間" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3"> <graphics-element title="2次の補間" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -338,7 +338,7 @@ function Bezier(3,t):
<graphics-element title="3次の補間" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4"> <graphics-element title="3次の補間" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -346,7 +346,7 @@ function Bezier(3,t):
<graphics-element title="15次の補間" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15"> <graphics-element title="15次の補間" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -402,7 +402,7 @@ 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> <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" > <graphics-element title="Our rational cubic Bézier curve" width="275" height="275" src="./chapters/weightcontrol/rational.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br> ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
@@ -460,12 +460,12 @@ function RationalBezier(3,t,w[],r[]):
<div class="figure"> <div class="figure">
<graphics-element title="無限区間の2次ベジエ曲線" width="275" height="275" src="./chapters/extended/extended.js" data-type="quadratic"> <graphics-element title="無限区間の2次ベジエ曲線" width="275" height="275" src="./chapters/extended/extended.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\extended\391a61142c56b79260680aefb08cd9c4.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\extended\391a61142c56b79260680aefb08cd9c4.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="無限区間の3次ベジエ曲線" width="275" height="275" src="./chapters/extended/extended.js" data-type="cubic"> <graphics-element title="無限区間の3次ベジエ曲線" width="275" height="275" src="./chapters/extended/extended.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\extended\baeceec6e1587794b8b275a90d5d85e9.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\extended\baeceec6e1587794b8b275a90d5d85e9.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -517,7 +517,7 @@ function RationalBezier(3,t,w[],r[]):
<p>下の図にマウスを乗せると、この様子を実際に見ることができます。ド・カステリョのアルゴリズムによって曲線上の点を明示的に計算していますが、マウスを動かすと求める点が変わります。マウスカーソルを左から右へ(もちろん、右から左へでも)動かせば、このアルゴリズムによって曲線が生成される様子がわかります。</p> <p>下の図にマウスを乗せると、この様子を実際に見ることができます。ド・カステリョのアルゴリズムによって曲線上の点を明示的に計算していますが、マウスを動かすと求める点が変わります。マウスカーソルを左から右へ(もちろん、右から左へでも)動かせば、このアルゴリズムによって曲線が生成される様子がわかります。</p>
<graphics-element title="ド・カステリョのアルゴリズムで曲線をたどる" width="275" height="275" src="./chapters/decasteljau/decasteljau.js" > <graphics-element title="ド・カステリョのアルゴリズムで曲線をたどる" width="275" height="275" src="./chapters/decasteljau/decasteljau.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\decasteljau\715d1d2eecc762d6bc1470954b145018.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\decasteljau\715d1d2eecc762d6bc1470954b145018.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -557,7 +557,7 @@ function RationalBezier(3,t,w[],r[]):
<div class="figure"> <div class="figure">
<graphics-element title="2次ベジエ曲線の平坦化" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="quadratic"> <graphics-element title="2次ベジエ曲線の平坦化" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\flattening\3deec756c96e53127cd1d615c61043ae.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\flattening\3deec756c96e53127cd1d615c61043ae.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="16" step="1" value="4" class="slide-control"> <input type="range" min="1" max="16" step="1" value="4" class="slide-control">
@@ -565,7 +565,7 @@ function RationalBezier(3,t,w[],r[]):
<graphics-element title="3次ベジエ曲線の平坦化" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="cubic"> <graphics-element title="3次ベジエ曲線の平坦化" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\flattening\e2bb7113d5cda2e3fd29bbc54fbe8841.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\flattening\e2bb7113d5cda2e3fd29bbc54fbe8841.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="24" step="1" value="8" class="slide-control"> <input type="range" min="1" max="24" step="1" value="8" class="slide-control">
@@ -601,7 +601,7 @@ function RationalBezier(3,t,w[],r[]):
<p>ベジエ曲線を分割して、繫ぎ合わせたときに元に戻るような小さい2曲線にしたい場合にも、ド・カステリョのアルゴリズムを使えば、これに必要な点をすべて求めることができます。ある値<code>t</code>に対してド・カステリョの骨格を組み立てると、その<code>t</code>で曲線を分割する際に必要になる点がすべて得られます。骨格内部の点のうち、曲線上の点から見て手前側にある点によって一方の曲線が定義され、向こう側にある点によってもう一方の曲線が定義されます。</p> <p>ベジエ曲線を分割して、繫ぎ合わせたときに元に戻るような小さい2曲線にしたい場合にも、ド・カステリョのアルゴリズムを使えば、これに必要な点をすべて求めることができます。ある値<code>t</code>に対してド・カステリョの骨格を組み立てると、その<code>t</code>で曲線を分割する際に必要になる点がすべて得られます。骨格内部の点のうち、曲線上の点から見て手前側にある点によって一方の曲線が定義され、向こう側にある点によってもう一方の曲線が定義されます。</p>
<graphics-element title="曲線の分割" width="825" height="275" src="./chapters/splitting/splitting.js" > <graphics-element title="曲線の分割" width="825" height="275" src="./chapters/splitting/splitting.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\splitting\bef2f09698c0d3d2b7c4c031be17ff69.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\splitting\bef2f09698c0d3d2b7c4c031be17ff69.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
@@ -634,7 +634,7 @@ function drawCurve(points[], t):
<section id="matrixsplit"> <section id="matrixsplit">
<h1><a href="ja-JP/index.html#matrixsplit">行列による曲線の分割</a></h1> <h1><a href="ja-JP/index.html#matrixsplit">行列による曲線の分割</a></h1>
<p>曲線分割には、ベジエ曲線の行列表現を利用する方法もあります。<a href="#matrix">行列についての節</a>では、行列の乗算で曲線が表現できることを確認しました。特に2次・3次のベジエ曲線に関しては、それぞれ以下のような形になりました読みやすさのため、ベジエの係数ベクトルを反転させています</p> <p>曲線分割には、ベジエ曲線の行列表現を利用する方法もあります。<a href="#matrix">行列についての節</a>では、行列の乗算で曲線が表現できることを確認しました。特に2次・3次のベジエ曲線に関しては、それぞれ以下のような形になりました読みやすさのため、ベジエの係数ベクトルを反転させています</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/77a11d65d7cffc4b84a85c4bec837792.svg" width="263px" height="55px" loading="lazy"> <script>console.log("LaTeX for 77a11d65d7cffc4b84a85c4bec837792 failed!");</script>
<p>ならびに</p> <p>ならびに</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy">
<p>曲線をある点<code>t = z</code>で分割し、新しく2つの自明ですが、より短いベジエ曲線を作ることを考えましょう。曲線の行列表現と線形代数を利用すると、この2つのベジエ曲線の座標を求めることができます。まず、実際の「曲線上の点」の情報を分解し、新しい行列の積のかたちにします。</p> <p>曲線をある点<code>t = z</code>で分割し、新しく2つの自明ですが、より短いベジエ曲線を作ることを考えましょう。曲線の行列表現と線形代数を利用すると、この2つのベジエ曲線の座標を求めることができます。まず、実際の「曲線上の点」の情報を分解し、新しい行列の積のかたちにします。</p>
@@ -724,7 +724,7 @@ 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> <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" > <graphics-element title="A variable-order Bézier curve" width="275" height="275" src="./chapters/reordering/reorder.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<button class="raise">raise</button> <button class="raise">raise</button>
@@ -799,12 +799,12 @@ treated as a sequence of three (elementary) shear operations. When we combine th
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" > <graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" > <graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -833,7 +833,7 @@ 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> <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" > <graphics-element title="Some known and unknown vectors" width="350" height="300" src="./chapters/pointvectors3d/frenet.js" >
<fallback-image> <fallback-image>
<img width="350px" height="300px" src="images\chapters\pointvectors3d\cbadec403b99dec015ab084ee10e1671.png" loading="lazy"> <img width="350px" height="300px" src="images\chapters\pointvectors3d\cbadec403b99dec015ab084ee10e1671.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -907,7 +907,7 @@ 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> <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" > <graphics-element title="Some known and unknown vectors" width="350" height="300" src="./chapters/pointvectors3d/rotation-minimizing.js" >
<fallback-image> <fallback-image>
<img width="350px" height="300px" src="images\chapters\pointvectors3d\9983328e50c2156c124bb1ea4ad5c05b.png" loading="lazy"> <img width="350px" height="300px" src="images\chapters\pointvectors3d\9983328e50c2156c124bb1ea4ad5c05b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -925,13 +925,13 @@ 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> <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"> <graphics-element title="Quadratic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\components\008604bc4c53bd7e0d97c99a67812ad1.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\components\008604bc4c53bd7e0d97c99a67812ad1.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="cubic"> <graphics-element title="Cubic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\components\5214256129e6396e7ac1f1713fa9c88d.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\components\5214256129e6396e7ac1f1713fa9c88d.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1067,14 +1067,14 @@ 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> <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"> <graphics-element title="Quadratic Bézier curve extremities" width="825" height="275" src="./chapters/extremities/extremities.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\extremities\9850eec01924deae7fda4400ce44270d.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\extremities\9850eec01924deae7fda4400ce44270d.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<p>And for cubic curves, that means first and second derivatives, in red and purple respectively:</p> <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"> <graphics-element title="Cubic Bézier curve extremities" width="825" height="275" src="./chapters/extremities/extremities.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\extremities\890406c7bc96904224f8f14940bf3e56.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\extremities\890406c7bc96904224f8f14940bf3e56.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1092,12 +1092,12 @@ function getCubicRoots(pa, pb, pc, pd) {
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="quadratic"> <graphics-element title="Quadratic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\boundingbox\e2c621442e98e2cd20af7efe1cfb041f.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\boundingbox\e2c621442e98e2cd20af7efe1cfb041f.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="cubic"> <graphics-element title="Cubic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\boundingbox\f8989a62ebec9d6f123291c146caab5b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\boundingbox\f8989a62ebec9d6f123291c146caab5b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1120,12 +1120,12 @@ function getCubicRoots(pa, pb, pc, pd) {
<div class="figure"> <div class="figure">
<graphics-element title="Aligning a quadratic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="quadratic"> <graphics-element title="Aligning a quadratic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="550px" height="275px" src="images\chapters\aligning\b3ccd45a72c815388aee6515fe37a486.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\aligning\b3ccd45a72c815388aee6515fe37a486.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Aligning a cubic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="cubic"> <graphics-element title="Aligning a cubic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="550px" height="275px" src="images\chapters\aligning\31655f24b7dd8b8871687b6610d9ac0e.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\aligning\31655f24b7dd8b8871687b6610d9ac0e.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1138,12 +1138,12 @@ function getCubicRoots(pa, pb, pc, pd) {
<div class="figure"> <div class="figure">
<graphics-element title="Aligning a quadratic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="quadratic"> <graphics-element title="Aligning a quadratic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\tightbounds\ccc77ae1f57d7dd7ce4d5397fe1b140b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\tightbounds\ccc77ae1f57d7dd7ce4d5397fe1b140b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Aligning a cubic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="cubic"> <graphics-element title="Aligning a cubic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\tightbounds\419415bee6ffd7598c035c42de09a94f.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\tightbounds\419415bee6ffd7598c035c42de09a94f.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1183,7 +1183,7 @@ 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> <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" > <graphics-element title="Finding cubic Bézier curve inflections" width="275" height="275" src="./chapters/inflections/inflection.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\inflections\9e1ce3975100600d4979370851929b73.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\inflections\9e1ce3975100600d4979370851929b73.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1195,7 +1195,7 @@ 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> <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" > <graphics-element title="The canonical curve map" width="400" height="400" src="./chapters/canonical/canonical.js" >
<fallback-image> <fallback-image>
<img width="400px" height="400px" src="images\chapters\canonical\675217e6df3d75c86503dc2af623e14e.png" loading="lazy"> <img width="400px" height="400px" src="images\chapters\canonical\675217e6df3d75c86503dc2af623e14e.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1261,7 +1261,7 @@ 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> <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" > <graphics-element title="A cubic curve mapped to canonical form" width="800" height="400" src="./chapters/canonical/interactive.js" >
<fallback-image> <fallback-image>
<img width="800px" height="400px" src="images\chapters\canonical\344346f09b6d5d7e3ddf91084ad50d46.png" loading="lazy"> <img width="800px" height="400px" src="images\chapters\canonical\344346f09b6d5d7e3ddf91084ad50d46.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1272,7 +1272,7 @@ 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> <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" > <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> <fallback-image>
<img width="550px" height="275px" src="images\chapters\yforx\dd28d64458d22f4fe89c98568258efcb.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\yforx\dd28d64458d22f4fe89c98568258efcb.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" class="slide-control"> <input type="range" min="0" max="1" step="0.01" class="slide-control">
@@ -1299,7 +1299,7 @@ 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> <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" > <graphics-element title="Finding By(t), by finding t for a given x" width="275" height="275" src="./chapters/yforx/yforx.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\yforx\efcfe9b48ca4e65eef3d4bf3e4c97bc3.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\yforx\efcfe9b48ca4e65eef3d4bf3e4c97bc3.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" class="slide-control"> <input type="range" min="0" max="1" step="0.01" class="slide-control">
@@ -1321,17 +1321,17 @@ y = curve.get(t).y</code></pre>
<div class="figure"> <div class="figure">
<graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10"> <graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24"> <graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99"> <graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1354,7 +1354,7 @@ 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> <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" > <graphics-element title="Arc length for a Bézier curve" width="275" height="275" src="./chapters/arclength/arclength.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\195f9a3c60f8dfe977c6450d21968f69.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\195f9a3c60f8dfe977c6450d21968f69.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1367,7 +1367,7 @@ 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"> <graphics-element title="Approximate quadratic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="24" step="1" value="4" class="slide-control"> <input type="range" min="1" max="24" step="1" value="4" class="slide-control">
@@ -1375,7 +1375,7 @@ y = curve.get(t).y</code></pre>
<graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic"> <graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="32" step="1" value="8" class="slide-control"> <input type="range" min="1" max="32" step="1" value="8" class="slide-control">
@@ -1422,7 +1422,7 @@ 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> <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" > <graphics-element title="Matching curvatures for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\5fcfb0572cae06717506c84768aa568c.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\curvature\5fcfb0572cae06717506c84768aa568c.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1431,7 +1431,7 @@ 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> <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"> <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> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\876d7b2750d7c29068ac6181c3634d25.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\curvature\876d7b2750d7c29068ac6181c3634d25.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control"> <input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control">
@@ -1446,7 +1446,7 @@ 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> <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" > <graphics-element title="The t-for-distance function" width="550" height="275" src="./chapters/tracing/distance-function.js" >
<fallback-image> <fallback-image>
<img width="550px" height="275px" src="images\chapters\tracing\52f815cefe99dabc47ca83d0b97b61fc.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\tracing\52f815cefe99dabc47ca83d0b97b61fc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1454,7 +1454,7 @@ y = curve.get(t).y</code></pre>
<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> <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" > <graphics-element title="Fixed-interval coloring a curve" width="825" height="275" src="./chapters/tracing/tracing.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\tracing\133bf9d02801a3149c9ddb8b313e6797.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\tracing\133bf9d02801a3149c9ddb8b313e6797.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="2" max="24" step="1" value="8" class="slide-control"> <input type="range" min="2" max="24" step="1" value="8" class="slide-control">
@@ -1473,7 +1473,7 @@ 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> <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" > <graphics-element title="Line/line intersections" width="275" height="275" src="./chapters/intersections/line-line.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\intersections\61876a2bd727df377619c5ad34ce86be.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\intersections\61876a2bd727df377619c5ad34ce86be.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1505,12 +1505,12 @@ lli = function(line1, line2):
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="quadratic"> <graphics-element title="Quadratic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\intersections\594c2df534a1736c03cd3a96ff4a9913.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\intersections\594c2df534a1736c03cd3a96ff4a9913.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="cubic"> <graphics-element title="Cubic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\intersections\dc26a6063dadc31d242f1c1c8f38bb5e.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\intersections\dc26a6063dadc31d242f1c1c8f38bb5e.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1537,7 +1537,7 @@ lli = function(line1, line2):
<p>(can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p> <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" > <graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control"> <input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
@@ -1550,7 +1550,7 @@ lli = function(line1, line2):
</section> </section>
<section id="abc"> <section id="abc">
<h1><a href="ja-JP/index.html#abc">The projection identity</a></h1> <h1><a href="ja-JP/index.html#abc">The projection identity</a></h1>
<p>De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mould" a curve, by picking it up at some point, and dragging that point around to change the curve's shape.</p> <p>De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mold" a curve, by picking it up at some point, and dragging that point around to change the curve's shape.</p>
<p>How does that work? Succinctly: we run de Casteljau's algorithm in reverse!</p> <p>How does that work? Succinctly: we run de Casteljau's algorithm in reverse!</p>
<p>In order to run de Casteljau's algorithm in reverse, we need a few basic things: a start and end point, a point on the curve that want to be moving around, which has an associated <em>t</em> value, and a point we've not explicitly talked about before, and as far as I know has no explicit name, but lives one iteration higher in the de Casteljau process then our on-curve point does. I like to call it "A" for reasons that will become obvious.</p> <p>In order to run de Casteljau's algorithm in reverse, we need a few basic things: a start and end point, a point on the curve that want to be moving around, which has an associated <em>t</em> value, and a point we've not explicitly talked about before, and as far as I know has no explicit name, but lives one iteration higher in the de Casteljau process then our on-curve point does. I like to call it "A" for reasons that will become obvious.</p>
<p>So let's use graphics instead of text to see where this "A" is, because text only gets us so far: move the sliders for the following graphics to see what, given specific <code>t</code> value, our <code>A</code> coordinate is. As well as some other coordinates, which taken together let us derive a value that the graphics call "ratio": if you move the curve's points around, A, B, and C will move, what happens to that value?</p> <p>So let's use graphics instead of text to see where this "A" is, because text only gets us so far: move the sliders for the following graphics to see what, given specific <code>t</code> value, our <code>A</code> coordinate is. As well as some other coordinates, which taken together let us derive a value that the graphics call "ratio": if you move the curve's points around, A, B, and C will move, what happens to that value?</p>
@@ -1558,14 +1558,14 @@ 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"> <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> <fallback-image>
<img width="275px" height="275px" src="images\chapters\abc\6e40975c21e70b73954a4dce02b9ba75.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\abc\7a69dd4350ddda5701712e1d3b46b863.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
</graphics-element> </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"> <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> <fallback-image>
<img width="275px" height="275px" src="images\chapters\abc\d744a4955a3ff4e2d85760887ea923d4.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\abc\eeec7cf16fb22c666e0143a3a030731f.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
@@ -1578,6 +1578,8 @@ lli = function(line1, line2):
<li>a point at the tip of the curve construction's "hat": let's call that <code>A</code>, as well as</li> <li>a point at the tip of the curve construction's "hat": let's call that <code>A</code>, as well as</li>
<li>our on-curve point give our chosen <code>t</code> value: let's call that <code>B</code>, and finally,</li> <li>our on-curve point give our chosen <code>t</code> value: let's call that <code>B</code>, and finally,</li>
<li>a point that we get by projecting A, through B, onto the line between the curve's start and end points: let's call that <code>C</code>.</li> <li>a point that we get by projecting A, through B, onto the line between the curve's start and end points: let's call that <code>C</code>.</li>
<li>for both qudratic and cubic curves, two points <code>e1</code> and <code>e2</code>, which represent the single-to-last step in de Casteljau's algorithm: in the last step, we find <code>B</code> at <code>(1-t) * e1 + t * e2</code>.</li>
<li>for cubic curves, also the points <code>v1</code> and <code>v2</code>, which together with <code>A</code> represent the first step in de Casteljau's algorithm: in the next step, we find <code>e1</code> and <code>e2</code>.</li>
</ol> </ol>
<p>These three values A, B, and C allow us to derive an important identity formula for quadratic and cubic Bézier curves: for any point on the curve with some <code>t</code> value, the ratio of distances from A to B and B to C is fixed: if some <code>t</code> value sets up a C that is 20% away from the start and 80% away from the end, then <em>it doesn't matter where the start, end, or control points are</em>; for that <code>t</code> value, <code>C</code> will <em>always</em> lie at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic and then move all the other points around: if you only move the control points, start and end won't move, and so neither will C, and if you move either start or end point, C will move but its relative position will not change.</p> <p>These three values A, B, and C allow us to derive an important identity formula for quadratic and cubic Bézier curves: for any point on the curve with some <code>t</code> value, the ratio of distances from A to B and B to C is fixed: if some <code>t</code> value sets up a C that is 20% away from the start and 80% away from the end, then <em>it doesn't matter where the start, end, or control points are</em>; for that <code>t</code> value, <code>C</code> will <em>always</em> lie at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic and then move all the other points around: if you only move the control points, start and end won't move, and so neither will C, and if you move either start or end point, C will move but its relative position will not change.</p>
<p>So, how can we compute <code>C</code>? We start with our observation that <code>C</code> always lies somewhere between the start and ends points, so logically <code>C</code> will have a function that interpolates between those two coordinates:</p> <p>So, how can we compute <code>C</code>? We start with our observation that <code>C</code> always lies somewhere between the start and ends points, so logically <code>C</code> will have a function that interpolates between those two coordinates:</p>
@@ -1595,12 +1597,60 @@ lli = function(line1, line2):
<img class="LaTeX SVG" src="./images/chapters/abc/b4987e9b77b0df604238b88596c5f7c3.svg" width="228px" height="41px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/abc/b4987e9b77b0df604238b88596c5f7c3.svg" width="228px" height="41px" loading="lazy">
<p>Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a <code>t</code> value, we can <em>contruct</em> curves: we can compute <code>C</code> using the start and end points, and our <code>u(t)</code> function, and once we have <code>C</code>, we can use our on-curve point (<code>B</code>) and the <code>ratio(t)</code> function to find <code>A</code>:</p> <p>Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a <code>t</code> value, we can <em>contruct</em> curves: we can compute <code>C</code> using the start and end points, and our <code>u(t)</code> function, and once we have <code>C</code>, we can use our on-curve point (<code>B</code>) and the <code>ratio(t)</code> function to find <code>A</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/abc/12aaf0d7fd20b3c551a0ec76b18bd7d2.svg" width="231px" height="39px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/abc/12aaf0d7fd20b3c551a0ec76b18bd7d2.svg" width="231px" height="39px" loading="lazy">
<p>So: if we have a curve's start and end point, then for any <code>t</code> value, we implicitly know all the ABC values, which gives us the necessary information to reconstruct a curve's "de Casteljau skeleton". Which means that we can now do several things: we can "fit" curves using only three points, which means we can also "mould" curves by moving an on-curve point but leaving its start and end point, and then reconstructing the curve based on where we moved the on-curve point to. These are very useful things, and we'll look at both in the next sections.</p> <p>With <code>A</code> found, finding <code>e1</code> and <code>e2</code> for quadratic curves is a matter of running the linear interpolation with <code>t</code> between start and <code>A</code> to yield <code>e1</code>, and between <code>A</code> and end to yield <code>e2</code>. For cubic curves, there is no single pair of points that can act as <code>e1</code> and <code>e2</code>: as long as the distance ratio between <code>e1</code> to <code>B</code> and <code>B</code> to <code>e2</code> is the Bézier ratio <code>(1-t):t</code>, we can reverse engineer <code>v1</code> and <code>v2</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/abc/bc245327e0b011712168bad1c48dfec4.svg" width="139px" height="75px" loading="lazy">
<p>And then reverse engineer the curve's control control points:</p>
<img class="LaTeX SVG" src="./images/chapters/abc/3c696e0364d61b1391695342707d6ccc.svg" width="177px" height="72px" loading="lazy">
<p>So: if we have a curve's start and end point, then for any <code>t</code> value we implicitly know all the ABC values, which (combined with an educated guess on appropriate <code>e1</code> and <code>e2</code> coordinates for cubic curves) gives us the necessary information to reconstruct a curve's "de Casteljau skeleton". Which means that we can now do several things: we can "fit" curves using only three points, which means we can also "mold" curves by moving an on-curve point but leaving its start and end point, and then reconstructing the curve based on where we moved the on-curve point to. These are very useful things, and we'll look at both in the next few sections.</p>
</section>
<section id="pointcurves">
<h1><a href="ja-JP/index.html#pointcurves">Creating a curve from three points</a></h1>
<p>Given the preceding section, you might be wondering if we can use that knowledge to just "create" curves by placing some points and having the computer do the rest, to which the answer is: that's exactly what we can now do!</p>
<p>For quadratic curves, things are pretty easy. Technically, we'll need a <code>t</code> value in order to compute the ratio function used in computing the ABC coordinates, but we can just as easily approximate one by treating the distance between the start and <code>B</code> point, and <code>B</code> and end point as a ratio, using</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/3c7516c16a5dea95df741f4263cecd1c.svg" width="132px" height="85px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\067a3df30e32708fc0d13f8eb78c0b05.png">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\173ea31517a72a927d561f121f0677db.png">
Scripts are disabled. Showing fallback image.
</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>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/55d4f7ed095dfea8f9772208abc83b51.svg" width="147px" height="49px" loading="lazy">
<p>Where <code>d</code> is the total length of the line segment from <code>e1</code> to <code>e2</code>. So how long do we make that? There are again all kinds of approaches we can take, and a simple-but-effective one is to set the length of that segment to "one third the length of the baseline". This forces <code>e1</code> and <code>e2</code> to always be the "linear curve" distance apart, which means if we place our three points on a line, it will actually <em>look</em> like a line. Nice! The last thing we'll need to do is make sure to flip the sign of <code>d</code> depending on which side of the baseline our <code>B</code> is located, so we don't up creating a funky curve with a loop in it. To do this, we can use the <a href="https://en.wikipedia.org/wiki/Atan2">atan2</a> function:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/9203537b7dca98ebb2d7017c76100fde.svg" width="507px" height="21px" loading="lazy">
<p>This angle φ will be between 0 and π if <code>B</code> is "above" the baseline (rotating all three points so that the start is on the left and the end is the right), so we can use a relatively straight forward check to make sure we're using the correct sign for our value <code>d</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/6f0e2b6494d7dae2ea79a46a499d7ed4.svg" width="183px" height="49px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\8d045d352f5017b65e60620b92d7ae29.png">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\eab6ea46fa93030e03ec0ef7deb571dc.png">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>That looks perfectly servicable!</p>
<p>Of course, we can take this one step further: we can't just "create" curves, we also have (almost!) all the tools available to "mold" curves, where we can reshape a curve by dragging a point on the curve around while leaving the start and end fixed, effectively molding the shape as if it were clay or the like. We'll see the last tool we need to do that in the next section, and then we'll look at implementing curve molding in the section after that, so read on!</p>
</section> </section>
<section id="projections"> <section id="projections">
<h1><a href="ja-JP/index.html#projections">Projecting a point onto a Bézier curve</a></h1> <h1><a href="ja-JP/index.html#projections">Projecting a point onto a Bézier curve</a></h1>
<p>Before we can move on to actual curve moulding, it'll be good if know how to actually be able to find "some point on the curve" that we're trying to click on. After all, if all we have is our Bézier coordinates, that is not in itself enough to figure out which point on the curve our cursor will be closest to. So, how do we project points onto a curve?</p> <p>Before we can move on to actual curve molding, it'll be good if know how to actually be able to find "some point on the curve" that we're trying to click on. After all, if all we have is our Bézier coordinates, that is not in itself enough to figure out which point on the curve our cursor will be closest to. So, how do we project points onto a curve?</p>
<p>If the Bézier curve is of low enough order, we might be able to <a href="https://web.archive.org/web/20140713004709/http://jazzros.blogspot.com/2011/03/projecting-point-on-bezier-curve.html">work out the maths for how to do this</a>, and get a perfect <code>t</code> value back, but in general this is an incredibly hard problem and the easiest solution is, really, a numerical approach again. We'll be finding our ideal <code>t</code> value using a <a href="https://en.wikipedia.org/wiki/Binary_search_algorithm">binary search</a>. First, we do a coarse distance-check based on <code>t</code> values associated with the curve's "to draw" coordinates (using a lookup table, or LUT). This is pretty fast:</p> <p>If the Bézier curve is of low enough order, we might be able to <a href="https://web.archive.org/web/20140713004709/http://jazzros.blogspot.com/2011/03/projecting-point-on-bezier-curve.html">work out the maths for how to do this</a>, and get a perfect <code>t</code> value back, but in general this is an incredibly hard problem and the easiest solution is, really, a numerical approach again. We'll be finding our ideal <code>t</code> value using a <a href="https://en.wikipedia.org/wiki/Binary_search_algorithm">binary search</a>. First, we do a coarse distance-check based on <code>t</code> values associated with the curve's "to draw" coordinates (using a lookup table, or LUT). This is pretty fast:</p>
<pre><code>p = some point to project onto the curve <pre><code>p = some point to project onto the curve
d = some initially huge value d = some initially huge value
@@ -1619,104 +1669,45 @@ 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> <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" > <graphics-element title="Projecting a point onto a Bézier curve" width="320" height="320" src="./chapters/projections/project.js" >
<fallback-image> <fallback-image>
<img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png" loading="lazy"> <img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</section> </section>
<section id="moulding"> <section id="molding">
<h1><a href="ja-JP/index.html#moulding">Manipulating a curve</a></h1> <h1><a href="ja-JP/index.html#molding">Molding a curve</a></h1>
<p>Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the <em>t</em>-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.</p> <p>Armed with knowledge of the "ABC" relation, point-on-curve projection, and guestimating reasonable looking helper values for cubic curve construction, we can finally cover curve molding: updating a curve's shape interactively, by dragging points on the curve around.</p>
<graphics-element title="Moulding a quadratic Bézier curve" width="825" height="275" src="./chapters/moulding/moulding.js" data-type="quadratic"> <p>For quadratic curve, this is a really simple trick: we project our cursor onto the curve, which gives us a <code>t</code> value and initial <code>B</code> coordinate. We don't even need the latter: with our <code>t</code> value and "whever the cursor is" as target <code>B</code>, we can compute the associated <code>C</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/molding/079d318ad693b6b17413a91f5de06be8.svg" width="256px" height="21px" loading="lazy">
<p>And then the associated <code>A</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/molding/82a99caec5f84fb26dce28277377c041.svg" width="244px" height="40px" loading="lazy">
<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> <fallback-image>
<img width="825px" height="275px" src="images\chapters\moulding\adc7e0785dade356b62fadcd903e73d9.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\molding\6b91671c2962530b863ae0da5789a9cc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<p>Click-dragging a point on the curve shows what we're using to compute the new coordinates: while dragging you will see the original point <code>B</code> and its corresponding <i>t</i>-value, and the original points <code>A</code> and <code>C</code> for that <i>t</i>-value, in light coloring, as well as the new <code>A'</code>, <code>B'</code>, and <code>C'</code> (although of course the <code>C</code> coordinates are the same ones, because that's the defining feature of point <code>C</code>) based on where you're dragging point <code>B</code> to, in purple.</p> <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>Since we know the new point <code>B'</code>, and the "new" point <code>C'</code> as well as the <code>t</code> value, we know our new point A' has to be:</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>
<img class="LaTeX SVG" src="./images/chapters/moulding/7159e1e6eeab38c5aba225fe7553dbe6.svg" width="247px" height="39px" loading="lazy"> <graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic">
<p>For quadratic curves, this means we're done, since the new point <code>A'</code> is equivalent to the new quadratic control point.</p>
<p>For cubic curves, we need to do a little more work, because while computing a new <code>A'</code> is exactly the same as before, we're not quite done once we've done so. For cubic curves, <code>B</code> has not just an associated <code>t</code> value, but also two associated "side" values. Let's revisit the graphic from the chapter on de Casteljau's algorithm, to see what we mean:</p>
<graphics-element title="The information necessary to manipulate cubic curves" width="275" height="275" src="./chapters/moulding/decasteljau.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\moulding\fbc7d78dd768eec4314eb4b5320f2ce5.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\molding\522f1edd37163772b81acb86d3a4f423.png">
Scripts are disabled. Showing fallback image.
</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>
<img width="825px" height="275px" src="images\chapters\molding\ea1656c068a60631135ad499e8a29453.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="10" max="200" step="1" value="100" class="slide-control">
</graphics-element> </graphics-element>
<p>In addition to the <code>A</code>, <code>B</code>, and <code>C</code> values, we also see the points <code>e1</code> and <code>e2</code>, without which constructing our de Casteljau "strut lines" becomes very difficult indeed; as well as the points <code>v1</code> and <code>v2</code>, which we can construct when we know our ABC values enriched with <code>e1</code> and <code>e2</code>:</p> <p>The slide controls the "falloff distance" relative to where the original point on the curve is, so that as we drag our point around, it interpolates with a bias towards "preserving <code>t</code>/<code>e1</code>/<code>e2</code>" closer to the original point, and bias towards "idealised" form the further away we move our point, with anything that's further than our falloff distance simply <em>being</em> the idealised curve. We don't even try to interpolate at that point.</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/bc245327e0b011712168bad1c48dfec4.svg" width="139px" height="75px" loading="lazy"> <p>A more advanced way to try to smooth things out is to implement <em>continuous</em> molding, where we constantly update the curve as we move around, and constantly change what our <code>B</code> point is, based on constantly projecting the cursor on the curve <em>as we're updating it</em> - this is, you won't be surprised to learn, tricky, and beyond the scope of this section: interpolation (with a reasonable distance) will do for now!</p>
<p>After which computing the new control points is straight-forward:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/3c696e0364d61b1391695342707d6ccc.svg" width="177px" height="72px" loading="lazy">
<p>So let's put that into practice:</p>
<graphics-element title="Moulding a cubic Bézier curve" width="825" height="275" src="./chapters/moulding/moulding.js" data-type="cubic">
<fallback-image>
<img width="825px" height="275px" src="images\chapters\moulding\b9928928e1f622e66b59e4a59cfac925.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>So that looks pretty good, but you may not like having <code>e1</code> and <code>e2</code> stay the same distances away from <code>B'</code> while moving the point around, and want to rearrange those to lead to "cleaner looking" curve manipulation. Unfortunately, there are so many differen ways in which we can do this that figuring out "good looking" alternatives, given what the curve is being manipulated for, could be an entire book on its own... so we're only going to look at one way that you might effect alternative <code>e1</code> and <code>e2</code> points, based on the idea of rotating a vector.</p>
<p>If we treat point <code>B</code> as a "a vector originating at <code>C</code>" then we can treat the points <code>e1</code> and <code>e2</code> as offets (let's call these <code>d1</code> and <code>d2</code>) of that vector, where:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/65c9a6f8a210d41b18f20e4da1ba1403.svg" width="95px" height="49px" loading="lazy">
<p>Which means that:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/28e90ffa101453e4c030174d36d185a5.svg" width="96px" height="49px" loading="lazy">
<p>Now, if we now <code>B</code> to some new coordinate <code>B'</code> we can treat that "moving of the coordinate" as a rotation and scaling of the vector for <code>B</code> instead. If the new point <code>B'</code> is the same distance away from <code>C</code> as <code>B</code> was, this is a pure rotation, but otherwise the length of the vector has decreased or increased by some factor.</p>
<p>We can use both those values to change where <code>e1</code> and <code>e2</code> end up, and thus how our curve moulding "feels", by placing new <code>e1'</code> and <code>e2'</code> where:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/54e423d1eb61157abd3acffc5271c3ac.svg" width="465px" height="67px" loading="lazy">
<p>Here, the <code>rotate()</code> function rotates a vector (in this case <code>d1</code> or <code>d2</code>) around some point (in this case, <code>B'</code>), by some angle (in this case, the angle by which we rotated our original <code>B</code> to become <code>B'</code>). So what does <em>that</em> look like?</p>
<graphics-element title="Moulding a cubic Bézier curve" width="825" height="275" src="./chapters/moulding/moulding.js" data-type="cubic" data-alternative="true">
<fallback-image>
<img width="825px" height="275px" src="images\chapters\moulding\db817c3899e954da6c882e4699a49353.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>As you can see, this is both better, and worse, depending on what you're trying to do with the curve, and there are many different ways in which you can try to change <code>e1</code> and <code>e2</code> such that they behave "as users would expect them to" based on the context in which you're implementing curve moulding. You might want to add reflections when <code>B'</code> crosses the baseline, or even some kind of weight-swapping when <code>B'</code> crosses the midline (perpendicular to the baseline, at its mid point), and instead of scaling both points with respects to <code>C</code>, you might want to scale them to coordinates 1/2rd and 2/3rd along the baseline, etc. etc.</p>
<p>There are too many options to go over here, so: the best behaviour is, of course, the behaviour <em>you</em> think is best, and it might be a lot of work to find that and/or implement that!</p>
</section>
<section id="pointcurves">
<h1><a href="ja-JP/index.html#pointcurves">Creating a curve from three points</a></h1>
<p>Given the preceding section on curve manipulation, we can also generate quadratic and cubic curves from any three points, although</p>
<p>For quadratic curves, things are pretty easy: technically we need a <code>t</code> value in order to compute the ratio function used in computing the ABC coordinates, but we can just as easily approximate one by treating the distance between the start and <code>B</code> point, and <code>B</code> and end point as a ratio, using</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/3c7516c16a5dea95df741f4263cecd1c.svg" width="132px" height="85px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\067a3df30e32708fc0d13f8eb78c0b05.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\93ee12566f93f241ad0970acd5505367.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</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>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/55d4f7ed095dfea8f9772208abc83b51.svg" width="147px" height="49px" loading="lazy">
<p>Where <code>d</code> is the total length of the line segment from <code>e1</code> to <code>e2</code>. So how long do we make that? There are again all kinds of approaches we can take, and a simple-but-effective one is to set the length of that segment to "one third the length of the baseline". This forces <code>e1</code> and <code>e2</code> to always be the "linear curve" distance apart, which means if we place our three points on a line, it will actually <em>look</em> like a line. Nice! The last thing we'll need to do is make sure to flip the sign of <code>d</code> depending on which side of the baseline our <code>B</code> is located, so we don't up creating a funky curve with a loop in it. To do this, we can use the <a href="https://en.wikipedia.org/wiki/Atan2">atan2</a> function:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/b4464f73fb2f79027d8e971fc66813f6.svg" width="393px" height="19px" loading="lazy">
<p>This angle φ will be between 0 and π if <code>B</code> is "above" the baseline (rotating all three points so that the start is on the left and the end is the right), so we can use a relatively straight forward check to make sure we're using the correct sign for our value <code>d</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/6f0e2b6494d7dae2ea79a46a499d7ed4.svg" width="183px" height="49px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\8b0e8e79ba09916a9af27b33f8a1919f.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\eab6ea46fa93030e03ec0ef7deb571dc.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>That looks perfectly servicable!</p>
</section> </section>
<section id="curvefitting"> <section id="curvefitting">
@@ -1883,8 +1874,8 @@ for (coordinate, index) in LUT:
<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="284px" height="77px" loading="lazy">
</section> </section>
<section id="catmullmoulding"> <section id="catmullmolding">
<h1><a href="ja-JP/index.html#catmullmoulding">Creating a Catmull-Rom curve from three points</a></h1> <h1><a href="ja-JP/index.html#catmullmolding">Creating a Catmull-Rom curve from three points</a></h1>
<p>Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead?</p> <p>Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead?</p>
<p>As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve.</p> <p>As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve.</p>
<p>Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> points (and that's without double-counting points that are shared by consecutive cubic curves).</p> <p>Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> points (and that's without double-counting points that are shared by consecutive cubic curves).</p>
@@ -2048,7 +2039,7 @@ for (coordinate, index) in LUT:
<p>which we can then substitute in the expression for <em>a</em>:</p> <p>which we can then substitute in the expression for <em>a</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy">
<p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p> <p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/fe32474b4616ee9478e1308308f1b6bf.svg" width="188px" height="32px" loading="lazy"> <script>console.log("LaTeX for fe32474b4616ee9478e1308308f1b6bf failed!");</script>
<p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p> <p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy">
<p>Which, worked out for the x and y components, gives:</p> <p>Which, worked out for the x and y components, gives:</p>

View File

@@ -38,6 +38,7 @@ graphics-element:not(:defined) fallback-image {
font-size: 60%; font-size: 60%;
text-align: center; text-align: center;
padding-bottom: 0.2em; padding-bottom: 0.2em;
visibility:collapse;
} }
/* /*
@@ -48,7 +49,7 @@ graphics-element:not(:defined) fallback-image {
graphics-element:not(:defined) fallback-image > img { graphics-element:not(:defined) fallback-image > img {
display: block; display: block;
margin: 0.9em; visibility:visible;
} }
/* /*
@@ -59,7 +60,6 @@ graphics-element:not(:defined) fallback-image > img {
graphics-element:defined { graphics-element:defined {
display: inline-block; display: inline-block;
padding: 0.5em; padding: 0.5em;
margin: 1em auto;
justify-self: center; justify-self: center;
font-size: revert; font-size: revert;
text-align: revert; text-align: revert;
@@ -70,6 +70,6 @@ graphics-element:defined {
the <fallback-image> does NOT show anymore! the <fallback-image> does NOT show anymore!
*/ */
graphics-element:defined fallback-image { graphics-element:defined fallback-image.loaded {
display: none; display: none;
} }

View File

@@ -16,6 +16,8 @@ CustomElement.register(class ProgramCode extends HTMLElement {});
* Our custom element * Our custom element
*/ */
class GraphicsElement extends CustomElement { class GraphicsElement extends CustomElement {
static DEBUG = false;
constructor() { constructor() {
super({ header: false, footer: false }); super({ header: false, footer: false });
@@ -220,12 +222,14 @@ class GraphicsElement extends CustomElement {
* can't actually find anywhere in the document or shadow DOM... * can't actually find anywhere in the document or shadow DOM...
*/ */
printCodeDueToError() { printCodeDueToError() {
console.log( if (GraphicsElement.DEBUG) {
this.code console.log(
.split(`\n`) this.code
.map((l, pos) => `${pos + 1}: ${l}`) .split(`\n`)
.join(`\n`) .map((l, pos) => `${pos + 1}: ${l}`)
); .join(`\n`)
);
}
} }
/** /**

View File

@@ -16,25 +16,6 @@ const pi = Math.PI;
// a zero coordinate, which is surprisingly useful // a zero coordinate, which is surprisingly useful
const ZERO = { x: 0, y: 0, z: 0 }; const ZERO = { x: 0, y: 0, z: 0 };
// TODO: figure out where this function goes, it has no reason to exist on its lonesome.
function getABC(n, S, B, E, t) {
if (typeof t === "undefined") {
t = 0.5;
}
const u = utils.projectionratio(t, n),
um = 1 - u,
C = {
x: u * S.x + um * E.x,
y: u * S.y + um * E.y,
},
s = utils.abcratio(t, n),
A = {
x: B.x + (B.x - C.x) / s,
y: B.y + (B.y - C.y) / s,
};
return { A, B, C };
}
/** /**
* Bezier curve constructor. * Bezier curve constructor.
* *
@@ -128,7 +109,7 @@ class Bezier {
return new Bezier(p1, p2, p2); return new Bezier(p1, p2, p2);
} }
// real fitting. // real fitting.
const abc = getABC(2, p1, p2, p3, t); const abc = Bezier.getABC(2, p1, p2, p3, t);
return new Bezier(p1, abc.A, p3); return new Bezier(p1, abc.A, p3);
} }
@@ -136,7 +117,7 @@ class Bezier {
if (typeof t === "undefined") { if (typeof t === "undefined") {
t = 0.5; t = 0.5;
} }
const abc = getABC(3, S, B, E, t); const abc = Bezier.getABC(3, S, B, E, t);
if (typeof d1 === "undefined") { if (typeof d1 === "undefined") {
d1 = utils.dist(B, abc.C); d1 = utils.dist(B, abc.C);
} }
@@ -239,10 +220,18 @@ class Bezier {
} }
static getABC(order = 2, S, B, E, t = 0.5) { static getABC(order = 2, S, B, E, t = 0.5) {
let ret = getABC(order, S, B, E, t); const u = utils.projectionratio(t, order),
ret.S = S; um = 1 - u,
ret.E = E; C = {
return ret; x: u * S.x + um * E.x,
y: u * S.y + um * E.y,
},
s = utils.abcratio(t, order),
A = {
x: B.x + (B.x - C.x) / s,
y: B.y + (B.y - C.y) / s,
};
return { A, B, C, S, E };
} }
getABC(t, B) { getABC(t, B) {

View File

@@ -95,12 +95,12 @@
<li><a href="zh-CN/index.html#intersections">Intersections</a></li> <li><a href="zh-CN/index.html#intersections">Intersections</a></li>
<li><a href="zh-CN/index.html#curveintersection">Curve/curve intersection</a></li> <li><a href="zh-CN/index.html#curveintersection">Curve/curve intersection</a></li>
<li><a href="zh-CN/index.html#abc">The projection identity</a></li> <li><a href="zh-CN/index.html#abc">The projection identity</a></li>
<li><a href="zh-CN/index.html#projections">Projecting a point onto a Bézier curve</a></li>
<li><a href="zh-CN/index.html#moulding">Manipulating a curve</a></li>
<li><a href="zh-CN/index.html#pointcurves">Creating a curve from three points</a></li> <li><a href="zh-CN/index.html#pointcurves">Creating a curve from three points</a></li>
<li><a href="zh-CN/index.html#projections">Projecting a point onto a Bézier curve</a></li>
<li><a href="zh-CN/index.html#molding">Molding a curve</a></li>
<li><a href="zh-CN/index.html#curvefitting">Curve fitting</a></li> <li><a href="zh-CN/index.html#curvefitting">Curve fitting</a></li>
<li><a href="zh-CN/index.html#catmullconv">Bézier curves and Catmull-Rom curves</a></li> <li><a href="zh-CN/index.html#catmullconv">Bézier curves and Catmull-Rom curves</a></li>
<li><a href="zh-CN/index.html#catmullmoulding">Creating a Catmull-Rom curve from three points</a></li> <li><a href="zh-CN/index.html#catmullmolding">Creating a Catmull-Rom curve from three points</a></li>
<li><a href="zh-CN/index.html#polybezier">Forming poly-Bézier curves</a></li> <li><a href="zh-CN/index.html#polybezier">Forming poly-Bézier curves</a></li>
<li><a href="zh-CN/index.html#shapes">Boolean shape operations</a></li> <li><a href="zh-CN/index.html#shapes">Boolean shape operations</a></li>
<li><a href="zh-CN/index.html#offsetting">Curve offsetting</a></li> <li><a href="zh-CN/index.html#offsetting">Curve offsetting</a></li>
@@ -194,12 +194,12 @@
<div class="figure"> <div class="figure">
<graphics-element title="二次贝塞尔曲线" width="275" height="275" src="./chapters/introduction/quadratic.js" > <graphics-element title="二次贝塞尔曲线" width="275" height="275" src="./chapters/introduction/quadratic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\introduction\54e9ec0600ac436b0e6f0c6b5005cf03.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\introduction\54e9ec0600ac436b0e6f0c6b5005cf03.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="三次贝塞尔曲线" width="275" height="275" src="./chapters/introduction/cubic.js" > <graphics-element title="三次贝塞尔曲线" width="275" height="275" src="./chapters/introduction/cubic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\introduction\8d158a13e9a86969b99c64057644cbc6.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -217,7 +217,7 @@
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" height="275" src="./chapters/whatis/interpolation.js" > <graphics-element title="Linear Interpolation leading to Bézier curves" width="825" height="275" src="./chapters/whatis/interpolation.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\whatis\9b3633889c38325c24c19ce18ab94ad6.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\whatis\9b3633889c38325c24c19ce18ab94ad6.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="10" max="90" step="1" value="25" class="slide-control"> <input type="range" min="10" max="90" step="1" value="25" class="slide-control">
@@ -242,7 +242,7 @@
<p>所以,参数曲线不像一般函数那样,通过<i>x</i>坐标来定义<i>y</i>坐标,而是用一个“控制”变量将它们连接起来。如果改变<i>t</i>的值,每次变化时我们都能得到<strong>两个</strong>值,这可以作为图形中的(<i>x</i>,<i>y</i>)坐标。比如上面的方程组,生成位于一个圆上的点:我们可以使<i>t</i>在正负极值间变化,得到的输出(<i>x</i>,<i>y</i>)都会位于一个以原点(0,0)为中心且半径为1的圆上。如果我们画出<i>t</i>从0到5时的值将得到如下图像</p> <p>所以,参数曲线不像一般函数那样,通过<i>x</i>坐标来定义<i>y</i>坐标,而是用一个“控制”变量将它们连接起来。如果改变<i>t</i>的值,每次变化时我们都能得到<strong>两个</strong>值,这可以作为图形中的(<i>x</i>,<i>y</i>)坐标。比如上面的方程组,生成位于一个圆上的点:我们可以使<i>t</i>在正负极值间变化,得到的输出(<i>x</i>,<i>y</i>)都会位于一个以原点(0,0)为中心且半径为1的圆上。如果我们画出<i>t</i>从0到5时的值将得到如下图像</p>
<graphics-element title="(一部分的)圆: x=sin(t), y=cos(t)" width="275" height="275" src="./chapters/explanation/circle.js" > <graphics-element title="(一部分的)圆: x=sin(t), y=cos(t)" width="275" height="275" src="./chapters/explanation/circle.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\explanation\6d2f915735ebdc05e42c0ea7adc85343.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\explanation\6d2f915735ebdc05e42c0ea7adc85343.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="10" step="0.1" value="5" class="slide-control"> <input type="range" min="0" max="10" step="0.1" value="5" class="slide-control">
@@ -324,7 +324,7 @@ function Bezier(3,t):
<div class="figure"> <div class="figure">
<graphics-element title="二次插值" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3"> <graphics-element title="二次插值" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -332,7 +332,7 @@ function Bezier(3,t):
<graphics-element title="三次插值" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4"> <graphics-element title="三次插值" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -340,7 +340,7 @@ function Bezier(3,t):
<graphics-element title="15次插值" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15"> <graphics-element title="15次插值" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -349,7 +349,7 @@ function Bezier(3,t):
<p>上面有一张是15<sup>th</sup>阶的插值方程。如你所见,在所有控制点中,起点和终点对曲线形状的贡献比其他点更大些。</p> <p>上面有一张是15<sup>th</sup>阶的插值方程。如你所见,在所有控制点中,起点和终点对曲线形状的贡献比其他点更大些。</p>
<p>如果我们要改变曲线,就需要改变每个点的权重,有效地改变插值。可以很直接地做到这个:只要用一个值乘以每个点,来改变它的强度。这个值照惯例称为“权重”,我们可以将它加入我们原始的贝塞尔函数:</p> <p>如果我们要改变曲线,就需要改变每个点的权重,有效地改变插值。可以很直接地做到这个:只要用一个值乘以每个点,来改变它的强度。这个值照惯例称为“权重”,我们可以将它加入我们原始的贝塞尔函数:</p>
<img class="LaTeX SVG" src="./images/chapters/control/14cb9fbbaae9e7d87ae6bef3ea7a782e.svg" width="379px" height="56px" loading="lazy"> <script>console.log("LaTeX for 14cb9fbbaae9e7d87ae6bef3ea7a782e failed!");</script>
<p>看起来很复杂但实际上“权重”只是我们想让曲线所拥有的坐标值对于一条n<sup>th</sup>阶曲线w<sup>0</sup>是起始坐标w<sup>n</sup>是终点坐标中间的所有点都是控制点坐标。假设说一条曲线的起点为120160终点为22040并受点35200和点220260的控制贝塞尔曲线方程就为</p> <p>看起来很复杂但实际上“权重”只是我们想让曲线所拥有的坐标值对于一条n<sup>th</sup>阶曲线w<sup>0</sup>是起始坐标w<sup>n</sup>是终点坐标中间的所有点都是控制点坐标。假设说一条曲线的起点为120160终点为22040并受点35200和点220260的控制贝塞尔曲线方程就为</p>
<img class="LaTeX SVG" src="./images/chapters/control/c0d4dbc07b8ec7c0a18ea43c8a386935.svg" width="476px" height="40px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/control/c0d4dbc07b8ec7c0a18ea43c8a386935.svg" width="476px" height="40px" loading="lazy">
<p>这就是我们在文章开头看到的曲线:</p> <p>这就是我们在文章开头看到的曲线:</p>
@@ -396,7 +396,7 @@ 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> <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" > <graphics-element title="Our rational cubic Bézier curve" width="275" height="275" src="./chapters/weightcontrol/rational.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br> ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
@@ -454,12 +454,12 @@ function RationalBezier(3,t,w[],r[]):
<div class="figure"> <div class="figure">
<graphics-element title="二次无限区间贝塞尔曲线" width="275" height="275" src="./chapters/extended/extended.js" data-type="quadratic"> <graphics-element title="二次无限区间贝塞尔曲线" width="275" height="275" src="./chapters/extended/extended.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\extended\391a61142c56b79260680aefb08cd9c4.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\extended\391a61142c56b79260680aefb08cd9c4.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="三次无限区间贝塞尔曲线" width="275" height="275" src="./chapters/extended/extended.js" data-type="cubic"> <graphics-element title="三次无限区间贝塞尔曲线" width="275" height="275" src="./chapters/extended/extended.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\extended\baeceec6e1587794b8b275a90d5d85e9.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\extended\baeceec6e1587794b8b275a90d5d85e9.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -511,7 +511,7 @@ function RationalBezier(3,t,w[],r[]):
<p>我们通过实际操作来观察这个过程。在以下的图表中移动鼠标来改变用de Casteljau算法计算得到的曲线点左右移动鼠标可以实时看到曲线是如何生成的。</p> <p>我们通过实际操作来观察这个过程。在以下的图表中移动鼠标来改变用de Casteljau算法计算得到的曲线点左右移动鼠标可以实时看到曲线是如何生成的。</p>
<graphics-element title="用de Casteljau算法来遍历曲线" width="275" height="275" src="./chapters/decasteljau/decasteljau.js" > <graphics-element title="用de Casteljau算法来遍历曲线" width="275" height="275" src="./chapters/decasteljau/decasteljau.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\decasteljau\715d1d2eecc762d6bc1470954b145018.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\decasteljau\715d1d2eecc762d6bc1470954b145018.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -551,7 +551,7 @@ function RationalBezier(3,t,w[],r[]):
<div class="figure"> <div class="figure">
<graphics-element title="拉平一条二次曲线" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="quadratic"> <graphics-element title="拉平一条二次曲线" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\flattening\3deec756c96e53127cd1d615c61043ae.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\flattening\3deec756c96e53127cd1d615c61043ae.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="16" step="1" value="4" class="slide-control"> <input type="range" min="1" max="16" step="1" value="4" class="slide-control">
@@ -559,7 +559,7 @@ function RationalBezier(3,t,w[],r[]):
<graphics-element title="拉平一条三次曲线" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="cubic"> <graphics-element title="拉平一条三次曲线" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\flattening\e2bb7113d5cda2e3fd29bbc54fbe8841.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\flattening\e2bb7113d5cda2e3fd29bbc54fbe8841.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="24" step="1" value="8" class="slide-control"> <input type="range" min="1" max="24" step="1" value="8" class="slide-control">
@@ -595,7 +595,7 @@ function RationalBezier(3,t,w[],r[]):
<p>使用 de Casteljau 算法我们也可以将一条贝塞尔曲线分割成两条更小的曲线,二者拼接起来即可形成原来的曲线。当采用某个 <code>t</code> 值构造 de Casteljau 算法时,该过程会给到我们在 <code>t</code> 点分割曲线的所有点: 一条曲线包含该曲线上点之前的所有点,另一条曲线包含该曲线上点之后的所有点。</p> <p>使用 de Casteljau 算法我们也可以将一条贝塞尔曲线分割成两条更小的曲线,二者拼接起来即可形成原来的曲线。当采用某个 <code>t</code> 值构造 de Casteljau 算法时,该过程会给到我们在 <code>t</code> 点分割曲线的所有点: 一条曲线包含该曲线上点之前的所有点,另一条曲线包含该曲线上点之后的所有点。</p>
<graphics-element title="分割一条曲线" width="825" height="275" src="./chapters/splitting/splitting.js" > <graphics-element title="分割一条曲线" width="825" height="275" src="./chapters/splitting/splitting.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\splitting\bef2f09698c0d3d2b7c4c031be17ff69.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\splitting\bef2f09698c0d3d2b7c4c031be17ff69.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
@@ -628,7 +628,7 @@ function drawCurve(points[], t):
<section id="matrixsplit"> <section id="matrixsplit">
<h1><a href="zh-CN/index.html#matrixsplit">Splitting curves using matrices</a></h1> <h1><a href="zh-CN/index.html#matrixsplit">Splitting curves using matrices</a></h1>
<p>Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a>, we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic and cubic curves respectively: (we'll reverse the Bézier coefficients vector for legibility)</p> <p>Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a>, we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic and cubic curves respectively: (we'll reverse the Bézier coefficients vector for legibility)</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/77a11d65d7cffc4b84a85c4bec837792.svg" width="263px" height="55px" loading="lazy"> <script>console.log("LaTeX for 77a11d65d7cffc4b84a85c4bec837792 failed!");</script>
<p>and</p> <p>and</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy">
<p>Let's say we want to split the curve at some point <code>t = z</code>, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we separate out the actual "point on the curve" information into a new matrix multiplication:</p> <p>Let's say we want to split the curve at some point <code>t = z</code>, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we separate out the actual "point on the curve" information into a new matrix multiplication:</p>
@@ -718,7 +718,7 @@ 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> <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" > <graphics-element title="A variable-order Bézier curve" width="275" height="275" src="./chapters/reordering/reorder.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<button class="raise">raise</button> <button class="raise">raise</button>
@@ -793,12 +793,12 @@ treated as a sequence of three (elementary) shear operations. When we combine th
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" > <graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" > <graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -827,7 +827,7 @@ 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> <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" > <graphics-element title="Some known and unknown vectors" width="350" height="300" src="./chapters/pointvectors3d/frenet.js" >
<fallback-image> <fallback-image>
<img width="350px" height="300px" src="images\chapters\pointvectors3d\cbadec403b99dec015ab084ee10e1671.png" loading="lazy"> <img width="350px" height="300px" src="images\chapters\pointvectors3d\cbadec403b99dec015ab084ee10e1671.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -901,7 +901,7 @@ 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> <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" > <graphics-element title="Some known and unknown vectors" width="350" height="300" src="./chapters/pointvectors3d/rotation-minimizing.js" >
<fallback-image> <fallback-image>
<img width="350px" height="300px" src="images\chapters\pointvectors3d\9983328e50c2156c124bb1ea4ad5c05b.png" loading="lazy"> <img width="350px" height="300px" src="images\chapters\pointvectors3d\9983328e50c2156c124bb1ea4ad5c05b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -919,13 +919,13 @@ 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> <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"> <graphics-element title="Quadratic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\components\008604bc4c53bd7e0d97c99a67812ad1.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\components\008604bc4c53bd7e0d97c99a67812ad1.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="cubic"> <graphics-element title="Cubic Bézier curve components" width="825" height="275" src="./chapters/components/components.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\components\5214256129e6396e7ac1f1713fa9c88d.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\components\5214256129e6396e7ac1f1713fa9c88d.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1061,14 +1061,14 @@ 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> <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"> <graphics-element title="Quadratic Bézier curve extremities" width="825" height="275" src="./chapters/extremities/extremities.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\extremities\9850eec01924deae7fda4400ce44270d.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\extremities\9850eec01924deae7fda4400ce44270d.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<p>And for cubic curves, that means first and second derivatives, in red and purple respectively:</p> <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"> <graphics-element title="Cubic Bézier curve extremities" width="825" height="275" src="./chapters/extremities/extremities.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\extremities\890406c7bc96904224f8f14940bf3e56.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\extremities\890406c7bc96904224f8f14940bf3e56.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1086,12 +1086,12 @@ function getCubicRoots(pa, pb, pc, pd) {
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="quadratic"> <graphics-element title="Quadratic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\boundingbox\e2c621442e98e2cd20af7efe1cfb041f.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\boundingbox\e2c621442e98e2cd20af7efe1cfb041f.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="cubic"> <graphics-element title="Cubic Bézier bounding box" width="275" height="275" src="./chapters/boundingbox/bbox.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\boundingbox\f8989a62ebec9d6f123291c146caab5b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\boundingbox\f8989a62ebec9d6f123291c146caab5b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1114,12 +1114,12 @@ function getCubicRoots(pa, pb, pc, pd) {
<div class="figure"> <div class="figure">
<graphics-element title="Aligning a quadratic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="quadratic"> <graphics-element title="Aligning a quadratic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="550px" height="275px" src="images\chapters\aligning\b3ccd45a72c815388aee6515fe37a486.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\aligning\b3ccd45a72c815388aee6515fe37a486.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Aligning a cubic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="cubic"> <graphics-element title="Aligning a cubic curve" width="550" height="275" src="./chapters/aligning/aligning.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="550px" height="275px" src="images\chapters\aligning\31655f24b7dd8b8871687b6610d9ac0e.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\aligning\31655f24b7dd8b8871687b6610d9ac0e.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1132,12 +1132,12 @@ function getCubicRoots(pa, pb, pc, pd) {
<div class="figure"> <div class="figure">
<graphics-element title="Aligning a quadratic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="quadratic"> <graphics-element title="Aligning a quadratic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\tightbounds\ccc77ae1f57d7dd7ce4d5397fe1b140b.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\tightbounds\ccc77ae1f57d7dd7ce4d5397fe1b140b.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Aligning a cubic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="cubic"> <graphics-element title="Aligning a cubic curve" width="275" height="275" src="./chapters/tightbounds/tightbounds.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\tightbounds\419415bee6ffd7598c035c42de09a94f.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\tightbounds\419415bee6ffd7598c035c42de09a94f.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1177,7 +1177,7 @@ 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> <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" > <graphics-element title="Finding cubic Bézier curve inflections" width="275" height="275" src="./chapters/inflections/inflection.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\inflections\9e1ce3975100600d4979370851929b73.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\inflections\9e1ce3975100600d4979370851929b73.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1189,7 +1189,7 @@ 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> <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" > <graphics-element title="The canonical curve map" width="400" height="400" src="./chapters/canonical/canonical.js" >
<fallback-image> <fallback-image>
<img width="400px" height="400px" src="images\chapters\canonical\675217e6df3d75c86503dc2af623e14e.png" loading="lazy"> <img width="400px" height="400px" src="images\chapters\canonical\675217e6df3d75c86503dc2af623e14e.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1255,7 +1255,7 @@ 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> <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" > <graphics-element title="A cubic curve mapped to canonical form" width="800" height="400" src="./chapters/canonical/interactive.js" >
<fallback-image> <fallback-image>
<img width="800px" height="400px" src="images\chapters\canonical\344346f09b6d5d7e3ddf91084ad50d46.png" loading="lazy"> <img width="800px" height="400px" src="images\chapters\canonical\344346f09b6d5d7e3ddf91084ad50d46.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1266,7 +1266,7 @@ 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> <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" > <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> <fallback-image>
<img width="550px" height="275px" src="images\chapters\yforx\dd28d64458d22f4fe89c98568258efcb.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\yforx\dd28d64458d22f4fe89c98568258efcb.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" class="slide-control"> <input type="range" min="0" max="1" step="0.01" class="slide-control">
@@ -1293,7 +1293,7 @@ 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> <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" > <graphics-element title="Finding By(t), by finding t for a given x" width="275" height="275" src="./chapters/yforx/yforx.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\yforx\efcfe9b48ca4e65eef3d4bf3e4c97bc3.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\yforx\efcfe9b48ca4e65eef3d4bf3e4c97bc3.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" class="slide-control"> <input type="range" min="0" max="1" step="0.01" class="slide-control">
@@ -1315,17 +1315,17 @@ y = curve.get(t).y</code></pre>
<div class="figure"> <div class="figure">
<graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10"> <graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24"> <graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99"> <graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1348,7 +1348,7 @@ 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> <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" > <graphics-element title="Arc length for a Bézier curve" width="275" height="275" src="./chapters/arclength/arclength.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclength\195f9a3c60f8dfe977c6450d21968f69.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclength\195f9a3c60f8dfe977c6450d21968f69.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1361,7 +1361,7 @@ 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"> <graphics-element title="Approximate quadratic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="24" step="1" value="4" class="slide-control"> <input type="range" min="1" max="24" step="1" value="4" class="slide-control">
@@ -1369,7 +1369,7 @@ y = curve.get(t).y</code></pre>
<graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic"> <graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="1" max="32" step="1" value="8" class="slide-control"> <input type="range" min="1" max="32" step="1" value="8" class="slide-control">
@@ -1416,7 +1416,7 @@ 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> <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" > <graphics-element title="Matching curvatures for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\5fcfb0572cae06717506c84768aa568c.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\curvature\5fcfb0572cae06717506c84768aa568c.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1425,7 +1425,7 @@ 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> <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"> <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> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\876d7b2750d7c29068ac6181c3634d25.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\curvature\876d7b2750d7c29068ac6181c3634d25.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control"> <input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control">
@@ -1440,7 +1440,7 @@ 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> <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" > <graphics-element title="The t-for-distance function" width="550" height="275" src="./chapters/tracing/distance-function.js" >
<fallback-image> <fallback-image>
<img width="550px" height="275px" src="images\chapters\tracing\52f815cefe99dabc47ca83d0b97b61fc.png" loading="lazy"> <img width="550px" height="275px" src="images\chapters\tracing\52f815cefe99dabc47ca83d0b97b61fc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1448,7 +1448,7 @@ y = curve.get(t).y</code></pre>
<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> <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" > <graphics-element title="Fixed-interval coloring a curve" width="825" height="275" src="./chapters/tracing/tracing.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\tracing\133bf9d02801a3149c9ddb8b313e6797.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\tracing\133bf9d02801a3149c9ddb8b313e6797.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="2" max="24" step="1" value="8" class="slide-control"> <input type="range" min="2" max="24" step="1" value="8" class="slide-control">
@@ -1467,7 +1467,7 @@ 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> <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" > <graphics-element title="Line/line intersections" width="275" height="275" src="./chapters/intersections/line-line.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\intersections\61876a2bd727df377619c5ad34ce86be.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\intersections\61876a2bd727df377619c5ad34ce86be.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
@@ -1499,12 +1499,12 @@ lli = function(line1, line2):
<div class="figure"> <div class="figure">
<graphics-element title="Quadratic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="quadratic"> <graphics-element title="Quadratic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="quadratic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\intersections\594c2df534a1736c03cd3a96ff4a9913.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\intersections\594c2df534a1736c03cd3a96ff4a9913.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<graphics-element title="Cubic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="cubic"> <graphics-element title="Cubic curve/line intersections" width="275" height="275" src="./chapters/intersections/curve-line.js" data-type="cubic">
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\intersections\dc26a6063dadc31d242f1c1c8f38bb5e.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\intersections\dc26a6063dadc31d242f1c1c8f38bb5e.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</div> </div>
@@ -1531,7 +1531,7 @@ lli = function(line1, line2):
<p>(can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p> <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" > <graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
<fallback-image> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control"> <input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
@@ -1544,7 +1544,7 @@ lli = function(line1, line2):
</section> </section>
<section id="abc"> <section id="abc">
<h1><a href="zh-CN/index.html#abc">The projection identity</a></h1> <h1><a href="zh-CN/index.html#abc">The projection identity</a></h1>
<p>De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mould" a curve, by picking it up at some point, and dragging that point around to change the curve's shape.</p> <p>De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mold" a curve, by picking it up at some point, and dragging that point around to change the curve's shape.</p>
<p>How does that work? Succinctly: we run de Casteljau's algorithm in reverse!</p> <p>How does that work? Succinctly: we run de Casteljau's algorithm in reverse!</p>
<p>In order to run de Casteljau's algorithm in reverse, we need a few basic things: a start and end point, a point on the curve that want to be moving around, which has an associated <em>t</em> value, and a point we've not explicitly talked about before, and as far as I know has no explicit name, but lives one iteration higher in the de Casteljau process then our on-curve point does. I like to call it "A" for reasons that will become obvious.</p> <p>In order to run de Casteljau's algorithm in reverse, we need a few basic things: a start and end point, a point on the curve that want to be moving around, which has an associated <em>t</em> value, and a point we've not explicitly talked about before, and as far as I know has no explicit name, but lives one iteration higher in the de Casteljau process then our on-curve point does. I like to call it "A" for reasons that will become obvious.</p>
<p>So let's use graphics instead of text to see where this "A" is, because text only gets us so far: move the sliders for the following graphics to see what, given specific <code>t</code> value, our <code>A</code> coordinate is. As well as some other coordinates, which taken together let us derive a value that the graphics call "ratio": if you move the curve's points around, A, B, and C will move, what happens to that value?</p> <p>So let's use graphics instead of text to see where this "A" is, because text only gets us so far: move the sliders for the following graphics to see what, given specific <code>t</code> value, our <code>A</code> coordinate is. As well as some other coordinates, which taken together let us derive a value that the graphics call "ratio": if you move the curve's points around, A, B, and C will move, what happens to that value?</p>
@@ -1552,14 +1552,14 @@ 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"> <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> <fallback-image>
<img width="275px" height="275px" src="images\chapters\abc\6e40975c21e70b73954a4dce02b9ba75.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\abc\7a69dd4350ddda5701712e1d3b46b863.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
</graphics-element> </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"> <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> <fallback-image>
<img width="275px" height="275px" src="images\chapters\abc\d744a4955a3ff4e2d85760887ea923d4.png" loading="lazy"> <img width="275px" height="275px" src="images\chapters\abc\eeec7cf16fb22c666e0143a3a030731f.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
@@ -1572,6 +1572,8 @@ lli = function(line1, line2):
<li>a point at the tip of the curve construction's "hat": let's call that <code>A</code>, as well as</li> <li>a point at the tip of the curve construction's "hat": let's call that <code>A</code>, as well as</li>
<li>our on-curve point give our chosen <code>t</code> value: let's call that <code>B</code>, and finally,</li> <li>our on-curve point give our chosen <code>t</code> value: let's call that <code>B</code>, and finally,</li>
<li>a point that we get by projecting A, through B, onto the line between the curve's start and end points: let's call that <code>C</code>.</li> <li>a point that we get by projecting A, through B, onto the line between the curve's start and end points: let's call that <code>C</code>.</li>
<li>for both qudratic and cubic curves, two points <code>e1</code> and <code>e2</code>, which represent the single-to-last step in de Casteljau's algorithm: in the last step, we find <code>B</code> at <code>(1-t) * e1 + t * e2</code>.</li>
<li>for cubic curves, also the points <code>v1</code> and <code>v2</code>, which together with <code>A</code> represent the first step in de Casteljau's algorithm: in the next step, we find <code>e1</code> and <code>e2</code>.</li>
</ol> </ol>
<p>These three values A, B, and C allow us to derive an important identity formula for quadratic and cubic Bézier curves: for any point on the curve with some <code>t</code> value, the ratio of distances from A to B and B to C is fixed: if some <code>t</code> value sets up a C that is 20% away from the start and 80% away from the end, then <em>it doesn't matter where the start, end, or control points are</em>; for that <code>t</code> value, <code>C</code> will <em>always</em> lie at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic and then move all the other points around: if you only move the control points, start and end won't move, and so neither will C, and if you move either start or end point, C will move but its relative position will not change.</p> <p>These three values A, B, and C allow us to derive an important identity formula for quadratic and cubic Bézier curves: for any point on the curve with some <code>t</code> value, the ratio of distances from A to B and B to C is fixed: if some <code>t</code> value sets up a C that is 20% away from the start and 80% away from the end, then <em>it doesn't matter where the start, end, or control points are</em>; for that <code>t</code> value, <code>C</code> will <em>always</em> lie at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic and then move all the other points around: if you only move the control points, start and end won't move, and so neither will C, and if you move either start or end point, C will move but its relative position will not change.</p>
<p>So, how can we compute <code>C</code>? We start with our observation that <code>C</code> always lies somewhere between the start and ends points, so logically <code>C</code> will have a function that interpolates between those two coordinates:</p> <p>So, how can we compute <code>C</code>? We start with our observation that <code>C</code> always lies somewhere between the start and ends points, so logically <code>C</code> will have a function that interpolates between those two coordinates:</p>
@@ -1589,12 +1591,60 @@ lli = function(line1, line2):
<img class="LaTeX SVG" src="./images/chapters/abc/b4987e9b77b0df604238b88596c5f7c3.svg" width="228px" height="41px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/abc/b4987e9b77b0df604238b88596c5f7c3.svg" width="228px" height="41px" loading="lazy">
<p>Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a <code>t</code> value, we can <em>contruct</em> curves: we can compute <code>C</code> using the start and end points, and our <code>u(t)</code> function, and once we have <code>C</code>, we can use our on-curve point (<code>B</code>) and the <code>ratio(t)</code> function to find <code>A</code>:</p> <p>Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a <code>t</code> value, we can <em>contruct</em> curves: we can compute <code>C</code> using the start and end points, and our <code>u(t)</code> function, and once we have <code>C</code>, we can use our on-curve point (<code>B</code>) and the <code>ratio(t)</code> function to find <code>A</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/abc/12aaf0d7fd20b3c551a0ec76b18bd7d2.svg" width="231px" height="39px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/abc/12aaf0d7fd20b3c551a0ec76b18bd7d2.svg" width="231px" height="39px" loading="lazy">
<p>So: if we have a curve's start and end point, then for any <code>t</code> value, we implicitly know all the ABC values, which gives us the necessary information to reconstruct a curve's "de Casteljau skeleton". Which means that we can now do several things: we can "fit" curves using only three points, which means we can also "mould" curves by moving an on-curve point but leaving its start and end point, and then reconstructing the curve based on where we moved the on-curve point to. These are very useful things, and we'll look at both in the next sections.</p> <p>With <code>A</code> found, finding <code>e1</code> and <code>e2</code> for quadratic curves is a matter of running the linear interpolation with <code>t</code> between start and <code>A</code> to yield <code>e1</code>, and between <code>A</code> and end to yield <code>e2</code>. For cubic curves, there is no single pair of points that can act as <code>e1</code> and <code>e2</code>: as long as the distance ratio between <code>e1</code> to <code>B</code> and <code>B</code> to <code>e2</code> is the Bézier ratio <code>(1-t):t</code>, we can reverse engineer <code>v1</code> and <code>v2</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/abc/bc245327e0b011712168bad1c48dfec4.svg" width="139px" height="75px" loading="lazy">
<p>And then reverse engineer the curve's control control points:</p>
<img class="LaTeX SVG" src="./images/chapters/abc/3c696e0364d61b1391695342707d6ccc.svg" width="177px" height="72px" loading="lazy">
<p>So: if we have a curve's start and end point, then for any <code>t</code> value we implicitly know all the ABC values, which (combined with an educated guess on appropriate <code>e1</code> and <code>e2</code> coordinates for cubic curves) gives us the necessary information to reconstruct a curve's "de Casteljau skeleton". Which means that we can now do several things: we can "fit" curves using only three points, which means we can also "mold" curves by moving an on-curve point but leaving its start and end point, and then reconstructing the curve based on where we moved the on-curve point to. These are very useful things, and we'll look at both in the next few sections.</p>
</section>
<section id="pointcurves">
<h1><a href="zh-CN/index.html#pointcurves">Creating a curve from three points</a></h1>
<p>Given the preceding section, you might be wondering if we can use that knowledge to just "create" curves by placing some points and having the computer do the rest, to which the answer is: that's exactly what we can now do!</p>
<p>For quadratic curves, things are pretty easy. Technically, we'll need a <code>t</code> value in order to compute the ratio function used in computing the ABC coordinates, but we can just as easily approximate one by treating the distance between the start and <code>B</code> point, and <code>B</code> and end point as a ratio, using</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/3c7516c16a5dea95df741f4263cecd1c.svg" width="132px" height="85px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\067a3df30e32708fc0d13f8eb78c0b05.png">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\173ea31517a72a927d561f121f0677db.png">
Scripts are disabled. Showing fallback image.
</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>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/55d4f7ed095dfea8f9772208abc83b51.svg" width="147px" height="49px" loading="lazy">
<p>Where <code>d</code> is the total length of the line segment from <code>e1</code> to <code>e2</code>. So how long do we make that? There are again all kinds of approaches we can take, and a simple-but-effective one is to set the length of that segment to "one third the length of the baseline". This forces <code>e1</code> and <code>e2</code> to always be the "linear curve" distance apart, which means if we place our three points on a line, it will actually <em>look</em> like a line. Nice! The last thing we'll need to do is make sure to flip the sign of <code>d</code> depending on which side of the baseline our <code>B</code> is located, so we don't up creating a funky curve with a loop in it. To do this, we can use the <a href="https://en.wikipedia.org/wiki/Atan2">atan2</a> function:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/9203537b7dca98ebb2d7017c76100fde.svg" width="507px" height="21px" loading="lazy">
<p>This angle φ will be between 0 and π if <code>B</code> is "above" the baseline (rotating all three points so that the start is on the left and the end is the right), so we can use a relatively straight forward check to make sure we're using the correct sign for our value <code>d</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/6f0e2b6494d7dae2ea79a46a499d7ed4.svg" width="183px" height="49px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\8d045d352f5017b65e60620b92d7ae29.png">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\eab6ea46fa93030e03ec0ef7deb571dc.png">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>That looks perfectly servicable!</p>
<p>Of course, we can take this one step further: we can't just "create" curves, we also have (almost!) all the tools available to "mold" curves, where we can reshape a curve by dragging a point on the curve around while leaving the start and end fixed, effectively molding the shape as if it were clay or the like. We'll see the last tool we need to do that in the next section, and then we'll look at implementing curve molding in the section after that, so read on!</p>
</section> </section>
<section id="projections"> <section id="projections">
<h1><a href="zh-CN/index.html#projections">Projecting a point onto a Bézier curve</a></h1> <h1><a href="zh-CN/index.html#projections">Projecting a point onto a Bézier curve</a></h1>
<p>Before we can move on to actual curve moulding, it'll be good if know how to actually be able to find "some point on the curve" that we're trying to click on. After all, if all we have is our Bézier coordinates, that is not in itself enough to figure out which point on the curve our cursor will be closest to. So, how do we project points onto a curve?</p> <p>Before we can move on to actual curve molding, it'll be good if know how to actually be able to find "some point on the curve" that we're trying to click on. After all, if all we have is our Bézier coordinates, that is not in itself enough to figure out which point on the curve our cursor will be closest to. So, how do we project points onto a curve?</p>
<p>If the Bézier curve is of low enough order, we might be able to <a href="https://web.archive.org/web/20140713004709/http://jazzros.blogspot.com/2011/03/projecting-point-on-bezier-curve.html">work out the maths for how to do this</a>, and get a perfect <code>t</code> value back, but in general this is an incredibly hard problem and the easiest solution is, really, a numerical approach again. We'll be finding our ideal <code>t</code> value using a <a href="https://en.wikipedia.org/wiki/Binary_search_algorithm">binary search</a>. First, we do a coarse distance-check based on <code>t</code> values associated with the curve's "to draw" coordinates (using a lookup table, or LUT). This is pretty fast:</p> <p>If the Bézier curve is of low enough order, we might be able to <a href="https://web.archive.org/web/20140713004709/http://jazzros.blogspot.com/2011/03/projecting-point-on-bezier-curve.html">work out the maths for how to do this</a>, and get a perfect <code>t</code> value back, but in general this is an incredibly hard problem and the easiest solution is, really, a numerical approach again. We'll be finding our ideal <code>t</code> value using a <a href="https://en.wikipedia.org/wiki/Binary_search_algorithm">binary search</a>. First, we do a coarse distance-check based on <code>t</code> values associated with the curve's "to draw" coordinates (using a lookup table, or LUT). This is pretty fast:</p>
<pre><code>p = some point to project onto the curve <pre><code>p = some point to project onto the curve
d = some initially huge value d = some initially huge value
@@ -1613,104 +1663,45 @@ 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> <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" > <graphics-element title="Projecting a point onto a Bézier curve" width="320" height="320" src="./chapters/projections/project.js" >
<fallback-image> <fallback-image>
<img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png" loading="lazy"> <img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
</section> </section>
<section id="moulding"> <section id="molding">
<h1><a href="zh-CN/index.html#moulding">Manipulating a curve</a></h1> <h1><a href="zh-CN/index.html#molding">Molding a curve</a></h1>
<p>Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the <em>t</em>-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.</p> <p>Armed with knowledge of the "ABC" relation, point-on-curve projection, and guestimating reasonable looking helper values for cubic curve construction, we can finally cover curve molding: updating a curve's shape interactively, by dragging points on the curve around.</p>
<graphics-element title="Moulding a quadratic Bézier curve" width="825" height="275" src="./chapters/moulding/moulding.js" data-type="quadratic"> <p>For quadratic curve, this is a really simple trick: we project our cursor onto the curve, which gives us a <code>t</code> value and initial <code>B</code> coordinate. We don't even need the latter: with our <code>t</code> value and "whever the cursor is" as target <code>B</code>, we can compute the associated <code>C</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/molding/079d318ad693b6b17413a91f5de06be8.svg" width="256px" height="21px" loading="lazy">
<p>And then the associated <code>A</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/molding/82a99caec5f84fb26dce28277377c041.svg" width="244px" height="40px" loading="lazy">
<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> <fallback-image>
<img width="825px" height="275px" src="images\chapters\moulding\adc7e0785dade356b62fadcd903e73d9.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\molding\6b91671c2962530b863ae0da5789a9cc.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element> </fallback-image></graphics-element>
<p>Click-dragging a point on the curve shows what we're using to compute the new coordinates: while dragging you will see the original point <code>B</code> and its corresponding <i>t</i>-value, and the original points <code>A</code> and <code>C</code> for that <i>t</i>-value, in light coloring, as well as the new <code>A'</code>, <code>B'</code>, and <code>C'</code> (although of course the <code>C</code> coordinates are the same ones, because that's the defining feature of point <code>C</code>) based on where you're dragging point <code>B</code> to, in purple.</p> <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>Since we know the new point <code>B'</code>, and the "new" point <code>C'</code> as well as the <code>t</code> value, we know our new point A' has to be:</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>
<img class="LaTeX SVG" src="./images/chapters/moulding/7159e1e6eeab38c5aba225fe7553dbe6.svg" width="247px" height="39px" loading="lazy"> <graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic">
<p>For quadratic curves, this means we're done, since the new point <code>A'</code> is equivalent to the new quadratic control point.</p>
<p>For cubic curves, we need to do a little more work, because while computing a new <code>A'</code> is exactly the same as before, we're not quite done once we've done so. For cubic curves, <code>B</code> has not just an associated <code>t</code> value, but also two associated "side" values. Let's revisit the graphic from the chapter on de Casteljau's algorithm, to see what we mean:</p>
<graphics-element title="The information necessary to manipulate cubic curves" width="275" height="275" src="./chapters/moulding/decasteljau.js" >
<fallback-image> <fallback-image>
<img width="275px" height="275px" src="images\chapters\moulding\fbc7d78dd768eec4314eb4b5320f2ce5.png" loading="lazy"> <img width="825px" height="275px" src="images\chapters\molding\522f1edd37163772b81acb86d3a4f423.png">
Scripts are disabled. Showing fallback image.
</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>
<img width="825px" height="275px" src="images\chapters\molding\ea1656c068a60631135ad499e8a29453.png">
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
</fallback-image> </fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control"> <input type="range" min="10" max="200" step="1" value="100" class="slide-control">
</graphics-element> </graphics-element>
<p>In addition to the <code>A</code>, <code>B</code>, and <code>C</code> values, we also see the points <code>e1</code> and <code>e2</code>, without which constructing our de Casteljau "strut lines" becomes very difficult indeed; as well as the points <code>v1</code> and <code>v2</code>, which we can construct when we know our ABC values enriched with <code>e1</code> and <code>e2</code>:</p> <p>The slide controls the "falloff distance" relative to where the original point on the curve is, so that as we drag our point around, it interpolates with a bias towards "preserving <code>t</code>/<code>e1</code>/<code>e2</code>" closer to the original point, and bias towards "idealised" form the further away we move our point, with anything that's further than our falloff distance simply <em>being</em> the idealised curve. We don't even try to interpolate at that point.</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/bc245327e0b011712168bad1c48dfec4.svg" width="139px" height="75px" loading="lazy"> <p>A more advanced way to try to smooth things out is to implement <em>continuous</em> molding, where we constantly update the curve as we move around, and constantly change what our <code>B</code> point is, based on constantly projecting the cursor on the curve <em>as we're updating it</em> - this is, you won't be surprised to learn, tricky, and beyond the scope of this section: interpolation (with a reasonable distance) will do for now!</p>
<p>After which computing the new control points is straight-forward:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/3c696e0364d61b1391695342707d6ccc.svg" width="177px" height="72px" loading="lazy">
<p>So let's put that into practice:</p>
<graphics-element title="Moulding a cubic Bézier curve" width="825" height="275" src="./chapters/moulding/moulding.js" data-type="cubic">
<fallback-image>
<img width="825px" height="275px" src="images\chapters\moulding\b9928928e1f622e66b59e4a59cfac925.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>So that looks pretty good, but you may not like having <code>e1</code> and <code>e2</code> stay the same distances away from <code>B'</code> while moving the point around, and want to rearrange those to lead to "cleaner looking" curve manipulation. Unfortunately, there are so many differen ways in which we can do this that figuring out "good looking" alternatives, given what the curve is being manipulated for, could be an entire book on its own... so we're only going to look at one way that you might effect alternative <code>e1</code> and <code>e2</code> points, based on the idea of rotating a vector.</p>
<p>If we treat point <code>B</code> as a "a vector originating at <code>C</code>" then we can treat the points <code>e1</code> and <code>e2</code> as offets (let's call these <code>d1</code> and <code>d2</code>) of that vector, where:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/65c9a6f8a210d41b18f20e4da1ba1403.svg" width="95px" height="49px" loading="lazy">
<p>Which means that:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/28e90ffa101453e4c030174d36d185a5.svg" width="96px" height="49px" loading="lazy">
<p>Now, if we now <code>B</code> to some new coordinate <code>B'</code> we can treat that "moving of the coordinate" as a rotation and scaling of the vector for <code>B</code> instead. If the new point <code>B'</code> is the same distance away from <code>C</code> as <code>B</code> was, this is a pure rotation, but otherwise the length of the vector has decreased or increased by some factor.</p>
<p>We can use both those values to change where <code>e1</code> and <code>e2</code> end up, and thus how our curve moulding "feels", by placing new <code>e1'</code> and <code>e2'</code> where:</p>
<img class="LaTeX SVG" src="./images/chapters/moulding/54e423d1eb61157abd3acffc5271c3ac.svg" width="465px" height="67px" loading="lazy">
<p>Here, the <code>rotate()</code> function rotates a vector (in this case <code>d1</code> or <code>d2</code>) around some point (in this case, <code>B'</code>), by some angle (in this case, the angle by which we rotated our original <code>B</code> to become <code>B'</code>). So what does <em>that</em> look like?</p>
<graphics-element title="Moulding a cubic Bézier curve" width="825" height="275" src="./chapters/moulding/moulding.js" data-type="cubic" data-alternative="true">
<fallback-image>
<img width="825px" height="275px" src="images\chapters\moulding\db817c3899e954da6c882e4699a49353.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>As you can see, this is both better, and worse, depending on what you're trying to do with the curve, and there are many different ways in which you can try to change <code>e1</code> and <code>e2</code> such that they behave "as users would expect them to" based on the context in which you're implementing curve moulding. You might want to add reflections when <code>B'</code> crosses the baseline, or even some kind of weight-swapping when <code>B'</code> crosses the midline (perpendicular to the baseline, at its mid point), and instead of scaling both points with respects to <code>C</code>, you might want to scale them to coordinates 1/2rd and 2/3rd along the baseline, etc. etc.</p>
<p>There are too many options to go over here, so: the best behaviour is, of course, the behaviour <em>you</em> think is best, and it might be a lot of work to find that and/or implement that!</p>
</section>
<section id="pointcurves">
<h1><a href="zh-CN/index.html#pointcurves">Creating a curve from three points</a></h1>
<p>Given the preceding section on curve manipulation, we can also generate quadratic and cubic curves from any three points, although</p>
<p>For quadratic curves, things are pretty easy: technically we need a <code>t</code> value in order to compute the ratio function used in computing the ABC coordinates, but we can just as easily approximate one by treating the distance between the start and <code>B</code> point, and <code>B</code> and end point as a ratio, using</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/3c7516c16a5dea95df741f4263cecd1c.svg" width="132px" height="85px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\067a3df30e32708fc0d13f8eb78c0b05.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\93ee12566f93f241ad0970acd5505367.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</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>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/55d4f7ed095dfea8f9772208abc83b51.svg" width="147px" height="49px" loading="lazy">
<p>Where <code>d</code> is the total length of the line segment from <code>e1</code> to <code>e2</code>. So how long do we make that? There are again all kinds of approaches we can take, and a simple-but-effective one is to set the length of that segment to "one third the length of the baseline". This forces <code>e1</code> and <code>e2</code> to always be the "linear curve" distance apart, which means if we place our three points on a line, it will actually <em>look</em> like a line. Nice! The last thing we'll need to do is make sure to flip the sign of <code>d</code> depending on which side of the baseline our <code>B</code> is located, so we don't up creating a funky curve with a loop in it. To do this, we can use the <a href="https://en.wikipedia.org/wiki/Atan2">atan2</a> function:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/b4464f73fb2f79027d8e971fc66813f6.svg" width="393px" height="19px" loading="lazy">
<p>This angle φ will be between 0 and π if <code>B</code> is "above" the baseline (rotating all three points so that the start is on the left and the end is the right), so we can use a relatively straight forward check to make sure we're using the correct sign for our value <code>d</code>:</p>
<img class="LaTeX SVG" src="./images/chapters/pointcurves/6f0e2b6494d7dae2ea79a46a499d7ed4.svg" width="183px" height="49px" loading="lazy">
<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>
<img width="275px" height="275px" src="images\chapters\pointcurves\8b0e8e79ba09916a9af27b33f8a1919f.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</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>
<img width="275px" height="275px" src="images\chapters\pointcurves\eab6ea46fa93030e03ec0ef7deb571dc.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>That looks perfectly servicable!</p>
</section> </section>
<section id="curvefitting"> <section id="curvefitting">
@@ -1877,8 +1868,8 @@ for (coordinate, index) in LUT:
<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="284px" height="77px" loading="lazy">
</section> </section>
<section id="catmullmoulding"> <section id="catmullmolding">
<h1><a href="zh-CN/index.html#catmullmoulding">Creating a Catmull-Rom curve from three points</a></h1> <h1><a href="zh-CN/index.html#catmullmolding">Creating a Catmull-Rom curve from three points</a></h1>
<p>Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead?</p> <p>Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead?</p>
<p>As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve.</p> <p>As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve.</p>
<p>Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> points (and that's without double-counting points that are shared by consecutive cubic curves).</p> <p>Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> points (and that's without double-counting points that are shared by consecutive cubic curves).</p>
@@ -2042,7 +2033,7 @@ for (coordinate, index) in LUT:
<p>which we can then substitute in the expression for <em>a</em>:</p> <p>which we can then substitute in the expression for <em>a</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy">
<p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p> <p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/fe32474b4616ee9478e1308308f1b6bf.svg" width="188px" height="32px" loading="lazy"> <script>console.log("LaTeX for fe32474b4616ee9478e1308308f1b6bf failed!");</script>
<p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p> <p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy"> <img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy">
<p>Which, worked out for the x and y components, gives:</p> <p>Which, worked out for the x and y components, gives:</p>

View File

@@ -95,7 +95,7 @@ async function preprocessGraphicsElement(chapter, localeStrings, markdown) {
const replacement = `width="${width}" height="${height}" src="${src}" ${remainder}> const replacement = `width="${width}" height="${height}" src="${src}" ${remainder}>
<fallback-image> <fallback-image>
<img width="${width}px" height="${height}px" src="${imgUrl}" loading="lazy"> <img width="${width}px" height="${height}px" src="${imgUrl}">
${translate`disabledMessage`} ${translate`disabledMessage`}
</fallback-image>`; </fallback-image>`;