added sliders to sketches that should have one, improved lazy loading
@@ -5,9 +5,17 @@ Bézier curves are, like all "splines", interpolation functions. This means that
|
||||
The following graphs show the interpolation functions for quadratic and cubic curves, with "S" being the strength of a point's contribution to the total sum of the Bézier function. Click-and-drag to see the interpolation percentages for each curve-defining point at a specific <i>t</i> value.
|
||||
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic interpolations" src="./lerp-quadratic.js"></graphics-element>
|
||||
<graphics-element title="Cubic interpolations" src="./lerp-cubic.js"></graphics-element>
|
||||
<graphics-element title="15th degree interpolations" src="./lerp-fifteenth.js"></graphics-element>
|
||||
<graphics-element title="Quadratic interpolations" src="./lerp-quadratic.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="Cubic interpolations" src="./lerp-cubic.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="15th degree interpolations" src="./lerp-fifteenth.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
Also shown is the interpolation function for a 15<sup>th</sup> order Bézier function. As you can see, the start and end point contribute considerably more to the curve's shape than any other point in the control point set.
|
||||
|
@@ -5,9 +5,17 @@
|
||||
下のグラフは、2次ベジエ曲線や3次ベジエ曲線の補間関数を表しています。ここでSは、ベジエ関数全体に対しての、その点の寄与の大きさを示します。ある<i>t</i>において、ベジエ曲線を定義する各点の補間率がどのようになっているのか、クリックドラッグをして確かめてみてください。
|
||||
|
||||
<div class="figure">
|
||||
<Graphic inline={true} title="2次の補間" draw={this.drawQuadraticLerp}/>
|
||||
<Graphic inline={true} title="3次の補間" draw={this.drawCubicLerp}/>
|
||||
<Graphic inline={true} title="15次の補間" draw={this.draw15thLerp}/>
|
||||
<graphics-element title="2次の補間" src="./lerp-quadratic.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="3次の補間" src="./lerp-cubic.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="15次の補間" src="./lerp-fifteenth.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
あわせて、15次ベジエ関数における補間関数も示しています。始点と終点は他の制御点と比較して、曲線の形に対してかなり大きな影響を与えていることがわかります。
|
||||
|
@@ -5,9 +5,17 @@
|
||||
下面的图形显示了二次曲线和三次曲线的差值方程,“S”代表了点对贝塞尔方程总和的贡献。点击拖动点来看看在特定的<i>t</i>值时,每个曲线定义的点的插值百分比。
|
||||
|
||||
<div class="figure">
|
||||
<Graphic inline={true} title="二次插值" draw={this.drawQuadraticLerp}/>
|
||||
<Graphic inline={true} title="三次插值" draw={this.drawCubicLerp}/>
|
||||
<Graphic inline={true} title="15次插值" draw={this.draw15thLerp}/>
|
||||
<graphics-element title="二次插值" src="./lerp-quadratic.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="三次插值" src="./lerp-cubic.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="15次插值" src="./lerp-fifteenth.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
上面有一张是15<sup>th</sup>阶的插值方程。如你所见,在所有控制点中,起点和终点对曲线形状的贡献比其他点更大些。
|
||||
|
@@ -10,6 +10,12 @@ setup() {
|
||||
];
|
||||
|
||||
this.s = this.f.map(f => plot(f) );
|
||||
this.position = 0;
|
||||
setSlider(`.slide-control`, v => this.setPosition(v))
|
||||
}
|
||||
|
||||
setPosition(v) {
|
||||
this.position = v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -33,9 +39,11 @@ draw() {
|
||||
}
|
||||
|
||||
drawHighlight() {
|
||||
if (this.cursor.down) {
|
||||
let c = screenToWorld({
|
||||
x: map(this.position, 0, 1, -10, this.width + 10),
|
||||
y: this.height/2
|
||||
});
|
||||
|
||||
let c = screenToWorld(this.cursor);
|
||||
if (c.x < 0) return;
|
||||
if (c.x > this.width) return;
|
||||
|
||||
@@ -50,13 +58,4 @@ drawHighlight() {
|
||||
circle(p.x, p.y, 3);
|
||||
text(`${ round(100 * p.y/this.height) }%`, p.x + 10, p.y);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
redraw();
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
redraw();
|
||||
}
|
||||
|
@@ -2,6 +2,12 @@ setup() {
|
||||
this.degree = 15;
|
||||
this.triangle = [[1], [1,1]];
|
||||
this.generate();
|
||||
this.position = 0;
|
||||
setSlider(`.slide-control`, v => this.setPosition(v))
|
||||
}
|
||||
|
||||
setPosition(v) {
|
||||
this.position = v;
|
||||
}
|
||||
|
||||
binomial(n,k) {
|
||||
@@ -52,9 +58,11 @@ draw() {
|
||||
}
|
||||
|
||||
drawHighlight() {
|
||||
if (this.cursor.down) {
|
||||
let c = screenToWorld({
|
||||
x: map(this.position, 0, 1, -10, this.width + 10),
|
||||
y: this.height/2
|
||||
});
|
||||
|
||||
let c = screenToWorld(this.cursor);
|
||||
if (c.x < 0) return;
|
||||
if (c.x > this.width) return;
|
||||
|
||||
@@ -69,13 +77,4 @@ drawHighlight() {
|
||||
circle(p.x, p.y, 3);
|
||||
text(`${ round(100 * p.y/this.height) }%`, p.x + 10, p.y);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
redraw();
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
redraw();
|
||||
}
|
||||
|
@@ -9,6 +9,12 @@ setup() {
|
||||
];
|
||||
|
||||
this.s = this.f.map(f => plot(f) );
|
||||
this.position = 0;
|
||||
setSlider(`.slide-control`, v => this.setPosition(v))
|
||||
}
|
||||
|
||||
setPosition(v) {
|
||||
this.position = v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -32,9 +38,11 @@ draw() {
|
||||
}
|
||||
|
||||
drawHighlight() {
|
||||
if (this.cursor.down) {
|
||||
let c = screenToWorld({
|
||||
x: map(this.position, 0, 1, -10, this.width + 10),
|
||||
y: this.height/2
|
||||
});
|
||||
|
||||
let c = screenToWorld(this.cursor);
|
||||
if (c.x < 0) return;
|
||||
if (c.x > this.width) return;
|
||||
|
||||
@@ -49,13 +57,4 @@ drawHighlight() {
|
||||
circle(p.x, p.y, 3);
|
||||
text(`${ round(100 * p.y/this.height) }%`, p.x + 10, p.y);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
redraw();
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
redraw();
|
||||
}
|
||||
|
@@ -15,7 +15,9 @@ Rather than using our calculus function to find `x/y` values for `t`, let's do t
|
||||
|
||||
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.
|
||||
|
||||
<graphics-element title="Traversing a curve using de Casteljau's algorithm" src="./decasteljau.js"></graphics-element>
|
||||
<graphics-element title="Traversing a curve using de Casteljau's algorithm" src="./decasteljau.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
|
||||
|
@@ -15,7 +15,9 @@
|
||||
|
||||
下の図にマウスを乗せると、この様子を実際に見ることができます。ド・カステリョのアルゴリズムによって曲線上の点を明示的に計算していますが、マウスを動かすと求める点が変わります。マウスカーソルを左から右へ(もちろん、右から左へでも)動かせば、このアルゴリズムによって曲線が生成される様子がわかります。
|
||||
|
||||
<graphics-element title="ド・カステリョのアルゴリズムで曲線をたどる" src="./decasteljau.js"></graphics-element>
|
||||
<graphics-element title="ド・カステリョのアルゴリズムで曲線をたどる" src="./decasteljau.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
|
||||
|
@@ -15,7 +15,9 @@
|
||||
|
||||
我们通过实际操作来观察这个过程。在以下的图表中,移动鼠标来改变用de Casteljau算法计算得到的曲线点,左右移动鼠标,可以实时看到曲线是如何生成的。
|
||||
|
||||
<graphics-element title="用de Casteljau算法来遍历曲线" src="./decasteljau.js"></graphics-element>
|
||||
<graphics-element title="用de Casteljau算法来遍历曲线" src="./decasteljau.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
|
||||
|
@@ -1,28 +1,37 @@
|
||||
let curve;
|
||||
|
||||
setup() {
|
||||
curve = new Bezier(this, 90, 200, 25, 100, 220, 40, 210, 240);
|
||||
setMovable(curve.points);
|
||||
this.curve = new Bezier(this, 90, 200, 25, 100, 220, 40, 210, 240);
|
||||
setMovable(this.curve.points);
|
||||
this.position = 0;
|
||||
setSlider(`.slide-control`, v => this.setPosition(v));
|
||||
}
|
||||
|
||||
setPosition(v) {
|
||||
this.position = v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
clear();
|
||||
const curve = this.curve;
|
||||
curve.drawSkeleton();
|
||||
curve.drawCurve();
|
||||
|
||||
setStroke("rgb(200,100,100)");
|
||||
|
||||
let dim = this.height;
|
||||
let t = this.cursor.x / dim;
|
||||
let t = this.position;
|
||||
if (0 < t && t < 1) {
|
||||
curve.drawStruts(t);
|
||||
}
|
||||
|
||||
curve.drawPoints();
|
||||
|
||||
if (0 < t && t < 1) {
|
||||
let p = curve.get(t);
|
||||
circle(p.x, p.y, 5);
|
||||
|
||||
let perc = (t*100)|0;
|
||||
t = perc/100;
|
||||
text(`Sequential interpolation for ${perc}% (t=${t})`, 10, 15);
|
||||
let rt = perc/100;
|
||||
text(`Sequential interpolation for ${perc}% (t=${rt})`, 10, 15);
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
|
@@ -1,5 +1,10 @@
|
||||
setup() {
|
||||
this.step = 5;
|
||||
setSlider(`.slide-control`, v => this.setStep(v));
|
||||
}
|
||||
|
||||
setStep(v) {
|
||||
this.step = v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -36,28 +41,3 @@ draw() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown() {
|
||||
const key = this.keyboard.currentKey;
|
||||
if (key === `ArrowUp`) { this.step += 0.1; }
|
||||
if (key === `ArrowDown`) { this.step -= 0.1; }
|
||||
if (this.step < 1) this.step = 1;
|
||||
redraw();
|
||||
}
|
||||
|
||||
onMouseDown() {
|
||||
this.mark = this.cursor.y;
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
this.mark = false;
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
if (this.mark) {
|
||||
let diff = this.mark - this.cursor.y,
|
||||
mapped = map(diff, -this.height/2, this.height/2, 0, 10, true);
|
||||
this.step = mapped;
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
|
@@ -39,9 +39,11 @@ Multiple functions, but only one variable. If we change the value for <i>t</i>,
|
||||
|
||||
There we go. <i>x</i>/<i>y</i> coordinates, linked through some mystery value <i>t</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):
|
||||
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:
|
||||
|
||||
<graphics-element title="A (partial) circle: x=sin(t), y=cos(t)" src="./circle.js"></graphics-element>
|
||||
<graphics-element title="A (partial) circle: x=sin(t), y=cos(t)" src="./circle.js">
|
||||
<input type="range" min="0" max="10" step="0.1" value="5" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
Bézier curves are just one out of the many classes of parametric functions, and are characterised by using the same base function for all of the output values. In the example we saw above, the <i>x</i> and <i>y</i> values were generated by different functions (one uses a sine, the other a cosine); but Bézier curves use the "binomial polynomial" for both the <i>x</i> and <i>y</i> outputs. So what are binomial polynomials?
|
||||
|
||||
|
@@ -37,9 +37,11 @@
|
||||
|
||||
きました。<i>x</i>/<i>y</i>座標です。謎の値<i>t</i>を通して繫がっています。
|
||||
|
||||
というわけで、普通の関数では<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まで変化させてプロットした場合は、このようになります(上下キーでプロットの上限を変更できます)。
|
||||
というわけで、普通の関数では<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まで変化させてプロットした場合は、このようになります。
|
||||
|
||||
<graphics-element title="(部分)円 x=sin(t), y=cos(t)" src="./circle.js"></graphics-element>
|
||||
<graphics-element title="(部分)円 x=sin(t), y=cos(t)" src="./circle.js">
|
||||
<input type="range" min="0" max="10" step="0.1" value="5" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
ベジエ曲線はパラメトリック関数の一種であり、どの次元に対しても同じ基底関数を使うという点で特徴づけられます。先ほどの例では、<i>x</i>の値と<i>y</i>の値とで異なる関数(正弦関数と余弦関数)を使っていましたが、ベジエ曲線では<i>x</i>と<i>y</i>の両方で「二項係数多項式」を使います。では、二項係数多項式とは何でしょう?
|
||||
|
||||
|
@@ -37,9 +37,11 @@
|
||||
|
||||
好了,通过一些神秘的<i>t</i>值将<i>x</i>/<i>y</i>坐标系联系起来。
|
||||
|
||||
所以,参数曲线不像一般函数那样,通过<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时的值,将得到如下图像(你可以用上下键来改变画的点和值):
|
||||
所以,参数曲线不像一般函数那样,通过<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时的值,将得到如下图像:
|
||||
|
||||
<graphics-element title="(一部分的)圆: x=sin(t), y=cos(t)" src="./circle.js"></graphics-element>
|
||||
<graphics-element title="(一部分的)圆: x=sin(t), y=cos(t)" src="./circle.js">
|
||||
<input type="range" min="0" max="10" step="0.1" value="5" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
贝塞尔曲线是(一种)参数方程,并在它的多个维度上使用相同的基本方程。在上述的例子中<i>x</i>值和<i>y</i>值使用了不同的方程,与此不同的是,贝塞尔曲线的<i>x</i>和<i>y</i>都用了“二项多项式”。那什么是二项多项式呢?
|
||||
|
||||
|
@@ -4,8 +4,15 @@ 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.
|
||||
|
||||
<graphics-element title="Flattening a quadratic curve" src="./quadratic.js"></graphics-element>
|
||||
<graphics-element title="Flattening a cubic curve" src="./cubic.js"></graphics-element>
|
||||
<div class="figure">
|
||||
<graphics-element title="Flattening a quadratic curve" src="./quadratic.js">
|
||||
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="Flattening a cubic curve" src="./cubic.js">
|
||||
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
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,15 @@
|
||||
|
||||
例えば「X個の線分がほしい」場合には、分割数がそうなるようにサンプリング間隔を選び、曲線をサンプリングします。この方法の利点は速さです。曲線の座標を100個だの1000個だの計算するのではなく、ずっと少ない回数のサンプリングでも、十分きれいに見えるような曲線を作ることができるのです。欠点はもちろん、「本物の曲線」に比べて精度が損なわれてしまうことです。したがって、交点の検出や曲線の位置揃えを正しく行いたい場合には、平坦化した曲線は普通利用できません。
|
||||
|
||||
<graphics-element title="2次ベジエ曲線の平坦化" src="./quadratic.js"></graphics-element>
|
||||
<graphics-element title="3次ベジエ曲線の平坦化" src="./cubic.js"></graphics-element>
|
||||
<div class="figure">
|
||||
<graphics-element title="2次ベジエ曲線の平坦化" src="./quadratic.js">
|
||||
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="3次ベジエ曲線の平坦化" src="./cubic.js">
|
||||
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
2次ベジエ曲線も3次ベジエ曲線も、図をクリックして上下キーを押すと曲線の分割数が増減しますので、試してみてください。ある曲線では分割数が少なくてもうまくいきますが、曲線が複雑になればなるほど、曲率の変化を正確に捉えるためにはより多くの分割数が必要になることがわかります(3次ベジエ曲線で試してみてください)。
|
||||
|
||||
|
@@ -4,8 +4,15 @@
|
||||
|
||||
我们可以先确定“想要X个分段”,然后在间隔的地方采样曲线,得到一定数量的分段。这种方法的优点是速度很快:比起遍历100甚至1000个曲线坐标,我们可以采样比较少的点,仍然得到看起来足够好的曲线。这么做的缺点是,我们失去了“真正的曲线”的精度,因此不能用此方法来做真实的相交检测或曲率对齐。
|
||||
|
||||
<graphics-element title="拉平一条二次曲线" src="./quadratic.js"></graphics-element>
|
||||
<graphics-element title="拉平一条三次曲线" src="./cubic.js"></graphics-element>
|
||||
<div class="figure">
|
||||
<graphics-element title="拉平一条二次曲线" src="./quadratic.js">
|
||||
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="拉平一条三次曲线" src="./cubic.js">
|
||||
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
试着点击图形,并用上下键来降低二次曲线和三次曲线的分段数量。你会发现对某些曲率来说,数量少的分段也能做的很好,但对于复杂的曲率(在三次曲线上试试),足够多的分段才能很好地满足曲率的变化。
|
||||
|
||||
|
@@ -2,6 +2,11 @@ setup() {
|
||||
this.steps = 8;
|
||||
this.curve = Bezier.defaultCubic(this);
|
||||
setMovable(this.curve.points);
|
||||
setSlider(`.slide-control`, v => this.setStep(v));
|
||||
}
|
||||
|
||||
setStep(v) {
|
||||
this.steps = v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -23,23 +28,6 @@ draw() {
|
||||
text(`Flattened to ${this.steps} segments`, 10, 15);
|
||||
}
|
||||
|
||||
onKeyDown() {
|
||||
let key = this.keyboard.currentKey;
|
||||
|
||||
if(key === `ArrowUp`) {
|
||||
this.steps++;
|
||||
}
|
||||
|
||||
if(key === `ArrowDown`) {
|
||||
if(this.steps > 1) this.steps--;
|
||||
}
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
if (this.cursor.down && !this.currentPoint) {
|
||||
this.steps = round( map(this.cursor.y, 0,this.height, 24, 1) );
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
@@ -2,6 +2,11 @@ setup() {
|
||||
this.steps = 4;
|
||||
this.curve = Bezier.defaultQuadratic(this);
|
||||
setMovable(this.curve.points);
|
||||
setSlider(`.slide-control`, v => this.setStep(v));
|
||||
}
|
||||
|
||||
setStep(v) {
|
||||
this.steps = v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -23,23 +28,6 @@ draw() {
|
||||
text(`Flattened to ${this.steps} segments`, 10, 15);
|
||||
}
|
||||
|
||||
onKeyDown() {
|
||||
let key = this.keyboard.currentKey;
|
||||
|
||||
if(key === `ArrowUp`) {
|
||||
this.steps++;
|
||||
}
|
||||
|
||||
if(key === `ArrowDown`) {
|
||||
if(this.steps > 1) this.steps--;
|
||||
}
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
if (this.cursor.down && !this.currentPoint) {
|
||||
this.steps = round( map(this.cursor.y, 0,this.height, 16, 1) );
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
@@ -24,7 +24,9 @@ Let's unpack that a little:
|
||||
|
||||
And then we're done, we found "the" normal vector for a 3D curve. Let's see what that looks like for a sample curve, shall we? You can move your cursor across the graphic from left to right, to show the normal at a point with a t value that is based on your cursor position: all the way on the left is 0, all the way on the right = 1, midway is t=0.5, etc:
|
||||
|
||||
<graphics-element title="Some known and unknown vectors" width="350" height="300" src="./frenet.js"></graphics-element>
|
||||
<graphics-element title="Some known and unknown vectors" width="350" height="300" src="./frenet.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around" around between t=0.5 and t=0.9... Why is doing that?
|
||||
|
||||
@@ -109,8 +111,10 @@ Ignoring comments, this is certainly more code than when we were just computing
|
||||
|
||||
Speaking of better looking, what does this actually look like? Let's revisit that earlier curve, but this time use rotation minimising frames rather than Frenet frames:
|
||||
|
||||
<graphics-element title="Some known and unknown vectors" width="350" height="300" src="./rotation-minimizing.js"></graphics-element>
|
||||
<graphics-element title="Some known and unknown vectors" width="350" height="300" src="./rotation-minimizing.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
That looks much better!
|
||||
That looks so much better!
|
||||
|
||||
For those reading along with the code: we don't even strictly speaking need a Frenet frame to start with: we could, for instance, treat the z-axis as our initial axis of rotation, so that our initial normal is **(0,0,1) × tangent**, and then take things from there, but having that initial "mathematically correct" frame so that the initial normal seems to line up based on the curve's orientation in 3D space is just nice.
|
||||
|
@@ -26,6 +26,11 @@ setup() {
|
||||
this.cxz = new Bezier(this, points.map(p => projectXZ(p)));
|
||||
this.cyz = new Bezier(this, points.map(p => projectYZ(p)));
|
||||
this.t = 0;
|
||||
setSlider(`.slide-control`, v => this.setPosition(v));
|
||||
}
|
||||
|
||||
setPosition(v) {
|
||||
this.t = v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -134,24 +139,3 @@ drawVector(from, vec, length, color, label) {
|
||||
});
|
||||
text(label, from.x + txt.x, from.y + txt.y);
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
this.t = constrain(
|
||||
map(this.cursor.x,0,this.width,-0.1, 1.1)
|
||||
,0,1
|
||||
);
|
||||
redraw();
|
||||
}
|
||||
|
||||
onKeyDown() {
|
||||
let key = this.keyboard.currentKey;
|
||||
if (key === `ArrowUp`) {
|
||||
this.t += 0.01;
|
||||
if (this.t > 1) this.t = 1;
|
||||
}
|
||||
if (key === `ArrowDown`) {
|
||||
this.t -= 0.01;
|
||||
if (this.t < 0) this.t = 0;
|
||||
}
|
||||
redraw();
|
||||
}
|
@@ -26,6 +26,11 @@ setup() {
|
||||
this.cxz = new Bezier(this, points.map(p => projectXZ(p)));
|
||||
this.cyz = new Bezier(this, points.map(p => projectYZ(p)));
|
||||
this.t = 0;
|
||||
setSlider(`.slide-control`, v => this.setPosition(v));
|
||||
}
|
||||
|
||||
setPosition(v) {
|
||||
this.t = v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -190,24 +195,3 @@ drawVector(from, vec, length, color, label) {
|
||||
});
|
||||
text(label, from.x + txt.x, from.y + txt.y);
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
this.t = constrain(
|
||||
map(this.cursor.x,0,this.width,-0.1, 1.1)
|
||||
,0,1
|
||||
);
|
||||
redraw();
|
||||
}
|
||||
|
||||
onKeyDown() {
|
||||
let key = this.keyboard.currentKey;
|
||||
if (key === `ArrowUp`) {
|
||||
this.t += 0.01;
|
||||
if (this.t > 1) this.t = 1;
|
||||
}
|
||||
if (key === `ArrowDown`) {
|
||||
this.t -= 0.01;
|
||||
if (this.t < 0) this.t = 0;
|
||||
}
|
||||
redraw();
|
||||
}
|
@@ -134,4 +134,7 @@ The steps taken here are:
|
||||
|
||||
And we're done: we now have an expression that lets us approximate an `n+1`<sup>th</sup> order curve with a lower `n`<sup>th</sup> order curve. It won't be an exact fit, but it's definitely a best approximation. So, let's implement these rules for raising and lowering curve order to a (semi) random curve, using the following graphic. Select the sketch, which has movable control points, and press your up and down arrow keys to raise or lower the curve order.
|
||||
|
||||
<graphics-element title="A variable-order Bézier curve" src="./reorder.js"></graphics-element>
|
||||
<graphics-element title="A variable-order Bézier curve" src="./reorder.js">
|
||||
<button class="raise">raise</button>
|
||||
<button class="lower">lower</button>
|
||||
</graphics-element>
|
||||
|
@@ -9,6 +9,15 @@ setup() {
|
||||
});
|
||||
}
|
||||
setMovable(points);
|
||||
this.bindButtons();
|
||||
}
|
||||
|
||||
bindButtons() {
|
||||
let rbutton = find(`.raise`);
|
||||
if (rbutton) rbutton.listen(`click`, v => this.raise());
|
||||
|
||||
let lbutton = find(`.lower`);
|
||||
if (lbutton) lbutton.listen(`click`, v => this.lower());
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -63,6 +72,7 @@ raise() {
|
||||
this.points = np;
|
||||
|
||||
resetMovable(this.points);
|
||||
redraw();
|
||||
}
|
||||
|
||||
lower() {
|
||||
@@ -116,43 +126,9 @@ lower() {
|
||||
}));
|
||||
|
||||
resetMovable(this.points);
|
||||
}
|
||||
|
||||
onKeyDown() {
|
||||
const key = this.keyboard.currentKey;
|
||||
if (key === `ArrowUp`) {
|
||||
this.raise();
|
||||
}
|
||||
if (key === `ArrowDown`) {
|
||||
this.lower();
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
if (this.cursor.down && !this.currentPoint) {
|
||||
if (this.cursor.y < this.height/2) {
|
||||
this.lowerTimer = clearInterval(this.lowerTimer);
|
||||
if (!this.raiseTimer) {
|
||||
this.raiseTimer = setInterval(() => {
|
||||
this.raise();
|
||||
redraw();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
if (this.cursor.y > this.height/2) {
|
||||
this.raiseTimer = clearInterval(this.raiseTimer);
|
||||
if (!this.lowerTimer) {
|
||||
this.lowerTimer = setInterval(() => {
|
||||
this.lower();
|
||||
redraw();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
} else { redraw(); }
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
this.raiseTimer = clearInterval(this.raiseTimer);
|
||||
this.lowerTimer = clearInterval(this.lowerTimer);
|
||||
}
|
||||
|
@@ -2,7 +2,9 @@
|
||||
|
||||
Using de Casteljau's algorithm, we can also find all the points we need to split up a Bézier curve into two, smaller curves, which taken together form the original curve. When we construct de Casteljau's skeleton for some value `t`, the procedure gives us all the points we need to split a curve at that `t` value: one curve is defined by all the inside skeleton points found prior to our on-curve point, with the other curve being defined by all the inside skeleton points after our on-curve point.
|
||||
|
||||
<graphics-element title="Splitting a curve" width="825" src="./splitting.js"></graphics-element>
|
||||
<graphics-element title="Splitting a curve" width="825" src="./splitting.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
|
||||
|
@@ -2,7 +2,9 @@
|
||||
|
||||
ベジエ曲線を分割して、繫ぎ合わせたときに元に戻るような小さい2曲線にしたい場合にも、ド・カステリョのアルゴリズムを使えば、これに必要な点をすべて求めることができます。ある値`t`に対してド・カステリョの骨格を組み立てると、その`t`で曲線を分割する際に必要になる点がすべて得られます。骨格内部の点のうち、曲線上の点から見て手前側にある点によって一方の曲線が定義され、向こう側にある点によってもう一方の曲線が定義されます。
|
||||
|
||||
<graphics-element title="曲線の分割" width="825" src="./splitting.js"></graphics-element>
|
||||
<graphics-element title="曲線の分割" width="825" src="./splitting.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
|
||||
|
@@ -2,7 +2,9 @@
|
||||
|
||||
使用 de Casteljau 算法我们也可以将一条贝塞尔曲线分割成两条更小的曲线,二者拼接起来即可形成原来的曲线。当采用某个 `t` 值构造 de Casteljau 算法时,该过程会给到我们在 `t` 点分割曲线的所有点: 一条曲线包含该曲线上点之前的所有点,另一条曲线包含该曲线上点之后的所有点。
|
||||
|
||||
<graphics-element title="分割一条曲线" width="825" src="./splitting.js"></graphics-element>
|
||||
<graphics-element title="分割一条曲线" width="825" src="./splitting.js">
|
||||
<input type="range" min="0" max="1" step="0.01" value="0.5" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
|
||||
|
@@ -1,91 +0,0 @@
|
||||
module.exports = {
|
||||
setupCubic: function(api) {
|
||||
var curve = api.getDefaultCubic();
|
||||
api.setCurve(curve);
|
||||
api.forward = true;
|
||||
},
|
||||
|
||||
drawSplit: function(api, curve) {
|
||||
api.setPanelCount(2);
|
||||
api.reset();
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
|
||||
var offset = {x:0, y:0};
|
||||
var t = 0.5;
|
||||
var pt = curve.get(0.5);
|
||||
var split = curve.split(t);
|
||||
api.drawCurve(split.left);
|
||||
api.drawCurve(split.right);
|
||||
api.setColor("red");
|
||||
api.drawCircle(pt,3);
|
||||
|
||||
api.setColor("black");
|
||||
offset.x = api.getPanelWidth();
|
||||
api.drawLine({x:0,y:0},{x:0,y:api.getPanelHeight()}, offset);
|
||||
|
||||
api.setColor("lightgrey");
|
||||
api.drawCurve(curve, offset);
|
||||
api.drawCircle(pt,4);
|
||||
|
||||
offset.x -= 20;
|
||||
offset.y -= 20;
|
||||
api.drawSkeleton(split.left, offset, true);
|
||||
api.drawCurve(split.left, offset);
|
||||
|
||||
offset.x += 40;
|
||||
offset.y += 40;
|
||||
api.drawSkeleton(split.right, offset, true);
|
||||
api.drawCurve(split.right, offset);
|
||||
},
|
||||
|
||||
drawAnimated: function(api, curve) {
|
||||
api.setPanelCount(3);
|
||||
api.reset();
|
||||
|
||||
var frame = api.getFrame();
|
||||
var interval = 5 * api.getPlayInterval();
|
||||
var t = (frame%interval)/interval;
|
||||
var forward = (frame%(2*interval)) < interval;
|
||||
if (forward) { t = t%1; } else { t = 1 - t%1; }
|
||||
var offset = {x:0, y:0};
|
||||
|
||||
api.setColor("lightblue");
|
||||
api.drawHull(curve, t);
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
var pt = curve.get(t);
|
||||
api.drawCircle(pt, 4);
|
||||
|
||||
api.setColor("black");
|
||||
offset.x += api.getPanelWidth();
|
||||
api.drawLine({x:0,y:0},{x:0,y:api.getPanelHeight()}, offset);
|
||||
|
||||
var split = curve.split(t);
|
||||
|
||||
api.setColor("lightgrey");
|
||||
api.drawCurve(curve, offset);
|
||||
api.drawHull(curve, t, offset);
|
||||
api.setColor("black");
|
||||
api.drawCurve(split.left, offset);
|
||||
api.drawPoints(split.left.points, offset);
|
||||
api.setFill("black");
|
||||
api.text("Left side of curve split at t = " + (((100*t)|0)/100), {x: 10 + offset.x, y: 15 + offset.y});
|
||||
|
||||
offset.x += api.getPanelWidth();
|
||||
api.drawLine({x:0,y:0},{x:0,y:api.getPanelHeight()}, offset);
|
||||
|
||||
api.setColor("lightgrey");
|
||||
api.drawCurve(curve, offset);
|
||||
api.drawHull(curve, t, offset);
|
||||
api.setColor("black");
|
||||
api.drawCurve(split.right, offset);
|
||||
api.drawPoints(split.right.points, offset);
|
||||
api.setFill("black");
|
||||
api.text("Right side of curve split at t = " + (((100*t)|0)/100), {x: 10 + offset.x, y: 15 + offset.y});
|
||||
},
|
||||
|
||||
togglePlay: function(evt, api) {
|
||||
if (api.playing) { api.pause(); } else { api.play(); }
|
||||
}
|
||||
};
|
@@ -1,28 +0,0 @@
|
||||
|
||||
|
||||
/*
|
||||
|
||||
// const { left, right } = this.getLeftAndRight(this.t, this.curve.points);
|
||||
// const c1 = new Bezier(this, left);
|
||||
// const c2 = new Bezier(this, right);
|
||||
|
||||
getLeftAndRight(t, points, left=[], right=[]) {
|
||||
if(points.length === 1) {
|
||||
left.push(points[0]);
|
||||
right.push(points[0]);
|
||||
return { left, right };
|
||||
}
|
||||
|
||||
let newpoints = points.slice(1);
|
||||
for(let i=0; i<newpoints.length; i++) {
|
||||
if(i === 0) left.push(points[i]);
|
||||
if(i === newpoints.length-1) right.push(points[i+1]);
|
||||
newpoints[i] = {
|
||||
x: (1-t) * points[i].x + t * points[i+1].x,
|
||||
y: (1-t) * points[i].y + t * points[i+1].y,
|
||||
}
|
||||
}
|
||||
|
||||
return this.getLeftAndRight(t, newpoints, left, right);
|
||||
}
|
||||
*/
|
@@ -2,6 +2,11 @@ setup() {
|
||||
this.t = 0.5;
|
||||
this.curve = Bezier.defaultCubic(this);
|
||||
setMovable(this.curve.points);
|
||||
setSlider(`.slide-control`, v => this.setPosition(v));
|
||||
}
|
||||
|
||||
setPosition(v) {
|
||||
this.t = v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -19,13 +24,13 @@ draw() {
|
||||
setStroke(`black`);
|
||||
line(0, 0, 0, this.height);
|
||||
|
||||
this.drawSegment(c1, p);
|
||||
this.drawSegment(c1, p, `first`);
|
||||
|
||||
translate(this.width/3, 0);
|
||||
setStroke(`black`);
|
||||
line(0, 0, 0, this.height);
|
||||
|
||||
this.drawSegment(c2, p);
|
||||
this.drawSegment(c2, p, `second`);
|
||||
}
|
||||
|
||||
drawBasics(p) {
|
||||
@@ -42,7 +47,7 @@ drawBasics(p) {
|
||||
text(`The full curve, with struts`, 10, 15);
|
||||
}
|
||||
|
||||
drawSegment(c, p) {
|
||||
drawSegment(c, p, halfLabel) {
|
||||
setStroke(`lightblue`);
|
||||
this.curve.drawCurve(`lightblue`);
|
||||
this.curve.drawSkeleton(`lightblue`);
|
||||
@@ -53,28 +58,9 @@ drawSegment(c, p) {
|
||||
setStroke(`red`);
|
||||
circle(p.x, p.y, 3);
|
||||
setFill(`black`)
|
||||
text(`The first half`, 10, 15);
|
||||
}
|
||||
|
||||
onKeyDown() {
|
||||
let key = this.keyboard.currentKey;
|
||||
|
||||
if(key === `ArrowUp`) {
|
||||
this.t += 0.01;
|
||||
}
|
||||
|
||||
if(key === `ArrowDown`) {
|
||||
this.t -= 0.01
|
||||
}
|
||||
|
||||
this.t = this.t < 0 ? 0 : this.t > 1 ? 1 : this.t;
|
||||
|
||||
redraw();
|
||||
text(`The ${halfLabel} half`, 10, 15);
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
if (this.cursor.down && !this.currentPoint) {
|
||||
this.t = map(this.cursor.y, 0,this.height, 0, 1);
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
@@ -19,7 +19,9 @@ Given \left (
|
||||
|
||||
So let's look at that in action: the following graphic is interactive in that you can use your up and down arrow keys to increase or decrease the interpolation ratio, to see what happens. We start with three points, which gives us two lines. Linear interpolation over those lines gives us two points, between which we can again perform linear interpolation, yielding a single point. And that point —and all points we can form in this way for all ratios taken together— form our Bézier curve:
|
||||
|
||||
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" height="275" src="./interpolation.js"></graphics-element>
|
||||
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" src="./interpolation.js">
|
||||
<input type="range" min="10" max="90" step="1" value="75" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
And that brings us to the complicated maths: calculus.
|
||||
|
||||
|
@@ -19,7 +19,9 @@
|
||||
|
||||
では、実際に見てみましょう。下の図はインタラクティブになっています。上下キーで補間の比率が増減しますので、どうなるか確かめてみましょう。最初に3点があり、それを結んで2本の直線が引かれています。この直線の上でそれぞれ線形補間を行うと、2つの点が得られます。この2点の間でさらに線形補間を行うと、1つの点を得ることができます。そして、あらゆる比率に対して同様に点を求め、それをすべて集めると、このようにベジエ曲線ができるのです。
|
||||
|
||||
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" height="275" src="./interpolation.js"></graphics-element>
|
||||
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" src="./interpolation.js">
|
||||
<input type="range" min="10" max="90" step="1" value="75" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
また、これが複雑な方の数学につながっていきます。微積分です。
|
||||
|
||||
|
@@ -19,7 +19,9 @@ Given \left (
|
||||
|
||||
让我们来通过实际操作看一下:下面的图形都是可交互的,因此你可以通过上下键来增加或减少插值距离,来观察图形的变化。我们从三个点构成的两条线段开始。通过对各条线段进行线性插值得到两个点,对点之间的线段再进行线性插值,产生一个新的点。最终这些点——所有的点都可以通过选取不同的距离插值产生——构成了贝塞尔曲线
|
||||
:
|
||||
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" height="275" src="./interpolation.js"></graphics-element>
|
||||
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" src="./interpolation.js">
|
||||
<input type="range" min="10" max="90" step="1" value="75" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
这为我们引出了复杂的数学:微积分。
|
||||
|
||||
|
@@ -2,6 +2,11 @@ setup() {
|
||||
this.step = 25;
|
||||
this.curve = Bezier.defaultQuadratic(this);
|
||||
setMovable(this.curve.points);
|
||||
setSlider(`.slide-control`, v => this.setStep(v))
|
||||
}
|
||||
|
||||
setStep(v) {
|
||||
this.step = 100 - v;
|
||||
}
|
||||
|
||||
draw() {
|
||||
@@ -101,41 +106,6 @@ drawCurveCoordinates() {
|
||||
this.curve.drawPoints();
|
||||
}
|
||||
|
||||
onKeyDown() {
|
||||
this.mark = false;
|
||||
if (this.keyboard[`ArrowDown`]) {
|
||||
this.stepDown();
|
||||
}
|
||||
if (this.keyboard[`ArrowUp`]) {
|
||||
this.stepUp();
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
||||
stepDown(value = 1) {
|
||||
this.step -= value;
|
||||
if (this.step < 10) this.step = 10;
|
||||
}
|
||||
|
||||
stepUp(value = 1) {
|
||||
this.step += value;
|
||||
if (this.step > 90) this.step = 90;
|
||||
}
|
||||
|
||||
onMouseDown() {
|
||||
this.mark = this.cursor.y;
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
this.mark = false;
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
if (this.mark && !this.currentPoint) {
|
||||
let diff = this.mark - this.cursor.y,
|
||||
mapped = map(diff, -this.height/2, this.height/2, 10, 90, true);
|
||||
this.step = mapped | 0;
|
||||
}
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 23 KiB |
BIN
docs/images/chapters/whatis/4d6b98490713508b5c560e29ffa535ed.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 26 KiB |
172
docs/index.html
@@ -599,12 +599,20 @@
|
||||
<img
|
||||
width="825px"
|
||||
height="275px"
|
||||
src="images\chapters\whatis\d39b17854b29fbb3c70bec7a12820aa1.png"
|
||||
src="images\chapters\whatis\4d6b98490713508b5c560e29ffa535ed.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="10"
|
||||
max="90"
|
||||
step="1"
|
||||
value="75"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>And that brings us to the complicated maths: calculus.</p>
|
||||
<p>
|
||||
@@ -712,8 +720,7 @@
|
||||
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):
|
||||
<i>t</i> from 0 to 5, we get this:
|
||||
</p>
|
||||
<graphics-element
|
||||
title="A (partial) circle: x=sin(t), y=cos(t)"
|
||||
@@ -725,12 +732,20 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\explanation\fdea63696e525033c5ea74fa8f90009a.png"
|
||||
src="images\chapters\explanation\1078bb1b1c8b7239aaa555e2d239e44d.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="10"
|
||||
step="0.1"
|
||||
value="5"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>
|
||||
Bézier curves are just one out of the many classes of parametric
|
||||
@@ -941,12 +956,21 @@ function Bezier(3,t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\13bba6ecec2fa000c575813d0cda815c.png"
|
||||
src="images\chapters\control\f319958c931e9f28e41b889c9689c87e.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element
|
||||
title="Cubic interpolations"
|
||||
width="275"
|
||||
@@ -957,12 +981,21 @@ function Bezier(3,t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\15e8c8492908851ddde1cb35297326bb.png"
|
||||
src="images\chapters\control\bf717f39221d5210e79ab8b0bcb38948.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element
|
||||
title="15th degree interpolations"
|
||||
width="275"
|
||||
@@ -973,12 +1006,20 @@ function Bezier(3,t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\23dae5b8acf92135ea4463d8d0342190.png"
|
||||
src="images\chapters\control\53d95f337568a2108c525c559aa66e2b.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
@@ -1525,12 +1566,20 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\decasteljau\425ee92efb13c790f63f8b3821327d3b.png"
|
||||
src="images\chapters\decasteljau\817f7e557caed67e173039d9032b3ab3.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
<h3>How to implement de Casteljau's algorithm</h3>
|
||||
@@ -1590,6 +1639,7 @@ function RationalBezier(3,t,w[],r[]):
|
||||
usually can't use the flattened for for doing true intersection
|
||||
detection, or curvature alignment.
|
||||
</p>
|
||||
<div class="figure">
|
||||
<graphics-element
|
||||
title="Flattening a quadratic curve"
|
||||
width="275"
|
||||
@@ -1600,12 +1650,21 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\flattening\4d4a648e8cac72a7041555ff885cbc2b.png"
|
||||
src="images\chapters\flattening\a8f7f97cb3b14c99e3cdf0c5283d7be4.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="16"
|
||||
step="1"
|
||||
value="4"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element
|
||||
title="Flattening a cubic curve"
|
||||
width="275"
|
||||
@@ -1616,12 +1675,21 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\flattening\1a22ba71ef9a5aaf9c55e0b8c2f3f6e5.png"
|
||||
src="images\chapters\flattening\ffe43c40bea1277394df5ff82100a966.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="24"
|
||||
step="1"
|
||||
value="8"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Try clicking on the sketch and using your up and down arrow keys to
|
||||
@@ -1684,12 +1752,20 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="825px"
|
||||
height="275px"
|
||||
src="images\chapters\splitting\7ad9b19d2a951c5eaf057edba3a37a5b.png"
|
||||
src="images\chapters\splitting\77d77c07832ad3adc3a3dec129a137bb.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0.5"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
<h3>implementing curve splitting</h3>
|
||||
@@ -2266,12 +2342,14 @@ function drawCurve(points[], t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\reordering\4541eeb2113d81cbc0c0a56122570d48.png"
|
||||
src="images\chapters\reordering\5ea06407f13c4b68a507c16d72fcb3e7.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<button class="raise">raise</button>
|
||||
<button class="lower">lower</button>
|
||||
</graphics-element>
|
||||
</section>
|
||||
<section id="derivatives">
|
||||
<h1><a href="#derivatives">Derivatives</a></h1>
|
||||
@@ -2758,12 +2836,20 @@ function drawCurve(points[], t):
|
||||
<img
|
||||
width="350px"
|
||||
height="300px"
|
||||
src="images\chapters\pointvectors3d\f5cf3e34415eccd1b03c4ef478862d44.png"
|
||||
src="images\chapters\pointvectors3d\769ab953d7f3542ab4c3a383f151b1bc.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>
|
||||
However, if you've played with that graphic a bit, you might have
|
||||
@@ -2908,14 +2994,22 @@ function drawCurve(points[], t):
|
||||
<img
|
||||
width="350px"
|
||||
height="300px"
|
||||
src="images\chapters\pointvectors3d\b11dfd6fef9931ac8715209785f63e0c.png"
|
||||
src="images\chapters\pointvectors3d\efd37ce3b7f4d8f084c287ec871c6b69.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>That looks much better!</p>
|
||||
<p>That looks so much better!</p>
|
||||
<p>
|
||||
For those reading along with the code: we don't even strictly
|
||||
speaking need a Frenet frame to start with: we could, for instance,
|
||||
|
@@ -571,12 +571,20 @@
|
||||
<img
|
||||
width="825px"
|
||||
height="275px"
|
||||
src="images\chapters\whatis\d39b17854b29fbb3c70bec7a12820aa1.png"
|
||||
src="images\chapters\whatis\4d6b98490713508b5c560e29ffa535ed.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="10"
|
||||
max="90"
|
||||
step="1"
|
||||
value="75"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>また、これが複雑な方の数学につながっていきます。微積分です。</p>
|
||||
<p>
|
||||
@@ -637,7 +645,7 @@
|
||||
</p>
|
||||
<p>
|
||||
というわけで、普通の関数では<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まで変化させてプロットした場合は、このようになります(上下キーでプロットの上限を変更できます)。
|
||||
(<i>x</i>,<i>y</i>)として使うことができます。例えば、先ほどの関数の組は円周上の点を生成します。負の無限大から正の無限大へと<i>t</i>を動かすと、得られる座標(<i>x</i>,<i>y</i>)は常に中心(0,0)・半径1の円の上に乗ります。<i>t</i>を0から5まで変化させてプロットした場合は、このようになります。
|
||||
</p>
|
||||
<graphics-element
|
||||
title="(部分)円 x=sin(t), y=cos(t)"
|
||||
@@ -649,12 +657,20 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\explanation\fdea63696e525033c5ea74fa8f90009a.png"
|
||||
src="images\chapters\explanation\1078bb1b1c8b7239aaa555e2d239e44d.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="10"
|
||||
step="0.1"
|
||||
value="5"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>
|
||||
ベジエ曲線はパラメトリック関数の一種であり、どの次元に対しても同じ基底関数を使うという点で特徴づけられます。先ほどの例では、<i>x</i>の値と<i>y</i>の値とで異なる関数(正弦関数と余弦関数)を使っていましたが、ベジエ曲線では<i>x</i>と<i>y</i>の両方で「二項係数多項式」を使います。では、二項係数多項式とは何でしょう?
|
||||
@@ -792,21 +808,80 @@ function Bezier(3,t):
|
||||
下のグラフは、2次ベジエ曲線や3次ベジエ曲線の補間関数を表しています。ここでSは、ベジエ関数全体に対しての、その点の寄与の大きさを示します。ある<i>t</i>において、ベジエ曲線を定義する各点の補間率がどのようになっているのか、クリックドラッグをして確かめてみてください。
|
||||
</p>
|
||||
<div class="figure">
|
||||
<Graphic
|
||||
inline="{true}"
|
||||
<graphics-element
|
||||
title="2次の補間"
|
||||
draw="{this.drawQuadraticLerp}"
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/control/lerp-quadratic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\f319958c931e9f28e41b889c9689c87e.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Graphic
|
||||
inline="{true}"
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element
|
||||
title="3次の補間"
|
||||
draw="{this.drawCubicLerp}"
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/control/lerp-cubic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\bf717f39221d5210e79ab8b0bcb38948.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Graphic
|
||||
inline="{true}"
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element
|
||||
title="15次の補間"
|
||||
draw="{this.draw15thLerp}"
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/control/lerp-fifteenth.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\53d95f337568a2108c525c559aa66e2b.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
@@ -1269,12 +1344,20 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\decasteljau\425ee92efb13c790f63f8b3821327d3b.png"
|
||||
src="images\chapters\decasteljau\817f7e557caed67e173039d9032b3ab3.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
<h3>ド・カステリョのアルゴリズムの実装方法</h3>
|
||||
@@ -1313,6 +1396,7 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<p>
|
||||
例えば「X個の線分がほしい」場合には、分割数がそうなるようにサンプリング間隔を選び、曲線をサンプリングします。この方法の利点は速さです。曲線の座標を100個だの1000個だの計算するのではなく、ずっと少ない回数のサンプリングでも、十分きれいに見えるような曲線を作ることができるのです。欠点はもちろん、「本物の曲線」に比べて精度が損なわれてしまうことです。したがって、交点の検出や曲線の位置揃えを正しく行いたい場合には、平坦化した曲線は普通利用できません。
|
||||
</p>
|
||||
<div class="figure">
|
||||
<graphics-element
|
||||
title="2次ベジエ曲線の平坦化"
|
||||
width="275"
|
||||
@@ -1323,12 +1407,21 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\flattening\4d4a648e8cac72a7041555ff885cbc2b.png"
|
||||
src="images\chapters\flattening\a8f7f97cb3b14c99e3cdf0c5283d7be4.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="16"
|
||||
step="1"
|
||||
value="4"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element
|
||||
title="3次ベジエ曲線の平坦化"
|
||||
width="275"
|
||||
@@ -1339,12 +1432,21 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\flattening\1a22ba71ef9a5aaf9c55e0b8c2f3f6e5.png"
|
||||
src="images\chapters\flattening\ffe43c40bea1277394df5ff82100a966.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="24"
|
||||
step="1"
|
||||
value="8"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
2次ベジエ曲線も3次ベジエ曲線も、図をクリックして上下キーを押すと曲線の分割数が増減しますので、試してみてください。ある曲線では分割数が少なくてもうまくいきますが、曲線が複雑になればなるほど、曲率の変化を正確に捉えるためにはより多くの分割数が必要になることがわかります(3次ベジエ曲線で試してみてください)。
|
||||
@@ -1389,12 +1491,20 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="825px"
|
||||
height="275px"
|
||||
src="images\chapters\splitting\7ad9b19d2a951c5eaf057edba3a37a5b.png"
|
||||
src="images\chapters\splitting\77d77c07832ad3adc3a3dec129a137bb.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0.5"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
<h3>曲線分割の実装方法</h3>
|
||||
@@ -1926,12 +2036,14 @@ function drawCurve(points[], t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\reordering\4541eeb2113d81cbc0c0a56122570d48.png"
|
||||
src="images\chapters\reordering\5ea06407f13c4b68a507c16d72fcb3e7.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<button class="raise">raise</button>
|
||||
<button class="lower">lower</button>
|
||||
</graphics-element>
|
||||
</section>
|
||||
<section id="derivatives">
|
||||
<h1><a href="ja-JP/index.html#derivatives">Derivatives</a></h1>
|
||||
@@ -2424,12 +2536,20 @@ function drawCurve(points[], t):
|
||||
<img
|
||||
width="350px"
|
||||
height="300px"
|
||||
src="images\chapters\pointvectors3d\f5cf3e34415eccd1b03c4ef478862d44.png"
|
||||
src="images\chapters\pointvectors3d\769ab953d7f3542ab4c3a383f151b1bc.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>
|
||||
However, if you've played with that graphic a bit, you might have
|
||||
@@ -2574,14 +2694,22 @@ function drawCurve(points[], t):
|
||||
<img
|
||||
width="350px"
|
||||
height="300px"
|
||||
src="images\chapters\pointvectors3d\b11dfd6fef9931ac8715209785f63e0c.png"
|
||||
src="images\chapters\pointvectors3d\efd37ce3b7f4d8f084c287ec871c6b69.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>That looks much better!</p>
|
||||
<p>That looks so much better!</p>
|
||||
<p>
|
||||
For those reading along with the code: we don't even strictly
|
||||
speaking need a Frenet frame to start with: we could, for instance,
|
||||
|
@@ -138,6 +138,17 @@ class GraphicsAPI extends BaseAPI {
|
||||
points.forEach((p) => this.movable.push(p));
|
||||
}
|
||||
|
||||
setSlider(qs, handler, redraw = true) {
|
||||
let slider = this.find(qs);
|
||||
if (slider) {
|
||||
slider.listen(`input`, (evt) => {
|
||||
handler(parseFloat(evt.target.value));
|
||||
if (redraw) this.redraw();
|
||||
});
|
||||
}
|
||||
return slider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the canvas to an image
|
||||
*/
|
||||
|
@@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
const images = Array.from(
|
||||
document.querySelectorAll(`img[loading=lazy]`)
|
||||
).filter((img) => img.parentNode.nodeName !== `FALLBACK-IMAGE`);
|
||||
document.querySelectorAll(`img.LaTeX.SVG[loading=lazy]`)
|
||||
);
|
||||
|
||||
// First, make images inert. As this happens before the document
|
||||
// becomes active, this prevents images from loading anything.
|
||||
@@ -41,12 +41,10 @@ function testImages() {
|
||||
|
||||
lock = true;
|
||||
|
||||
let top = window.scrollY;
|
||||
let height = document.documentElement.clientHeight;
|
||||
let bottom = top + height;
|
||||
|
||||
for (let pos = images.length - 1; pos >= 0; pos--) {
|
||||
test(images[pos], pos, top, bottom, height);
|
||||
test(images[pos], pos, height);
|
||||
}
|
||||
|
||||
if (images.length === 0) {
|
||||
@@ -60,9 +58,12 @@ function testImages() {
|
||||
/**
|
||||
* Test individual images for whether or not they should load.
|
||||
*/
|
||||
function test(img, pos, top, bottom, threshold) {
|
||||
top = Math.abs(img.offsetTop - top) < threshold;
|
||||
bottom = Math.abs(img.offsetTop + img.offsetHeight - bottom) < threshold;
|
||||
function test(img, pos, height) {
|
||||
let top = img.getBoundingClientRect().top;
|
||||
top = top < height;
|
||||
|
||||
let bottom = img.getBoundingClientRect().bottom;
|
||||
bottom = bottom > -height;
|
||||
|
||||
if (top || bottom) {
|
||||
img.src = img.dataset.src;
|
||||
|
@@ -141,3 +141,7 @@ p code {
|
||||
font-size: 1.2em;
|
||||
letter-spacing: -2px;
|
||||
}
|
||||
|
||||
.slide-control {
|
||||
width: 100%;
|
||||
}
|
@@ -550,12 +550,20 @@
|
||||
<img
|
||||
width="825px"
|
||||
height="275px"
|
||||
src="images\chapters\whatis\d39b17854b29fbb3c70bec7a12820aa1.png"
|
||||
src="images\chapters\whatis\4d6b98490713508b5c560e29ffa535ed.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="10"
|
||||
max="90"
|
||||
step="1"
|
||||
value="75"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
</p>
|
||||
<p>这为我们引出了复杂的数学:微积分。</p>
|
||||
<p>
|
||||
@@ -616,7 +624,7 @@
|
||||
好了,通过一些神秘的<i>t</i>值将<i>x</i>/<i>y</i>坐标系联系起来。
|
||||
</p>
|
||||
<p>
|
||||
所以,参数曲线不像一般函数那样,通过<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时的值,将得到如下图像(你可以用上下键来改变画的点和值):
|
||||
所以,参数曲线不像一般函数那样,通过<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时的值,将得到如下图像:
|
||||
</p>
|
||||
<graphics-element
|
||||
title="(一部分的)圆: x=sin(t), y=cos(t)"
|
||||
@@ -628,12 +636,20 @@
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\explanation\fdea63696e525033c5ea74fa8f90009a.png"
|
||||
src="images\chapters\explanation\1078bb1b1c8b7239aaa555e2d239e44d.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="10"
|
||||
step="0.1"
|
||||
value="5"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>
|
||||
贝塞尔曲线是(一种)参数方程,并在它的多个维度上使用相同的基本方程。在上述的例子中<i>x</i>值和<i>y</i>值使用了不同的方程,与此不同的是,贝塞尔曲线的<i>x</i>和<i>y</i>都用了“二项多项式”。那什么是二项多项式呢?
|
||||
@@ -769,21 +785,80 @@ function Bezier(3,t):
|
||||
下面的图形显示了二次曲线和三次曲线的差值方程,“S”代表了点对贝塞尔方程总和的贡献。点击拖动点来看看在特定的<i>t</i>值时,每个曲线定义的点的插值百分比。
|
||||
</p>
|
||||
<div class="figure">
|
||||
<Graphic
|
||||
inline="{true}"
|
||||
<graphics-element
|
||||
title="二次插值"
|
||||
draw="{this.drawQuadraticLerp}"
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/control/lerp-quadratic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\f319958c931e9f28e41b889c9689c87e.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Graphic
|
||||
inline="{true}"
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element
|
||||
title="三次插值"
|
||||
draw="{this.drawCubicLerp}"
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/control/lerp-cubic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\bf717f39221d5210e79ab8b0bcb38948.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Graphic
|
||||
inline="{true}"
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element
|
||||
title="15次插值"
|
||||
draw="{this.draw15thLerp}"
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/control/lerp-fifteenth.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\control\53d95f337568a2108c525c559aa66e2b.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
@@ -1224,12 +1299,20 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\decasteljau\425ee92efb13c790f63f8b3821327d3b.png"
|
||||
src="images\chapters\decasteljau\817f7e557caed67e173039d9032b3ab3.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
<h3>如何实现de Casteljau算法</h3>
|
||||
@@ -1269,6 +1352,7 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<p>
|
||||
我们可以先确定“想要X个分段”,然后在间隔的地方采样曲线,得到一定数量的分段。这种方法的优点是速度很快:比起遍历100甚至1000个曲线坐标,我们可以采样比较少的点,仍然得到看起来足够好的曲线。这么做的缺点是,我们失去了“真正的曲线”的精度,因此不能用此方法来做真实的相交检测或曲率对齐。
|
||||
</p>
|
||||
<div class="figure">
|
||||
<graphics-element
|
||||
title="拉平一条二次曲线"
|
||||
width="275"
|
||||
@@ -1279,12 +1363,21 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\flattening\4d4a648e8cac72a7041555ff885cbc2b.png"
|
||||
src="images\chapters\flattening\a8f7f97cb3b14c99e3cdf0c5283d7be4.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="16"
|
||||
step="1"
|
||||
value="4"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element
|
||||
title="拉平一条三次曲线"
|
||||
width="275"
|
||||
@@ -1295,12 +1388,21 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\flattening\1a22ba71ef9a5aaf9c55e0b8c2f3f6e5.png"
|
||||
src="images\chapters\flattening\ffe43c40bea1277394df5ff82100a966.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="24"
|
||||
step="1"
|
||||
value="8"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
试着点击图形,并用上下键来降低二次曲线和三次曲线的分段数量。你会发现对某些曲率来说,数量少的分段也能做的很好,但对于复杂的曲率(在三次曲线上试试),足够多的分段才能很好地满足曲率的变化。
|
||||
@@ -1347,12 +1449,20 @@ function RationalBezier(3,t,w[],r[]):
|
||||
<img
|
||||
width="825px"
|
||||
height="275px"
|
||||
src="images\chapters\splitting\7ad9b19d2a951c5eaf057edba3a37a5b.png"
|
||||
src="images\chapters\splitting\77d77c07832ad3adc3a3dec129a137bb.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0.5"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<div class="howtocode">
|
||||
<h3>分割曲线的代码实习</h3>
|
||||
@@ -1936,12 +2046,14 @@ function drawCurve(points[], t):
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\reordering\4541eeb2113d81cbc0c0a56122570d48.png"
|
||||
src="images\chapters\reordering\5ea06407f13c4b68a507c16d72fcb3e7.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<button class="raise">raise</button>
|
||||
<button class="lower">lower</button>
|
||||
</graphics-element>
|
||||
</section>
|
||||
<section id="derivatives">
|
||||
<h1><a href="zh-CN/index.html#derivatives">Derivatives</a></h1>
|
||||
@@ -2434,12 +2546,20 @@ function drawCurve(points[], t):
|
||||
<img
|
||||
width="350px"
|
||||
height="300px"
|
||||
src="images\chapters\pointvectors3d\f5cf3e34415eccd1b03c4ef478862d44.png"
|
||||
src="images\chapters\pointvectors3d\769ab953d7f3542ab4c3a383f151b1bc.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>
|
||||
However, if you've played with that graphic a bit, you might have
|
||||
@@ -2584,14 +2704,22 @@ function drawCurve(points[], t):
|
||||
<img
|
||||
width="350px"
|
||||
height="300px"
|
||||
src="images\chapters\pointvectors3d\b11dfd6fef9931ac8715209785f63e0c.png"
|
||||
src="images\chapters\pointvectors3d\efd37ce3b7f4d8f084c287ec871c6b69.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
</fallback-image>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value="0"
|
||||
class="slide-control"
|
||||
/>
|
||||
</graphics-element>
|
||||
|
||||
<p>That looks much better!</p>
|
||||
<p>That looks so much better!</p>
|
||||
<p>
|
||||
For those reading along with the code: we don't even strictly
|
||||
speaking need a Frenet frame to start with: we could, for instance,
|
||||
|
19
package-lock.json
generated
@@ -210,13 +210,12 @@
|
||||
}
|
||||
},
|
||||
"canvas": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz",
|
||||
"integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==",
|
||||
"version": "git://github.com/Automattic/node-canvas.git#595d5590a0c4199c483d4244b59d28341086edc9",
|
||||
"from": "git://github.com/Automattic/node-canvas.git#master",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nan": "^2.14.0",
|
||||
"node-pre-gyp": "^0.11.0",
|
||||
"node-pre-gyp": "^0.15.0",
|
||||
"simple-get": "^3.0.3"
|
||||
}
|
||||
},
|
||||
@@ -1261,21 +1260,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
|
||||
"integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==",
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz",
|
||||
"integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"needle": "^2.2.1",
|
||||
"mkdirp": "^0.5.3",
|
||||
"needle": "^2.5.0",
|
||||
"nopt": "^4.0.1",
|
||||
"npm-packlist": "^1.1.6",
|
||||
"npmlog": "^4.0.2",
|
||||
"rc": "^1.2.7",
|
||||
"rimraf": "^2.6.1",
|
||||
"semver": "^5.3.0",
|
||||
"tar": "^4"
|
||||
"tar": "^4.4.2"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
|
@@ -41,7 +41,7 @@
|
||||
"ebook"
|
||||
],
|
||||
"devDependencies": {
|
||||
"canvas": "^2.6.1",
|
||||
"canvas": "git://github.com/Automattic/node-canvas#master",
|
||||
"chokidar-cli": "^2.1.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"glob": "^7.1.6",
|
||||
|
14
src/build.js
@@ -8,12 +8,18 @@ import { createIndexPages } from "./build/create-index-page.js";
|
||||
* - aggregate all content files organized by locale
|
||||
* -
|
||||
*/
|
||||
getAllChapterFiles().then((chapterFiles) => {
|
||||
getAllChapterFiles().then(async (chapterFiles) => {
|
||||
const start = Date.now();
|
||||
const languageCodes = Object.keys(chapterFiles);
|
||||
|
||||
languageCodes.forEach(async (locale) => {
|
||||
await Promise.all(
|
||||
languageCodes.map(async (locale) => {
|
||||
const localeStrings = new LocaleStrings(locale);
|
||||
const chapters = await processLocale(locale, localeStrings, chapterFiles);
|
||||
createIndexPages(locale, localeStrings, chapters);
|
||||
});
|
||||
return createIndexPages(locale, localeStrings, chapters);
|
||||
})
|
||||
);
|
||||
|
||||
const runtime = Date.now() - start;
|
||||
console.log(`Finished build (${(runtime / 1000).toFixed(2)}s)`);
|
||||
});
|
||||
|