var React = require("react"); var Graphic = require("../../Graphic.jsx"); var SectionHeader = require("../../SectionHeader.jsx"); var abs = Math.abs; var ABC = React.createClass({ getDefaultProps: function() { return { title: "The 'ABC' curve identity" }; }, onClick: function(evt, api) { api.t = api.curve.on({x: evt.offsetX, y: evt.offsetY},7); if (api.t < 0.05 || api.t > 0.95) api.t = false; api.redraw(); }, setupQuadratic: function(api) { var curve = api.getDefaultQuadratic(); curve.points[0].y -= 10; api.setCurve(curve); }, setupCubic: function(api) { var curve = api.getDefaultCubic(); curve.points[2].y -= 20; api.setCurve(curve); api.lut = curve.getLUT(100); }, draw: function(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var h = api.getPanelHeight(); api.setColor("black"); if (!!api.t) { api.drawCircle(api.curve.get(api.t),3); api.setColor("lightgrey"); var hull = api.drawHull(curve, api.t); var utils = api.utils; var A, B, C; if(hull.length === 6) { A = curve.points[1]; B = hull[5]; C = utils.lli4(A, B, curve.points[0], curve.points[2]); api.setColor("lightgrey"); api.drawLine(curve.points[0], curve.points[2]); } else if(hull.length === 10) { A = hull[5] B = hull[9]; C = utils.lli4(A, B, curve.points[0], curve.points[3]); api.setColor("lightgrey"); api.drawLine(curve.points[0], curve.points[3]); } api.setColor("#00FF00"); api.drawLine(A,B); api.setColor("red"); api.drawLine(B,C); api.setColor("black"); api.drawCircle(C,3); api.setFill("black"); api.text("A", {x:10 + A.x, y: A.y}); api.text("B", {x:10 + B.x, y: B.y}); api.text("C", {x:10 + C.x, y: C.y}); var d1 = utils.dist(A, B); var d2 = utils.dist(B, C); var ratio = d1/d2; api.text("d1 (A-B): " + utils.round(d1,2) + ", d2 (B-C): "+ utils.round(d2,2) + ", ratio (d1/d2): " + utils.round(ratio,4), {x:10, y:h-7}); } }, render: function() { return (

De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mould" a curve, by picking it up at some point, and dragging that point around to change the curve's shape.

How does that work? Succinctly: we run de Casteljau's algorithm in reverse!

Let's start out with a pre-existing curve, defined by start, two control points, and end. We can mould this curve by picking a point somewhere on the curve, at some t value, and the moving it to a new location and reconstructing the curve that goes through start, our new point with the original tangent, and end. In order to see how and why we can do this, let's look at some identity information for Bézier curves. There's actually a hidden goldmine of identities that we can exploit when doing Bézier operations, and this will only scratch the surface. But, in a good way!

In the following graphic, click anywhere on the curves to see the identity information that we'll be using to run de Casteljau in reverse (you can manipulate the curve even after picking a point. Note the "ratio" value when you do so: does it change?):

So, what exactly do we see in these graphics? First off, there's the three points A, B and C.

Point B is our "on curve" point, A is the first "strut" point when running de Casteljau's algorithm in reverse; for quadratic curves, this happens to also be the curve's control point. For cubic curves, it's the "top of the triangle" for the struts that lead to point B. Point C, finally, is the intersection of the line that goes through A and B and the baseline, between our start and end points.

There is some important identity information here: as long as we don't pick a new t coordinate, the location of point C on the line start-end represents a fixed ratio distance. We can drag around the control points as much as we like, that point won't move at all, and if we can drag around the start or end point, C will stay at the same ratio-value. For instance, if it was located midway between start and end, it'll stay midway between start and end, even if the line segment between start and end becomes longer or shorter.

We can also see that the distances for the lines d1 = A-B and d2 = B-C may vary, but the ratio between them, d1/d2, is a constant value. We can drag any of the start, end, or control points around as much as we like, but that value also stays the same.

In fact, because the distance ratio is a fixed value for each point B, which we get by picking some t value on our curve, the distance ratio is actually an identity function for Bézier curves. If we were to plot all the ratio values for all possible t values for quadratic and cubic curves, we'd see two very interesting functions: asymptotic at t=0 and t=1, tending towards positive infinity, with a zero-derivative minimum at t=0.5.

Since these are ratios, we can actually express the ratio values as a function of t. I actually failed at coming up with the precise functions, but thanks to some help from Boris Zbarsky we can see that the ratio functions are actually remarkably simple:

Quadratic curves:\[ ratio(t)_2 = \left | \frac{2t^2 - 2t}{2t^2 - 2t + 1} \right | \]

Cubic curves: \[ ratio(t)_3 = \left | \frac{t^3 + (1-t)^3 - 1}{t^3 + (1-t)^3} \right | \]

Unfortunately, this trick only works for quadratic and cubic curves. Once we hit higher order curves, things become a lot less predictable; the "fixed point C" is no longer fixed, moving around as we move the control points, and projections of B onto the line between start and end may actually lie on that line before the start, or after the end, and there are no simple ratios that we can exploit.

); } }); module.exports = ABC;