mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-09-02 12:54:23 +02:00
@@ -285,6 +285,8 @@ var BSplineGraphic = React.createClass({
|
||||
this.height = (h||w)|0;
|
||||
this.cvs.width = this.width;
|
||||
this.cvs.height = this.height;
|
||||
this.cvs.style.width = this.width + "px";
|
||||
this.cvs.style.height = this.height + "px";
|
||||
this.ctx = this.cvs.getContext("2d");
|
||||
},
|
||||
|
||||
|
@@ -28,12 +28,36 @@ var baseClass = {
|
||||
<figcaption>
|
||||
<a className="source" href={sourceLink}>view source</a>
|
||||
{this.props.title}
|
||||
{this.state.sliders && this.renderSliders(this.state.sliders)}
|
||||
{this.props.children}
|
||||
</figcaption>
|
||||
</figure>
|
||||
);
|
||||
},
|
||||
|
||||
// Note: requires `sliders` and `onSlide` _and_ `context` to be set!
|
||||
renderSliders: function(sliders) {
|
||||
var api = this;
|
||||
var onSlide = this.props.onSlide.bind(this.props.context);
|
||||
return sliders.map(function(v, pos) {
|
||||
var handle = function(evt) {
|
||||
evt.preventDefault();
|
||||
var value = parseFloat(evt.target.value);
|
||||
v.value = value;
|
||||
onSlide(api, value, pos);
|
||||
api.setState({ sliders: sliders });
|
||||
};
|
||||
return (
|
||||
<div className="slider">
|
||||
{ v.label && <label>{v.label}</label> }
|
||||
<span className="min val">{v.min}</span>
|
||||
<input type="range" min={v.min} max={v.max} value={v.value} step={v.step} onChange={handle} />
|
||||
<span className="max val">{v.max}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var cvs = this.refs.canvas;
|
||||
var dpr = this.getPixelRatio();
|
||||
@@ -60,6 +84,14 @@ var baseClass = {
|
||||
if (this.props.autoplay) {
|
||||
this.play();
|
||||
}
|
||||
|
||||
if (this.props.sliders) {
|
||||
var sliders = this.props.sliders;
|
||||
this.setState({
|
||||
sliders: sliders
|
||||
});
|
||||
this.props.setSliders(this, sliders.map(v => v.value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -437,7 +437,7 @@ var API = {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var p = curve.points;
|
||||
|
||||
if (p.length <= 3 || 5 <= p.length) {
|
||||
if (curve.getLUT) {
|
||||
var points = curve.getLUT(100);
|
||||
var p0 = points[0];
|
||||
points.forEach((p1,i) => {
|
||||
|
@@ -14,6 +14,7 @@ module.exports = {
|
||||
whatis: require("./whatis"),
|
||||
explanation: require("./explanation"),
|
||||
control: require("./control"),
|
||||
weightcontrol: require("./weightcontrol"),
|
||||
extended: require("./extended"),
|
||||
|
||||
// basic operations
|
||||
|
@@ -42,6 +42,12 @@ var Reordering = {
|
||||
var Mt = transpose(M);
|
||||
var Mc = multiply(Mt, M);
|
||||
var Mi = invert(Mc);
|
||||
|
||||
if (!Mi) {
|
||||
console.error('MtM has no inverse?');
|
||||
return curve;
|
||||
}
|
||||
|
||||
var V = multiply(Mi, Mt);
|
||||
|
||||
// And then we map our k-order list of coordinates
|
||||
|
73
components/sections/weightcontrol/content.en-GB.md
Normal file
73
components/sections/weightcontrol/content.en-GB.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Controlling Bézier curvatures, part 2: Rational Béziers
|
||||
|
||||
We can further control Bézier curves by "rationalising" them: that is, adding a "ratio" value in addition to the weight value discussed in the previous section, thereby gaining control over "how strongly" each coordinate influences the curve.
|
||||
|
||||
Adding these ratio values to the regular Bézier curve function is fairly easy. Where the regular function is the following:
|
||||
|
||||
\[
|
||||
Bézier(n,t) = \sum_{i=0}^{n} \binom{n}{i} \cdot (1-t)^{n-i} \cdot t^{i} \cdot w_i \cdot ratio_i
|
||||
\]
|
||||
|
||||
The function for rational Bézier curves has two more terms:
|
||||
|
||||
\[
|
||||
Rational\ Bézier(n,t) = \left ( \sum_{i=0}^{n} \binom{n}{i} \cdot (1-t)^{n-i} \cdot t^{i} \cdot w_i \cdot BLUE[ratio_i] \right ) \cdot BLUE[\frac{ 1 }{ Basis_{(n,t)} }]
|
||||
\]
|
||||
|
||||
In this, the first new term represents the ratio for each coordinate, as a multiplication factor. If our ratio values are [1, 0.5, 0.5, 1] then <code>ratio<sub>0</sub> = 1</code>, <code>ratio<sub>1</sub> = 0.5</code>, and so on. The second term then corrects for all those multiplications by dividing the total value by the "basis" value of the Bézier curve, i.e. the value we get by computing the curve without any weighting at (but _with_ ratios):
|
||||
|
||||
\[
|
||||
Basis_{n,t} = \sum_{i=0}^{n} \binom{n}{i} \cdot (1-t)^{n-i} \cdot t^{i} \cdot ratio_i
|
||||
\]
|
||||
|
||||
So what does this actually do?
|
||||
|
||||
Let's look at the effect of "rationalising" our Bézier curves by interacting with the curve and ratios. The following graphic shows the curve from the previous section, enriched with ratio factors: the closer to zero we set one or more terms, the less relative influence the associated coordinates exert on the curve (and of course the higher we set them, the more influence they have).
|
||||
|
||||
<Graphic title="Our rational cubic Bézier curve" setup={this.drawCubic} draw={this.drawCurve} sliders={[
|
||||
{min:0, max:2, value:1, step:0.01, label:`ratio 1`},
|
||||
{min:0, max:2, value:1, step:0.01, label:`ratio 2`},
|
||||
{min:0, max:2, value:1, step:0.01, label:`ratio 3`},
|
||||
{min:0, max:2, value:1, step:0.01, label:`ratio 4`}
|
||||
]} setSliders={this.setRatio} onSlide={this.changeRatio} context={this}/>
|
||||
|
||||
You can think of the ratio values as each coordinate's "gravity": the higher the gravity, the closer to that coordinate the curve will want to be. You'll also notice that if you simply increase or decrease all the ratios by the same amount, nothing changes... much like with gravity, if the relative strengths stay the same, nothing really changes. The values define each coordinate's influence _relative to all other points_.
|
||||
|
||||
<div className="howtocode">
|
||||
|
||||
### How to implement rational curves
|
||||
|
||||
Extending the code of the previous section to include ratios is almost trivial:
|
||||
|
||||
```
|
||||
function RationalBezier(2,t,w[],r[]):
|
||||
t2 = t * t
|
||||
mt = 1-t
|
||||
mt2 = mt * mt
|
||||
f = [
|
||||
r[0] * mt2,
|
||||
2 * r[1] * mt * t,
|
||||
r[2] * t2
|
||||
]
|
||||
basis = f[0] + f[1] + f[2]
|
||||
return (f[0] * w[0] + f[1] * w[1] + f[2] * w[2])/basis
|
||||
|
||||
function RationalBezier(3,t,w[],r[]):
|
||||
t2 = t * t
|
||||
t3 = t2 * t
|
||||
mt = 1-t
|
||||
mt2 = mt * mt
|
||||
mt3 = mt2 * mt
|
||||
f = [
|
||||
r[0] * mt3,
|
||||
3 * r[1] * mt2 * t,
|
||||
3 * r[2] * mt * t2,
|
||||
r[3] * t3
|
||||
]
|
||||
basis = f[0] + f[1] + f[2] + f[3]
|
||||
return (f[0] * w[0] + f[1] * w[1] + f[2] * w[2] + f[3] * w[3])/basis
|
||||
```
|
||||
|
||||
And that's all we have to do.
|
||||
|
||||
</div>
|
34
components/sections/weightcontrol/handler.js
Normal file
34
components/sections/weightcontrol/handler.js
Normal file
@@ -0,0 +1,34 @@
|
||||
var ratios;
|
||||
|
||||
module.exports = {
|
||||
drawCubic: function(api) {
|
||||
var curve = new api.Bezier(
|
||||
120, 160,
|
||||
35, 200,
|
||||
220, 260,
|
||||
220, 40
|
||||
);
|
||||
api.setCurve(curve);
|
||||
},
|
||||
|
||||
drawCurve: function(api, curve) {
|
||||
api.reset();
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
},
|
||||
|
||||
setRatio: function(api, values) {
|
||||
ratios = values;
|
||||
this.update(api);
|
||||
},
|
||||
|
||||
changeRatio: function(api, value, pos) {
|
||||
ratios[pos] = parseFloat(value) || 0.00001;
|
||||
this.update(api);
|
||||
},
|
||||
|
||||
update: function(api) {
|
||||
api.curve.setRatios(ratios.slice());
|
||||
this.drawCurve(api, api.curve);
|
||||
}
|
||||
};
|
@@ -30,16 +30,18 @@ You might be wondering "where did all the other 'minus x' for all the other valu
|
||||
|
||||
...Of course "just" is not the right qualifier here, there is nothing "just" about finding the roots of a cubic function, but thankfully we've already covered the tool to do this in the [root finding](#extremities) section: Gerolano Cardano's solution for cubic roots. Of course, we still need to be a bit careful, because cubic roots are complicated things: you can get up to three roots back, even though we only "want" one root. In our case, only one will be both a real number (as opposed to a complex number) _and_ lie in the `t` interval [0,1], so we need to filter for that:
|
||||
|
||||
double x = some value we know!
|
||||
double[] roots = getTforX(x);
|
||||
double t;
|
||||
if (roots.length > 0) {
|
||||
for (double _t: roots) {
|
||||
if (_t<0 || _t>1) continue;
|
||||
t = _t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
double x = some value we know!
|
||||
double[] roots = getTforX(x);
|
||||
double t;
|
||||
if (roots.length > 0) {
|
||||
for (double _t: roots) {
|
||||
if (_t<0 || _t>1) continue;
|
||||
t = _t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And that's it, we're done: we now have the `t` value corresponding to our `x`, and we can just evaluate our curve for that `t` value to find a coordinate that has our original, known `x`, and our unknown `y` value. Mouse over the following graphic, which performs this root finding based on a knowledge of which "x" value we're interested in.
|
||||
|
||||
|
Reference in New Issue
Block a user