var React = require("react"); var Graphic = require("../../Graphic.jsx"); var SectionHeader = require("../../SectionHeader.jsx"); var Reordering = React.createClass({ getDefaultProps: function() { return { title: "Lowering and elevating curve order" }; }, getInitialState: function() { return { order: 0 }; }, setup: function(api) { var points = []; var w = api.getPanelWidth(), h = api.getPanelHeight(); for (var i=0; i<10; i++) { points.push({ x: w/2 + (Math.random() * 20) + Math.cos(Math.PI*2 * i/10) * (w/2 - 40), y: h/2 + (Math.random() * 20) + Math.sin(Math.PI*2 * i/10) * (h/2 - 40) }); } var curve = new api.Bezier(points); api.setCurve(curve); }, draw: function(api, curve) { api.reset(); var pts = curve.points; this.setState({ order: pts.length }); var p0 = pts[0]; // we can't "just draw" this curve, since it'll be an arbitrary order, // And the canvas only does 2nd and 3rd - we use de Casteljau's algorithm: for(var t=0; t<=1; t+=0.01) { var q = JSON.parse(JSON.stringify(pts)); while(q.length > 1) { for (var i=0; i { if(p===p0) return; api.setColor("#DDD"); api.drawLine(p0,p); api.setColor("black"); api.drawCircle(p,3); p0 = p; }); }, // Improve this based on http://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/ lower: function(curve) { var pts = curve.points, q=[], n = pts.length; pts.forEach((p,k) => { if (!k) { return (q[k] = p); } var f1 = k/n, f2 = 1 - f1; q[k] = { x: f1 * p.x + f2 * pts[k-1].x, y: f1 * p.y + f2 * pts[k-1].y }; }); q.splice(n-1,1); q[n-2] = pts[n-1]; curve.points = q; return curve; }, onKeyDown: function(evt, api) { evt.preventDefault(); if(evt.key === "ArrowUp") { api.setCurve(api.curve.raise()); } else if(evt.key === "ArrowDown") { api.setCurve(this.lower(api.curve)); } }, getOrder: function() { var order = this.state.order; if (order%10 === 1 && order !== 11) { order += "st"; } else if (order%10 === 2 && order !== 12) { order += "nd"; } else if (order%10 === 3 && order !== 13) { order += "rd"; } else { order += "th"; } return order; }, render: function() { return (

One interesting property of Bézier curves is that an nth order curve can always be perfectly represented by an (n+1)th order curve, by giving the higher order curve specific control points.

If we have a curve with three points, then we can create a four point curve that exactly reproduce the original curve as long as we give it the same start and end points, and for its two control points we pick "1/3rd start + 2/3rd control" and "2/3rd control + 1/3rd end", and now we have exactly the same curve as before, except represented as a cubic curve, rather than a quadratic curve.

The general rule for raising an nth order curve to an (n+1)th order curve is as follows (observing that the start and end weights are the same as the start and end weights for the old curve):

\[ Bézier(k,t) = \sum_{i=0}^{k} \underset{binomial\ term}{\underbrace{\binom{k}{i}}} \cdot\ \underset{polynomial\ term}{\underbrace{(1-t)^{k-i} \cdot t^{i}}} \ \cdot \ \underset{new\ weights}{\underbrace{\left ( \frac{(k-i) \cdot w_i + i \cdot w_{i-1}}{k} \right )}} \ ,\ with\ k = n+1 \]

However, this rule also has as direct consequence that you cannot generally safely lower a curve from nth order to (n-1)th 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.

We can apply this to a (semi) random curve, as is done in the following graphic. Select the sketch and press your up and down cursor keys to elevate or lower the curve order.

There is a good, if mathematical, explanation on the matrices necessary for optimal reduction over on Sirver's Castle, which given time will find its way in a more direct description into this article.

); } }); module.exports = Reordering; /* void setupCurve() { int d = dim - 2*pad; int order = 10; ArrayList pts = new ArrayList(); float dst = d/2.5, nx, ny, a=0, step = 2*PI/order, r; for(a=0; a<2*PI; a+=step) { r = random(-dst/4,dst/4); pts.add(new Point(d/2 + cos(a) * (r+dst), d/2 + sin(a) * (r+dst))); dst -= 1.2; } Point[] points = new Point[pts.size()]; for(int p=0,last=points.length; p */