some polish
@ -35,7 +35,7 @@ So, how can we compute `C`? We start with our observation that `C` always lies s
|
||||
C = u(t) \cdot P_{start} + (1-u(t)) \cdot P_{end}
|
||||
\]
|
||||
|
||||
If we can figure out what the function `u(t)` looks like, we'll be done. Although we do need to remember that this `u(t)` will have a different for depending on whether we're working with quadratic or cubic curves. [Running through the maths](http://mathoverflow.net/questions/122257/finding-the-formula-for-Bézier-curve-ratios-hull-point-point-baseline) (with thanks to Boris Zbarsky) shows us the following two formulae:
|
||||
If we can figure out what the function `u(t)` looks like, we'll be done. Although we do need to remember that this `u(t)` will have a different for depending on whether we're working with quadratic or cubic curves. [Running through the maths](https://mathoverflow.net/questions/122257/finding-the-formula-for-bezier-curve-ratios-hull-point-point-baseline) (with thanks to Boris Zbarsky) shows us the following two formulae:
|
||||
|
||||
\[
|
||||
u(t)_{quadratic} = \frac{(1-t)^2}{t^2 + (1-t)^2}
|
||||
|
@ -12,11 +12,11 @@ or, more commonly written using Leibnitz notation as:
|
||||
length = \int_{0}^{z}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
|
||||
\]
|
||||
|
||||
This formula says that the length of a parametric curve is in fact equal to the **area** underneath a function that looks a remarkable amount like Pythagoras' rule for computing the diagonal of a straight angled triangle. This sounds pretty simple, right? Sadly, it's far from simple... cutting straight to after the chase is over: for quadratic curves, this formula generates an [unwieldy computation](http://www.wolframalpha.com/input/?i=antiderivative+for+sqrt((2*(1-t)*t*B+%2B+t%5E2*C)%27%5E2+%2B+(2*(1-t)*t*E)%27%5E2)&incParTime=true), and we're simply not going to implement things that way. For cubic Bézier curves, things get even more fun, because there is no "closed form" solution, meaning that due to the way calculus works, there is no generic formula that allows you to calculate the arc length. Let me just repeat this, because it's fairly crucial: ***for cubic and higher Bézier curves, there is no way to solve this function if you want to use it "for all possible coordinates"***.
|
||||
This formula says that the length of a parametric curve is in fact equal to the **area** underneath a function that looks a remarkable amount like Pythagoras' rule for computing the diagonal of a straight angled triangle. This sounds pretty simple, right? Sadly, it's far from simple... cutting straight to after the chase is over: for quadratic curves, this formula generates an [unwieldy computation](https://www.wolframalpha.com/input/?i=antiderivative+for+sqrt((2*(1-t)*t*B+%2B+t%5E2*C)%27%5E2+%2B+(2*(1-t)*t*E)%27%5E2)&incParTime=true), and we're simply not going to implement things that way. For cubic Bézier curves, things get even more fun, because there is no "closed form" solution, meaning that due to the way calculus works, there is no generic formula that allows you to calculate the arc length. Let me just repeat this, because it's fairly crucial: ***for cubic and higher Bézier curves, there is no way to solve this function if you want to use it "for all possible coordinates"***.
|
||||
|
||||
Seriously: [It cannot be done](https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem).
|
||||
|
||||
So we turn to numerical approaches again. The method we'll look at here is the [Gauss quadrature](http://www.youtube.com/watch?v=unWguclP-Ds&feature=BFa&list=PLC8FC40C714F5E60F&index=1). This approximation is a really neat trick, because for any *n<sup>th</sup>* degree polynomial it finds approximated values for an integral really efficiently. Explaining this procedure in length is way beyond the scope of this page, so if you're interested in finding out why it works, I can recommend the University of South Florida video lecture on the procedure, linked in this very paragraph. The general solution we're looking for is the following:
|
||||
So we turn to numerical approaches again. The method we'll look at here is the [Gauss quadrature](https://www.youtube.com/watch?v=unWguclP-Ds&feature=BFa&list=PLC8FC40C714F5E60F&index=1). This approximation is a really neat trick, because for any *n<sup>th</sup>* degree polynomial it finds approximated values for an integral really efficiently. Explaining this procedure in length is way beyond the scope of this page, so if you're interested in finding out why it works, I can recommend the University of South Florida video lecture on the procedure, linked in this very paragraph. The general solution we're looking for is the following:
|
||||
|
||||
\[
|
||||
\int_{-1}^{1}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
|
||||
@ -73,7 +73,7 @@ Note that one requirement for the approach we'll use is that the integral must r
|
||||
|
||||
That may look a bit more complicated, but the fraction involving *z* is a fixed number, so the summation, and the evaluation of the *f(t)* values are still pretty simple.
|
||||
|
||||
So, what do we need to perform this calculation? For one, we'll need an explicit formula for *f(t)*, because that derivative notation is handy on paper, but not when we have to implement it. We'll also need to know what these *C<sub>i</sub>* and *t<sub>i</sub>* values should be. Luckily, that's less work because there are actually many tables available that give these values, for any *n*, so if we want to approximate our integral with only two terms (which is a bit low, really) then [these tables](legendre-gauss.html) would tell us that for *n=2* we must use the following values:
|
||||
So, what do we need to perform this calculation? For one, we'll need an explicit formula for *f(t)*, because that derivative notation is handy on paper, but not when we have to implement it. We'll also need to know what these *C<sub>i</sub>* and *t<sub>i</sub>* values should be. Luckily, that's less work because there are actually many tables available that give these values, for any *n*, so if we want to approximate our integral with only two terms (which is a bit low, really) then [these tables](./legendre-gauss.html) would tell us that for *n=2* we must use the following values:
|
||||
|
||||
\[
|
||||
\begin{array}{l}
|
||||
|
@ -59,7 +59,7 @@ And this function finally has a straight up evaluation: if a `t` value lies with
|
||||
|
||||
We can, yes.
|
||||
|
||||
People far smarter than us have looked at this work, and two in particular — [Maurice Cox](http://www.npl.co.uk/people/maurice-cox) and [Carl de Boor](https://en.wikipedia.org/wiki/Carl_R._de_Boor) — came to a mathematically pleasing solution: to compute a point P(t), we can compute this point by evaluating *d(t)* on a curve section between knots *i* and *i+1*:
|
||||
People far smarter than us have looked at this work, and two in particular — [Maurice Cox](https://www.npl.co.uk/people/maurice-cox) and [Carl de Boor](https://en.wikipedia.org/wiki/Carl_R._de_Boor) — came to a mathematically pleasing solution: to compute a point P(t), we can compute this point by evaluating *d(t)* on a curve section between knots *i* and *i+1*:
|
||||
|
||||
\[
|
||||
d^k_i(t) = \alpha_{i,k} \cdot d^{k-1}_i(t) + (1-\alpha_{i,k}) \cdot d^{k-1}_{i-1}(t)
|
||||
@ -135,21 +135,9 @@ That is, we compute `d(3,3)` as a mixture of `d(2,3)` and `d(2,2)`, where those
|
||||
|
||||
One thing we need to keep in mind is that we're working with a spline that is constrained by its control points, so even though the `d(..., k)` values are zero or one at the lowest level, they are really "zero or one, times their respective control point", so in the next section you'll see the algorithm for running through the computation in a way that starts with a copy of the control point vector and then works its way up to that single point, rather than first starting "on the left", working our way "to the right" and then summing back up "to the left". We can just start on the right and work our way left immediately.
|
||||
|
||||
<!--
|
||||
## Cool, cool... but I don't know what to do with that information
|
||||
|
||||
I know, this is pretty mathy, so let's have a look at what happens when we change parameters here. We can't change the maths for the interpolation functions, so that gives us only one way to control what happens here: the knot vector itself. As such, let's look at the graph that shows the interpolation functions for a cubic B-Spline with seven points with a uniform knot vector (so we see seven identical functions), representing how much each point (represented by one function each) influences the total curvature, given our knot values. And, because exploration is the key to discovery, let's make the knot vector a thing we can actually manipulate (you will notice that knots are constrained in their value: any knot must strictly be equal to, or greater than, the previous value).
|
||||
|
||||
<graphics-element title="Visualising relative interpolation strengths" width="600" height="300" src="./interpolation.js"></graphics-element>
|
||||
|
||||
Changing the values in the knot vector changes how much each point influences the total curvature (with some clever knot value manipulation, we can even make the influence of certain points disappear entirely!), so we can see that while the control points define the hull inside of which we're going to be drawing a curve, it is actually the knot vector that determines the actual *shape* of the curve inside that hull.
|
||||
|
||||
After reading the rest of this section you may want to come back here to try some specific knot vectors, and see if the resulting interpolation landscape makes sense given what you will now think should happen!
|
||||
-->
|
||||
|
||||
## Running the computation
|
||||
|
||||
Unlike the de Casteljau algorithm, where the `t` value stays the same at every iteration, for B-Splines that is not the case, and so we end having to (for each point we evaluate) run a fairly involving bit of recursive computation. The algorithm is discussed on [this Michigan Tech](http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/de-Boor.html) page, but an easier to read version is implemented by [b-spline.js](https://github.com/thibauts/b-spline/blob/master/index.js#L59-L71), so we'll look at its code.
|
||||
Unlike the de Casteljau algorithm, where the `t` value stays the same at every iteration, for B-Splines that is not the case, and so we end having to (for each point we evaluate) run a fairly involving bit of recursive computation. The algorithm is discussed on [this Michigan Tech](https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/de-Boor.html) page, but an easier to read version is implemented by [b-spline.js](https://github.com/thibauts/b-spline/blob/master/index.js#L59-L71), so we'll look at its code.
|
||||
|
||||
Given an input value `t`, we first map the input to a value from the domain [0,1] to the domain [knots[degree], knots[knots.length - 1 - degree]. Then, we find the section number `s` that this mapped `t` value lies on:
|
||||
|
||||
|
@ -1,125 +0,0 @@
|
||||
let cache = { N: [] },
|
||||
points,
|
||||
degree = 3,
|
||||
spline,
|
||||
pad=20,
|
||||
knots,
|
||||
colors = ['#C00','#CC0','#0C0','#0CC','#00C','#C0C','#600','#660','#060','#066','#006','#606'];
|
||||
|
||||
setup() {
|
||||
points = [
|
||||
{x:0, y: 0},
|
||||
{x:100, y:-100},
|
||||
{x:200, y: 100},
|
||||
{x:300, y:-100},
|
||||
{x:400, y: 100},
|
||||
{x:500, y: 0}
|
||||
];
|
||||
|
||||
knots = new BSpline(this, points).formKnots();
|
||||
let min = 0, max = knots.length-1;
|
||||
knots.forEach((_,i) => {
|
||||
addSlider(`slide-control`, `!knot ${i+1}`, min, max, 0.01, i, (v) => this.setKnotValue(i,v));
|
||||
});
|
||||
}
|
||||
|
||||
setKnotValue(i, v) {
|
||||
if (i>0 && v < knots[i-1]) throw {value: knots[i-1]};
|
||||
if (i<knots.length-1 && v > knots[i+1]) throw {value: knots[i+1]};
|
||||
knots[i] = v;
|
||||
redraw();
|
||||
}
|
||||
|
||||
draw() {
|
||||
clear();
|
||||
setWidth(1);
|
||||
setStroke(`lightgrey`);
|
||||
drawGrid(pad);
|
||||
|
||||
setStroke(`black`);
|
||||
this.line(pad,0,pad,this.height);
|
||||
var y = this.height - pad;
|
||||
this.line(0,y,this.width,y);
|
||||
|
||||
var n = points.length || 4;
|
||||
|
||||
for (let i=0, e=n+degree+1; i<e; i++) {
|
||||
this.drawN(i, degree, pad, (this.width-pad)/(2*(n+2)), this.height-2*pad);
|
||||
}
|
||||
}
|
||||
|
||||
drawN(i, k, pad, w, h) {
|
||||
noFill();
|
||||
setStroke(colors[i]);
|
||||
setWidth(2);
|
||||
start()
|
||||
for (let start=i-1, t=start, step=0.1, end=i+k+1; t<end; t+=step) {
|
||||
let x = pad + i*w + t*w;
|
||||
let v = this.N(i, k, t);
|
||||
let y = this.height - pad - h * v;
|
||||
vertex(x, y);
|
||||
}
|
||||
end();
|
||||
}
|
||||
|
||||
N(i, k, t) {
|
||||
let t_i = knots[i];
|
||||
let t_i1 = knots[i+1];
|
||||
let t_ik1 = knots[i+k-1];
|
||||
let t_ik = knots[i+k];
|
||||
|
||||
// terminal condition: if the degree is one, then
|
||||
// the return value is either 1 ("contributes to
|
||||
// the interpolation at this point"), or 0 ("does
|
||||
// not contribute").
|
||||
|
||||
if (k===1) {
|
||||
return (t_i <= t && t <= t_i1) ? 1 : 0;
|
||||
}
|
||||
|
||||
// If we're not at k=1 yet, we can still determine
|
||||
// whether we need to "do work", or whether the
|
||||
// index and degree such that we know this knot
|
||||
// contributes nothing:
|
||||
|
||||
let n1 = t - t_i;
|
||||
let d1 = t_ik1 - t_i;
|
||||
let a1 = d1===0? 0: n1/d1;
|
||||
|
||||
let n2 = t_ik - t;
|
||||
let d2 = t_ik - t_i1;
|
||||
let a2 = d2===0? 0: n2/d2;
|
||||
|
||||
let N1 = 0;
|
||||
if (a1 !== 0) {
|
||||
// iteration: get the current index's (k-1)th value
|
||||
let n1v = this.ensureN(i,k-1,t);
|
||||
N1 = n1v === undefined ? this.N(i,k-1,t) : n1v;
|
||||
}
|
||||
let v1 = a1 * N1;
|
||||
|
||||
let N2 = 0;
|
||||
if (a2 !== 0) {
|
||||
// iteration: get the next index's (k-1)th value
|
||||
let n2v = this.ensureN(i+1,k-1,t);
|
||||
N2 = n2v === undefined ? this.N(i+1,k-1,t) : n2v;
|
||||
}
|
||||
let v2 = a2 * N2;
|
||||
|
||||
// store their interpolation
|
||||
this.cacheN(i,k,t, v1 + v2);
|
||||
return cache.N[i][k][t];
|
||||
}
|
||||
|
||||
cacheN(i,k,t,value) {
|
||||
this.ensureN(i,k,t);
|
||||
cache.N[i][k][t] = value;
|
||||
}
|
||||
|
||||
ensureN(i,k,t) {
|
||||
const N = cache.N ?? [];
|
||||
N[i] = N[i] ?? [];
|
||||
N[i][k] = N[i][k] ?? [];
|
||||
cache.N = N;
|
||||
return N[i][k][t];
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
While quadratic curves are relatively simple curves to analyze, the same cannot be said of the cubic curve. As a curvature is controlled by more than one control point, it exhibits all kinds of features like loops, cusps, odd colinear features, and as many as two inflection points because the curvature can change direction up to three times. Now, knowing what kind of curve we're dealing with means that some algorithms can be run more efficiently than if we have to implement them as generic solvers, so is there a way to determine the curve type without lots of work?
|
||||
|
||||
As it so happens, the answer is yes, and the solution we're going to look at was presented by Maureen C. Stone from Xerox PARC and Tony D. deRose from the University of Washington in their joint paper ["A Geometric Characterization of Parametric Cubic curves"](http://graphics.pixar.com/people/derose/publications/CubicClassification/paper.pdf). It was published in 1989, and defines curves as having a "canonical" form (i.e. a form that all curves can be reduced to) from which we can immediately tell what features a curve will have. So how does it work?
|
||||
As it so happens, the answer is yes, and the solution we're going to look at was presented by Maureen C. Stone from Xerox PARC and Tony D. deRose from the University of Washington in their joint paper ["A Geometric Characterization of Parametric Cubic curves"](https://graphics.pixar.com/people/derose/publications/CubicClassification/paper.pdf). It was published in 1989, and defines curves as having a "canonical" form (i.e. a form that all curves can be reduced to) from which we can immediately tell what features a curve will have. So how does it work?
|
||||
|
||||
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:
|
||||
|
||||
@ -287,9 +287,9 @@ That's kind of super-simple to write out in code, I think you'll agree. Coding m
|
||||
|
||||
### How do you track all that?
|
||||
|
||||
Doing maths can be a pain, so whenever possible, I like to make computers do the work for me. Especially for things like this, I simply use [Mathematica](http://www.wolfram.com/mathematica). Tracking all this math by hand is insane, and we invented computers, literally, to do this for us. I have no reason to use pen and paper when I can write out what I want to do in a program, and have the program do the math for me. And real math, too, with symbols, not with numbers. In fact, [here's](http://pomax.github.io/gh-weblog-2/downloads/canonical-curve.nb) the Mathematica notebook if you want to see how this works for yourself.
|
||||
Doing maths can be a pain, so whenever possible, I like to make computers do the work for me. Especially for things like this, I simply use [Mathematica](https://www.wolfram.com/mathematica/). Tracking all this math by hand is insane, and we invented computers, literally, to do this for us. I have no reason to use pen and paper when I can write out what I want to do in a program, and have the program do the math for me. And real math, too, with symbols, not with numbers. In fact, [here's](https://pomax.github.io/gh-weblog-2/downloads/canonical-curve.nb) the Mathematica notebook if you want to see how this works for yourself.
|
||||
|
||||
Now, I know, you're thinking "but Mathematica is super expensive!" and that's true, it's [$295 for home use](http://www.wolfram.com/mathematica-home-edition), but it's **also** [free when you buy a $35 raspberry pi](http://www.wolfram.com/raspberry-pi). Obviously, I bought a raspberry pi, and I encourage you to do the same. With that, as long as you know what you want to *do*, Mathematica can just do it for you. And we don't have to be geniuses to work out what the maths looks like. That's what we have computers for.
|
||||
Now, I know, you're thinking "but Mathematica is super expensive!" and that's true, it's [$344 for home use, up from $295 when I original wrote this](https://www.wolfram.com/mathematica-home-edition/), but it's **also** [free when you buy a $35 raspberry pi](https://www.wolfram.com/raspberry-pi/). Obviously, I bought a raspberry pi, and I encourage you to do the same. With that, as long as you know what you want to *do*, Mathematica can just do it for you. And we don't have to be geniuses to work out what the maths looks like. That's what we have computers for.
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -4,4 +4,4 @@ Much shorter than the previous section: we saw that Catmull-Rom curves need at l
|
||||
|
||||
Short and sweet: we don't.
|
||||
|
||||
We run through the maths that lets us [create a cubic Bézier curve](pointcurves), and then convert its coordinates to Catmull-Rom form using the conversion formulae we saw above.
|
||||
We run through the maths that lets us [create a cubic Bézier curve](#pointcurves), and then convert its coordinates to Catmull-Rom form using the conversion formulae we saw above.
|
||||
|
@ -109,7 +109,7 @@ So, what does this distance function look like when we plot it for a number of r
|
||||
<img src="images/arc-q-pi2.gif" height="187"/>
|
||||
plotted for 0 ≤ φ ≤ ½π:
|
||||
</td><td>
|
||||
<a href="http://www.wolframalpha.com/input/?i=plot+sqrt%28%281%2F4+*+%28sin%28x%29+%2B+2tan%28x%2F2%29%29+-+sin%28x%2F2%29%29%5E2+%2B+%282sin%5E4%28x%2F4%29%29%5E2%29+for+0+%3C%3D+x+%3C%3D+pi%2F4">
|
||||
<a href="https://www.wolframalpha.com/input/?i=plot+sqrt%28%281%2F4+*+%28sin%28x%29+%2B+2tan%28x%2F2%29%29+-+sin%28x%2F2%29%29%5E2+%2B+%282sin%5E4%28x%2F4%29%29%5E2%29+for+0+%3C%3D+x+%3C%3D+pi%2F4">
|
||||
<img src="images/arc-q-pi4.gif" height="174"/>
|
||||
</a>
|
||||
plotted for 0 ≤ φ ≤ ¼π:
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script src="./js/site/disqus.js" async defer>
|
||||
/* ----------------------------------------------------------------------------- *
|
||||
*
|
||||
* PLEASE DO NOT LOCALISE THIS FILE
|
||||
@ -11,8 +11,9 @@
|
||||
|
||||
# Comments and questions
|
||||
|
||||
First off, if you enjoyed this book, or you simply found it useful for something you were trying to get done, and you were wondering how to let me know you appreciated this book, you have two options: you can either head on over to the [Patreon page](https://patreon.com/bezierinfo) for this book, or if you prefer to make a one-time donation, head on over to the [buy Pomax a coffee](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QPRDLNGDANJSW) page. This work has grown from a small primer to a 70-plus print-page-equivalent reader on the subject of Bézier curves over the years, and a lot of coffee went into the making of it. I don't regret a minute I spent on writing it, but I can always do with some more coffee to keep on writing.
|
||||
First off, if you enjoyed this book, or you simply found it useful for something you were trying to get done, and you were wondering how to let me know you appreciated this book, you have two options: you can either head on over to the [Patreon page](https://www.patreon.com/bezierinfo) for this book, or if you prefer to make a one-time donation, head on over to the [buy Pomax a coffee](https://www.paypal.com/donate/?token=4OeU2bI9WLfex_fYcraxmooLUcJ_WDTn8AofsN1WYchMI7RB5Jq6CSZuAWNQTekJGyOh3G) page. This work has grown from a small primer to a 70-plus print-page-equivalent reader on the subject of Bézier curves over the years, and a lot of coffee went into the making of it. I don't regret a minute I spent on writing it, but I can always do with some more coffee to keep on writing.
|
||||
|
||||
With that said, on to the comments!
|
||||
|
||||
<div id="disqus_thread" />
|
||||
|
||||
|
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* We REALLY don't want disqus to load unless the user
|
||||
* is actually looking at the comments section, because it
|
||||
* tacks on 2.5+ MB in network transfers...
|
||||
*/
|
||||
module.exports = {
|
||||
componentDidMount() {
|
||||
if (typeof document === "undefined") {
|
||||
return this.silence();
|
||||
}
|
||||
this.heading = document.getElementById(this.props.page);
|
||||
document.addEventListener("scroll", this.scrollHandler, {passive:true});
|
||||
},
|
||||
|
||||
scrollHandler(evt) {
|
||||
var bbox = this.heading.getBoundingClientRect();
|
||||
var top = bbox.top;
|
||||
var limit = window.innerHeight;
|
||||
if (top<limit) { this.loadDisqus(); }
|
||||
},
|
||||
|
||||
loadDisqus() {
|
||||
var script = document.createElement("script");
|
||||
script.src = "lib/site/disqus.js";
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
this.silence();
|
||||
this.unlisten();
|
||||
},
|
||||
|
||||
silence() {
|
||||
this.loadDisqus = () => {};
|
||||
},
|
||||
|
||||
unlisten() {
|
||||
document.removeEventListener("scroll", this.scrollHandler);
|
||||
}
|
||||
};
|
@ -14,7 +14,7 @@ So what we really want is some kind of expression that's not based on any partic
|
||||
|
||||
We've seen this before... that's the arc length function.
|
||||
|
||||
So you might think that in order to find the curvature of a curve, we now need to solve the arc length function itself, and that this would be quite a problem because we just saw that there is no way to actually do that. Thankfully, we don't. We only need to know the _form_ of the arc length function, which we saw above and is fairly simple, rather than needing to _solve_ the arc length function. If we start with the arc length expression and the [run through the steps necessary](http://mathworld.wolfram.com/Curvature.html) to determine _its_ derivative (with an alternative, shorter demonstration of how to do this found [over on Stackexchange](https://math.stackexchange.com/a/275324/71940)), then the integral that was giving us so much problems in solving the arc length function disappears entirely (because of the [fundamental theorem of calculus](https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus)), and what we're left with us some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.
|
||||
So you might think that in order to find the curvature of a curve, we now need to solve the arc length function itself, and that this would be quite a problem because we just saw that there is no way to actually do that. Thankfully, we don't. We only need to know the _form_ of the arc length function, which we saw above and is fairly simple, rather than needing to _solve_ the arc length function. If we start with the arc length expression and the [run through the steps necessary](https://mathworld.wolfram.com/Curvature.html) to determine _its_ derivative (with an alternative, shorter demonstration of how to do this found [over on Stackexchange](https://math.stackexchange.com/questions/275248/deriving-curvature-formula/275324#275324)), then the integral that was giving us so much problems in solving the arc length function disappears entirely (because of the [fundamental theorem of calculus](https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus)), and what we're left with us some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.
|
||||
|
||||
Let me highlight what just happened, because it's pretty special:
|
||||
|
||||
|
@ -235,7 +235,7 @@ So, we have our error function: we now need to figure out the expression for whe
|
||||
|
||||
That... is a good question. In fact, when trying to run through this approach, I ran into the same question! And you know what? I straight up had no idea. I'm decent enough at calculus, I'm decent enough at linear algebra, and I just don't know.
|
||||
|
||||
So I did what I always do when I don't understand something: I asked someone to help me understand how things work. In this specific case, I [posted a question](https://math.stackexchange.com/questions/2825438) to [Math.stackexchange](https://math.stackexchange.com), and received a answer that goes into way more detail than I had hoped to receive.
|
||||
So I did what I always do when I don't understand something: I asked someone to help me understand how things work. In this specific case, I [posted a question](https://math.stackexchange.com/questions/2825438/how-do-you-compute-the-derivative-of-a-matrix-algebra-expression) to [Math.stackexchange](https://math.stackexchange.com), and received a answer that goes into way more detail than I had hoped to receive.
|
||||
|
||||
Is that answer useful to you? Probably: no. At least, not unless you like understanding maths on a recreational level. And I do mean maths in general, not just basic algebra. But it does help in giving us a reference in case you ever wonder "Hang on. Why was that true?". There are answers. They might just require some time to come to understand.
|
||||
|
||||
|
@ -26,7 +26,7 @@ Sometimes just being told "this is the derivative" is nice, but you might want t
|
||||
B_{n,k}(t) \frac{d}{dt} = {n \choose k} t^k (1-t)^{n-k} \frac{d}{dt}
|
||||
\]
|
||||
|
||||
Applying the [product](http://en.wikipedia.org/wiki/Product_rule) and [chain](http://en.wikipedia.org/wiki/Chain_rule) rules gives us:
|
||||
Applying the [product](https://en.wikipedia.org/wiki/Product_rule) and [chain](https://en.wikipedia.org/wiki/Chain_rule) rules gives us:
|
||||
|
||||
\[
|
||||
\begin{array}{l}
|
||||
|
@ -27,4 +27,4 @@ The following two graphics show you Bézier curves rendered "the usual way", as
|
||||
<graphics-element title="Cubic infinite interval Bézier curve" src="./extended.js" data-type="cubic"></graphics-element>
|
||||
</div>
|
||||
|
||||
In fact, there are curves used in graphics design and computer modelling that do the opposite of Bézier curves; rather than fixing the interval, and giving you freedom to choose the coordinates, they fix the coordinates, but give you freedom over the interval. A great example of this is the ["Spiro" curve](http://levien.com/phd/phd.html), which is a curve based on part of a [Cornu Spiral, also known as Euler's Spiral](https://en.wikipedia.org/wiki/Euler_spiral). It's a very aesthetically pleasing curve and you'll find it in quite a few graphics packages like [FontForge](https://fontforge.github.io) and [Inkscape](https://inkscape.org). It has even been used in font design, for example for the Inconsolata typeface.
|
||||
In fact, there are curves used in graphics design and computer modelling that do the opposite of Bézier curves; rather than fixing the interval, and giving you freedom to choose the coordinates, they fix the coordinates, but give you freedom over the interval. A great example of this is the ["Spiro" curve](https://levien.com/phd/phd.html), which is a curve based on part of a [Cornu Spiral, also known as Euler's Spiral](https://en.wikipedia.org/wiki/Euler_spiral). It's a very aesthetically pleasing curve and you'll find it in quite a few graphics packages like [FontForge](https://fontforge.org/en-US/) and [Inkscape](https://inkscape.org). It has even been used in font design, for example for the Inconsolata typeface.
|
||||
|
@ -27,4 +27,4 @@
|
||||
<graphics-element title="無限区間の3次ベジエ曲線" src="./extended.js" data-type="cubic"></graphics-element>
|
||||
</div>
|
||||
|
||||
実際に、グラフィックデザインやコンピュータモデリングで使われている曲線の中には、座標が固定されていて、区間は自由に動かせるような曲線があります。これは、区間が固定されていて、座標を自由に動かすことのできるベジエ曲線とは反対になっています。すばらしい例が[「Spiro」曲線](http://levien.com/phd/phd.html)で、これは[オイラー螺旋とも呼ばれるクロソイド曲線](https://ja.wikipedia.org/wiki/クロソイド曲線)の一部分に基づいた曲線です。非常に美しく心地よい曲線で、[FontForge](https://fontforge.github.io)や[Inkscape](https://inkscape.org/ja/)など多くのグラフィックアプリに実装されており、フォントデザインにも利用されています(Inconsolataフォントなど)。
|
||||
実際に、グラフィックデザインやコンピュータモデリングで使われている曲線の中には、座標が固定されていて、区間は自由に動かせるような曲線があります。これは、区間が固定されていて、座標を自由に動かすことのできるベジエ曲線とは反対になっています。すばらしい例が[「Spiro」曲線](https://levien.com/phd/phd.html)で、これは[オイラー螺旋とも呼ばれるクロソイド曲線](https://ja.wikipedia.org/wiki/クロソイド曲線)の一部分に基づいた曲線です。非常に美しく心地よい曲線で、[FontForge](https://fontforge.org/en-US/)や[Inkscape](https://inkscape.org/ja/)など多くのグラフィックアプリに実装されており、フォントデザインにも利用されています(Inconsolataフォントなど)。
|
||||
|
@ -27,4 +27,4 @@
|
||||
<graphics-element title="三次无限区间贝塞尔曲线" src="./extended.js" data-type="cubic"></graphics-element>
|
||||
</div>
|
||||
|
||||
实际上,图形设计和计算机建模中还用了一些和贝塞尔曲线相反的曲线,这些曲线没有固定区间和自由的坐标,相反,它们固定座标但给你自由的区间。["Spiro"曲线](http://levien.com/phd/phd.html)就是一个很好的例子,它的构造是基于[羊角螺线,也就是欧拉螺线](https://zh.wikipedia.org/wiki/%E7%BE%8A%E8%A7%92%E8%9E%BA%E7%BA%BF)的一部分。这是在美学上很令人满意的曲线,你可以在一些图形包中看到它,比如[FontForge](https://fontforge.github.io)和[Inkscape](https://inkscape.org),它也被用在一些字体设计中(比如Inconsolata字体)。
|
||||
实际上,图形设计和计算机建模中还用了一些和贝塞尔曲线相反的曲线,这些曲线没有固定区间和自由的坐标,相反,它们固定座标但给你自由的区间。["Spiro"曲线](https://levien.com/phd/phd.html)就是一个很好的例子,它的构造是基于[羊角螺线,也就是欧拉螺线](https://zh.wikipedia.org/wiki/%E7%BE%8A%E8%A7%92%E8%9E%BA%E7%BA%BF)的一部分。这是在美学上很令人满意的曲线,你可以在一些图形包中看到它,比如[FontForge](https://fontforge.org/en-US/)和[Inkscape](https://inkscape.org),它也被用在一些字体设计中(比如Inconsolata字体)。
|
||||
|
@ -87,7 +87,7 @@ Back in the 16<sup>th</sup> century, before Bézier curves were a thing, and eve
|
||||
\end{aligned}
|
||||
\]
|
||||
|
||||
We can see that the easier formula only has two constants, rather than four, and only two expressions involving `t`, rather than three: this makes things considerably easier to solve because it lets us use [regular calculus](http://www.wolframalpha.com/input/?i=t^3+%2B+pt+%2B+q) to find the values that satisfy the equasion.
|
||||
We can see that the easier formula only has two constants, rather than four, and only two expressions involving `t`, rather than three: this makes things considerably easier to solve because it lets us use [regular calculus](https://www.wolframalpha.com/input/?i=t^3+%2B+pt+%2B+q) to find the values that satisfy the equasion.
|
||||
|
||||
Now, there is one small hitch: as a cubic function, the solutions may be [complex numbers](https://en.wikipedia.org/wiki/Complex_number) rather than plain numbers... And Cardona realised this, centuries befor complex numbers were a well-understood and established part of number theory. His interpretation of them was "these numbers are impossible but that's okay because they disappear again in later steps", allowing him to not think about them too much, but we have it even easier: as we're trying to find the roots for display purposes, we don't even _care_ about complex numbers: we're going to simplify Cardano's approach just that tiny bit further by throwing away any solution that's not a plain number.
|
||||
|
||||
@ -196,7 +196,7 @@ And this is where thing stop, because we _cannot_ find the roots for polynomials
|
||||
|
||||
That's a fancy term for saying "rather than trying to find exact answers by manipulating symbols, find approximate answers by describing the underlying process as a combination of steps, each of which _can_ be assigned a number via symbolic manipulation". For example, trying to mathematically compute how much water fits in a completely crazy three dimensional shape is very hard, even if it got you the perfect, precise answer. A much easier approach, which would be less perfect but still entirely useful, would be to just grab a buck and start filling the shape until it was full: just count the number of buckets of water you used. And if we want a more precise answer, we can use smaller buckets.
|
||||
|
||||
So that's what we're going to do here, too: we're going to treat the problem as a sequence of steps, and the smaller we can make each step, the closer we'll get to that "perfect, precise" answer. And as it turns out, there is a really nice numerical root-finding algorithm, called the [Newton-Raphson](http://en.wikipedia.org/wiki/Newton-Raphson) root finding method (yes, after *[that](https://en.wikipedia.org/wiki/Isaac_Newton)* Newton), which we can make use of. The Newton-Raphson approach consists of taking our impossible-to-solve function `f(x)`, picking some intial value `x` (literally any value will do), and calculating `f(x)`. We can think of that value as the "height" of the function at `x`. If that height is zero, we're done, we have found a root. If it isn't, we calculate the tangent line at `f(x)` and calculate at which `x` value _its_ height is zero (which we've already seen is very easy). That will give us a new `x` and we repeat the process until we find a root.
|
||||
So that's what we're going to do here, too: we're going to treat the problem as a sequence of steps, and the smaller we can make each step, the closer we'll get to that "perfect, precise" answer. And as it turns out, there is a really nice numerical root-finding algorithm, called the [Newton-Raphson](https://en.wikipedia.org/wiki/Newton-Raphson) root finding method (yes, after *[that](https://en.wikipedia.org/wiki/Isaac_Newton)* Newton), which we can make use of. The Newton-Raphson approach consists of taking our impossible-to-solve function `f(x)`, picking some intial value `x` (literally any value will do), and calculating `f(x)`. We can think of that value as the "height" of the function at `x`. If that height is zero, we're done, we have found a root. If it isn't, we calculate the tangent line at `f(x)` and calculate at which `x` value _its_ height is zero (which we've already seen is very easy). That will give us a new `x` and we repeat the process until we find a root.
|
||||
|
||||
Mathematically, this means that for some `x`, at step `n=1`, we perform the following calculation until `f<sub>y</sub>(x)` is zero, so that the next `t` is the same as the one we already have:
|
||||
|
||||
|
@ -6,7 +6,7 @@ We'll do this in steps, because it's a bit of a journey to get to curve/curve in
|
||||
|
||||
### Line-line intersections
|
||||
|
||||
If we have two line segments with two coordinates each, segments A-B and C-D, we can find the intersection of the lines these segments are an intervals on by linear algebra, using the procedure outlined in this [top coder](http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2#line_line_intersection) article. Of course, we need to make sure that the intersection isn't just on the lines our line segments lie on, but actually on our line segments themselves. So after we find the intersection, we need to verify that it lies without the bounds of our original line segments.
|
||||
If we have two line segments with two coordinates each, segments A-B and C-D, we can find the intersection of the lines these segments are an intervals on by linear algebra, using the procedure outlined in this [top coder](https://www.topcoder.com/community/competitive-programming/tutorials/geometry-concepts-line-intersection-and-its-applications/) article. Of course, we need to make sure that the intersection isn't just on the lines our line segments lie on, but actually on our line segments themselves. So after we find the intersection, we need to verify that it lies without the bounds of our original line segments.
|
||||
|
||||
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).
|
||||
|
||||
|
@ -65,8 +65,8 @@ Which is the "long" version of the following matrix transformation:
|
||||
|
||||
And that's all we need to rotate any coordinate. Note that for quarter, half, and three-quarter turns these functions become even easier, since *sin* and *cos* for these angles are, respectively: 0 and 1, -1 and 0, and 0 and -1.
|
||||
|
||||
But ***why*** does this work? Why this matrix multiplication? [Wikipedia](http://en.wikipedia.org/wiki/Rotation_matrix#Decomposition_into_shears) (technically, Thomas Herter and Klaus Lott) tells us that a rotation matrix can be
|
||||
treated as a sequence of three (elementary) shear operations. When we combine this into a single matrix operation (because all matrix multiplications can be collapsed), we get the matrix that you see above. [DataGenetics](http://datagenetics.com/blog/august32013/index.html) have an excellent article about this very thing: it's really quite cool, and I strongly recommend taking a quick break from this primer to read that article.
|
||||
But ***why*** does this work? Why this matrix multiplication? [Wikipedia](https://en.wikipedia.org/wiki/Rotation_matrix#Decomposition_into_shears) (technically, Thomas Herter and Klaus Lott) tells us that a rotation matrix can be
|
||||
treated as a sequence of three (elementary) shear operations. When we combine this into a single matrix operation (because all matrix multiplications can be collapsed), we get the matrix that you see above. [DataGenetics](https://datagenetics.com/blog/august32013/index.html) have an excellent article about this very thing: it's really quite cool, and I strongly recommend taking a quick break from this primer to read that article.
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -70,7 +70,6 @@ drawCurveProjections() {
|
||||
this.cyz.drawCurve(`#EEF`);
|
||||
}
|
||||
|
||||
|
||||
drawPoint(t) {
|
||||
const {o, r, n, dt} = this.getFrenetVectors(t, this.points);
|
||||
|
||||
@ -109,8 +108,8 @@ getFrenetVectors(t, originalPoints) {
|
||||
const dt = d1curve.get(t);
|
||||
const ddt = d1curve.derivative(t);
|
||||
const o = curve.get(t);
|
||||
const b = vec.normalize(vec.plus(dt, ddt));
|
||||
const r = vec.normalize(vec.cross(b, dt));
|
||||
const b = vec.plus(dt, ddt);
|
||||
const r = vec.cross(b, dt);
|
||||
const n = vec.normalize(vec.cross(r, dt));
|
||||
return { o, dt, r, n };
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ But what if you need to program them yourself? What are the pitfalls? How do you
|
||||
|
||||
If you enjoyed this book enough to print it out, you might be wondering if there is some way to give something back. To answer that question: you can always buy me a coffee, however-much a coffee is where you live. Or, if you want to pay a fair price for this book, you can buy me a really expensive coffee =)
|
||||
|
||||
This book has grown over the years from a short primer to a 100+ print-page-equivalent ebook on the subject of Bézier curves, and a lot of coffee went into the making of it. I don't regret a minute I spent on writing it, but I can always do with some more coffee to keep on writing! Please visit https://pomax.github.io/bezierinfo and click on the link in the "Help support the book" preface section to donate some coffee money.
|
||||
This book has grown over the years from a short primer to a 100+ print-page-equivalent ebook on the subject of Bézier curves, and a lot of coffee went into the making of it. I don't regret a minute I spent on writing it, but I can always do with some more coffee to keep on writing! Please visit https://pomax.github.io/bezierinfo/ and click on the link in the "Help support the book" preface section to donate some coffee money.
|
||||
|
||||
</div>
|
||||
|
||||
@ -26,7 +26,7 @@ This book has grown over the years from a short primer to a 100+ print-page-equi
|
||||
|
||||
## Virtually all Bézier graphics are interactive.
|
||||
|
||||
This page uses interactive examples, relying heavily on [Bezier.js](http://pomax.github.io/bezierjs), as well as maths formulae which are typeset into SVG using the [XeLaTeX](https://ctan.org/pkg/xetex) typesetting system and [pdf2svg](https://github.com/dawbarton/pdf2svg) by [David Barton](http://www.cityinthesky.co.uk/).
|
||||
This page uses interactive examples, relying heavily on [Bezier.js](https://pomax.github.io/bezierjs/), as well as maths formulae which are typeset into SVG using the [XeLaTeX](https://ctan.org/pkg/xetex) typesetting system and [pdf2svg](https://github.com/dawbarton/pdf2svg) by [David Barton](https://cityinthesky.co.uk/).
|
||||
|
||||
## This book is open source.
|
||||
|
||||
@ -42,6 +42,6 @@ If you have suggestions for new sections, hit up the [Github issue tracker](http
|
||||
|
||||
## Help support the book!
|
||||
|
||||
If you enjoyed this book, or you simply found it useful for something you were trying to get done, and you were wondering how to let me know you appreciated this book, you have two options: you can either head on over to the [Patreon page](https://patreon.com/bezierinfo) for this book, or if you prefer to make a one-time donation, head on over to the [buy Pomax a coffee](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QPRDLNGDANJSW) page. This work has grown from a small primer to a 100-plus print-page-equivalent reader on the subject of Bézier curves over the years, and a lot of coffee went into the making of it. I don't regret a minute I spent on writing it, but I can always do with some more coffee to keep on writing!
|
||||
If you enjoyed this book, or you simply found it useful for something you were trying to get done, and you were wondering how to let me know you appreciated this book, you have two options: you can either head on over to the [Patreon page](https://www.patreon.com/bezierinfo) for this book, or if you prefer to make a one-time donation, head on over to the [buy Pomax a coffee](https://www.paypal.com/donate/?token=4OeU2bI9WLfex_fYcraxmooLUcJ_WDTn8AofsN1WYchMI7RB5Jq6CSZuAWNQTekJGyOh3G) page. This work has grown from a small primer to a 100-plus print-page-equivalent reader on the subject of Bézier curves over the years, and a lot of coffee went into the making of it. I don't regret a minute I spent on writing it, but I can always do with some more coffee to keep on writing!
|
||||
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
この本のことを印刷するほど気に入ったのであれば、あなたは「何かお礼をする方法はないか」と考えているかもしれません。この質問に対する答えはこうです:いつでもわたしにコーヒーをおごってください。あなたが住んでいるところの、コーヒー1杯の値段でかまいません。この本にかなりの金額を支払いたいのであれば、非常に高価なコーヒーをおごってくれてもかまいません =)
|
||||
|
||||
この本は小さな入門からはじまり、印刷85ページ以上に相当するようなベジエ曲線の電子書籍へと、年々成長してきています。そして、多くのコーヒーがその執筆に費やされてきました。わたしは執筆に使った時間が惜しいとは思いませんが、もう少しコーヒーがあれば、ずっと書き続けることができるのです!https://pomax.github.io/bezierinfoにアクセスしてオンライン版のまえがきにあるリンクをクリックし、コーヒー代を寄付してください。
|
||||
この本は小さな入門からはじまり、印刷85ページ以上に相当するようなベジエ曲線の電子書籍へと、年々成長してきています。そして、多くのコーヒーがその執筆に費やされてきました。わたしは執筆に使った時間が惜しいとは思いませんが、もう少しコーヒーがあれば、ずっと書き続けることができるのです!https://pomax.github.io/bezierinfo/にアクセスしてオンライン版のまえがきにあるリンクをクリックし、コーヒー代を寄付してください。
|
||||
|
||||
</div>
|
||||
|
||||
@ -24,10 +24,10 @@
|
||||
|
||||
## 注:ベジエの図はすべてインタラクティブになっています。
|
||||
|
||||
このページでは[Bezier.js](http://pomax.github.io/bezierjs)を活用し、インタラクティブな例示を行っています。
|
||||
このページでは[Bezier.js](https://pomax.github.io/bezierjs/)を活用し、インタラクティブな例示を行っています。
|
||||
|
||||
<!-- The following is no longer true
|
||||
また、[MathJax](http://mathjax.org)というすばらしいライブラリによって、(LaTeX式の)「本物」の数学組版を行っています。このページはWebpackを使い、Reactアプリケーションとしてオフラインで生成されていますが、このために「ソースを表示」オプションを追加することがかなり難しくなってしまいました。これをどうやって追加すべきかは今もまだ考え中ですが、かといって、数年ぶりとなる今回の更新を延期したくはありませんでした。
|
||||
また、[MathJax](https://mathjax.org)というすばらしいライブラリによって、(LaTeX式の)「本物」の数学組版を行っています。このページはWebpackを使い、Reactアプリケーションとしてオフラインで生成されていますが、このために「ソースを表示」オプションを追加することがかなり難しくなってしまいました。これをどうやって追加すべきかは今もまだ考え中ですが、かといって、数年ぶりとなる今回の更新を延期したくはありませんでした。
|
||||
-->
|
||||
|
||||
## 本書はオープンソースです。
|
||||
@ -44,6 +44,6 @@
|
||||
|
||||
## コーヒーをおごってくれませんか?
|
||||
|
||||
この本が気に入った場合や、取り組んでいたことに役に立つと思った場合、あるいは、この本への感謝をわたしに伝えるにはどうすればいいのかわからない場合。そのような場合には、[わたしにコーヒーをおごってください](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QPRDLNGDANJSW)。あなたが住んでいるところのコーヒー1杯の値段でかまいません。この本は小さな入門からはじまり、印刷で70ページほどに相当するようなベジエ曲線の読み物へと、年々成長してきています。そして、多くのコーヒーが執筆に費やされてきました。わたしは執筆に使った時間が惜しいとは思いませんが、もう少しコーヒーがあれば、ずっと書き続けることができるのです!
|
||||
この本が気に入った場合や、取り組んでいたことに役に立つと思った場合、あるいは、この本への感謝をわたしに伝えるにはどうすればいいのかわからない場合。そのような場合には、[わたしにコーヒーをおごってください](https://www.paypal.com/donate/?token=4OeU2bI9WLfex_fYcraxmooLUcJ_WDTn8AofsN1WYchMI7RB5Jq6CSZuAWNQTekJGyOh3G)。あなたが住んでいるところのコーヒー1杯の値段でかまいません。この本は小さな入門からはじまり、印刷で70ページほどに相当するようなベジエ曲線の読み物へと、年々成長してきています。そして、多くのコーヒーが執筆に費やされてきました。わたしは執筆に使った時間が惜しいとは思いませんが、もう少しコーヒーがあれば、ずっと書き続けることができるのです!
|
||||
|
||||
</div>
|
||||
|
@ -15,10 +15,10 @@
|
||||
|
||||
## 注意:几乎所有的贝塞尔图形都是可交互的。
|
||||
|
||||
这个页面使用了基于[Bezier.js](http://pomax.github.io/bezierjs) 的可交互例子。
|
||||
这个页面使用了基于[Bezier.js](https://pomax.github.io/bezierjs/) 的可交互例子。
|
||||
|
||||
<!-- The following is no longer true
|
||||
,还有一些用[MathJax](http://MathJax.org) 排版的“真正的”数学(LaTeX形式)。这个页面是用Webpack离线生成的React应用,这便让加入“查看源码”选项更具挑战性了。我仍然试图将它们添加回来,但跟前几年的版本相比,不觉得它能够支撑部署这个更新。
|
||||
,还有一些用[MathJax](https://MathJax.org) 排版的“真正的”数学(LaTeX形式)。这个页面是用Webpack离线生成的React应用,这便让加入“查看源码”选项更具挑战性了。我仍然试图将它们添加回来,但跟前几年的版本相比,不觉得它能够支撑部署这个更新。
|
||||
-->
|
||||
|
||||
## 这本书是开源的。
|
||||
@ -35,6 +35,6 @@
|
||||
|
||||
## 给我买杯咖啡?
|
||||
|
||||
如果你很喜欢这本书,或发现它对你要做的事很有帮助,或者你想知道怎么表达自己对这本书的感激,你可以 [给我买杯咖啡](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QPRDLNGDANJSW) ,所少钱取决于你。这份工作持续了很多年,从一份小小的简要到70多页关于贝塞尔曲线的读物,在完成它的过程中倾注了很多咖啡。我从未后悔花在这上面的每一分钟,但如果有更多咖啡的话,我可以坚持写下去!
|
||||
如果你很喜欢这本书,或发现它对你要做的事很有帮助,或者你想知道怎么表达自己对这本书的感激,你可以 [给我买杯咖啡](https://www.paypal.com/donate/?token=4OeU2bI9WLfex_fYcraxmooLUcJ_WDTn8AofsN1WYchMI7RB5Jq6CSZuAWNQTekJGyOh3G) ,所少钱取决于你。这份工作持续了很多年,从一份小小的简要到70多页关于贝塞尔曲线的读物,在完成它的过程中倾注了很多咖啡。我从未后悔花在这上面的每一分钟,但如果有更多咖啡的话,我可以坚持写下去!
|
||||
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ The general rule for raising an *n<sup>th</sup>* order curve to an *(n+1)<sup>th
|
||||
|
||||
However, this rule also has as direct consequence that you **cannot** generally safely lower a curve from *n<sup>th</sup>* order to *(n-1)<sup>th</sup>* order, because the control points cannot be "pulled apart" cleanly. We can try to, but the resulting curve will not be identical to the original, and may in fact look completely different.
|
||||
|
||||
However, there is a surprisingly good way to ensure that a lower order curve looks "as close as reasonably possible" to the original curve: we can optimise the "least-squares distance" between the original curve and the lower order curve, in a single operation (also explained over on [Sirver's Castle](http://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves)). However, to use it, we'll need to do some calculus work and then switch over to linear algebra. As mentioned in the section on matrix representations, some things can be done much more easily with matrices than with calculus functions, and this is one of those things. So... let's go!
|
||||
However, there is a surprisingly good way to ensure that a lower order curve looks "as close as reasonably possible" to the original curve: we can optimise the "least-squares distance" between the original curve and the lower order curve, in a single operation (also explained over on [Sirver's Castle](https://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/)). However, to use it, we'll need to do some calculus work and then switch over to linear algebra. As mentioned in the section on matrix representations, some things can be done much more easily with matrices than with calculus functions, and this is one of those things. So... let's go!
|
||||
|
||||
We start by taking the standard Bézier function, and condensing it a little:
|
||||
|
||||
@ -112,7 +112,7 @@ That might look unwieldy, but it's really just a mostly-zeroes matrix, with a ve
|
||||
|
||||
Not too bad!
|
||||
|
||||
Equally interesting, though, is that with this matrix operation established, we can now use an incredibly powerful and ridiculously simple way to find out a "best fit" way to reverse the operation, called [the normal equation](http://mathworld.wolfram.com/NormalEquation.html). What it does is minimize the sum of the square differences between one set of values and another set of values. Specifically, if we can express that as some function **A x = b**, we can use it. And as it so happens, that's exactly what we're dealing with, so:
|
||||
Equally interesting, though, is that with this matrix operation established, we can now use an incredibly powerful and ridiculously simple way to find out a "best fit" way to reverse the operation, called [the normal equation](https://mathworld.wolfram.com/NormalEquation.html). What it does is minimize the sum of the square differences between one set of values and another set of values. Specifically, if we can express that as some function **A x = b**, we can use it. And as it so happens, that's exactly what we're dealing with, so:
|
||||
|
||||
\[
|
||||
\begin{aligned}
|
||||
|
@ -78,7 +78,7 @@ raise() {
|
||||
}
|
||||
|
||||
lower() {
|
||||
// Based on http://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/
|
||||
// Based on https://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/
|
||||
|
||||
// TODO: FIXME: this is the same code as in the old codebase,
|
||||
// and it does something odd to the either the
|
||||
|
@ -40,9 +40,9 @@ Once we have all the new poly-Bézier curves, we run the first step of the desir
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
The main complication in the outlined procedure here is determining how sections qualify in terms of being "inside" and "outside" of our shapes. For this, we need to be able to perform point-in-shape detection, for which we'll use a classic algorithm: getting the "crossing number" by using ray casting, and then testing for "insidedness" by applying the [even-odd rule](http://folk.uio.no/bjornw/doc/bifrost-ref/bifrost-ref-12.html): For any point and any shape, we can cast a ray from our point, to some point that we know lies outside of the shape (such as a corner of our drawing surface). We then count how many times that line crosses our shape (remember that we can perform line/curve intersection detection quite easily). If the number of times it crosses the shape's outline is even, the point did not actually lie inside our shape. If the number of intersections is odd, our point did lie inside out shape. With that knowledge, we can decide whether to treat a section that such a point lies on "needs removal" (under union rules), "needs preserving" (under intersection rules), or "needs flipping" (under exclusion rules).
|
||||
The main complication in the outlined procedure here is determining how sections qualify in terms of being "inside" and "outside" of our shapes. For this, we need to be able to perform point-in-shape detection, for which we'll use a classic algorithm: getting the "crossing number" by using ray casting, and then testing for "insidedness" by applying the [even-odd rule](https://folk.uio.no/bjornw/doc/bifrost-ref/bifrost-ref-12.html): For any point and any shape, we can cast a ray from our point, to some point that we know lies outside of the shape (such as a corner of our drawing surface). We then count how many times that line crosses our shape (remember that we can perform line/curve intersection detection quite easily). If the number of times it crosses the shape's outline is even, the point did not actually lie inside our shape. If the number of intersections is odd, our point did lie inside out shape. With that knowledge, we can decide whether to treat a section that such a point lies on "needs removal" (under union rules), "needs preserving" (under intersection rules), or "needs flipping" (under exclusion rules).
|
||||
|
||||
These operations are expensive, and implementing your own code for this is generally a bad idea if there is already a geometry package available for your language of choice. In this case, for JavaScript the most excellent [Paper.js](http://paperjs.org) already comes with all the code in place to perform efficient boolean shape operations, so rather that implement an inferior version here, I can strongly recommend the Paper.js library if you intend to do any boolean shape work.
|
||||
These operations are expensive, and implementing your own code for this is generally a bad idea if there is already a geometry package available for your language of choice. In this case, for JavaScript the most excellent [Paper.js](https://paperjs.org) already comes with all the code in place to perform efficient boolean shape operations, so rather that implement an inferior version here, I can strongly recommend the Paper.js library if you intend to do any boolean shape work.
|
||||
|
||||
(Of course, as a general geometry library, Paper.js is also roughly the size of this entire primer, so for illustrative purposes the following graphic implements its own boolean operations, and may not do quite the right thing on all edge cases!)
|
||||
|
||||
|
@ -20,4 +20,4 @@ So let's do exactly that: the following graph is similar to the previous one, sh
|
||||
|
||||
Use the slider to increase or decrease the number of equidistant segments used to colour the curve.
|
||||
|
||||
However, are there better ways? One such way is discussed in "[Moving Along a Curve with Specified Speed](http://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf)" by David Eberly of Geometric Tools, LLC, but basically because we have no explicit length function (or rather, one we don't have to constantly compute for different intervals), you may simply be better off with a traditional lookup table (LUT).
|
||||
However, are there better ways? One such way is discussed in "[Moving Along a Curve with Specified Speed](https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf)" by David Eberly of Geometric Tools, LLC, but basically because we have no explicit length function (or rather, one we don't have to constantly compute for different intervals), you may simply be better off with a traditional lookup table (LUT).
|
||||
|
@ -1,34 +0,0 @@
|
||||
var ratios;
|
||||
|
||||
module.exports = {
|
||||
drawCubic: function(api) {
|
||||
var curve = new api.Bezier(
|
||||
120, 160,
|
||||
35, 200,
|
||||
220, 260,
|
||||
220, 40
|
||||
);
|
||||
api.setCurve(curve);
|
||||
},
|
||||
|
||||
drawCurve: function(api, curve) {
|
||||
api.reset();
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
},
|
||||
|
||||
setRatio: function(api, values) {
|
||||
ratios = values;
|
||||
this.update(api);
|
||||
},
|
||||
|
||||
changeRatio: function(api, value, pos) {
|
||||
ratios[pos] = parseFloat(value) || 0.00001;
|
||||
this.update(api);
|
||||
},
|
||||
|
||||
update: function(api) {
|
||||
api.curve.setRatios(ratios.slice());
|
||||
this.drawCurve(api, api.curve);
|
||||
}
|
||||
};
|
@ -4,7 +4,7 @@ One common task that pops up in things like CSS work, or parametric equalisers,
|
||||
|
||||
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 `x` coordinate: this is a vertical line in the left graphic, and a horizontal line on the right.
|
||||
|
||||
<graphics-element title="Finding t, given x=x(t). Left: our curve, right: the x=x(t) function" width="550" src="./basics.js">
|
||||
<graphics-element title="Finding t, given x=x(t). Left: our curve, right: the function x=f(t)" width="550" src="./basics.js">
|
||||
<input type="range" min="0" max="1" step="0.01" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 29 KiB |