diff --git a/docs/chapters/abc/content.en-GB.md b/docs/chapters/abc/content.en-GB.md index 3dc17100..6c66bd75 100644 --- a/docs/chapters/abc/content.en-GB.md +++ b/docs/chapters/abc/content.en-GB.md @@ -24,7 +24,7 @@ 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 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`. -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 both quadratic 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. @@ -67,7 +67,7 @@ And ratio(t)_{cubic} = \left | \frac{t^3 + (1-t)^3 - 1}{t^3 + (1-t)^3} \right | \] -Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a `t` value, we can _contruct_ curves: we can compute `C` using the start and end points, and our `u(t)` function, and once we have `C`, we can use our on-curve point (`B`) and the `ratio(t)` function to find `A`: +Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a `t` value, we can _construct_ curves: we can compute `C` using the start and end points, and our `u(t)` function, and once we have `C`, we can use our on-curve point (`B`) and the `ratio(t)` function to find `A`: \[ A = B - \frac{C - B}{ratio(t)} = B + \frac{B - C}{ratio(t)} diff --git a/docs/chapters/bsplines/content.en-GB.md b/docs/chapters/bsplines/content.en-GB.md index 1bbd9bfd..c808e0ee 100644 --- a/docs/chapters/bsplines/content.en-GB.md +++ b/docs/chapters/bsplines/content.en-GB.md @@ -221,7 +221,7 @@ This is essentially the "free form" version of a B-Spline, and also the least in ## One last thing: Rational B-Splines -While it is true that this section on B-Splines is running quite long already, there is one more thing we need to talk about, and that's "Rational" splines, where the rationality applies to the "ratio", or relative weights, of the control points themselves. By introducing a ratio vector with weights to apply to each control point, we greatly increase our influence over the final curve shape: the more weight a control point carries, the closer to that point the spline curve will lie, a bit like turning up the gravity of a control pointl, just like for rational Bézier curves. +While it is true that this section on B-Splines is running quite long already, there is one more thing we need to talk about, and that's "Rational" splines, where the rationality applies to the "ratio", or relative weights, of the control points themselves. By introducing a ratio vector with weights to apply to each control point, we greatly increase our influence over the final curve shape: the more weight a control point carries, the closer to that point the spline curve will lie, a bit like turning up the gravity of a control point, just like for rational Bézier curves. diff --git a/docs/chapters/canonical/content.en-GB.md b/docs/chapters/canonical/content.en-GB.md index f39b5e93..9f0cfb09 100644 --- a/docs/chapters/canonical/content.en-GB.md +++ b/docs/chapters/canonical/content.en-GB.md @@ -48,7 +48,7 @@ Without repeating the paper mentioned at the top of this section, the loop-bound The right bound for the loop region, indicating where the curve switches from "having inflections" to "having a loop", for the general cubic curve, is actually mirrored over x=1, but for Bézier curves this right half doesn't apply, so we don't need to pay attention to it. Similarly, the boundaries for t=0 and t=1 loops are also nice clean curves but get "cut off" when we only look at what the general curve does over the interval t=0 to t=1. -For the full details, head over to the paper and read through sections 3 and 4. If you still remember your high school precalculus, you can probably follow along with this paper, although you might have to read it a few times before all the bits "click". +For the full details, head over to the paper and read through sections 3 and 4. If you still remember your high school pre-calculus, you can probably follow along with this paper, although you might have to read it a few times before all the bits "click". diff --git a/docs/chapters/catmullconv/content.en-GB.md b/docs/chapters/catmullconv/content.en-GB.md index be538ba4..5c8ea0ee 100644 --- a/docs/chapters/catmullconv/content.en-GB.md +++ b/docs/chapters/catmullconv/content.en-GB.md @@ -1,6 +1,6 @@ # Bézier curves and Catmull-Rom curves -Taking an excursion to different splines, the other common design curve is the [Catmull-Rom spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline), which unlike Bézier curves pass _through_ each control point, so they offer a kind of "nuilt-in" curve fitting. +Taking an excursion to different splines, the other common design curve is the [Catmull-Rom spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline), which unlike Bézier curves pass _through_ each control point, so they offer a kind of "built-in" curve fitting. In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, and lets you add points by clicking/tapping the background, as well as let you control "how fast" the curve passes through its point using the tension slider. The tenser the curve, the more the curve tends towards straight lines from one point to the next. @@ -28,7 +28,7 @@ Now, it may look like Catmull-Rom curves are very different from Bézier curves, \right ]_{point-tangent} \] -One downside of this is that—as you may have noticed from the graphic—the first and last point of the overall curve don't actually join up with the rest of the curve: they don't have a previous/next point respectively, and so there is no way to calculate what their tangent should be. Which also makes it rather tricky to fit a Camull-Rom curve to three points like we were able to do for Bézier curves. More on that in [the next section](#catmullfitting). +One downside of this is that—as you may have noticed from the graphic—the first and last point of the overall curve don't actually join up with the rest of the curve: they don't have a previous/next point respectively, and so there is no way to calculate what their tangent should be. Which also makes it rather tricky to fit a Catmull-Rom curve to three points like we were able to do for Bézier curves. More on that in [the next section](#catmullfitting). In fact, before we move on, let's look at how to actually draw the basic form of these curves (I say basic, because there are a number of variations that make things [considerable](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline#Definition) more [complex](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline)): @@ -54,11 +54,11 @@ for p = 1 to points.length-3 (inclusive): point(c0 * v1 + c1 * dv1 + c2 * v2 + c3 * dv2) ``` -Now, since a Catmull-Rom curve is a form of [cubic Hermite spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline), and as cubic Bézier curves are _also_ a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can losslessly convert between the two, and the maths for doing so is surprisingly simple! +Now, since a Catmull-Rom curve is a form of [cubic Hermite spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline), and as cubic Bézier curves are _also_ a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can convert one to the other and back, and the maths for doing so is surprisingly simple! The main difference between Catmull-Rom curves and Bézier curves is "what the points mean": -- A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point, plus a characterising matrix that we can multiply by that point vector to get on-curve coordinates. +- A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point, plus a characterizing matrix that we can multiply by that point vector to get on-curve coordinates. - A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point, plus a characteristic matrix that we can multiple by the point vector to get on-curve coordinates. Those are _very_ similar, so let's see exactly _how_ similar they are. We've already see the matrix form for Bézier curves, so how different is the matrix form for Catmull-Rom curves?: diff --git a/docs/chapters/curvature/content.en-GB.md b/docs/chapters/curvature/content.en-GB.md index 01020d22..ce0a62ce 100644 --- a/docs/chapters/curvature/content.en-GB.md +++ b/docs/chapters/curvature/content.en-GB.md @@ -8,7 +8,7 @@ What we want is to ensure that the [curvature](https://en.wikipedia.org/wiki/Cur Problem solved! -However, there's a problem with this approach: if we think about this a little more, we realise that "what a curve looks like" and its derivative values are pretty much entirely unrelated. After all, the section on [reordering curves](#reordering) showed us that the same looking curve can have an infinite number of curve expressions of arbitraryly high Bézier degree, and each of those will have _widly_ different derivative values. +However, there's a problem with this approach: if we think about this a little more, we realise that "what a curve looks like" and its derivative values are pretty much entirely unrelated. After all, the section on [reordering curves](#reordering) showed us that the same looking curve can have an infinite number of curve expressions of arbitrarily high Bézier degree, and each of those will have _wildly_ different derivative values. So what we really want is some kind of expression that's not based on any particular expression of `t`, but is based on something that is invariant to the _kind_ of function(s) we use to draw our curve. And the prime candidate for this is our curve expression, reparameterised for distance: no matter what order of Bézier curve we use, if we were able to rewrite it as a function of distance-along-the-curve, all those different degree Bézier functions would end up being _the same_ function for "coordinate at some distance D along the curve". @@ -39,7 +39,7 @@ Which is really just a "short form" that glosses over the fact that we're dealin \kappa(t) = \frac{{B_x}'(t){B_y}''(t) - {B_x}''(t){B_y}'(t)}{({B_x}'(t)^2+{B_y}'(t)^2)^{\frac{3}{2}}} \] -And while that's a litte more verbose, it's still just as simple to work with as the first function: the curvature at some point on any (and this cannot be overstated: _any_) curve is a ratio between the first and second derivative cross product, and something that looks oddly similar to the standard Euclidean distance function. And nothing in these functions is hard to calculate either: for Bézier curves, simply knowing our curve coordinates means [we know what the first and second derivatives are](#derivatives), and so evaluating this function for any **t** value is just a matter of basic arithematics. +And while that's a little more verbose, it's still just as simple to work with as the first function: the curvature at some point on any (and this cannot be overstated: _any_) curve is a ratio between the first and second derivative cross product, and something that looks oddly similar to the standard Euclidean distance function. And nothing in these functions is hard to calculate either: for Bézier curves, simply knowing our curve coordinates means [we know what the first and second derivatives are](#derivatives), and so evaluating this function for any **t** value is just a matter of basic arithematics. In fact, let's just implement it right now: @@ -53,9 +53,9 @@ function kappa(t, B): return numerator / denominator ``` -That was easy! (Well okay, that "not a number" value will need to be taken into account by downstream code, but that's a reality of programming anwyay) +That was easy! (Well okay, that "not a number" value will need to be taken into account by downstream code, but that's a reality of programming anyway) -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. +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 formula 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. diff --git a/docs/chapters/curvefitting/content.en-GB.md b/docs/chapters/curvefitting/content.en-GB.md index 4bf7fc89..b7178380 100644 --- a/docs/chapters/curvefitting/content.en-GB.md +++ b/docs/chapters/curvefitting/content.en-GB.md @@ -249,7 +249,7 @@ Now, given the above derivative, we can rearrange the terms (following the rules Here, the "to the power negative one" is the notation for the [matrix inverse](https://en.wikipedia.org/wiki/Invertible_matrix). But that's all we have to do: we're done. Starting with **P** and inventing some `t` values based on the polygon the coordinates in **P** define, we can compute the corresponding Bézier coordinates **C** that specify a curve that goes through our points. Or, if it can't go through them exactly, as near as possible. -So before we try that out, how much code is involved in implementing this? Honestly, that answer depends on how much you're going to be writing yourself. If you already have a matrix maths library available, then really not that much code at all. On the other hand, if you are writing this from scratch, you're going to have to write some utility functions for doing your matrix work for you, so it's really anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to prespecified coordinates. +So before we try that out, how much code is involved in implementing this? Honestly, that answer depends on how much you're going to be writing yourself. If you already have a matrix maths library available, then really not that much code at all. On the other hand, if you are writing this from scratch, you're going to have to write some utility functions for doing your matrix work for you, so it's really anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to pre-specified coordinates. So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once there's enough points for compound [floating point rounding errors](https://en.wikipedia.org/wiki/Round-off_error#Floating-point_number_system) to start making a difference (which is around 10~11 points). diff --git a/docs/chapters/extremities/content.en-GB.md b/docs/chapters/extremities/content.en-GB.md index f96f54b0..b76d6add 100644 --- a/docs/chapters/extremities/content.en-GB.md +++ b/docs/chapters/extremities/content.en-GB.md @@ -87,9 +87,9 @@ Back in the 16th 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](https://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 equation. -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. +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 Cardano realised this, centuries before 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. So, how do we rewrite the hard formula into the easier formula? This is explained in detail over at [Ken J. Ward's page](https://trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm) for solving the cubic equation, so instead of showing the maths, I'm simply going to show the programming code for solving the cubic equation, with the complex roots getting totally ignored, but if you're interested you should definitely head over to Ken's page and give the procedure a read-through. @@ -187,7 +187,7 @@ function getCubicRoots(pa, pb, pc, pd) { And that's it. The maths is complicated, but the code is pretty much just "follow the maths, while caching as many values as we can to prevent recomputing things as much as possible" and now we have a way to find all roots for a cubic function and can just move on with using that to find extremities of our curves. -And of course, as a quartic curve also has meaningful second and third derivatives, we can quite easily compute those by using the derivative of the derivative (of the derivative), just as for cubic cuvers. +And of course, as a quartic curve also has meaningful second and third derivatives, we can quite easily compute those by using the derivative of the derivative (of the derivative), just as for cubic curves. ### Quintic and higher order curves: finding numerical solutions @@ -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](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. +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 initial 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 `fy(x)` is zero, so that the next `t` is the same as the one we already have: diff --git a/docs/chapters/molding/content.en-GB.md b/docs/chapters/molding/content.en-GB.md index 4f660148..347d0724 100644 --- a/docs/chapters/molding/content.en-GB.md +++ b/docs/chapters/molding/content.en-GB.md @@ -2,7 +2,7 @@ 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`: +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 "wherever 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 @@ -26,12 +26,12 @@ For example, let's see what happens if we just "go with what we get" when we pic 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: +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 "idealized" curve through the start/cursor/end points, with idealized `t` value, and then interpolating between those two curves: -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. +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 "idealized" form the further away we move our point, with anything that's further than our falloff distance simply _being_ the idealized 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! diff --git a/docs/chapters/pointcurves/content.en-GB.md b/docs/chapters/pointcurves/content.en-GB.md index e923205f..feb143fd 100644 --- a/docs/chapters/pointcurves/content.en-GB.md +++ b/docs/chapters/pointcurves/content.en-GB.md @@ -54,6 +54,6 @@ It is important to remember that even though we're using a circular arc to come -That looks perfectly servicable! +That looks perfectly serviceable! 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! diff --git a/docs/chapters/pointvectors/content.en-GB.md b/docs/chapters/pointvectors/content.en-GB.md index a3383299..c0be12fc 100644 --- a/docs/chapters/pointvectors/content.en-GB.md +++ b/docs/chapters/pointvectors/content.en-GB.md @@ -39,7 +39,7 @@ The tangent is very useful for moving along a line, but what if we want to move
Rotating coordinates is actually very easy, if you know the rule for it. You might find it explained as "applying a [rotation matrix](https://en.wikipedia.org/wiki/Rotation_matrix), which is what we'll look at here, too. Essentially, the idea is to take the circles over which we can rotate, and simply "sliding the coordinates" over these circles by the desired -angle. If we want a quarter circle turn, we take the coordinate, slide it along the cirle by a quarter turn, and done. +angle. If we want a quarter circle turn, we take the coordinate, slide it along the circle by a quarter turn, and done. To turn any point (x,y) into a rotated point (x',y') (over 0,0) by some angle φ, we apply this nice and easy computation: diff --git a/docs/chapters/yforx/content.en-GB.md b/docs/chapters/yforx/content.en-GB.md index 7ca10b38..facc6fe8 100644 --- a/docs/chapters/yforx/content.en-GB.md +++ b/docs/chapters/yforx/content.en-GB.md @@ -1,6 +1,6 @@ # Finding Y, given X -One common task that pops up in things like CSS work, or parametric equalisers, or image leveling, or any other number of applications where Bézier curves are used as control curves in a way that there is really only ever one "y" value associated with one "x" value, you might want to cut out the middle man, as it were, and compute "y" directly based on "x". After all, the function looks simple enough, finding the "y" value should be simple too, right? Unfortunately, not really. However, it _is_ possible and as long as you have some code in place to help, it's not a lot of a work either. +One common task that pops up in things like CSS work, or parametric equalizers, or image leveling, or any other number of applications where Bézier curves are used as control curves in a way that there is really only ever one "y" value associated with one "x" value, you might want to cut out the middle man, as it were, and compute "y" directly based on "x". After all, the function looks simple enough, finding the "y" value should be simple too, right? Unfortunately, not really. However, it _is_ possible and as long as you have some code in place to help, it's not a lot of a work either. 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. @@ -8,7 +8,7 @@ We'll be tackling this problem in two stages: the first, which is the hard part, -Now, if you look more closely at that right graphic, you'll notice something interesting: if we treat the red line as "the x axis", then the point where the function crosses our line is really just a root for the cubic function x(t) through a shifted "x-axis"... and [we've already seen](#extremities) how to calculate roots, so let's just run cubuc root finding - and not even the complicated cubic case either: because of the kind of curve we're starting with, we _know_ there is only root, simplifying the code we need! +Now, if you look more closely at that right graphic, you'll notice something interesting: if we treat the red line as "the x axis", then the point where the function crosses our line is really just a root for the cubic function x(t) through a shifted "x-axis"... and [we've already seen](#extremities) how to calculate roots, so let's just run cubic root finding - and not even the complicated cubic case either: because of the kind of curve we're starting with, we _know_ there is only root, simplifying the code we need! First, let's look at the function for x(t): @@ -43,7 +43,7 @@ t = getRoots(p[0], p[1], p[2], p[3])[0] y = curve.get(t).y ``` -So the procedure is fairly straight forward: pick an `x`, find the associted `t` value, evaluate our curve _for_ that `t` value, which gives us the curve's {x,y} coordinate, which means we know `y` for this `x`. Move the slider for the following graphic to see this in action: +So the procedure is fairly straight forward: pick an `x`, find the associated `t` value, evaluate our curve _for_ that `t` value, which gives us the curve's {x,y} coordinate, which means we know `y` for this `x`. Move the slider for the following graphic to see this in action: diff --git a/docs/index.html b/docs/index.html index 386b55f0..34016d7b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -31,7 +31,7 @@ - + @@ -2364,7 +2364,7 @@ function drawCurve(points[], t): Rotating coordinates is actually very easy, if you know the rule for it. You might find it explained as "applying a rotation matrix, which is what we'll look at here, too. Essentially, the idea is to take the circles over which we can rotate, and simply "sliding the coordinates" over these circles by the desired angle. If - we want a quarter circle turn, we take the coordinate, slide it along the cirle by a quarter turn, and done. + we want a quarter circle turn, we take the coordinate, slide it along the circle by a quarter turn, and done.

To turn any point (x,y) into a rotated point (x',y') (over 0,0) by some angle φ, we apply this nice and easy computation: @@ -2930,12 +2930,12 @@ generateRMFrames(steps) -> frames:

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 to find the values that satisfy the equasion. + regular calculus to find the values that satisfy the equation.

Now, there is one small hitch: as a cubic function, the solutions may be - complex numbers 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 + complex numbers rather than plain numbers... And Cardano realised this, + centuries before 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. @@ -3295,7 +3295,7 @@ function getCubicRoots(pa, pb, pc, pd) {

And of course, as a quartic curve also has meaningful second and third derivatives, we can quite easily compute those by using the - derivative of the derivative (of the derivative), just as for cubic cuvers. + derivative of the derivative (of the derivative), just as for cubic curves.

Quintic and higher order curves: finding numerical solutions

@@ -3316,7 +3316,7 @@ function getCubicRoots(pa, pb, pc, pd) { 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 root finding method (yes, after that 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 + consists of taking our impossible-to-solve function f(x), picking some initial 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 @@ -3754,7 +3754,7 @@ function getCubicRoots(pa, pb, pc, pd) { curve does over the interval t=0 to t=1.

- For the full details, head over to the paper and read through sections 3 and 4. If you still remember your high school precalculus, you + For the full details, head over to the paper and read through sections 3 and 4. If you still remember your high school pre-calculus, you can probably follow along with this paper, although you might have to read it a few times before all the bits "click".

@@ -3876,7 +3876,7 @@ function getCubicRoots(pa, pb, pc, pd) { Finding Y, given X

- One common task that pops up in things like CSS work, or parametric equalisers, or image leveling, or any other number of applications + One common task that pops up in things like CSS work, or parametric equalizers, or image leveling, or any other number of applications where Bézier curves are used as control curves in a way that there is really only ever one "y" value associated with one "x" value, you might want to cut out the middle man, as it were, and compute "y" directly based on "x". After all, the function looks simple enough, finding the "y" value should be simple too, right? Unfortunately, not really. However, it is possible and as long as you have @@ -3905,7 +3905,7 @@ function getCubicRoots(pa, pb, pc, pd) {

Now, if you look more closely at that right graphic, you'll notice something interesting: if we treat the red line as "the x axis", then the point where the function crosses our line is really just a root for the cubic function x(t) through a shifted "x-axis"... and - we've already seen how to calculate roots, so let's just run cubuc root finding - and not even the complicated + we've already seen how to calculate roots, so let's just run cubic root finding - and not even the complicated cubic case either: because of the kind of curve we're starting with, we know there is only root, simplifying the code we need!

First, let's look at the function for x(t):

@@ -3974,7 +3974,7 @@ y = curve.get(t).y

- So the procedure is fairly straight forward: pick an x, find the associted t value, evaluate our curve + So the procedure is fairly straight forward: pick an x, find the associated t value, evaluate our curve for that t value, which gives us the curve's {x,y} coordinate, which means we know y for this x. Move the slider for the following graphic to see this in action:

@@ -4211,8 +4211,8 @@ y = curve.get(t).y However, there's a problem with this approach: if we think about this a little more, we realise that "what a curve looks like" and its derivative values are pretty much entirely unrelated. After all, the section on reordering curves showed us that - the same looking curve can have an infinite number of curve expressions of arbitraryly high Bézier degree, and each of those will have - widly different derivative values. + the same looking curve can have an infinite number of curve expressions of arbitrarily high Bézier degree, and each of those will have + wildly different derivative values.

So what we really want is some kind of expression that's not based on any particular expression of t, but is based on @@ -4256,7 +4256,7 @@ y = curve.get(t).y

- And while that's a litte more verbose, it's still just as simple to work with as the first function: the curvature at some point on any + And while that's a little more verbose, it's still just as simple to work with as the first function: the curvature at some point on any (and this cannot be overstated: any) curve is a ratio between the first and second derivative cross product, and something that looks oddly similar to the standard Euclidean distance function. And nothing in these functions is hard to calculate either: for Bézier curves, simply knowing our curve coordinates means we know what the first and second derivatives are, and so @@ -4301,12 +4301,12 @@ function kappa(t, B):

That was easy! (Well okay, that "not a number" value will need to be taken into account by downstream code, but that's a reality of - programming anwyay) + programming anyway)

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 + derived based on maths that "ignores" specific function derivative, and instead gives a formula 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.

@@ -4705,7 +4705,7 @@ lli = function(line1, line2): 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.
  • - for both qudratic and cubic curves, two points e1 and e2, which represent the single-to-last step in de + for both quadratic 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.
  • @@ -4751,7 +4751,7 @@ lli = function(line1, line2):

    Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a - t value, we can contruct curves: we can compute C using the start and end points, and our + t value, we can construct curves: we can compute C using the start and end points, and our u(t) function, and once we have C, we can use our on-curve point (B) and the ratio(t) function to find A:

    @@ -4894,7 +4894,7 @@ lli = function(line1, line2): -

    That looks perfectly servicable!

    +

    That looks perfectly serviceable!

    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 @@ -5004,7 +5004,7 @@ for (coordinate, index) in LUT:

    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 + initial B coordinate. We don't even need the latter: with our t value and "wherever the cursor is" as target B, we can compute the associated C:

    @@ -5048,7 +5048,7 @@ for (coordinate, index) in LUT:

    One way to combat this might be to combine the above approach with the approach from the creating curves 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 + well as the "idealized" curve through the start/cursor/end points, with idealized t value, and then interpolating between those two curves:

    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. + towards "idealized" form the further away we move our point, with anything that's further than our falloff distance simply + being the idealized 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 @@ -5385,7 +5385,7 @@ for (coordinate, index) in LUT: So before we try that out, how much code is involved in implementing this? Honestly, that answer depends on how much you're going to be writing yourself. If you already have a matrix maths library available, then really not that much code at all. On the other hand, if you are writing this from scratch, you're going to have to write some utility functions for doing your matrix work for you, so it's really - anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to prespecified + anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to pre-specified coordinates.

    @@ -5421,7 +5421,7 @@ for (coordinate, index) in LUT:

    Taking an excursion to different splines, the other common design curve is the Catmull-Rom spline, which unlike Bézier curves - pass through each control point, so they offer a kind of "nuilt-in" curve fitting. + pass through each control point, so they offer a kind of "built-in" curve fitting.

    In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, and lets @@ -5455,7 +5455,7 @@ for (coordinate, index) in LUT:

    One downside of this is that—as you may have noticed from the graphic—the first and last point of the overall curve don't actually join up with the rest of the curve: they don't have a previous/next point respectively, and so there is no way to calculate what their tangent - should be. Which also makes it rather tricky to fit a Camull-Rom curve to three points like we were able to do for Bézier curves. More on + should be. Which also makes it rather tricky to fit a Catmull-Rom curve to three points like we were able to do for Bézier curves. More on that in the next section.

    @@ -5550,14 +5550,14 @@ for p = 1 to points.length-3 (inclusive):

    Now, since a Catmull-Rom curve is a form of cubic Hermite spline, and as - cubic Bézier curves are also a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can - losslessly convert between the two, and the maths for doing so is surprisingly simple! + cubic Bézier curves are also a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can convert + one to the other and back, and the maths for doing so is surprisingly simple!

    The main difference between Catmull-Rom curves and Bézier curves is "what the points mean":

    • A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies - the tangent at the end, and an end point, plus a characterising matrix that we can multiply by that point vector to get on-curve + the tangent at the end, and an end point, plus a characterizing matrix that we can multiply by that point vector to get on-curve coordinates.
    • @@ -7081,8 +7081,8 @@ for(let L = 1; L <= order; L++) { While it is true that this section on B-Splines is running quite long already, there is one more thing we need to talk about, and that's "Rational" splines, where the rationality applies to the "ratio", or relative weights, of the control points themselves. By introducing a ratio vector with weights to apply to each control point, we greatly increase our influence over the final curve shape: the more weight a - control point carries, the closer to that point the spline curve will lie, a bit like turning up the gravity of a control pointl, just - like for rational Bézier curves. + control point carries, the closer to that point the spline curve will lie, a bit like turning up the gravity of a control point, just like + for rational Bézier curves.

      diff --git a/docs/ja-JP/index.html b/docs/ja-JP/index.html index 532cac77..77d4a83c 100644 --- a/docs/ja-JP/index.html +++ b/docs/ja-JP/index.html @@ -33,7 +33,7 @@ - + @@ -2204,7 +2204,7 @@ function drawCurve(points[], t): Rotating coordinates is actually very easy, if you know the rule for it. You might find it explained as "applying a rotation matrix, which is what we'll look at here, too. Essentially, the idea is to take the circles over which we can rotate, and simply "sliding the coordinates" over these circles by the desired angle. If - we want a quarter circle turn, we take the coordinate, slide it along the cirle by a quarter turn, and done. + we want a quarter circle turn, we take the coordinate, slide it along the circle by a quarter turn, and done.

      To turn any point (x,y) into a rotated point (x',y') (over 0,0) by some angle φ, we apply this nice and easy computation: @@ -2770,12 +2770,12 @@ generateRMFrames(steps) -> frames:

      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 to find the values that satisfy the equasion. + regular calculus to find the values that satisfy the equation.

      Now, there is one small hitch: as a cubic function, the solutions may be - complex numbers 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 + complex numbers rather than plain numbers... And Cardano realised this, + centuries before 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. @@ -3135,7 +3135,7 @@ function getCubicRoots(pa, pb, pc, pd) {

      And of course, as a quartic curve also has meaningful second and third derivatives, we can quite easily compute those by using the - derivative of the derivative (of the derivative), just as for cubic cuvers. + derivative of the derivative (of the derivative), just as for cubic curves.

      Quintic and higher order curves: finding numerical solutions

      @@ -3156,7 +3156,7 @@ function getCubicRoots(pa, pb, pc, pd) { 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 root finding method (yes, after that 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 + consists of taking our impossible-to-solve function f(x), picking some initial 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 @@ -3594,7 +3594,7 @@ function getCubicRoots(pa, pb, pc, pd) { curve does over the interval t=0 to t=1.

      - For the full details, head over to the paper and read through sections 3 and 4. If you still remember your high school precalculus, you + For the full details, head over to the paper and read through sections 3 and 4. If you still remember your high school pre-calculus, you can probably follow along with this paper, although you might have to read it a few times before all the bits "click".

      @@ -3716,7 +3716,7 @@ function getCubicRoots(pa, pb, pc, pd) { Finding Y, given X

      - One common task that pops up in things like CSS work, or parametric equalisers, or image leveling, or any other number of applications + One common task that pops up in things like CSS work, or parametric equalizers, or image leveling, or any other number of applications where Bézier curves are used as control curves in a way that there is really only ever one "y" value associated with one "x" value, you might want to cut out the middle man, as it were, and compute "y" directly based on "x". After all, the function looks simple enough, finding the "y" value should be simple too, right? Unfortunately, not really. However, it is possible and as long as you have @@ -3745,7 +3745,7 @@ function getCubicRoots(pa, pb, pc, pd) {

      Now, if you look more closely at that right graphic, you'll notice something interesting: if we treat the red line as "the x axis", then the point where the function crosses our line is really just a root for the cubic function x(t) through a shifted "x-axis"... and - we've already seen how to calculate roots, so let's just run cubuc root finding - and not even the complicated + we've already seen how to calculate roots, so let's just run cubic root finding - and not even the complicated cubic case either: because of the kind of curve we're starting with, we know there is only root, simplifying the code we need!

      First, let's look at the function for x(t):

      @@ -3814,7 +3814,7 @@ y = curve.get(t).y

      - So the procedure is fairly straight forward: pick an x, find the associted t value, evaluate our curve + So the procedure is fairly straight forward: pick an x, find the associated t value, evaluate our curve for that t value, which gives us the curve's {x,y} coordinate, which means we know y for this x. Move the slider for the following graphic to see this in action:

      @@ -4051,8 +4051,8 @@ y = curve.get(t).y However, there's a problem with this approach: if we think about this a little more, we realise that "what a curve looks like" and its derivative values are pretty much entirely unrelated. After all, the section on reordering curves showed us that - the same looking curve can have an infinite number of curve expressions of arbitraryly high Bézier degree, and each of those will have - widly different derivative values. + the same looking curve can have an infinite number of curve expressions of arbitrarily high Bézier degree, and each of those will have + wildly different derivative values.

      So what we really want is some kind of expression that's not based on any particular expression of t, but is based on @@ -4096,7 +4096,7 @@ y = curve.get(t).y

      - And while that's a litte more verbose, it's still just as simple to work with as the first function: the curvature at some point on any + And while that's a little more verbose, it's still just as simple to work with as the first function: the curvature at some point on any (and this cannot be overstated: any) curve is a ratio between the first and second derivative cross product, and something that looks oddly similar to the standard Euclidean distance function. And nothing in these functions is hard to calculate either: for Bézier curves, simply knowing our curve coordinates means we know what the first and second derivatives are, and so @@ -4141,12 +4141,12 @@ function kappa(t, B):

      That was easy! (Well okay, that "not a number" value will need to be taken into account by downstream code, but that's a reality of - programming anwyay) + programming anyway)

      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 + derived based on maths that "ignores" specific function derivative, and instead gives a formula 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.

      @@ -4545,7 +4545,7 @@ lli = function(line1, line2): 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.
    • - for both qudratic and cubic curves, two points e1 and e2, which represent the single-to-last step in de + for both quadratic 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.
    • @@ -4591,7 +4591,7 @@ lli = function(line1, line2):

      Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a - t value, we can contruct curves: we can compute C using the start and end points, and our + t value, we can construct curves: we can compute C using the start and end points, and our u(t) function, and once we have C, we can use our on-curve point (B) and the ratio(t) function to find A:

      @@ -4734,7 +4734,7 @@ lli = function(line1, line2): -

      That looks perfectly servicable!

      +

      That looks perfectly serviceable!

      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 @@ -4844,7 +4844,7 @@ for (coordinate, index) in LUT:

      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 + initial B coordinate. We don't even need the latter: with our t value and "wherever the cursor is" as target B, we can compute the associated C:

      @@ -4888,7 +4888,7 @@ for (coordinate, index) in LUT:

      One way to combat this might be to combine the above approach with the approach from the creating curves 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 + well as the "idealized" curve through the start/cursor/end points, with idealized t value, and then interpolating between those two curves:

      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. + towards "idealized" form the further away we move our point, with anything that's further than our falloff distance simply + being the idealized 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 @@ -5225,7 +5225,7 @@ for (coordinate, index) in LUT: So before we try that out, how much code is involved in implementing this? Honestly, that answer depends on how much you're going to be writing yourself. If you already have a matrix maths library available, then really not that much code at all. On the other hand, if you are writing this from scratch, you're going to have to write some utility functions for doing your matrix work for you, so it's really - anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to prespecified + anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to pre-specified coordinates.

      @@ -5261,7 +5261,7 @@ for (coordinate, index) in LUT:

      Taking an excursion to different splines, the other common design curve is the Catmull-Rom spline, which unlike Bézier curves - pass through each control point, so they offer a kind of "nuilt-in" curve fitting. + pass through each control point, so they offer a kind of "built-in" curve fitting.

      In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, and lets @@ -5295,7 +5295,7 @@ for (coordinate, index) in LUT:

      One downside of this is that—as you may have noticed from the graphic—the first and last point of the overall curve don't actually join up with the rest of the curve: they don't have a previous/next point respectively, and so there is no way to calculate what their tangent - should be. Which also makes it rather tricky to fit a Camull-Rom curve to three points like we were able to do for Bézier curves. More on + should be. Which also makes it rather tricky to fit a Catmull-Rom curve to three points like we were able to do for Bézier curves. More on that in the next section.

      @@ -5390,14 +5390,14 @@ for p = 1 to points.length-3 (inclusive):

      Now, since a Catmull-Rom curve is a form of cubic Hermite spline, and as - cubic Bézier curves are also a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can - losslessly convert between the two, and the maths for doing so is surprisingly simple! + cubic Bézier curves are also a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can convert + one to the other and back, and the maths for doing so is surprisingly simple!

      The main difference between Catmull-Rom curves and Bézier curves is "what the points mean":

      • A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies - the tangent at the end, and an end point, plus a characterising matrix that we can multiply by that point vector to get on-curve + the tangent at the end, and an end point, plus a characterizing matrix that we can multiply by that point vector to get on-curve coordinates.
      • @@ -6921,8 +6921,8 @@ for(let L = 1; L <= order; L++) { While it is true that this section on B-Splines is running quite long already, there is one more thing we need to talk about, and that's "Rational" splines, where the rationality applies to the "ratio", or relative weights, of the control points themselves. By introducing a ratio vector with weights to apply to each control point, we greatly increase our influence over the final curve shape: the more weight a - control point carries, the closer to that point the spline curve will lie, a bit like turning up the gravity of a control pointl, just - like for rational Bézier curves. + control point carries, the closer to that point the spline curve will lie, a bit like turning up the gravity of a control point, just like + for rational Bézier curves.

        diff --git a/docs/news/2020-09-18.html b/docs/news/2020-09-18.html index 7f49610b..194efba3 100644 --- a/docs/news/2020-09-18.html +++ b/docs/news/2020-09-18.html @@ -27,7 +27,7 @@ - + diff --git a/docs/news/index.html b/docs/news/index.html index b6dc84ca..0bb722c8 100644 --- a/docs/news/index.html +++ b/docs/news/index.html @@ -26,7 +26,7 @@ - + diff --git a/docs/news/rss.xml b/docs/news/rss.xml index 8beccaee..0c7b7024 100644 --- a/docs/news/rss.xml +++ b/docs/news/rss.xml @@ -6,7 +6,7 @@ News updates for the primer on Bézier Curves by Pomax en-GB - Sat Sep 26 2020 15:21:50 +00:00 + Sat Sep 26 2020 15:53:06 +00:00 https://pomax.github.io/bezierinfo/images/og-image.png A Primer on Bézier Curves diff --git a/docs/zh-CN/index.html b/docs/zh-CN/index.html index 7b9965f4..37f1776d 100644 --- a/docs/zh-CN/index.html +++ b/docs/zh-CN/index.html @@ -33,7 +33,7 @@ - + @@ -2178,7 +2178,7 @@ function drawCurve(points[], t): Rotating coordinates is actually very easy, if you know the rule for it. You might find it explained as "applying a rotation matrix, which is what we'll look at here, too. Essentially, the idea is to take the circles over which we can rotate, and simply "sliding the coordinates" over these circles by the desired angle. If - we want a quarter circle turn, we take the coordinate, slide it along the cirle by a quarter turn, and done. + we want a quarter circle turn, we take the coordinate, slide it along the circle by a quarter turn, and done.

        To turn any point (x,y) into a rotated point (x',y') (over 0,0) by some angle φ, we apply this nice and easy computation: @@ -2744,12 +2744,12 @@ generateRMFrames(steps) -> frames:

        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 to find the values that satisfy the equasion. + regular calculus to find the values that satisfy the equation.

        Now, there is one small hitch: as a cubic function, the solutions may be - complex numbers 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 + complex numbers rather than plain numbers... And Cardano realised this, + centuries before 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. @@ -3109,7 +3109,7 @@ function getCubicRoots(pa, pb, pc, pd) {

        And of course, as a quartic curve also has meaningful second and third derivatives, we can quite easily compute those by using the - derivative of the derivative (of the derivative), just as for cubic cuvers. + derivative of the derivative (of the derivative), just as for cubic curves.

        Quintic and higher order curves: finding numerical solutions

        @@ -3130,7 +3130,7 @@ function getCubicRoots(pa, pb, pc, pd) { 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 root finding method (yes, after that 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 + consists of taking our impossible-to-solve function f(x), picking some initial 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 @@ -3568,7 +3568,7 @@ function getCubicRoots(pa, pb, pc, pd) { curve does over the interval t=0 to t=1.

        - For the full details, head over to the paper and read through sections 3 and 4. If you still remember your high school precalculus, you + For the full details, head over to the paper and read through sections 3 and 4. If you still remember your high school pre-calculus, you can probably follow along with this paper, although you might have to read it a few times before all the bits "click".

        @@ -3690,7 +3690,7 @@ function getCubicRoots(pa, pb, pc, pd) { Finding Y, given X

        - One common task that pops up in things like CSS work, or parametric equalisers, or image leveling, or any other number of applications + One common task that pops up in things like CSS work, or parametric equalizers, or image leveling, or any other number of applications where Bézier curves are used as control curves in a way that there is really only ever one "y" value associated with one "x" value, you might want to cut out the middle man, as it were, and compute "y" directly based on "x". After all, the function looks simple enough, finding the "y" value should be simple too, right? Unfortunately, not really. However, it is possible and as long as you have @@ -3719,7 +3719,7 @@ function getCubicRoots(pa, pb, pc, pd) {

        Now, if you look more closely at that right graphic, you'll notice something interesting: if we treat the red line as "the x axis", then the point where the function crosses our line is really just a root for the cubic function x(t) through a shifted "x-axis"... and - we've already seen how to calculate roots, so let's just run cubuc root finding - and not even the complicated + we've already seen how to calculate roots, so let's just run cubic root finding - and not even the complicated cubic case either: because of the kind of curve we're starting with, we know there is only root, simplifying the code we need!

        First, let's look at the function for x(t):

        @@ -3788,7 +3788,7 @@ y = curve.get(t).y

        - So the procedure is fairly straight forward: pick an x, find the associted t value, evaluate our curve + So the procedure is fairly straight forward: pick an x, find the associated t value, evaluate our curve for that t value, which gives us the curve's {x,y} coordinate, which means we know y for this x. Move the slider for the following graphic to see this in action:

        @@ -4025,8 +4025,8 @@ y = curve.get(t).y However, there's a problem with this approach: if we think about this a little more, we realise that "what a curve looks like" and its derivative values are pretty much entirely unrelated. After all, the section on reordering curves showed us that - the same looking curve can have an infinite number of curve expressions of arbitraryly high Bézier degree, and each of those will have - widly different derivative values. + the same looking curve can have an infinite number of curve expressions of arbitrarily high Bézier degree, and each of those will have + wildly different derivative values.

        So what we really want is some kind of expression that's not based on any particular expression of t, but is based on @@ -4070,7 +4070,7 @@ y = curve.get(t).y

        - And while that's a litte more verbose, it's still just as simple to work with as the first function: the curvature at some point on any + And while that's a little more verbose, it's still just as simple to work with as the first function: the curvature at some point on any (and this cannot be overstated: any) curve is a ratio between the first and second derivative cross product, and something that looks oddly similar to the standard Euclidean distance function. And nothing in these functions is hard to calculate either: for Bézier curves, simply knowing our curve coordinates means we know what the first and second derivatives are, and so @@ -4115,12 +4115,12 @@ function kappa(t, B):

        That was easy! (Well okay, that "not a number" value will need to be taken into account by downstream code, but that's a reality of - programming anwyay) + programming anyway)

        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 + derived based on maths that "ignores" specific function derivative, and instead gives a formula 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.

        @@ -4519,7 +4519,7 @@ lli = function(line1, line2): 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.
      • - for both qudratic and cubic curves, two points e1 and e2, which represent the single-to-last step in de + for both quadratic 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.
      • @@ -4565,7 +4565,7 @@ lli = function(line1, line2):

        Which now leaves us with some powerful tools: given thee points (start, end, and "some point on the curve"), as well as a - t value, we can contruct curves: we can compute C using the start and end points, and our + t value, we can construct curves: we can compute C using the start and end points, and our u(t) function, and once we have C, we can use our on-curve point (B) and the ratio(t) function to find A:

        @@ -4708,7 +4708,7 @@ lli = function(line1, line2): -

        That looks perfectly servicable!

        +

        That looks perfectly serviceable!

        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 @@ -4818,7 +4818,7 @@ for (coordinate, index) in LUT:

        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 + initial B coordinate. We don't even need the latter: with our t value and "wherever the cursor is" as target B, we can compute the associated C:

        @@ -4862,7 +4862,7 @@ for (coordinate, index) in LUT:

        One way to combat this might be to combine the above approach with the approach from the creating curves 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 + well as the "idealized" curve through the start/cursor/end points, with idealized t value, and then interpolating between those two curves:

        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. + towards "idealized" form the further away we move our point, with anything that's further than our falloff distance simply + being the idealized 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 @@ -5199,7 +5199,7 @@ for (coordinate, index) in LUT: So before we try that out, how much code is involved in implementing this? Honestly, that answer depends on how much you're going to be writing yourself. If you already have a matrix maths library available, then really not that much code at all. On the other hand, if you are writing this from scratch, you're going to have to write some utility functions for doing your matrix work for you, so it's really - anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to prespecified + anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to pre-specified coordinates.

        @@ -5235,7 +5235,7 @@ for (coordinate, index) in LUT:

        Taking an excursion to different splines, the other common design curve is the Catmull-Rom spline, which unlike Bézier curves - pass through each control point, so they offer a kind of "nuilt-in" curve fitting. + pass through each control point, so they offer a kind of "built-in" curve fitting.

        In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, and lets @@ -5269,7 +5269,7 @@ for (coordinate, index) in LUT:

        One downside of this is that—as you may have noticed from the graphic—the first and last point of the overall curve don't actually join up with the rest of the curve: they don't have a previous/next point respectively, and so there is no way to calculate what their tangent - should be. Which also makes it rather tricky to fit a Camull-Rom curve to three points like we were able to do for Bézier curves. More on + should be. Which also makes it rather tricky to fit a Catmull-Rom curve to three points like we were able to do for Bézier curves. More on that in the next section.

        @@ -5364,14 +5364,14 @@ for p = 1 to points.length-3 (inclusive):

        Now, since a Catmull-Rom curve is a form of cubic Hermite spline, and as - cubic Bézier curves are also a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can - losslessly convert between the two, and the maths for doing so is surprisingly simple! + cubic Bézier curves are also a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can convert + one to the other and back, and the maths for doing so is surprisingly simple!

        The main difference between Catmull-Rom curves and Bézier curves is "what the points mean":

        • A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies - the tangent at the end, and an end point, plus a characterising matrix that we can multiply by that point vector to get on-curve + the tangent at the end, and an end point, plus a characterizing matrix that we can multiply by that point vector to get on-curve coordinates.
        • @@ -6895,8 +6895,8 @@ for(let L = 1; L <= order; L++) { While it is true that this section on B-Splines is running quite long already, there is one more thing we need to talk about, and that's "Rational" splines, where the rationality applies to the "ratio", or relative weights, of the control points themselves. By introducing a ratio vector with weights to apply to each control point, we greatly increase our influence over the final curve shape: the more weight a - control point carries, the closer to that point the spline curve will lie, a bit like turning up the gravity of a control pointl, just - like for rational Bézier curves. + control point carries, the closer to that point the spline curve will lie, a bit like turning up the gravity of a control point, just like + for rational Bézier curves.