mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-09-03 13:23:27 +02:00
bit of cleanup
This commit is contained in:
@@ -3,27 +3,26 @@ var React = require("react");
|
||||
var baseClass = {
|
||||
render: function() {
|
||||
var cprops = {
|
||||
'data-section': this.props.fragmentid,
|
||||
'data-setup': this.props.sname,
|
||||
'data-draw': this.props.dname
|
||||
tabIndex: 0,
|
||||
style: {
|
||||
width: this.panelCount * this.defaultWidth + "px",
|
||||
height: this.defaultHeight + "px"
|
||||
}
|
||||
};
|
||||
console.log(cprops);
|
||||
|
||||
var handlers = {
|
||||
onMouseDown: this.mouseDown,
|
||||
onMouseMove: this.mouseMove,
|
||||
onMouseUp: this.mouseUp,
|
||||
onClick: this.onClick,
|
||||
onKeyUp: this.onKeyUp,
|
||||
onKeyDown: this.onKeyDown,
|
||||
onKeyPress: this.onKeyPress
|
||||
};
|
||||
|
||||
return (
|
||||
<figure className={this.props.inline ? "inline": false}>
|
||||
<canvas ref="canvas"
|
||||
tabIndex="0"
|
||||
style={{
|
||||
width: this.panelCount * this.defaultWidth + "px",
|
||||
height: this.defaultHeight + "px"
|
||||
}}
|
||||
onMouseDown={this.mouseDown}
|
||||
onMouseMove={this.mouseMove}
|
||||
onMouseUp={this.mouseUp}
|
||||
onClick={this.onClick}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onKeyPress={this.onKeyPress}
|
||||
/>
|
||||
<canvas ref="canvas" {...cprops} {...handlers} />
|
||||
<figcaption>{this.props.title} {this.props.children}</figcaption>
|
||||
</figure>
|
||||
);
|
||||
|
@@ -2,13 +2,16 @@ var React = require("react");
|
||||
var Locale = require("../lib/locale");
|
||||
var locale = new Locale();
|
||||
|
||||
|
||||
module.exports = function generateBase(page, handler) {
|
||||
|
||||
// the basic class just has a title and basic content.
|
||||
var componentClass = {
|
||||
var ComponentClass = {
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
title: locale.getTitle(page)
|
||||
page: page,
|
||||
title: locale.getTitle(page),
|
||||
handler: handler
|
||||
};
|
||||
},
|
||||
|
||||
@@ -20,11 +23,11 @@ module.exports = function generateBase(page, handler) {
|
||||
// if the content requires code bindings, ensure those exist:
|
||||
if (handler) {
|
||||
Object.keys(handler).forEach(key => {
|
||||
componentClass[key] = handler[key];
|
||||
ComponentClass[key] = handler[key];
|
||||
});
|
||||
}
|
||||
|
||||
// then build the actual React class
|
||||
return React.createClass(componentClass);
|
||||
return React.createClass(ComponentClass);
|
||||
|
||||
};
|
||||
|
@@ -9,8 +9,8 @@ In order to run de Casteljau's algorithm in reverse, we need a few basic things:
|
||||
So let's use graphics instead of text to see where this "A" is, because text only gets us so far: 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?):
|
||||
|
||||
<div className="figure">
|
||||
<Graphic inline={true} preset="abc" title="Projections in a quadratic Bézier curve" setup={this.setupQuadratic} draw={this.draw} onClick={this.onClick} />
|
||||
<Graphic inline={true} preset="abc" title="Projections in a cubic Bézier curve" setup={this.setupCubic} draw={this.draw} onClick={this.onClick} />
|
||||
<Graphic inline={true} title="Projections in a quadratic Bézier curve" setup={this.setupQuadratic} draw={this.draw} onClick={this.onClick} />
|
||||
<Graphic inline={true} title="Projections in a cubic Bézier curve" setup={this.setupCubic} draw={this.draw} onClick={this.onClick} />
|
||||
</div>
|
||||
|
||||
Clicking anywhere on the curves shows us three things:
|
||||
@@ -42,8 +42,8 @@ with quadratic or cubic curves:
|
||||
So, if we know the start and end coordinates, and we know the *t* value, we know C:
|
||||
|
||||
<div className="figure">
|
||||
<Graphic inline={true} preset="abc" title="Quadratic value of C for t" draw={this.drawQCT} onMouseMove={this.setCT}/>
|
||||
<Graphic inline={true} preset="abc" title="Cubic value of C for t" draw={this.drawCCT} onMouseMove={this.setCT}/>
|
||||
<Graphic inline={true} title="Quadratic value of C for t" draw={this.drawQCT} onMouseMove={this.setCT}/>
|
||||
<Graphic inline={true} title="Cubic value of C for t" draw={this.drawCCT} onMouseMove={this.setCT}/>
|
||||
</div>
|
||||
|
||||
Mouse-over the graphs to see the expression for C, given the *t* value at the mouse pointer.
|
||||
|
@@ -40,5 +40,5 @@ If we drop all the zero-terms, this gives us:
|
||||
|
||||
We can see that our original curve definition has been simplified considerably. The following graphics illustrate the result of aligning our example curves to the x-axis, with the cubic case using the coordinates that were just used in the example formulae:
|
||||
|
||||
<Graphic preset="twopanel" title="Aligning a quadratic curve" setup={this.setupQuadratic} draw={this.draw} />
|
||||
<Graphic preset="twopanel" title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />
|
||||
<Graphic title="Aligning a quadratic curve" setup={this.setupQuadratic} draw={this.draw} />
|
||||
<Graphic title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />
|
||||
|
@@ -39,9 +39,9 @@ So we turn to numerical approaches again. The method we'll look at here is the [
|
||||
In plain text: an integral function can always be treated as the sum of an (infinite) number of (infinitely thin) rectangular strips sitting "under" the function's plotted graph. To illustrate this idea, the following graph shows the integral for a sinoid function. The more strips we use (and of course the more we use, the thinner they get) the closer we get to the true area under the curve, and thus the better the approximation:
|
||||
|
||||
<div className="figure">
|
||||
<Graphic inline={true} static={true} preset="empty" title="A function's approximated integral" setup={this.setup} draw={this.drawCoarseIntegral}/>
|
||||
<Graphic inline={true} static={true} preset="empty" title="A better approximation" setup={this.setup} draw={this.drawFineIntegral}/>
|
||||
<Graphic inline={true} static={true} preset="empty" title="An even better approximation" setup={this.setup} draw={this.drawSuperFineIntegral}/>
|
||||
<Graphic inline={true} static={true} title="A function's approximated integral" setup={this.setup} draw={this.drawCoarseIntegral}/>
|
||||
<Graphic inline={true} static={true} title="A better approximation" setup={this.setup} draw={this.drawFineIntegral}/>
|
||||
<Graphic inline={true} static={true} title="An even better approximation" setup={this.setup} draw={this.drawSuperFineIntegral}/>
|
||||
</div>
|
||||
|
||||
Now, infinitely many terms to sum and infinitely thin rectangles are not something that computers can work with, so instead we're going to approximate the infinite summation by using a sum of a finite number of "just thin" rectangular strips. As long as we use a high enough number of thin enough rectangular strips, this will give us an approximation that is pretty close to what the real value is.
|
||||
|
@@ -4,7 +4,7 @@ Sometimes, we don't actually need the precision of a true arc length, and we can
|
||||
|
||||
If we combine the work done in the previous sections on curve flattening and arc length computation, we can implement these with minimal effort:
|
||||
|
||||
<Graphic preset="twopanel" title="Approximate quadratic curve arc length" setup={this.setupQuadratic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
|
||||
<Graphic preset="twopanel" title="Approximate cubic curve arc length" setup={this.setupCubic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
|
||||
<Graphic title="Approximate quadratic curve arc length" setup={this.setupQuadratic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
|
||||
<Graphic title="Approximate cubic curve arc length" setup={this.setupCubic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
|
||||
|
||||
Try clicking on the sketch and using your up and down arrow keys to lower the number of segments for both the quadratic and cubic curve. You may notice that the error in length is actually pretty significant, even if the percentage is fairly low: if the number of segments used yields an error of 0.1% or higher, the flattened curve already looks fairly obviously flattened. And of course, the longer the curve, the more significant the error will be.
|
||||
|
@@ -8,6 +8,6 @@ Using Catmull-Rom curves, we need virtually no computation, but even though we e
|
||||
|
||||
In the following graphic, on the left we see three points that we want to draw a Catmull-Rom curve through (which we can move around freely, by the way), with in the second panel some of the "interesting" Catmull-Rom information: in black there's the baseline start--end, which will act as tangent orientation for the curve at point p2. We also see a virtual point p0 and p4, which are initially just point p2 reflected over the baseline. However, by using the up and down cursor key we can offset these points parallel to the baseline. Why would we want to do this? Because the line p0--p2 acts as departure tangent at p1, and the line p2--p4 acts as arrival tangent at p3. Play around with the graphic a bit to get an idea of what all of that meant:
|
||||
|
||||
<Graphic preset="threepanel" title="Catmull-Rom curve fitting" setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
|
||||
<Graphic title="Catmull-Rom curve fitting" setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
|
||||
|
||||
As should be obvious by now, Catmull-Rom curves are great for "fitting a curvature to some points", but if we want to convert that curve to Bézier form we're going to end up with a lot of separate (but visually joined) Bézier curves. Depending on what we want to do, that'll be either unnecessary work, or exactly what we want: which it is depends entirely on you.
|
||||
|
@@ -10,7 +10,7 @@ Since arcs are mid-point-symmetrical, we need the control points to set up a sym
|
||||
|
||||
First, let's try to fit the quadratic curve onto a circular arc. In the following sketch you can move the mouse around over a unit circle, to see how well, or poorly, a quadratic curve can approximate the arc from (1,0) to where your mouse cursor is:
|
||||
|
||||
<Graphic preset="arcfitting" title="Quadratic Bézier arc approximation" setup={this.setup} draw={this.draw} onMouseMove={this.onMouseMove}/>
|
||||
<Graphic title="Quadratic Bézier arc approximation" setup={this.setup} draw={this.draw} onMouseMove={this.onMouseMove}/>
|
||||
|
||||
As you can see, things go horribly wrong quite quickly; even trying to approximate a quarter circle using a quadratic curve is a bad idea. An eighth of a turns might look okay, but how okay is okay? Let's apply some maths and find out. What we're interested in is how far off our on-curve coordinates are with respect to a circular arc, given a specific start and end angle. We'll be looking at how much space there is between the circular arc, and the quadratic curve's midpoint.
|
||||
|
||||
|
@@ -8,7 +8,7 @@ The first thing we can do is "guess" what the curve should look like, based on t
|
||||
|
||||
So have a graphical look at a "bad" guess versus the true fit, where we'll be using the bad guess and the description in the second paragraph to derive the maths for the true fit:
|
||||
|
||||
<Graphic preset="arcfitting" title="Cubic Bézier arc approximation" setup={this.setup} draw={this.draw} onMouseMove={this.onMouseMove}/>
|
||||
<Graphic title="Cubic Bézier arc approximation" setup={this.setup} draw={this.draw} onMouseMove={this.onMouseMove}/>
|
||||
|
||||
We see two curves here; in blue, our "guessed" curve and its control points, and in grey/black, the true curve fit, with proper control points that were shifted in, along line between our guessed control points, such that the derivatives at the start and end points are correct.
|
||||
|
||||
|
@@ -1,10 +1,38 @@
|
||||
/**
|
||||
* We REALLY don't want disqus to load unless the user
|
||||
* is actually looking at the comments section, because it
|
||||
* tacks on 2.5+ MB in network transfers...
|
||||
*/
|
||||
module.exports = {
|
||||
componentDidMount() {
|
||||
if (typeof document !== "undefined") {
|
||||
var script = document.createElement("script");
|
||||
script.src = "lib/site/disqus.js";
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
if (typeof document === "undefined") {
|
||||
return this.silence();
|
||||
}
|
||||
this.heading = document.getElementById(this.props.page);
|
||||
document.addEventListener("scroll", this.scrollHandler, {passive:true});
|
||||
},
|
||||
|
||||
scrollHandler(evt) {
|
||||
var bbox = this.heading.getBoundingClientRect();
|
||||
var top = bbox.top;
|
||||
var limit = window.innerHeight;
|
||||
if (top<limit) { this.loadDisqus(); }
|
||||
},
|
||||
|
||||
loadDisqus() {
|
||||
var script = document.createElement("script");
|
||||
script.src = "lib/site/disqus.js";
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
this.silence();
|
||||
this.unlisten();
|
||||
},
|
||||
|
||||
silence() {
|
||||
this.loadDisqus = () => {};
|
||||
},
|
||||
|
||||
unlisten() {
|
||||
document.removeEventListener("scroll", this.scrollHandler);
|
||||
}
|
||||
};
|
||||
|
@@ -14,7 +14,7 @@ This algorithm will start with a single pair, "balloon" until it runs in paralle
|
||||
|
||||
The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. The algorithm resets once it's found a solution, so you can try this with lots of different curves (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)
|
||||
|
||||
<Graphic preset="clipping" title="Curve/curve intersections" setup={this.setup} draw={this.draw}>
|
||||
<Graphic title="Curve/curve intersections" setup={this.setup} draw={this.draw}>
|
||||
<button onClick={this.stepUp}>advance one step</button>
|
||||
</Graphic>
|
||||
|
||||
|
@@ -51,4 +51,4 @@ So what does this do? This draws a point, if the passed list of points is only 1
|
||||
|
||||
To see this in action, mouse-over the following sketch. Moving the mouse changes which curve point is explicitly evaluated using de Casteljau's algorithm, moving the cursor left-to-right (or, of course, right-to-left), shows you how a curve is generated using this approach.
|
||||
|
||||
<Graphic preset="simple"title="Traversing a curve using de Casteljau's algorithm" setup={this.setup} draw={this.draw}/>
|
||||
<Graphictitle="Traversing a curve using de Casteljau's algorithm" setup={this.setup} draw={this.draw}/>
|
||||
|
@@ -17,7 +17,7 @@
|
||||
|
||||
### 如何实现de Casteljau算法
|
||||
|
||||
让我们使用刚才描述过的算法,并实现它:
|
||||
让我们使用刚才描述过的算法,并实现它:
|
||||
|
||||
```
|
||||
function drawCurve(points[], t):
|
||||
@@ -51,4 +51,4 @@ function drawCurve(points[], t):
|
||||
|
||||
我们通过实际操作来观察这个过程。在以下的图表中,移动鼠标来改变用de Casteljau算法计算得到的曲线点,左右移动鼠标,可以实时看到曲线是如何生成的。
|
||||
|
||||
<Graphic preset="simple"title="用de Casteljau算法来遍历曲线" setup={this.setup} draw={this.draw}/>
|
||||
<Graphictitle="用de Casteljau算法来遍历曲线" setup={this.setup} draw={this.draw}/>
|
||||
|
@@ -39,7 +39,7 @@ There we go. <i>x</i>/<i>y</i> coordinates, linked through some mystery value <i
|
||||
|
||||
So, parametric curves don't define a <i>y</i> coordinate in terms of an <i>x</i> coordinate, like normal functions do, but they instead link the values to a "control" variable. If we vary the value of <i>t</i>, then with every change we get <strong>two</strong> values, which we can use as (<i>x</i>,<i>y</i>) coordinates in a graph. The above set of functions, for instance, generates points on a circle: We can range <i>t</i> from negative to positive infinity, and the resulting (<i>x</i>,<i>y</i>) coordinates will always lie on a circle with radius 1 around the origin (0,0). If we plot it for <i>t</i> from 0 to 5, we get this (use your up and down arrow keys to change the plot end value):
|
||||
|
||||
<Graphic preset="empty" title="A (partial) circle: x=sin(t), y=cos(t)" static={true} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
|
||||
<Graphic title="A (partial) circle: x=sin(t), y=cos(t)" static={true} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
|
||||
|
||||
Bézier curves are (one in many classes of) parametric functions, and are characterised by using the same base function for all its dimensions. Unlike the above example, where the <i>x</i> and <i>y</i> values use different functions (one uses a sine, the other a cosine), Bézier curves use the "binomial polynomial" for both <i>x</i> and <i>y</i>. So what are binomial polynomials?
|
||||
|
||||
|
@@ -39,7 +39,7 @@
|
||||
|
||||
というわけで、普通の関数では<i>y</i>座標を<i>x</i>座標によって定義しますが、パラメトリック曲線ではそうではなく、座標の値を「制御」変数と結びつけます。<i>t</i>の値を変化させるたびに<strong>2つ</strong>の値が変化するので、これをグラフ上の座標 (<i>x</i>,<i>y</i>)として使うことができます。例えば、先ほどの関数の組は円周上の点を生成します。負の無限大から正の無限大へと<i>t</i>を動かすと、得られる座標(<i>x</i>,<i>y</i>)は常に中心(0,0)・半径1の円の上に乗ります。<i>t</i>を0から5まで変化させてプロットした場合は、このようになります(上下キーでプロットの上限を変更できます)。
|
||||
|
||||
<Graphic preset="empty" title="(部分)円 x=sin(t), y=cos(t)" static={true} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
|
||||
<Graphic title="(部分)円 x=sin(t), y=cos(t)" static={true} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
|
||||
|
||||
ベジエ曲線はパラメトリック関数の一種であり、どの次元に対しても同じ基底関数を使うという点で特徴づけられます。先ほどの例では、<i>x</i>の値と<i>y</i>の値とで異なる関数(正弦関数と余弦関数)を使っていましたが、ベジエ曲線では<i>x</i>と<i>y</i>の両方で「二項係数多項式」を使います。では、二項係数多項式とは何でしょう?
|
||||
|
||||
|
@@ -39,7 +39,7 @@
|
||||
|
||||
所以,参数曲线不像一般函数那样,通过<i>x</i>坐标来定义<i>y</i>坐标,而是用一个“控制”变量将它们连接起来。如果改变<i>t</i>的值,每次变化时我们都能得到<strong>两个</strong>值,这可以作为图形中的(<i>x</i>,<i>y</i>)坐标。比如上面的方程组,生成位于一个圆上的点:我们可以使<i>t</i>在正负极值间变化,得到的输出(<i>x</i>,<i>y</i>)都会位于一个以原点(0,0)为中心且半径为1的圆上。如果我们画出<i>t</i>从0到5时的值,将得到如下图像(你可以用上下键来改变画的点和值):
|
||||
|
||||
<Graphic preset="empty" title="(一部分的)圆: x=sin(t), y=cos(t)" static={true} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
|
||||
<Graphic title="(一部分的)圆: x=sin(t), y=cos(t)" static={true} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
|
||||
|
||||
贝塞尔曲线是(一种)参数方程,并在它的多个维度上使用相同的基本方程。在上述的例子中<i>x</i>值和<i>y</i>值使用了不同的方程,与此不同的是,贝塞尔曲线的<i>x</i>和<i>y</i>都用了“二项多项式”。那什么是二项多项式呢?
|
||||
|
||||
|
@@ -4,8 +4,8 @@ We can also simplify the drawing process by "sampling" the curve at certain poin
|
||||
|
||||
We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we end up with the number of segments we wanted. The advantage of this method is that it's fast: instead of evaluating 100 or even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can't use the flattened for for doing true intersection detection, or curvature alignment.
|
||||
|
||||
<Graphic preset="twopanel" title="Flattening a quadratic curve" setup={this.setupQuadratic} draw={this.drawFlattened} onKeyDown={this.onKeyDown}/>
|
||||
<Graphic preset="twopanel" title="Flattening a cubic curve" setup={this.setupCubic} draw={this.drawFlattened} onKeyDown={this.onKeyDown} />
|
||||
<Graphic title="Flattening a quadratic curve" setup={this.setupQuadratic} draw={this.drawFlattened} onKeyDown={this.onKeyDown}/>
|
||||
<Graphic title="Flattening a cubic curve" setup={this.setupCubic} draw={this.drawFlattened} onKeyDown={this.onKeyDown} />
|
||||
|
||||
Try clicking on the sketch and using your up and down arrow keys to lower the number of segments for both the quadratic and cubic curve. You'll notice that for certain curvatures, a low number of segments works quite well, but for more complex curvatures (try this for the cubic curve), a higher number is required to capture the curvature changes properly.
|
||||
|
||||
|
@@ -4,8 +4,8 @@
|
||||
|
||||
例えば「X個の線分がほしい」場合には、分割数がそうなるようにサンプリング間隔を選び、曲線をサンプリングします。この方法の利点は速さです。曲線の座標を100個だの1000個だの計算するのではなく、ずっと少ない回数のサンプリングでも、十分きれいに見えるような曲線を作ることができるのです。欠点はもちろん、「本物の曲線」に比べて精度が損なわれてしまうことです。したがって、交点の検出や曲線の位置揃えを正しく行いたい場合には、平坦化した曲線は普通利用できません。
|
||||
|
||||
<Graphic preset="twopanel" title="2次ベジエ曲線の平坦化" setup={this.setupQuadratic} draw={this.drawFlattened} onKeyDown={this.onKeyDown}/>
|
||||
<Graphic preset="twopanel" title="3次ベジエ曲線の平坦化" setup={this.setupCubic} draw={this.drawFlattened} onKeyDown={this.onKeyDown} />
|
||||
<Graphic title="2次ベジエ曲線の平坦化" setup={this.setupQuadratic} draw={this.drawFlattened} onKeyDown={this.onKeyDown}/>
|
||||
<Graphic title="3次ベジエ曲線の平坦化" setup={this.setupCubic} draw={this.drawFlattened} onKeyDown={this.onKeyDown} />
|
||||
|
||||
2次ベジエ曲線も3次ベジエ曲線も、図をクリックして上下キーを押すと曲線の分割数が増減しますので、試してみてください。ある曲線では分割数が少なくてもうまくいきますが、曲線が複雑になればなるほど、曲率の変化を正確に捉えるためにはより多くの分割数が必要になることがわかります(3次ベジエ曲線で試してみてください)。
|
||||
|
||||
|
@@ -4,8 +4,8 @@
|
||||
|
||||
我们可以先确定“想要X个分段”,然后在间隔的地方采样曲线,得到一定数量的分段。这种方法的优点是速度很快:比起遍历100甚至1000个曲线坐标,我们可以采样比较少的点,仍然得到看起来足够好的曲线。这么做的缺点是,我们失去了“真正的曲线”的精度,因此不能用此方法来做真实的相交检测或曲率对齐。
|
||||
|
||||
<Graphic preset="twopanel" title="拉平一条二次曲线" setup={this.setupQuadratic} draw={this.drawFlattened} onKeyDown={this.onKeyDown}/>
|
||||
<Graphic preset="twopanel" title="拉平一条三次曲线" setup={this.setupCubic} draw={this.drawFlattened} onKeyDown={this.onKeyDown} />
|
||||
<Graphic title="拉平一条二次曲线" setup={this.setupQuadratic} draw={this.drawFlattened} onKeyDown={this.onKeyDown}/>
|
||||
<Graphic title="拉平一条三次曲线" setup={this.setupCubic} draw={this.drawFlattened} onKeyDown={this.onKeyDown} />
|
||||
|
||||
试着点击图形,并用上下键来降低二次曲线和三次曲线的分段数量。你会发现对某些曲率来说,数量少的分段也能做的很好,但对于复杂的曲率(在三次曲线上试试),足够多的分段才能很好地满足曲率的变化。
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
### 如何实现曲线的拉平
|
||||
|
||||
让我们来实现刚才简述过的算法:
|
||||
让我们来实现刚才简述过的算法:
|
||||
|
||||
```
|
||||
function flattenCurve(curve, segmentCount):
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the <em>t</em>-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.
|
||||
|
||||
<Graphic preset="moulding" title="Moulding a quadratic Bézier curve" setup={this.setupQuadratic} draw={this.drawMould} onClick={this.placeMouldPoint} onMouseDown={this.markQB} onMouseDrag={this.dragQB} onMouseUp={this.saveCurve}/>
|
||||
<Graphic title="Moulding a quadratic Bézier curve" setup={this.setupQuadratic} draw={this.drawMould} onClick={this.placeMouldPoint} onMouseDown={this.markQB} onMouseDrag={this.dragQB} onMouseUp={this.saveCurve}/>
|
||||
|
||||
**Click-dragging the curve itself** shows what we're using to compute the new coordinates: while dragging you will see the original points B and its corresponding <i>t</i>-value, the original point C for that <i>t</i>-value, as well as the new point B' based on the mouse cursor. Since we know the <i>t</i>-value for this configuration, we can compute the ABC ratio for this configuration, and we know that our new point A' should like at a distance:
|
||||
|
||||
@@ -12,7 +12,7 @@ Armed with knowledge of the "ABC" relation, we can now update a curve interactiv
|
||||
|
||||
For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point. For cubic curves, we need to do a little more work:
|
||||
|
||||
<Graphic preset="moulding" title="Moulding a cubic Bézier curve" setup={this.setupCubic} draw={this.drawMould} onClick={this.placeMouldPoint} onMouseDown={this.markCB} onMouseDrag={this.dragCB} onMouseUp={this.saveCurve}/>
|
||||
<Graphic title="Moulding a cubic Bézier curve" setup={this.setupCubic} draw={this.drawMould} onClick={this.placeMouldPoint} onMouseDown={this.markCB} onMouseDrag={this.dragCB} onMouseUp={this.saveCurve}/>
|
||||
|
||||
To help understand what's going on, the cubic graphic shows the full de Casteljau construction "hull" when repositioning point B. We compute A` in exactly the same way as before, but we also record the final strut line that forms B in the original curve. Given A', B', and the endpoints e1 and e2 of the strut line relative to B', we can now compute where the new control points should be. Remember that B' lies on line e1--e2 at a distance <i>t</i>, because that's how Bézier curves work. In the same manner, we know the distance A--e1 is only line-interval [0,t] of the full segment, and A--e2 is only line-interval [t,1], so constructing the new control points is fairly easy.
|
||||
|
||||
|
@@ -4,10 +4,10 @@ Given the preceding section on curve manipulation, we can also generate quadrati
|
||||
|
||||
The following graphic lets you place three points, and will use the preceding sections on the ABC ratio and curve construction to form a quadratic curve through them. You can move the points you've placed around by click-dragging, or try a new curve by drawing new points with pure clicks. (There's some freedom here, so for illustrative purposes we clamped *t* to simply be 0.5, lets us bypass some maths, since a *t* value of 0.5 always puts C in the middle of the start--end line segment)
|
||||
|
||||
<Graphic preset="generate" title="Fitting a quadratic Bézier curve" setup={this.setup} draw={this.drawQuadratic} onClick={this.onClick} />
|
||||
<Graphic title="Fitting a quadratic Bézier curve" setup={this.setup} draw={this.drawQuadratic} onClick={this.onClick} />
|
||||
|
||||
For cubic curves we also need some values to construct the "de Casteljau line through B" with, and that gives us quite a bit of choice. Since we've clamped *t* to 0.5, we'll set up a line through B parallel to the line start--end, with a length that is proportional to the length of the line B--C: the further away from the baseline B is, the wider its construction line will be, and so the more "bulby" the curve will look. This still gives us some freedom in terms of exactly how to scale the length of the construction line as we move B closer or further away from the baseline, so I simply picked some values that sort-of-kind-of look right in that if a circle through (start,B,end) forms a perfect hemisphere, the cubic curve constructed forms something close to a hemisphere, too, and if the points lie on a line, then the curve constructed has the control points very close to B, while still lying between B and the correct curve end point:
|
||||
|
||||
<Graphic preset="generate" title="Fitting a cubic Bézier curve" setup={this.setup} draw={this.drawCubic} onClick={this.onClick} />
|
||||
<Graphic title="Fitting a cubic Bézier curve" setup={this.setup} draw={this.drawCubic} onClick={this.onClick} />
|
||||
|
||||
In each graphic, the blue parts are the values that we "just have" simply by setting up our three points, combined with our decision on which *t* value to use (and construction line orientation and length for cubic curves). There are of course many ways to determine a combination of *t* and tangent values that lead to a more "æsthetic" curve, but this will be left as an exercise to the reader, since there are many, and æsthetics are often quite personal.
|
||||
|
@@ -9,8 +9,8 @@ Unless, of course, you want discontinuities; then you don't even need 2.
|
||||
|
||||
We'll cover three forms of poly-Bézier curves in this section. First, we'll look at the kind that just follows point 1. where the end point of a segment is the same point as the start point of the next segment. This leads to poly-Béziers that are pretty hard to work with, but they're the easiest to implement:
|
||||
|
||||
<Graphic preset="poly" title="Unlinked quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw}/>
|
||||
<Graphic preset="poly" title="Unlinked cubic poly-Bézier" setup={this.setupCubic} draw={this.draw}/>
|
||||
<Graphic title="Unlinked quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw}/>
|
||||
<Graphic title="Unlinked cubic poly-Bézier" setup={this.setupCubic} draw={this.draw}/>
|
||||
|
||||
Dragging the control points around only affects the curve segments that the control point belongs to, and moving an on-curve point leaves the control points where they are, which is not the most useful for practical modelling purposes. So, let's add in the logic we need to make things a little better. We'll start by linking up control points by ensuring that the "incoming" derivative at an on-curve point is the same as it's "outgoing" derivative:
|
||||
|
||||
@@ -30,8 +30,8 @@ We can effect this quite easily, because we know that the vector from a curve's
|
||||
|
||||
So let's implement that and see what it gets us. The following two graphics show a quadratic and a cubic poly-Bézier curve again, but this time moving the control points around moves others, too. However, you might see something unexpected going on for quadratic curves...
|
||||
|
||||
<Graphic preset="poly" title="Loosely connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
|
||||
<Graphic preset="poly" title="Loosely connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
|
||||
<Graphic title="Loosely connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
|
||||
<Graphic title="Loosely connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
|
||||
|
||||
As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point's positions is different depending on whether it's the reflection of its left or right neighbouring control point: we can't even form a proper rule-conforming curve! This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little.
|
||||
|
||||
@@ -39,15 +39,15 @@ So: let's relax the requirement a little.
|
||||
|
||||
We can change the constraint so that we still preserve the *angle* of the derivatives across sections (so transitions from one section to the next will still look natural), but give up the requirement that they should also have the same *vector length*. Doing so will give us a much more useful kind of poly-Bézier curve:
|
||||
|
||||
<Graphic preset="poly" title="Loosely connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDirection}/>
|
||||
<Graphic preset="poly" title="Loosely connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDirection}/>
|
||||
<Graphic title="Loosely connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDirection}/>
|
||||
<Graphic title="Loosely connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDirection}/>
|
||||
|
||||
Cubic curves are now better behaved when it comes to dragging control points around, but the quadratic poly-Bézier still has the problem that moving one control points will move the control points and may ending up defining "the next" control point in a way that doesn't work. Quadratic curves really aren't very useful to work with...
|
||||
|
||||
Finally, we also want to make sure that moving the on-curve coordinates preserves the relative positions of the associated control points. With that, we get to the kind of curve control that you might be familiar with from applications like Photoshop, Inkscape, Blender, etc.
|
||||
|
||||
<Graphic preset="poly" title="Loosely connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
|
||||
<Graphic preset="poly" title="Loosely connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
|
||||
<Graphic title="Loosely connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
|
||||
<Graphic title="Loosely connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
|
||||
|
||||
Again, we see that cubic curves are now rather nice to work with, but quadratic curves have a new, very serious problem: we can move an on-curve point in such a way that we can't compute what needs to "happen next". Move the top point down, below the left and right points, for instance. There is no way to preserve correct control points without a kink at the bottom point. Quadratic curves: just not that good...
|
||||
|
||||
|
@@ -35,4 +35,4 @@ After running this function for some value `t`, the `left` and `right` arrays wi
|
||||
|
||||
This is best illustrated with an animated graphic (click to play/pause):
|
||||
|
||||
<Graphic preset="threepanel" title="Bézier curve splitting" setup={this.setupCubic} draw={this.drawAnimated} onClick={this.togglePlay} />
|
||||
<Graphic title="Bézier curve splitting" setup={this.setupCubic} draw={this.drawAnimated} onClick={this.togglePlay} />
|
||||
|
@@ -35,4 +35,4 @@ function drawCurve(points[], t):
|
||||
|
||||
これはアニメーションで見るのがわかりやすいでしょう(クリックで再生・停止します)。
|
||||
|
||||
<Graphic preset="threepanel" title="ベジエ曲線の分割" setup={this.setupCubic} draw={this.drawAnimated} onClick={this.togglePlay} />
|
||||
<Graphic title="ベジエ曲線の分割" setup={this.setupCubic} draw={this.drawAnimated} onClick={this.togglePlay} />
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for curves. We first align our curve, recording the translation we performed, "T", and the rotation angle we used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that bounding box back to our original curve by rotating it by -R, and then translating it by -T. We now have nice tight bounding boxes for our curves:
|
||||
|
||||
<Graphic preset="twopanel" title="Aligning a quadratic curve" setup={this.setupQuadratic} draw={this.draw} />
|
||||
<Graphic preset="twopanel" title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />
|
||||
<Graphic title="Aligning a quadratic curve" setup={this.setupQuadratic} draw={this.draw} />
|
||||
<Graphic title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />
|
||||
|
||||
These are, strictly speaking, not necessarily the tightest possible bounding boxes. It is possible to compute the optimal bounding box by determining which spanning lines we need to effect a minimal box area, but because of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding precision is often not worth it. If there is high demand for it, I'll add a section on how to precisely compute the best fit bounding box, but the maths is fairly gruelling and just not really worth spending time on.
|
||||
|
@@ -8,7 +8,7 @@ The reason you have a problem is that Bézier curves are parametric functions wi
|
||||
|
||||
The following graphic shows a particularly illustrative curve, and it's length-to-t plot. For linear traversal, this line needs to be straight, running from (0,0) to (length,1). This is, it's safe to say, not what we'll see, we'll see something wobbly instead. To make matters even worse, the length-to-*t* function is also of a much higher order than our curve is: while the curve we're using for this exercise is a cubic curve, which can switch concave/convex form once at best, the plot shows that the distance function along the curve is able to switch forms three times (to see this, try creating an S curve with the start/end close together, but the control points far apart).
|
||||
|
||||
<Graphic preset="twopanel" title="The t-for-distance function" setup={this.setup} draw={this.plotOnly}/>
|
||||
<Graphic title="The t-for-distance function" setup={this.setup} draw={this.plotOnly}/>
|
||||
|
||||
We see a function that might be invertible, but we won't be able to do so, symbolically. You may remember from the section on arc length that we cannot actually compute the true arc length function as an expression of *t*, which means we also can't compute the true inverted function that gives *t* as an expression of length. So how do we fix this?
|
||||
|
||||
@@ -16,7 +16,7 @@ One way is to do what the graphic does: simply run through the curve, determine
|
||||
|
||||
We can use some colour to show the difference between distance-based and time based intervals: the following graph is similar to the previous one, except it segments the curve in terms of equal-distance intervals. This shows as regular colour intervals going down the graph, but the mapping to *t* values is not linear, so there will be (highly) irregular intervals along the horizontal axis. It also shows the curve in an alternating colouring based on the t-for-distance values we find our LUT:
|
||||
|
||||
<Graphic preset="threepanel" title="Fixed-interval coloring a curve" setup={this.setup} draw={this.drawColoured} onKeyDown={this.props.onKeyDown}/>
|
||||
<Graphic title="Fixed-interval coloring a curve" setup={this.setup} draw={this.drawColoured} onKeyDown={this.props.onKeyDown}/>
|
||||
|
||||
Use your up and down arrow keys to increase or decrease the number of equidistant segments used to colour the curve.
|
||||
|
||||
|
Reference in New Issue
Block a user