1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-25 09:30:52 +02:00

1/3rd there

This commit is contained in:
Pomax
2016-01-01 17:52:50 -08:00
parent 71c42b8a2f
commit 3ba0ed40ae
35 changed files with 7090 additions and 156 deletions

View File

@@ -0,0 +1,182 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Reordering = React.createClass({
statics: {
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<q.length-1; i++) {
q[i] = {
x: q[i].x + (q[i+1].x - q[i].x) * t,
y: q[i].y + (q[i+1].y - q[i].y) * t
}
}
q.splice(q.length-1, 1);
}
api.drawLine(p0, q[0]);
p0 = q[0];
}
p0 = pts[0];
api.setColor("black");
api.drawCircle(p0,3);
pts.forEach(p => {
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 (
<section>
<SectionHeader {...this.props}>{ Reordering.title }</SectionHeader>
<p>One interesting property of Bézier curves is that an <i>n<sup>th</sup></i> order curve can
always be perfectly represented by an <i>(n+1)<sup>th</sup></i> order curve, by giving the
higher order curve specific control points.</p>
<p>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/3<sup>rd</sup> start + 2/3<sup>rd</sup> control" and
"2/3<sup>rd</sup> control + 1/3<sup>rd</sup> end", and now we have exactly the same curve as
before, except represented as a cubic curve, rather than a quadratic curve.</p>
<p>The general rule for raising an <i>n<sup>th</sup></i> order curve to an <i>(n+1)<sup>th</sup></i>
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):</p>
<p>\[
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
\]</p>
<p>However, this rule also has as direct consequence that you <strong>cannot</strong> generally
safely lower a curve from <i>n<sup>th</sup></i> order to <i>(n-1)<sup>th</sup></i> 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.</p>
<p>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.</p>
<Graphic preset="simple" title={"A " + this.getOrder() + " order Bézier curve"} setup={this.setup} draw={this.draw} onKeyDown={this.onKeyDown} />
<p>There is a good, if mathematical, explanation on the matrices necessary for optimal reduction
over on <a href="http://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/">Sirver's Castle</a>,
which given time will find its way in a more direct description into this article.</p>
</section>
);
}
});
module.exports = Reordering;
/*
void setupCurve() {
int d = dim - 2*pad;
int order = 10;
ArrayList<Point> pts = new ArrayList<Point>();
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<last; p++) { points[p] = pts.get(p); }
curves.add(new BezierCurve(points));
reorder();
}
void drawCurve(BezierCurve curve) {
curve.draw();
}</textarea>
*/