figured out how to reuse sketches with data-attribute parameters
@@ -21,7 +21,7 @@ React is nice, Webpack is convenient, but there's just very little need to serve
|
|||||||
- [ ] ja-JP
|
- [ ] ja-JP
|
||||||
- [ ] zh-CN
|
- [ ] zh-CN
|
||||||
- [x] Figure out why pages scroll on focus in Firefox (https://github.com/Pomax/BezierInfo-2/issues/262)
|
- [x] Figure out why pages scroll on focus in Firefox (https://github.com/Pomax/BezierInfo-2/issues/262)
|
||||||
- [ ] Firefox for Android does not support static class fields (nightly does). Should I care, or will it not matter a month from now?
|
- [x] ~Firefox for Android does not support static class fields (nightly does). Should I care, or will it not matter a month from now?~ Mozilla released a new Firefox for Android that's finally up to date wrt modern JS, so this is no longer an issue.
|
||||||
- [x] now that github supports gh-pages from not just the root dir, move all the code into a `src` dir, and all the content into a `docs` dir. It's a stupid name, but GH doesn't support `public`. Hopefully "yet" but who knows how they work.
|
- [x] now that github supports gh-pages from not just the root dir, move all the code into a `src` dir, and all the content into a `docs` dir. It's a stupid name, but GH doesn't support `public`. Hopefully "yet" but who knows how they work.
|
||||||
- [x] implement custom lazy loading that kicks in when images are about 2 screens away from being in screen. The standard browser `loading="lazy"` distance is entirely useless.
|
- [x] implement custom lazy loading that kicks in when images are about 2 screens away from being in screen. The standard browser `loading="lazy"` distance is entirely useless.
|
||||||
- [x] scope LaTeX images to each section (similar to the placeholder images) so that it's easier to redo just one section's latex code, rather than clearing and regenerating all ~250 latex blocks.
|
- [x] scope LaTeX images to each section (similar to the placeholder images) so that it's easier to redo just one section's latex code, rather than clearing and regenerating all ~250 latex blocks.
|
||||||
@@ -29,6 +29,7 @@ React is nice, Webpack is convenient, but there's just very little need to serve
|
|||||||
- [x] Add a `setSlider(qs, handler)` API function so that sketches can hook into locally scoped HTML UI elements (like `<input type="range">`
|
- [x] Add a `setSlider(qs, handler)` API function so that sketches can hook into locally scoped HTML UI elements (like `<input type="range">`
|
||||||
- [ ] figure out how to force `graphics-element` elements to preallocate their bounding box, so that progressive page loading doesn't cause reflow.
|
- [ ] figure out how to force `graphics-element` elements to preallocate their bounding box, so that progressive page loading doesn't cause reflow.
|
||||||
- [ ] consider swammping out the `perform-code-surgery.js` regex approach to a dedicated DFA lexer, with scope tracking.
|
- [ ] consider swammping out the `perform-code-surgery.js` regex approach to a dedicated DFA lexer, with scope tracking.
|
||||||
|
- [x] add data-attributes to sketches for same-sketch, different-parameters runs, in a way that works with the placeholder generation.
|
||||||
|
|
||||||
### Section conversion:
|
### Section conversion:
|
||||||
|
|
||||||
|
29
docs/chapters/arclengthapprox/approximate.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
let type = getParameter(`type`, `quadratic`);
|
||||||
|
curve = (type === `quadratic`) ? Bezier.defaultQuadratic(this) : Bezier.defaultCubic(this);
|
||||||
|
setMovable(curve.points);
|
||||||
|
setSlider(`.slide-control`, `steps`, type === `quadratic` ? 4 : 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
let alen = 0;
|
||||||
|
const len = curve.length();
|
||||||
|
const LUT = curve.getLUT(this.steps + 1);
|
||||||
|
|
||||||
|
setStroke("red");
|
||||||
|
curve.drawSkeleton(`lightblue`);
|
||||||
|
LUT.forEach((p1,i) => {
|
||||||
|
if (i===0) return;
|
||||||
|
let p0 = LUT[i-1];
|
||||||
|
line(p0.x, p0.y, p1.x, p1.y);
|
||||||
|
alen += dist(p0.x, p0.y, p1.x, p1.y);
|
||||||
|
});
|
||||||
|
|
||||||
|
curve.drawPoints();
|
||||||
|
setFill(`black`);
|
||||||
|
text(`Approximate length, ${this.steps} steps: ${alen.toFixed(2)} (true: ${len.toFixed(2)})`, 10, 15);
|
||||||
|
};
|
@@ -4,7 +4,16 @@ 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:
|
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 title="Approximate quadratic curve arc length" setup={this.setupQuadratic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
|
<div class="figure">
|
||||||
<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.
|
<graphics-element title="Approximate quadratic curve arc length" src="./approximate.js" data-type="quadratic">
|
||||||
|
<input type="range" min="1" max="24" step="1" value="4" class="slide-control">
|
||||||
|
</graphics-element>
|
||||||
|
|
||||||
|
<graphics-element title="Approximate cubic curve arc length" src="./approximate.js" data-type="cubic">
|
||||||
|
<input type="range" min="1" max="32" step="1" value="8" class="slide-control">
|
||||||
|
</graphics-element>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
You may notice that even though the error in length is actually pretty significant in absolute terms, even at a low number of segments we get a length that agrees with the true length when it comes to just the integer part of the arc length. Quite often, approximations can drastically speed things up!
|
||||||
|
@@ -1,72 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
// These are functions that can be called "From the page",
|
|
||||||
// rather than being internal to the sketch. This is useful
|
|
||||||
// for making on-page controls hook into the sketch code.
|
|
||||||
statics: {
|
|
||||||
keyHandlingOptions: {
|
|
||||||
propName: "steps",
|
|
||||||
values: {
|
|
||||||
"38": 1, // up arrow
|
|
||||||
"40": -1 // down arrow
|
|
||||||
},
|
|
||||||
controller: function(api) {
|
|
||||||
if (api.steps < 1) {
|
|
||||||
api.steps = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the default quadratic curve.
|
|
||||||
*/
|
|
||||||
setupQuadratic: function(api) {
|
|
||||||
var curve = api.getDefaultQuadratic();
|
|
||||||
api.setCurve(curve);
|
|
||||||
api.steps = 10;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the default cubic curve.
|
|
||||||
*/
|
|
||||||
setupCubic: function(api) {
|
|
||||||
var curve = api.getDefaultCubic();
|
|
||||||
api.setCurve(curve);
|
|
||||||
api.steps = 16;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a curve and its polygon-approximation,
|
|
||||||
* showing the "true" length of the curve vs. the
|
|
||||||
* length based on tallying up the polygon sections.
|
|
||||||
*/
|
|
||||||
draw: function(api, curve) {
|
|
||||||
api.reset();
|
|
||||||
api.drawSkeleton(curve);
|
|
||||||
|
|
||||||
var pts = curve.getLUT(api.steps);
|
|
||||||
|
|
||||||
var step = 1 / api.steps;
|
|
||||||
var p0 = curve.points[0], pc;
|
|
||||||
for(var t=step; t<1.0+step; t+=step) {
|
|
||||||
pc = curve.get(Math.min(t,1));
|
|
||||||
api.setColor("red");
|
|
||||||
api.drawLine(p0,pc);
|
|
||||||
p0 = pc;
|
|
||||||
}
|
|
||||||
|
|
||||||
var len = curve.length();
|
|
||||||
var alen = 0;
|
|
||||||
for(var i=0,p1,dx,dy; i<pts.length-1; i++) {
|
|
||||||
p0 = pts[i];
|
|
||||||
p1 = pts[i+1];
|
|
||||||
dx = p1.x-p0.x;
|
|
||||||
dy = p1.y-p0.y;
|
|
||||||
alen += Math.sqrt(dx*dx+dy*dy);
|
|
||||||
}
|
|
||||||
alen = ((100*alen)|0)/100;
|
|
||||||
len = ((100*len)|0)/100;
|
|
||||||
|
|
||||||
api.text("Approximate length, "+api.steps+" steps: "+alen+" (true: "+len+")", {x:10, y: 15});
|
|
||||||
}
|
|
||||||
};
|
|
@@ -1,26 +1,33 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = Bezier.defaultQuadratic(this);
|
let type = getParameter(`type`, `quadratic`);
|
||||||
setMovable(this.curve.points);
|
if (type === `quadratic`) {
|
||||||
|
curve = Bezier.defaultQuadratic(this);
|
||||||
|
} else {
|
||||||
|
curve = Bezier.defaultCubic(this);
|
||||||
|
curve.points[2].x = 210;
|
||||||
|
}
|
||||||
|
setMovable(curve.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
clear();
|
clear();
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
curve.drawPoints();
|
curve.drawPoints();
|
||||||
|
|
||||||
noFill();
|
|
||||||
|
|
||||||
let minx = Number.MAX_SAFE_INTEGER,
|
let minx = Number.MAX_SAFE_INTEGER,
|
||||||
miny = minx,
|
miny = minx,
|
||||||
maxx = Number.MIN_SAFE_INTEGER,
|
maxx = Number.MIN_SAFE_INTEGER,
|
||||||
maxy = maxx;
|
maxy = maxx,
|
||||||
|
extrema = curve.extrema();
|
||||||
|
|
||||||
|
noFill();
|
||||||
setStroke(`red`);
|
setStroke(`red`);
|
||||||
|
|
||||||
let extrema = curve.extrema();
|
|
||||||
|
|
||||||
[0, ...extrema.x, ...extrema.y, 1].forEach(t => {
|
[0, ...extrema.x, ...extrema.y, 1].forEach(t => {
|
||||||
let p = curve.get(t);
|
let p = curve.get(t);
|
||||||
if (p.x < minx) minx = p.x;
|
if (p.x < minx) minx = p.x;
|
@@ -10,7 +10,9 @@ If we have the extremities, and the start/end points, a simple for-loop that tes
|
|||||||
|
|
||||||
Applying this approach to our previous root finding, we get the following [axis-aligned bounding boxes](https://en.wikipedia.org/wiki/Bounding_volume#Common_types) (with all curve extremity points shown on the curve):
|
Applying this approach to our previous root finding, we get the following [axis-aligned bounding boxes](https://en.wikipedia.org/wiki/Bounding_volume#Common_types) (with all curve extremity points shown on the curve):
|
||||||
|
|
||||||
<graphics-element title="Quadratic Bézier bounding box" src="./quadratic.js"></graphics-element>
|
<div class="figure">
|
||||||
<graphics-element title="Cubic Bézier bounding box" src="./cubic.js"></graphics-element>
|
<graphics-element title="Quadratic Bézier bounding box" src="./bbox.js" data-type="quadratic"></graphics-element>
|
||||||
|
<graphics-element title="Cubic Bézier bounding box" src="./bbox.js" data-type="cubic"></graphics-element>
|
||||||
|
</div>
|
||||||
|
|
||||||
We can construct even nicer boxes by aligning them along our curve, rather than along the x- and y-axis, but in order to do so we first need to look at how aligning works.
|
We can construct even nicer boxes by aligning them along our curve, rather than along the x- and y-axis, but in order to do so we first need to look at how aligning works.
|
||||||
|
@@ -1,41 +0,0 @@
|
|||||||
setup() {
|
|
||||||
const curve = this.curve = Bezier.defaultCubic(this);
|
|
||||||
curve.points[2].x = 210;
|
|
||||||
setMovable(curve.points);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
clear();
|
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
|
||||||
curve.drawCurve();
|
|
||||||
curve.drawPoints();
|
|
||||||
|
|
||||||
noFill();
|
|
||||||
|
|
||||||
let minx = Number.MAX_SAFE_INTEGER,
|
|
||||||
miny = minx,
|
|
||||||
maxx = Number.MIN_SAFE_INTEGER,
|
|
||||||
maxy = maxx;
|
|
||||||
|
|
||||||
setStroke(`red`);
|
|
||||||
|
|
||||||
let extrema = curve.extrema();
|
|
||||||
|
|
||||||
[0, ...extrema.x, ...extrema.y, 1].forEach(t => {
|
|
||||||
let p = curve.get(t);
|
|
||||||
if (p.x < minx) minx = p.x;
|
|
||||||
if (p.x > maxx) maxx = p.x;
|
|
||||||
if (p.y < miny) miny = p.y;
|
|
||||||
if (p.y > maxy) maxy = p.y;
|
|
||||||
if (t > 0 && t< 1) circle(p.x, p.y, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
setStroke(`#0F0`);
|
|
||||||
rect(minx, miny, maxx - minx, maxy - miny);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
@@ -1,14 +1,20 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const curve = this.curve = Bezier.defaultQuadratic(this);
|
let type = getParameter(`type`, `quadratic`);
|
||||||
curve.points[2].x = 210;
|
if (type === `quadratic`) {
|
||||||
setMovable(curve.points);
|
curve = Bezier.defaultQuadratic(this);
|
||||||
|
} else {
|
||||||
|
curve = Bezier.defaultCubic(this);
|
||||||
|
curve.points[2].x = 210;
|
||||||
|
}
|
||||||
|
setMovable(curve.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
resetTransform();
|
resetTransform();
|
||||||
clear();
|
clear();
|
||||||
const dim = this.height;
|
const dim = this.height;
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
curve.drawPoints();
|
curve.drawPoints();
|
||||||
@@ -21,8 +27,9 @@ draw() {
|
|||||||
translate(40,20);
|
translate(40,20);
|
||||||
drawAxes(`t`, 0, 1, `X`, 0, dim, dim, dim);
|
drawAxes(`t`, 0, 1, `X`, 0, dim, dim, dim);
|
||||||
|
|
||||||
|
let pcount = curve.points.length;
|
||||||
new Bezier(this, curve.points.map((p,i) => ({
|
new Bezier(this, curve.points.map((p,i) => ({
|
||||||
x: (i/2) * dim,
|
x: (i/(pcount-1)) * dim,
|
||||||
y: p.x
|
y: p.x
|
||||||
}))).drawCurve();
|
}))).drawCurve();
|
||||||
|
|
||||||
@@ -36,11 +43,7 @@ draw() {
|
|||||||
drawAxes(`t`, 0,1, `Y`, 0, dim, dim, dim);
|
drawAxes(`t`, 0,1, `Y`, 0, dim, dim, dim);
|
||||||
|
|
||||||
new Bezier(this, curve.points.map((p,i) => ({
|
new Bezier(this, curve.points.map((p,i) => ({
|
||||||
x: (i/2) * dim,
|
x: (i/(pcount-1)) * dim,
|
||||||
y: p.y
|
y: p.y
|
||||||
}))).drawCurve();
|
}))).drawCurve();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
@@ -8,5 +8,6 @@ Let's look at how a parametric Bézier curve "splits up" into two normal functio
|
|||||||
|
|
||||||
If you move points in a curve sideways, you should only see the middle graph change; likewise, moving points vertically should only show a change in the right graph.
|
If you move points in a curve sideways, you should only see the middle graph change; likewise, moving points vertically should only show a change in the right graph.
|
||||||
|
|
||||||
<graphics-element title="Quadratic Bézier curve components" width="825" src="./quadratic.js"></graphics-element>
|
<graphics-element title="Quadratic Bézier curve components" width="825" src="./components.js" data-type="quadratic"></graphics-element>
|
||||||
<graphics-element title="Cubic Bézier curve components" width="825" src="./cubic.js"></graphics-element>
|
|
||||||
|
<graphics-element title="Cubic Bézier curve components" width="825" src="./components.js" data-type="cubic"></graphics-element>
|
||||||
|
@@ -1,46 +0,0 @@
|
|||||||
setup() {
|
|
||||||
const curve = this.curve = Bezier.defaultCubic(this);
|
|
||||||
curve.points[2].x = 210;
|
|
||||||
setMovable(curve.points);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
resetTransform();
|
|
||||||
clear();
|
|
||||||
const dim = this.height;
|
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
|
||||||
curve.drawCurve();
|
|
||||||
curve.drawPoints();
|
|
||||||
|
|
||||||
translate(dim, 0);
|
|
||||||
setStroke(`black`);
|
|
||||||
line(0,0,0,dim);
|
|
||||||
|
|
||||||
scale(0.8, 0.9);
|
|
||||||
translate(40,20);
|
|
||||||
drawAxes(`t`, 0, 1, `X`, 0, dim, dim, dim);
|
|
||||||
|
|
||||||
new Bezier(this, curve.points.map((p,i) => ({
|
|
||||||
x: (i/3) * dim,
|
|
||||||
y: p.x
|
|
||||||
}))).drawCurve();
|
|
||||||
|
|
||||||
resetTransform();
|
|
||||||
translate(2*dim, 0);
|
|
||||||
setStroke(`black`);
|
|
||||||
line(0,0,0,dim);
|
|
||||||
|
|
||||||
scale(0.8, 0.9);
|
|
||||||
translate(40,20);
|
|
||||||
drawAxes(`t`, 0,1, `Y`, 0, dim, dim, dim);
|
|
||||||
|
|
||||||
new Bezier(this, curve.points.map((p,i) => ({
|
|
||||||
x: (i/3) * dim,
|
|
||||||
y: p.y
|
|
||||||
}))).drawCurve();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
@@ -5,15 +5,15 @@ 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.
|
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">
|
<div class="figure">
|
||||||
<graphics-element title="Quadratic interpolations" src="./lerp-quadratic.js">
|
<graphics-element title="Quadratic interpolations" src="./lerp.js" data-degree="3">
|
||||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
|
|
||||||
<graphics-element title="Cubic interpolations" src="./lerp-cubic.js">
|
<graphics-element title="Cubic interpolations" src="./lerp.js" data-degree="4">
|
||||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
|
|
||||||
<graphics-element title="15th degree interpolations" src="./lerp-fifteenth.js">
|
<graphics-element title="15th degree interpolations" src="./lerp.js" data-degree="15">
|
||||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -5,15 +5,15 @@
|
|||||||
下のグラフは、2次ベジエ曲線や3次ベジエ曲線の補間関数を表しています。ここでSは、ベジエ関数全体に対しての、その点の寄与の大きさを示します。ある<i>t</i>において、ベジエ曲線を定義する各点の補間率がどのようになっているのか、クリックドラッグをして確かめてみてください。
|
下のグラフは、2次ベジエ曲線や3次ベジエ曲線の補間関数を表しています。ここでSは、ベジエ関数全体に対しての、その点の寄与の大きさを示します。ある<i>t</i>において、ベジエ曲線を定義する各点の補間率がどのようになっているのか、クリックドラッグをして確かめてみてください。
|
||||||
|
|
||||||
<div class="figure">
|
<div class="figure">
|
||||||
<graphics-element title="2次の補間" src="./lerp-quadratic.js">
|
<graphics-element title="2次の補間" src="./lerp.js" data-degree="3">
|
||||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
|
|
||||||
<graphics-element title="3次の補間" src="./lerp-cubic.js">
|
<graphics-element title="3次の補間" src="./lerp.js" data-degree="4">
|
||||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
|
|
||||||
<graphics-element title="15次の補間" src="./lerp-fifteenth.js">
|
<graphics-element title="15次の補間" src="./lerp.js" data-degree="15">
|
||||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -5,15 +5,15 @@
|
|||||||
下面的图形显示了二次曲线和三次曲线的差值方程,“S”代表了点对贝塞尔方程总和的贡献。点击拖动点来看看在特定的<i>t</i>值时,每个曲线定义的点的插值百分比。
|
下面的图形显示了二次曲线和三次曲线的差值方程,“S”代表了点对贝塞尔方程总和的贡献。点击拖动点来看看在特定的<i>t</i>值时,每个曲线定义的点的插值百分比。
|
||||||
|
|
||||||
<div class="figure">
|
<div class="figure">
|
||||||
<graphics-element title="二次插值" src="./lerp-quadratic.js">
|
<graphics-element title="二次插值" src="./lerp.js" data-degree="3">
|
||||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
|
|
||||||
<graphics-element title="三次插值" src="./lerp-cubic.js">
|
<graphics-element title="三次插值" src="./lerp.js" data-degree="4">
|
||||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
|
|
||||||
<graphics-element title="15次插值" src="./lerp-fifteenth.js">
|
<graphics-element title="15次插值" src="./lerp.js" data-degree="15">
|
||||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,56 +0,0 @@
|
|||||||
setup() {
|
|
||||||
const w = this.width,
|
|
||||||
h = this.height;
|
|
||||||
|
|
||||||
this.f = [
|
|
||||||
t => ({ x: t * w, y: h * (1-t) ** 3 }),
|
|
||||||
t => ({ x: t * w, y: h * 3 * (1-t) ** 2 * t }),
|
|
||||||
t => ({ x: t * w, y: h * 3 * (1-t) * t ** 2 }),
|
|
||||||
t => ({ x: t * w, y: h * t ** 3})
|
|
||||||
];
|
|
||||||
|
|
||||||
this.s = this.f.map(f => plot(f) );
|
|
||||||
setSlider(`.slide-control`, `position`, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
resetTransform();
|
|
||||||
clear();
|
|
||||||
setFill(`black`);
|
|
||||||
setStroke(`black`);
|
|
||||||
|
|
||||||
scale(0.8, 0.9);
|
|
||||||
translate(40,20);
|
|
||||||
drawAxes(`t`, 0, 1, `S`, `0%`, `100%`);
|
|
||||||
|
|
||||||
noFill();
|
|
||||||
|
|
||||||
this.s.forEach(s => {
|
|
||||||
setStroke( randomColor() );
|
|
||||||
drawShape(s);
|
|
||||||
})
|
|
||||||
|
|
||||||
this.drawHighlight();
|
|
||||||
}
|
|
||||||
|
|
||||||
drawHighlight() {
|
|
||||||
let c = screenToWorld({
|
|
||||||
x: map(this.position, 0, 1, -10, this.width + 10),
|
|
||||||
y: this.height/2
|
|
||||||
});
|
|
||||||
|
|
||||||
if (c.x < 0) return;
|
|
||||||
if (c.x > this.width) return;
|
|
||||||
|
|
||||||
noStroke();
|
|
||||||
setFill(`rgba(255,0,0,0.3)`);
|
|
||||||
rect(c.x - 2, 0, 5, this.height);
|
|
||||||
|
|
||||||
const p = this.f.map(f => f(c.x / this.width));
|
|
||||||
|
|
||||||
setFill(`black`);
|
|
||||||
p.forEach(p => {
|
|
||||||
circle(p.x, p.y, 3);
|
|
||||||
text(`${ round(100 * p.y/this.height) }%`, p.x + 10, p.y);
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,56 +0,0 @@
|
|||||||
setup() {
|
|
||||||
const w = this.width,
|
|
||||||
h = this.height;
|
|
||||||
|
|
||||||
this.f = [
|
|
||||||
t => ({ x: t * w, y: h * (1-t) ** 2 }),
|
|
||||||
t => ({ x: t * w, y: h * 2 * (1-t) * t }),
|
|
||||||
t => ({ x: t * w, y: h * t ** 2 })
|
|
||||||
];
|
|
||||||
|
|
||||||
this.s = this.f.map(f => plot(f) );
|
|
||||||
setSlider(`.slide-control`, `position`, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
resetTransform();
|
|
||||||
clear();
|
|
||||||
setFill(`black`);
|
|
||||||
setStroke(`black`);
|
|
||||||
|
|
||||||
scale(0.8, 0.9);
|
|
||||||
translate(40,20);
|
|
||||||
drawAxes(`t`, 0, 1, `S`, `0%`, `100%`);
|
|
||||||
|
|
||||||
noFill();
|
|
||||||
|
|
||||||
this.s.forEach(s => {
|
|
||||||
setStroke( randomColor() );
|
|
||||||
drawShape(s);
|
|
||||||
})
|
|
||||||
|
|
||||||
this.drawHighlight();
|
|
||||||
}
|
|
||||||
|
|
||||||
drawHighlight() {
|
|
||||||
let c = screenToWorld({
|
|
||||||
x: map(this.position, 0, 1, -10, this.width + 10),
|
|
||||||
y: this.height/2
|
|
||||||
});
|
|
||||||
|
|
||||||
if (c.x < 0) return;
|
|
||||||
if (c.x > this.width) return;
|
|
||||||
|
|
||||||
noStroke();
|
|
||||||
setFill(`rgba(255,0,0,0.3)`);
|
|
||||||
rect(c.x - 2, 0, 5, this.height);
|
|
||||||
|
|
||||||
const p = this.f.map(f => f(c.x / this.width));
|
|
||||||
|
|
||||||
setFill(`black`);
|
|
||||||
p.forEach(p => {
|
|
||||||
circle(p.x, p.y, 3);
|
|
||||||
text(`${ round(100 * p.y/this.height) }%`, p.x + 10, p.y);
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,7 +1,33 @@
|
|||||||
setup() {
|
setup() {
|
||||||
this.degree = 15;
|
const w = this.width,
|
||||||
this.triangle = [[1], [1,1]];
|
h = this.height;
|
||||||
this.generate();
|
|
||||||
|
const degree = this.getParameter(`degree`, 3);
|
||||||
|
|
||||||
|
if (degree === 3) {
|
||||||
|
this.f = [
|
||||||
|
t => ({ x: t * w, y: h * (1-t) ** 2 }),
|
||||||
|
t => ({ x: t * w, y: h * 2 * (1-t) * t }),
|
||||||
|
t => ({ x: t * w, y: h * t ** 2 })
|
||||||
|
];
|
||||||
|
} else if (degree === 4) {
|
||||||
|
this.f = [
|
||||||
|
t => ({ x: t * w, y: h * (1-t) ** 3 }),
|
||||||
|
t => ({ x: t * w, y: h * 3 * (1-t) ** 2 * t }),
|
||||||
|
t => ({ x: t * w, y: h * 3 * (1-t) * t ** 2 }),
|
||||||
|
t => ({ x: t * w, y: h * t ** 3})
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
this.triangle = [[1], [1,1]];
|
||||||
|
this.f = [...new Array(degree + 1)].map((_,i) => {
|
||||||
|
return t => ({
|
||||||
|
x: t * w,
|
||||||
|
y: h * this.binomial(degree,i) * (1-t) ** (degree-i) * t ** (i)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.s = this.f.map(f => plot(f, 0, 1, degree*4) );
|
||||||
setSlider(`.slide-control`, `position`, 0)
|
setSlider(`.slide-control`, `position`, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,21 +43,6 @@ binomial(n,k) {
|
|||||||
return this.triangle[n][k];
|
return this.triangle[n][k];
|
||||||
}
|
}
|
||||||
|
|
||||||
generate() {
|
|
||||||
const w = this.width,
|
|
||||||
h = this.height,
|
|
||||||
d = this.degree;
|
|
||||||
|
|
||||||
this.f = [...new Array(d+1)].map((_,i) => {
|
|
||||||
return t => ({
|
|
||||||
x: t * w,
|
|
||||||
y: h * this.binomial(d,i) * (1-t) ** (d-i) * t ** (i)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.s = this.f.map(f => plot(f, 0, 1, d*4) );
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
resetTransform();
|
resetTransform();
|
||||||
clear();
|
clear();
|
@@ -6,7 +6,7 @@ Problem solved!
|
|||||||
|
|
||||||
But, if we think about this a little more, this cannot possible work, because of something that you may have noticed in the section on [reordering curves](#reordering): what a curve looks like, and the function that draws that curve, are not in some kind of universal, fixed, one-to-one relation. If we have some quadratic curve, then simply by raising the curve order we can get corresponding cubic, quartic, and higher and higher mathematical expressions that all draw the _exact same curve_ but with wildly different derivatives. So: if we want to make a transition from one curve to the next look good, and we want to use the derivative, then we suddenly need to answer the question: "Which derivative?".
|
But, if we think about this a little more, this cannot possible work, because of something that you may have noticed in the section on [reordering curves](#reordering): what a curve looks like, and the function that draws that curve, are not in some kind of universal, fixed, one-to-one relation. If we have some quadratic curve, then simply by raising the curve order we can get corresponding cubic, quartic, and higher and higher mathematical expressions that all draw the _exact same curve_ but with wildly different derivatives. So: if we want to make a transition from one curve to the next look good, and we want to use the derivative, then we suddenly need to answer the question: "Which derivative?".
|
||||||
|
|
||||||
How would you even decide? What makes the cubic derivatives better or less suited than, say, quintic derivatives? Wouldn't it be nicer if we could use something that was inherent to the curve, without being tied to the functions that yield that curve? And (of course) as it turns out, there is a way to define curvature in such a way that it only relies on what the curve actually looks like, and given where this section is in the larger body of this Primer, it should hopefully not be surprising that thee thing we can use to define curvature is the thing we talked about in the previous section: arc length.
|
How would you even decide? What makes the cubic derivatives better or less suited than, say, quintic derivatives? Wouldn't it be nicer if we could use something that was inherent to the curve, without being tied to the functions that yield that curve? And (of course) as it turns out, there is a way to define curvature in such a way that it only relies on what the curve actually looks like, and given where this section is in the larger body of this Primer, it should hopefully not be surprising that the thing we can use to define curvature is the thing we talked about in the previous section: arc length.
|
||||||
|
|
||||||
Intuitively, this should make sense, even if we have no idea what the maths would look like: if we travel some fixed distance along some curve, then the point at that distance is simply the point at that distance. It doesn't matter what function we used to draw the curve: once we know what the curve looks like, the function(s) used to draw it become irrelevant: a point a third along the full distance of the curve is simply the point a third along the distance of the curve.
|
Intuitively, this should make sense, even if we have no idea what the maths would look like: if we travel some fixed distance along some curve, then the point at that distance is simply the point at that distance. It doesn't matter what function we used to draw the curve: once we know what the curve looks like, the function(s) used to draw it become irrelevant: a point a third along the full distance of the curve is simply the point a third along the distance of the curve.
|
||||||
|
|
||||||
|
@@ -22,7 +22,9 @@ In the case of Bézier curves, extending the interval simply makes our curve "ke
|
|||||||
|
|
||||||
The following two graphics show you Bézier curves rendered "the usual way", as well as the curves they "lie on" if we were to extend the `t` values much further. As you can see, there's a lot more "shape" hidden in the rest of the curve, and we can model those parts by moving the curve points around.
|
The following two graphics show you Bézier curves rendered "the usual way", as well as the curves they "lie on" if we were to extend the `t` values much further. As you can see, there's a lot more "shape" hidden in the rest of the curve, and we can model those parts by moving the curve points around.
|
||||||
|
|
||||||
<graphics-element title="Quadratic infinite interval Bézier curve" src="./quadratic.js"></graphics-element>
|
<div class="figure">
|
||||||
<graphics-element title="Cubic infinite interval Bézier curve" src="./cubic.js"></graphics-element>
|
<graphics-element title="Quadratic infinite interval Bézier curve" src="./extended.js" data-type="quadratic"></graphics-element>
|
||||||
|
<graphics-element title="Cubic infinite interval Bézier curve" src="./extended.js" data-type="cubic"></graphics-element>
|
||||||
|
</div>
|
||||||
|
|
||||||
In fact, there are curves used in graphics design and computer modelling that do the opposite of Bézier curves; rather than fixing the interval, and giving you freedom to choose the coordinates, they fix the coordinates, but give you freedom over the interval. A great example of this is the ["Spiro" curve](http://levien.com/phd/phd.html), which is a curve based on part of a [Cornu Spiral, also known as Euler's Spiral](https://en.wikipedia.org/wiki/Euler_spiral). It's a very aesthetically pleasing curve and you'll find it in quite a few graphics packages like [FontForge](https://fontforge.github.io) and [Inkscape](https://inkscape.org). It has even been used in font design, for example for the Inconsolata typeface.
|
In fact, there are curves used in graphics design and computer modelling that do the opposite of Bézier curves; rather than fixing the interval, and giving you freedom to choose the coordinates, they fix the coordinates, but give you freedom over the interval. A great example of this is the ["Spiro" curve](http://levien.com/phd/phd.html), which is a curve based on part of a [Cornu Spiral, also known as Euler's Spiral](https://en.wikipedia.org/wiki/Euler_spiral). It's a very aesthetically pleasing curve and you'll find it in quite a few graphics packages like [FontForge](https://fontforge.github.io) and [Inkscape](https://inkscape.org). It has even been used in font design, for example for the Inconsolata typeface.
|
||||||
|
@@ -22,7 +22,9 @@
|
|||||||
|
|
||||||
下の2つの図は「いつもの方法」で描いたベジエ曲線ですが、これと一緒に、`t`の値をずっと先まで広げた場合の「延びた」ベジエ曲線も表示しています。見てわかるように、曲線の残りの部分には多くの「かたち」が隠れています。そして曲線の点を動かせば、その部分の形状も変わります。
|
下の2つの図は「いつもの方法」で描いたベジエ曲線ですが、これと一緒に、`t`の値をずっと先まで広げた場合の「延びた」ベジエ曲線も表示しています。見てわかるように、曲線の残りの部分には多くの「かたち」が隠れています。そして曲線の点を動かせば、その部分の形状も変わります。
|
||||||
|
|
||||||
<graphics-element title="無限区間の2次ベジエ曲線" src="./quadratic.js"></graphics-element>
|
<div class="figure">
|
||||||
<graphics-element title="無限区間の3次ベジエ曲線" src="./cubic.js"></graphics-element>
|
<graphics-element title="無限区間の2次ベジエ曲線" src="./extended.js" data-type="quadratic"></graphics-element>
|
||||||
|
<graphics-element title="無限区間の3次ベジエ曲線" src="./extended.js" data-type="cubic"></graphics-element>
|
||||||
|
</div>
|
||||||
|
|
||||||
実際に、グラフィックデザインやコンピュータモデリングで使われている曲線の中には、座標が固定されていて、区間は自由に動かせるような曲線があります。これは、区間が固定されていて、座標を自由に動かすことのできるベジエ曲線とは反対になっています。すばらしい例が[「Spiro」曲線](http://levien.com/phd/phd.html)で、これは[オイラー螺旋とも呼ばれるクロソイド曲線](https://ja.wikipedia.org/wiki/クロソイド曲線)の一部分に基づいた曲線です。非常に美しく心地よい曲線で、[FontForge](https://fontforge.github.io)や[Inkscape](https://inkscape.org/ja/)など多くのグラフィックアプリに実装されており、フォントデザインにも利用されています(Inconsolataフォントなど)。
|
実際に、グラフィックデザインやコンピュータモデリングで使われている曲線の中には、座標が固定されていて、区間は自由に動かせるような曲線があります。これは、区間が固定されていて、座標を自由に動かすことのできるベジエ曲線とは反対になっています。すばらしい例が[「Spiro」曲線](http://levien.com/phd/phd.html)で、これは[オイラー螺旋とも呼ばれるクロソイド曲線](https://ja.wikipedia.org/wiki/クロソイド曲線)の一部分に基づいた曲線です。非常に美しく心地よい曲線で、[FontForge](https://fontforge.github.io)や[Inkscape](https://inkscape.org/ja/)など多くのグラフィックアプリに実装されており、フォントデザインにも利用されています(Inconsolataフォントなど)。
|
||||||
|
@@ -22,7 +22,9 @@
|
|||||||
|
|
||||||
下面两个图形给你展示了以“普通方式”来渲染的贝塞尔曲线,以及如果我们扩大`t`值时它们所“位于”的曲线。如你所见,曲线的剩余部分隐藏了很多“形状”,我们可以通过移动曲线的点来建模这部分。
|
下面两个图形给你展示了以“普通方式”来渲染的贝塞尔曲线,以及如果我们扩大`t`值时它们所“位于”的曲线。如你所见,曲线的剩余部分隐藏了很多“形状”,我们可以通过移动曲线的点来建模这部分。
|
||||||
|
|
||||||
<graphics-element title="二次无限区间贝塞尔曲线" src="./quadratic.js"></graphics-element>
|
<div class="figure">
|
||||||
<graphics-element title="三次无限区间贝塞尔曲线" src="./cubic.js"></graphics-element>
|
<graphics-element title="二次无限区间贝塞尔曲线" src="./extended.js" data-type="quadratic"></graphics-element>
|
||||||
|
<graphics-element title="三次无限区间贝塞尔曲线" src="./extended.js" data-type="cubic"></graphics-element>
|
||||||
|
</div>
|
||||||
|
|
||||||
实际上,图形设计和计算机建模中还用了一些和贝塞尔曲线相反的曲线,这些曲线没有固定区间和自由的坐标,相反,它们固定座标但给你自由的区间。["Spiro"曲线](http://levien.com/phd/phd.html)就是一个很好的例子,它的构造是基于[羊角螺线,也就是欧拉螺线](https://zh.wikipedia.org/wiki/%E7%BE%8A%E8%A7%92%E8%9E%BA%E7%BA%BF)的一部分。这是在美学上很令人满意的曲线,你可以在一些图形包中看到它,比如[FontForge](https://fontforge.github.io)和[Inkscape](https://inkscape.org),它也被用在一些字体设计中(比如Inconsolata字体)。
|
实际上,图形设计和计算机建模中还用了一些和贝塞尔曲线相反的曲线,这些曲线没有固定区间和自由的坐标,相反,它们固定座标但给你自由的区间。["Spiro"曲线](http://levien.com/phd/phd.html)就是一个很好的例子,它的构造是基于[羊角螺线,也就是欧拉螺线](https://zh.wikipedia.org/wiki/%E7%BE%8A%E8%A7%92%E8%9E%BA%E7%BA%BF)的一部分。这是在美学上很令人满意的曲线,你可以在一些图形包中看到它,比如[FontForge](https://fontforge.github.io)和[Inkscape](https://inkscape.org),它也被用在一些字体设计中(比如Inconsolata字体)。
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = Bezier.defaultCubic(this);
|
const type = this.getParameter(`type`, `quadratic`);
|
||||||
setMovable(this.curve.points);
|
curve = (type === `quadratic`) ? Bezier.defaultQuadratic(this) : Bezier.defaultCubic(this);
|
||||||
|
setMovable(curve.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
const curve = this.curve;
|
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
@@ -1,32 +0,0 @@
|
|||||||
setup() {
|
|
||||||
this.curve = Bezier.defaultQuadratic(this);
|
|
||||||
setMovable(this.curve.points);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
const curve = this.curve;
|
|
||||||
|
|
||||||
clear();
|
|
||||||
curve.drawCurve();
|
|
||||||
curve.drawSkeleton();
|
|
||||||
|
|
||||||
let step=0.05, min=-10, max=10;
|
|
||||||
let pt = curve.get(min - step), pn;
|
|
||||||
|
|
||||||
setStroke(`skyblue`);
|
|
||||||
|
|
||||||
for (let t=min; t<=step; t+=step) {
|
|
||||||
pn = curve.get(t);
|
|
||||||
line(pt.x, pt.y, pn.x, pn.y);
|
|
||||||
pt = pn;
|
|
||||||
}
|
|
||||||
|
|
||||||
pt = curve.get(1);
|
|
||||||
for (let t=1+step; t<=max; t+=step) {
|
|
||||||
pn = curve.get(t);
|
|
||||||
line(pt.x, pt.y, pn.x, pn.y);
|
|
||||||
pt = pn;
|
|
||||||
}
|
|
||||||
|
|
||||||
curve.drawPoints();
|
|
||||||
}
|
|
@@ -214,8 +214,8 @@ As it turns out, Newton-Raphson is so blindingly fast that we could get away wit
|
|||||||
|
|
||||||
So now that we know how to do root finding, we can determine the first and second derivative roots for our Bézier curves, and show those roots overlaid on the previous graphics. For the quadratic curve, that means just the first derivative, in red:
|
So now that we know how to do root finding, we can determine the first and second derivative roots for our Bézier curves, and show those roots overlaid on the previous graphics. For the quadratic curve, that means just the first derivative, in red:
|
||||||
|
|
||||||
<graphics-element title="Quadratic Bézier curve extremities" width="825" src="./quadratic.js"></graphics-element>
|
<graphics-element title="Quadratic Bézier curve extremities" width="825" src="./extremities.js" data-type="quadratic"></graphics-element>
|
||||||
|
|
||||||
And for cubic curves, that means first and second derivatives, in red and purple respectively:
|
And for cubic curves, that means first and second derivatives, in red and purple respectively:
|
||||||
|
|
||||||
<graphics-element title="Cubic Bézier curve extremities" width="825" src="./cubic.js"></graphics-element>
|
<graphics-element title="Cubic Bézier curve extremities" width="825" src="./extremities.js" data-type="cubic"></graphics-element>
|
||||||
|
@@ -1,122 +0,0 @@
|
|||||||
setup() {
|
|
||||||
const curve = this.curve = Bezier.defaultCubic(this);
|
|
||||||
curve.points[2].x = 210;
|
|
||||||
setMovable(curve.points);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
resetTransform();
|
|
||||||
clear();
|
|
||||||
const dim = this.height;
|
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
|
||||||
curve.drawCurve();
|
|
||||||
curve.drawPoints();
|
|
||||||
|
|
||||||
translate(dim, 0);
|
|
||||||
setStroke(`black`);
|
|
||||||
line(0,0,0,dim);
|
|
||||||
|
|
||||||
scale(0.8, 0.9);
|
|
||||||
translate(40,20);
|
|
||||||
drawAxes(`t`, 0, 1, `X`, 0, dim, dim, dim);
|
|
||||||
|
|
||||||
this.plotDimension(dim, new Bezier(this, curve.points.map((p,i) => ({
|
|
||||||
x: (i/3) * dim,
|
|
||||||
y: p.x
|
|
||||||
}))));
|
|
||||||
|
|
||||||
resetTransform();
|
|
||||||
translate(2*dim, 0);
|
|
||||||
setStroke(`black`);
|
|
||||||
line(0,0,0,dim);
|
|
||||||
|
|
||||||
scale(0.8, 0.9);
|
|
||||||
translate(40,20);
|
|
||||||
drawAxes(`t`, 0,1, `Y`, 0, dim, dim, dim);
|
|
||||||
|
|
||||||
this.plotDimension(dim, new Bezier(this, curve.points.map((p,i) => ({
|
|
||||||
x: (i/3) * dim,
|
|
||||||
y: p.y
|
|
||||||
}))));
|
|
||||||
}
|
|
||||||
|
|
||||||
plotDimension(dim, dimension) {
|
|
||||||
cacheStyle();
|
|
||||||
dimension.drawCurve();
|
|
||||||
|
|
||||||
setFill(`red`);
|
|
||||||
setStroke(`red`);
|
|
||||||
|
|
||||||
// There are four possible extrema: t=0, t=1, and
|
|
||||||
// up to two t values that solves B'(t)=0, provided
|
|
||||||
// that they lie between 0 and 1. But of those four,
|
|
||||||
// only two will be real extrema (one minimum value,
|
|
||||||
// and one maximum value)
|
|
||||||
|
|
||||||
// First we compute the "simple" cases:
|
|
||||||
let t1 = 0; let y1 = dimension.get(t1).y;
|
|
||||||
let t2 = 1; let y2 = dimension.get(t2).y;
|
|
||||||
|
|
||||||
// We assume y1 < y2, but is that actually true?
|
|
||||||
let reverse = (y2 < y1);
|
|
||||||
|
|
||||||
// Are there a solution for B'(t) = 0?
|
|
||||||
let roots = this.getRoots(...dimension.dpoints[0].map(p => p.y));
|
|
||||||
|
|
||||||
roots.forEach(t =>{
|
|
||||||
// Is that solution a value in [0,1]?
|
|
||||||
if (t > 0 && t < 1) {
|
|
||||||
// It is, so we have either a new minimum value
|
|
||||||
// or new maximum value:
|
|
||||||
let dp = dimension.get(t);
|
|
||||||
if (reverse) {
|
|
||||||
if (dp.y < y2) { t2 = t; y2 = dp.y; }
|
|
||||||
if (dp.y > y1) { t1 = t; y1 = dp.y; }
|
|
||||||
} else {
|
|
||||||
if (dp.y < y1) { t1 = t; y1 = dp.y; }
|
|
||||||
if (dp.y > y2) { t2 = t; y2 = dp.y; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Done, show our derivative-based extrema:
|
|
||||||
circle(t1 * dim, y1, 3);
|
|
||||||
text(`t = ${t1.toFixed(2)}`, map(t1, 0,1, 15,dim-15), y1 + 25);
|
|
||||||
circle(t2 * dim, y2, 3);
|
|
||||||
text(`t = ${t2.toFixed(2)}`, map(t2, 0,1, 15,dim-15), y2 + 25);
|
|
||||||
|
|
||||||
// And then show the second derivate inflection, if there is one
|
|
||||||
setFill(`purple`);
|
|
||||||
setStroke(`purple`);
|
|
||||||
this.getRoots(...dimension.dpoints[1].map(p => p.y)).forEach(t =>{
|
|
||||||
if (t > 0 && t < 1) {
|
|
||||||
let d = dimension.get(t);
|
|
||||||
circle(t * dim, d.y, 3);
|
|
||||||
text(`t = ${t.toFixed(2)}`, map(t, 0,1, 15,dim-15), d.y + 25);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
restoreStyle();
|
|
||||||
}
|
|
||||||
|
|
||||||
getRoots(v1, v2, v3) {
|
|
||||||
if (v3 === undefined) {
|
|
||||||
return [-v1 / (v2 - v1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
const a = v1 - 2*v2 + v3,
|
|
||||||
b = 2 * (v2 - v1),
|
|
||||||
c = v1,
|
|
||||||
d = b*b - 4*a*c;
|
|
||||||
if (a === 0) return [];
|
|
||||||
if (d < 0) return [];
|
|
||||||
const f = -b / (2*a);
|
|
||||||
if (d === 0) return [f]
|
|
||||||
const l = sqrt(d) / (2*a);
|
|
||||||
return [f-l, f+l];
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
162
docs/chapters/extremities/extremities.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const type = this.getParameter(`type`, `quadratic`);
|
||||||
|
if (type === `quadratic`) {
|
||||||
|
curve = Bezier.defaultQuadratic(this);
|
||||||
|
} else {
|
||||||
|
curve = Bezier.defaultCubic(this);
|
||||||
|
curve.points[2].x = 210;
|
||||||
|
}
|
||||||
|
setMovable(curve.points);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
resetTransform();
|
||||||
|
clear();
|
||||||
|
const dim = this.height;
|
||||||
|
const degree = curve.points.length - 1;
|
||||||
|
curve.drawSkeleton();
|
||||||
|
curve.drawCurve();
|
||||||
|
curve.drawPoints();
|
||||||
|
|
||||||
|
translate(dim, 0);
|
||||||
|
setStroke(`black`);
|
||||||
|
line(0,0,0,dim);
|
||||||
|
|
||||||
|
scale(0.8, 0.9);
|
||||||
|
translate(40,20);
|
||||||
|
drawAxes(`t`, 0, 1, `X`, 0, dim, dim, dim);
|
||||||
|
|
||||||
|
this.plotDimension(dim, new Bezier(this, curve.points.map((p,i) => ({
|
||||||
|
x: (i/degree) * dim,
|
||||||
|
y: p.x
|
||||||
|
}))));
|
||||||
|
|
||||||
|
resetTransform();
|
||||||
|
translate(2*dim, 0);
|
||||||
|
setStroke(`black`);
|
||||||
|
line(0,0,0,dim);
|
||||||
|
|
||||||
|
scale(0.8, 0.9);
|
||||||
|
translate(40,20);
|
||||||
|
drawAxes(`t`, 0,1, `Y`, 0, dim, dim, dim);
|
||||||
|
|
||||||
|
this.plotDimension(dim, new Bezier(this, curve.points.map((p,i) => ({
|
||||||
|
x: (i/degree) * dim,
|
||||||
|
y: p.y
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
|
||||||
|
plotDimension(dim, dimension) {
|
||||||
|
cacheStyle();
|
||||||
|
dimension.drawCurve();
|
||||||
|
|
||||||
|
setFill(`red`);
|
||||||
|
setStroke(`red`);
|
||||||
|
|
||||||
|
// There are three possible extrema: t=0, t=1, and
|
||||||
|
// the t value that solves B'(t)=0, provided that
|
||||||
|
// value is between 0 and 1. But of those three,
|
||||||
|
// only two will be real extrema (one minimum value,
|
||||||
|
// and one maximum value)
|
||||||
|
|
||||||
|
// First we compute the "simple" cases:
|
||||||
|
let t1 = 0; let y1 = dimension.get(t1).y;
|
||||||
|
let t2 = 1; let y2 = dimension.get(t2).y;
|
||||||
|
|
||||||
|
// We assume y1 < y2, but is that actually true?
|
||||||
|
let reverse = (y2 < y1);
|
||||||
|
|
||||||
|
if (curve.points.length === 3) {
|
||||||
|
this.plotQuadraticDimension(t1, y1, t2, y2, dim, dimension, reverse);
|
||||||
|
} else {
|
||||||
|
this.plotCubicDimension(t1, y1, t2, y2, dim, dimension, reverse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plotQuadraticDimension(t1, y1, t2, y2, dim, dimension, reverse) {
|
||||||
|
|
||||||
|
// Is there a solution for B'(t) = 0?
|
||||||
|
let dpoints = dimension.dpoints[0];
|
||||||
|
let t3 = -dpoints[0].y / (dpoints[1].y - dpoints[0].y);
|
||||||
|
|
||||||
|
// Is that solution a value in [0,1]?
|
||||||
|
if (t3 > 0 && t3 < 1) {
|
||||||
|
// It is, so we have either a new minimum value
|
||||||
|
// or new maximum value:
|
||||||
|
let dp = dimension.get(t3);
|
||||||
|
if (reverse) {
|
||||||
|
if (dp.y < y2) { t2 = t3; y2 = dp.y; }
|
||||||
|
if (dp.y > y1) { t1 = t3; y1 = dp.y; }
|
||||||
|
} else {
|
||||||
|
if (dp.y < y1) { t1 = t3; y1 = dp.y; }
|
||||||
|
if (dp.y > y2) { t2 = t3; y2 = dp.y; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done, show our extrema:
|
||||||
|
circle(t1 * dim, y1, 3);
|
||||||
|
text(`t = ${t1.toFixed(2)}`, map(t1, 0,1, 15,dim-15), y1 + 25);
|
||||||
|
circle(t2 * dim, y2, 3);
|
||||||
|
text(`t = ${t2.toFixed(2)}`, map(t2, 0,1, 15,dim-15), y2 + 25);
|
||||||
|
restoreStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
plotCubicDimension(t1, y1, t2, y2, dim, dimension, reverse) {
|
||||||
|
// Are there a solution for B'(t) = 0?
|
||||||
|
let roots = this.getRoots(...dimension.dpoints[0].map(p => p.y));
|
||||||
|
|
||||||
|
roots.forEach(t => {
|
||||||
|
// Is that solution a value in [0,1]?
|
||||||
|
if (t > 0 && t < 1) {
|
||||||
|
// It is, so we have either a new minimum value
|
||||||
|
// or new maximum value:
|
||||||
|
let dp = dimension.get(t);
|
||||||
|
if (reverse) {
|
||||||
|
if (dp.y < y2) { t2 = t; y2 = dp.y; }
|
||||||
|
if (dp.y > y1) { t1 = t; y1 = dp.y; }
|
||||||
|
} else {
|
||||||
|
if (dp.y < y1) { t1 = t; y1 = dp.y; }
|
||||||
|
if (dp.y > y2) { t2 = t; y2 = dp.y; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Done, show our derivative-based extrema:
|
||||||
|
circle(t1 * dim, y1, 3);
|
||||||
|
text(`t = ${t1.toFixed(2)}`, map(t1, 0,1, 15,dim-15), y1 + 25);
|
||||||
|
circle(t2 * dim, y2, 3);
|
||||||
|
text(`t = ${t2.toFixed(2)}`, map(t2, 0,1, 15,dim-15), y2 + 25);
|
||||||
|
|
||||||
|
// And then show the second derivate inflection, if there is one
|
||||||
|
setFill(`purple`);
|
||||||
|
setStroke(`purple`);
|
||||||
|
this.getRoots(...dimension.dpoints[1].map(p => p.y)).forEach(t =>{
|
||||||
|
if (t > 0 && t < 1) {
|
||||||
|
let d = dimension.get(t);
|
||||||
|
circle(t * dim, d.y, 3);
|
||||||
|
text(`t = ${t.toFixed(2)}`, map(t, 0,1, 15,dim-15), d.y + 25);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
restoreStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoots(v1, v2, v3) {
|
||||||
|
if (v3 === undefined) {
|
||||||
|
return [-v1 / (v2 - v1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = v1 - 2*v2 + v3,
|
||||||
|
b = 2 * (v2 - v1),
|
||||||
|
c = v1,
|
||||||
|
d = b*b - 4*a*c;
|
||||||
|
if (a === 0) return [];
|
||||||
|
if (d < 0) return [];
|
||||||
|
const f = -b / (2*a);
|
||||||
|
if (d === 0) return [f]
|
||||||
|
const l = sqrt(d) / (2*a);
|
||||||
|
return [f-l, f+l];
|
||||||
|
}
|
@@ -1,87 +0,0 @@
|
|||||||
setup() {
|
|
||||||
this.curve = Bezier.defaultQuadratic(this);
|
|
||||||
setMovable(this.curve.points);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
resetTransform();
|
|
||||||
clear();
|
|
||||||
const dim = this.height;
|
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
|
||||||
curve.drawCurve();
|
|
||||||
curve.drawPoints();
|
|
||||||
|
|
||||||
translate(dim, 0);
|
|
||||||
setStroke(`black`);
|
|
||||||
line(0,0,0,dim);
|
|
||||||
|
|
||||||
scale(0.8, 0.9);
|
|
||||||
translate(40,20);
|
|
||||||
drawAxes(`t`, 0, 1, `X`, 0, dim, dim, dim);
|
|
||||||
|
|
||||||
this.plotDimension(dim, new Bezier(this, curve.points.map((p,i) => ({
|
|
||||||
x: (i/2) * dim,
|
|
||||||
y: p.x
|
|
||||||
}))));
|
|
||||||
|
|
||||||
resetTransform();
|
|
||||||
translate(2*dim, 0);
|
|
||||||
setStroke(`black`);
|
|
||||||
line(0,0,0,dim);
|
|
||||||
|
|
||||||
scale(0.8, 0.9);
|
|
||||||
translate(40,20);
|
|
||||||
drawAxes(`t`, 0,1, `Y`, 0, dim, dim, dim);
|
|
||||||
|
|
||||||
this.plotDimension(dim, new Bezier(this, curve.points.map((p,i) => ({
|
|
||||||
x: (i/2) * dim,
|
|
||||||
y: p.y
|
|
||||||
}))))
|
|
||||||
}
|
|
||||||
|
|
||||||
plotDimension(dim, dimension) {
|
|
||||||
cacheStyle();
|
|
||||||
dimension.drawCurve();
|
|
||||||
|
|
||||||
setFill(`red`);
|
|
||||||
setStroke(`red)`);
|
|
||||||
|
|
||||||
// There are three possible extrema: t=0, t=1, and
|
|
||||||
// the t value that solves B'(t)=0, provided that
|
|
||||||
// value is between 0 and 1. But of those three,
|
|
||||||
// only two will be real extrema (one minimum value,
|
|
||||||
// and one maximum value)
|
|
||||||
|
|
||||||
// First we compute the "simple" cases:
|
|
||||||
let t1 = 0; let y1 = dimension.get(t1).y;
|
|
||||||
let t2 = 1; let y2 = dimension.get(t2).y;
|
|
||||||
|
|
||||||
// We assume y1 < y2, but is that actually true?
|
|
||||||
let reverse = (y2 < y1);
|
|
||||||
|
|
||||||
// Is there a solution for B'(t) = 0?
|
|
||||||
let dpoints = dimension.dpoints[0];
|
|
||||||
let t3 = -dpoints[0].y / (dpoints[1].y - dpoints[0].y);
|
|
||||||
|
|
||||||
// Is that solution a value in [0,1]?
|
|
||||||
if (t3 > 0 && t3 < 1) {
|
|
||||||
// It is, so we have either a new minimum value
|
|
||||||
// or new maximum value:
|
|
||||||
let dp = dimension.get(t3);
|
|
||||||
if (reverse) {
|
|
||||||
if (dp.y < y2) { t2 = t3; y2 = dp.y; }
|
|
||||||
if (dp.y > y1) { t1 = t3; y1 = dp.y; }
|
|
||||||
} else {
|
|
||||||
if (dp.y < y1) { t1 = t3; y1 = dp.y; }
|
|
||||||
if (dp.y > y2) { t2 = t3; y2 = dp.y; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done, show our extrema:
|
|
||||||
circle(t1 * dim, y1, 3);
|
|
||||||
text(`t = ${t1.toFixed(2)}`, map(t1, 0,1, 15,dim-15), y1 + 25);
|
|
||||||
circle(t2 * dim, y2, 3);
|
|
||||||
text(`t = ${t2.toFixed(2)}`, map(t2, 0,1, 15,dim-15), y2 + 25);
|
|
||||||
restoreStyle();
|
|
||||||
}
|
|
@@ -5,11 +5,11 @@ 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.
|
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.
|
||||||
|
|
||||||
<div class="figure">
|
<div class="figure">
|
||||||
<graphics-element title="Flattening a quadratic curve" src="./quadratic.js">
|
<graphics-element title="Flattening a quadratic curve" src="./flatten.js" data-type="quadratic">
|
||||||
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
|
|
||||||
<graphics-element title="Flattening a cubic curve" src="./cubic.js">
|
<graphics-element title="Flattening a cubic curve" src="./flatten.js" data-type="cubic">
|
||||||
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -5,11 +5,11 @@
|
|||||||
例えば「X個の線分がほしい」場合には、分割数がそうなるようにサンプリング間隔を選び、曲線をサンプリングします。この方法の利点は速さです。曲線の座標を100個だの1000個だの計算するのではなく、ずっと少ない回数のサンプリングでも、十分きれいに見えるような曲線を作ることができるのです。欠点はもちろん、「本物の曲線」に比べて精度が損なわれてしまうことです。したがって、交点の検出や曲線の位置揃えを正しく行いたい場合には、平坦化した曲線は普通利用できません。
|
例えば「X個の線分がほしい」場合には、分割数がそうなるようにサンプリング間隔を選び、曲線をサンプリングします。この方法の利点は速さです。曲線の座標を100個だの1000個だの計算するのではなく、ずっと少ない回数のサンプリングでも、十分きれいに見えるような曲線を作ることができるのです。欠点はもちろん、「本物の曲線」に比べて精度が損なわれてしまうことです。したがって、交点の検出や曲線の位置揃えを正しく行いたい場合には、平坦化した曲線は普通利用できません。
|
||||||
|
|
||||||
<div class="figure">
|
<div class="figure">
|
||||||
<graphics-element title="2次ベジエ曲線の平坦化" src="./quadratic.js">
|
<graphics-element title="2次ベジエ曲線の平坦化" src="./flatten.js" data-type="quadratic">
|
||||||
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
|
|
||||||
<graphics-element title="3次ベジエ曲線の平坦化" src="./cubic.js">
|
<graphics-element title="3次ベジエ曲線の平坦化" src="./flatten.js" data-type="cubic">
|
||||||
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -5,11 +5,11 @@
|
|||||||
我们可以先确定“想要X个分段”,然后在间隔的地方采样曲线,得到一定数量的分段。这种方法的优点是速度很快:比起遍历100甚至1000个曲线坐标,我们可以采样比较少的点,仍然得到看起来足够好的曲线。这么做的缺点是,我们失去了“真正的曲线”的精度,因此不能用此方法来做真实的相交检测或曲率对齐。
|
我们可以先确定“想要X个分段”,然后在间隔的地方采样曲线,得到一定数量的分段。这种方法的优点是速度很快:比起遍历100甚至1000个曲线坐标,我们可以采样比较少的点,仍然得到看起来足够好的曲线。这么做的缺点是,我们失去了“真正的曲线”的精度,因此不能用此方法来做真实的相交检测或曲率对齐。
|
||||||
|
|
||||||
<div class="figure">
|
<div class="figure">
|
||||||
<graphics-element title="拉平一条二次曲线" src="./quadratic.js">
|
<graphics-element title="拉平一条二次曲线" src="./flatten.js" data-type="quadratic">
|
||||||
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
<input type="range" min="1" max="16" step="1" value="4" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
|
|
||||||
<graphics-element title="拉平一条三次曲线" src="./cubic.js">
|
<graphics-element title="拉平一条三次曲线" src="./flatten.js" data-type="cubic">
|
||||||
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
<input type="range" min="1" max="24" step="1" value="8" class="slide-control">
|
||||||
</graphics-element>
|
</graphics-element>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
setup() {
|
|
||||||
this.curve = Bezier.defaultCubic(this);
|
|
||||||
setMovable(this.curve.points);
|
|
||||||
setSlider(`.slide-control`, `steps`, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
clear();
|
|
||||||
|
|
||||||
this.curve.drawSkeleton();
|
|
||||||
|
|
||||||
noFill();
|
|
||||||
start();
|
|
||||||
for(let i=0, e=this.steps; i<=e; i++) {
|
|
||||||
let p = this.curve.get(i/e);
|
|
||||||
vertex(p.x, p.y);
|
|
||||||
}
|
|
||||||
end();
|
|
||||||
|
|
||||||
this.curve.drawPoints();
|
|
||||||
|
|
||||||
setFill(`black`);
|
|
||||||
text(`Flattened to ${this.steps} segments`, 10, 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
27
docs/chapters/flattening/flatten.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const type = getParameter(`type`, `quadratic`);
|
||||||
|
curve = (type === `quadratic`) ? Bezier.defaultQuadratic(this) : Bezier.defaultCubic(this);
|
||||||
|
setMovable(curve.points);
|
||||||
|
setSlider(`.slide-control`, `steps`, (type === `quadratic`) ? 4 : 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
curve.drawSkeleton();
|
||||||
|
|
||||||
|
noFill();
|
||||||
|
start();
|
||||||
|
for(let i=0, e=this.steps; i<=e; i++) {
|
||||||
|
let p = curve.get(i/e);
|
||||||
|
vertex(p.x, p.y);
|
||||||
|
}
|
||||||
|
end();
|
||||||
|
|
||||||
|
curve.drawPoints();
|
||||||
|
|
||||||
|
setFill(`black`);
|
||||||
|
text(`Flattened to ${this.steps} segments`, 10, 15);
|
||||||
|
}
|
@@ -1,28 +0,0 @@
|
|||||||
setup() {
|
|
||||||
this.curve = Bezier.defaultQuadratic(this);
|
|
||||||
setMovable(this.curve.points);
|
|
||||||
setSlider(`.slide-control`, `steps`, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
clear();
|
|
||||||
|
|
||||||
this.curve.drawSkeleton();
|
|
||||||
|
|
||||||
noFill();
|
|
||||||
start();
|
|
||||||
for(let i=0, e=this.steps; i<=e; i++) {
|
|
||||||
let p = this.curve.get(i/e);
|
|
||||||
vertex(p.x, p.y);
|
|
||||||
}
|
|
||||||
end();
|
|
||||||
|
|
||||||
this.curve.drawPoints();
|
|
||||||
|
|
||||||
setFill(`black`);
|
|
||||||
text(`Flattened to ${this.steps} segments`, 10, 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
@@ -1,12 +1,13 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const curve = this.curve = new Bezier(this, 70,250, 120,15, 20,95, 225,80);
|
curve = new Bezier(this, 70,250, 120,15, 20,95, 225,80);
|
||||||
setMovable(curve.points);
|
setMovable(curve.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
curve.drawPoints();
|
curve.drawPoints();
|
||||||
|
@@ -1,16 +1,13 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = Bezier.defaultCubic(this);
|
curve = Bezier.defaultCubic(this);
|
||||||
setMovable(this.curve.points);
|
setMovable(curve.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
clear();
|
clear();
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
curve.drawPoints();
|
curve.drawPoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
|
@@ -1,16 +1,13 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = Bezier.defaultQuadratic(this);
|
curve = Bezier.defaultQuadratic(this);
|
||||||
setMovable(this.curve.points);
|
setMovable(curve.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
clear();
|
clear();
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
curve.drawPoints();
|
curve.drawPoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = Bezier.defaultCubic(this);
|
curve = Bezier.defaultCubic(this);
|
||||||
setMovable(this.curve.points);
|
setMovable(curve.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
clear();
|
clear();
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
|
|
||||||
const pts = curve.points;
|
const pts = curve.points;
|
||||||
const f = 15;
|
const f = 15;
|
||||||
|
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = Bezier.defaultQuadratic(this);
|
curve = Bezier.defaultQuadratic(this);
|
||||||
setMovable(this.curve.points);
|
setMovable(curve.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
clear();
|
clear();
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
|
|
||||||
const pts = curve.points;
|
const pts = curve.points;
|
||||||
const f = 15;
|
const f = 15;
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
|
let points = [];
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const points = this.points = [],
|
const w = this.width,
|
||||||
w = this.width,
|
|
||||||
h = this.height;
|
h = this.height;
|
||||||
for (let i=0; i<10; i++) {
|
for (let i=0; i<10; i++) {
|
||||||
points.push({
|
points.push({
|
||||||
@@ -28,12 +29,10 @@ draw() {
|
|||||||
drawCurve() {
|
drawCurve() {
|
||||||
// we can't "just draw" this curve, since it'll be an arbitrary order,
|
// we can't "just draw" this curve, since it'll be an arbitrary order,
|
||||||
// And the canvas only does 2nd and 3rd - we use de Casteljau's algorithm:
|
// And the canvas only does 2nd and 3rd - we use de Casteljau's algorithm:
|
||||||
const pts = this.points;
|
|
||||||
|
|
||||||
start();
|
start();
|
||||||
noFill();
|
noFill();
|
||||||
for(let t=0; t<=1; t+=0.01) {
|
for(let t=0; t<=1; t+=0.01) {
|
||||||
let q = JSON.parse(JSON.stringify(pts));
|
let q = JSON.parse(JSON.stringify(points));
|
||||||
while(q.length > 1) {
|
while(q.length > 1) {
|
||||||
for (let i=0; i<q.length-1; i++) {
|
for (let i=0; i<q.length-1; i++) {
|
||||||
q[i] = {
|
q[i] = {
|
||||||
@@ -49,15 +48,15 @@ drawCurve() {
|
|||||||
|
|
||||||
start();
|
start();
|
||||||
setStroke(`lightgrey`);
|
setStroke(`lightgrey`);
|
||||||
pts.forEach(p => vertex(p.x, p.y));
|
points.forEach(p => vertex(p.x, p.y));
|
||||||
end();
|
end();
|
||||||
|
|
||||||
setStroke(`black`);
|
setStroke(`black`);
|
||||||
pts.forEach(p => circle(p.x, p.y, 3));
|
points.forEach(p => circle(p.x, p.y, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
raise() {
|
raise() {
|
||||||
const p = this.points,
|
const p = points,
|
||||||
np = [p[0]],
|
np = [p[0]],
|
||||||
k = p.length;
|
k = p.length;
|
||||||
for (let i = 1, pi, pim; i < k; i++) {
|
for (let i = 1, pi, pim; i < k; i++) {
|
||||||
@@ -69,9 +68,9 @@ raise() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
np[k] = p[k - 1];
|
np[k] = p[k - 1];
|
||||||
this.points = np;
|
points = np;
|
||||||
|
|
||||||
resetMovable(this.points);
|
resetMovable(points);
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,8 +82,8 @@ lower() {
|
|||||||
// first or last point... it starts to travel
|
// first or last point... it starts to travel
|
||||||
// A LOT more than it looks like it should... O_o
|
// A LOT more than it looks like it should... O_o
|
||||||
|
|
||||||
const pts = this.points,
|
const p = points,
|
||||||
k = pts.length,
|
k = p.length,
|
||||||
data = [],
|
data = [],
|
||||||
n = k-1;
|
n = k-1;
|
||||||
|
|
||||||
@@ -108,8 +107,7 @@ lower() {
|
|||||||
const Mi = Mc.invert();
|
const Mi = Mc.invert();
|
||||||
|
|
||||||
if (!Mi) {
|
if (!Mi) {
|
||||||
console.error('MtM has no inverse?');
|
return console.error('MtM has no inverse?');
|
||||||
return curve;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// And then we map our k-order list of coordinates
|
// And then we map our k-order list of coordinates
|
||||||
@@ -120,15 +118,11 @@ lower() {
|
|||||||
const y = new Matrix(pts.map(p => [p.y]));
|
const y = new Matrix(pts.map(p => [p.y]));
|
||||||
const ny = V.multiply(y);
|
const ny = V.multiply(y);
|
||||||
|
|
||||||
this.points = nx.data.map((x,i) => ({
|
points = nx.data.map((x,i) => ({
|
||||||
x: x[0],
|
x: x[0],
|
||||||
y: ny.data[i][0]
|
y: ny.data[i][0]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
resetMovable(this.points);
|
resetMovable(points);
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = Bezier.defaultCubic(this);
|
curve = Bezier.defaultCubic(this);
|
||||||
setMovable(this.curve.points);
|
setMovable(curve.points);
|
||||||
setSlider(`.slide-control`, `position`, 0.5);
|
setSlider(`.slide-control`, `position`, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8,8 +10,8 @@ draw() {
|
|||||||
resetTransform();
|
resetTransform();
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
let p = this.curve.get(this.position);
|
let p = curve.get(this.position);
|
||||||
const struts = this.struts = this.curve.getStrutPoints(this.position);
|
const struts = this.struts = curve.getStrutPoints(this.position);
|
||||||
const c1 = new Bezier(this, [struts[0], struts[4], struts[7], struts[9]]);
|
const c1 = new Bezier(this, [struts[0], struts[4], struts[7], struts[9]]);
|
||||||
const c2 = new Bezier(this, [struts[9], struts[8], struts[6], struts[3]]);
|
const c2 = new Bezier(this, [struts[9], struts[8], struts[6], struts[3]]);
|
||||||
|
|
||||||
@@ -29,24 +31,24 @@ draw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
drawBasics(p) {
|
drawBasics(p) {
|
||||||
this.curve.drawCurve(`lightgrey`);
|
curve.drawCurve(`lightgrey`);
|
||||||
this.curve.drawSkeleton(`lightgrey`);
|
curve.drawSkeleton(`lightgrey`);
|
||||||
this.curve.drawPoints(false);
|
curve.drawPoints(false);
|
||||||
noFill();
|
noFill();
|
||||||
setStroke(`red`);
|
setStroke(`red`);
|
||||||
circle(p.x, p.y, 3);
|
circle(p.x, p.y, 3);
|
||||||
|
|
||||||
setStroke(`lightblue`);
|
setStroke(`lightblue`);
|
||||||
this.curve.drawStruts(this.struts);
|
curve.drawStruts(this.struts);
|
||||||
setFill(`black`)
|
setFill(`black`)
|
||||||
text(`The full curve, with struts`, 10, 15);
|
text(`The full curve, with struts`, 10, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSegment(c, p, halfLabel) {
|
drawSegment(c, p, halfLabel) {
|
||||||
setStroke(`lightblue`);
|
setStroke(`lightblue`);
|
||||||
this.curve.drawCurve(`lightblue`);
|
curve.drawCurve(`lightblue`);
|
||||||
this.curve.drawSkeleton(`lightblue`);
|
curve.drawSkeleton(`lightblue`);
|
||||||
this.curve.drawStruts(this.struts);
|
curve.drawStruts(this.struts);
|
||||||
c.drawCurve();
|
c.drawCurve();
|
||||||
c.drawSkeleton(`black`);
|
c.drawSkeleton(`black`);
|
||||||
noFill();
|
noFill();
|
||||||
|
@@ -4,7 +4,9 @@ With our knowledge of bounding boxes, and curve alignment, We can now form the "
|
|||||||
|
|
||||||
We now have nice tight bounding boxes for our curves:
|
We now have nice tight bounding boxes for our curves:
|
||||||
|
|
||||||
<graphics-element title="Aligning a quadratic curve" src="./quadratic.js"></graphics-element>
|
<div class="figure">
|
||||||
<graphics-element title="Aligning a cubic curve" src="./cubic.js"></graphics-element>
|
<graphics-element title="Aligning a quadratic curve" src="./tightbounds.js" data-type="quadratic"></graphics-element>
|
||||||
|
<graphics-element title="Aligning a cubic curve" src="./tightbounds.js" data-type="cubic"></graphics-element>
|
||||||
|
</div>
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
@@ -1,74 +0,0 @@
|
|||||||
setup() {
|
|
||||||
this.curve = Bezier.defaultQuadratic(this);
|
|
||||||
setMovable(this.curve.points);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
|
||||||
const curve = this.curve;
|
|
||||||
|
|
||||||
clear();
|
|
||||||
curve.drawSkeleton();
|
|
||||||
curve.drawCurve();
|
|
||||||
curve.drawPoints();
|
|
||||||
|
|
||||||
let translated = this.translatePoints(curve.points);
|
|
||||||
let rotated = this.rotatePoints(translated);
|
|
||||||
let rtcurve = new Bezier(this, rotated);
|
|
||||||
let extrema = rtcurve.extrema();
|
|
||||||
|
|
||||||
let minx = Number.MAX_SAFE_INTEGER,
|
|
||||||
miny = minx,
|
|
||||||
maxx = Number.MIN_SAFE_INTEGER,
|
|
||||||
maxy = maxx;
|
|
||||||
|
|
||||||
setStroke(`red`);
|
|
||||||
|
|
||||||
[0, ...extrema.x, ...extrema.y, 1].forEach(t => {
|
|
||||||
let p = curve.get(t);
|
|
||||||
let rtp = rtcurve.get(t);
|
|
||||||
if (rtp.x < minx) minx = rtp.x;
|
|
||||||
if (rtp.x > maxx) maxx = rtp.x;
|
|
||||||
if (rtp.y < miny) miny = rtp.y;
|
|
||||||
if (rtp.y > maxy) maxy = rtp.y;
|
|
||||||
if (t > 0 && t< 1) circle(p.x, p.y, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
noFill();
|
|
||||||
setStroke(`#0F0`);
|
|
||||||
|
|
||||||
let tx = curve.points[0].x;
|
|
||||||
let ty = curve.points[0].y;
|
|
||||||
let a = rotated[0].a;
|
|
||||||
|
|
||||||
start();
|
|
||||||
vertex(tx + minx * cos(a) - miny * sin(a), ty + minx * sin(a) + miny * cos(a));
|
|
||||||
vertex(tx + maxx * cos(a) - miny * sin(a), ty + maxx * sin(a) + miny * cos(a));
|
|
||||||
vertex(tx + maxx * cos(a) - maxy * sin(a), ty + maxx * sin(a) + maxy * cos(a));
|
|
||||||
vertex(tx + minx * cos(a) - maxy * sin(a), ty + minx * sin(a) + maxy * cos(a));
|
|
||||||
end(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
translatePoints(points) {
|
|
||||||
// translate to (0,0)
|
|
||||||
let m = points[0];
|
|
||||||
return points.map(v => {
|
|
||||||
return {
|
|
||||||
x: v.x - m.x,
|
|
||||||
y: v.y - m.y
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
rotatePoints(points) {
|
|
||||||
// rotate so that last point is (...,0)
|
|
||||||
let dx = points[2].x;
|
|
||||||
let dy = points[2].y;
|
|
||||||
let a = atan2(dy, dx);
|
|
||||||
return points.map(v => {
|
|
||||||
return {
|
|
||||||
a: a,
|
|
||||||
x: v.x * cos(-a) - v.y * sin(-a),
|
|
||||||
y: v.x * sin(-a) + v.y * cos(-a)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,12 +1,12 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const curve = this.curve = Bezier.defaultCubic(this);
|
const type = this.type = getParameter(`type`, `quadratic`);
|
||||||
curve.points[2].x = 210;
|
curve = (type === `quadratic`) ? Bezier.defaultQuadratic(this) : Bezier.defaultCubic(this);
|
||||||
setMovable(curve.points);
|
setMovable(curve.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
const curve = this.curve;
|
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
@@ -62,8 +62,9 @@ translatePoints(points) {
|
|||||||
|
|
||||||
rotatePoints(points) {
|
rotatePoints(points) {
|
||||||
// rotate so that last point is (...,0)
|
// rotate so that last point is (...,0)
|
||||||
let dx = points[3].x;
|
let last = this.type === `quadratic` ? 2 : 3;
|
||||||
let dy = points[3].y;
|
let dx = points[last].x;
|
||||||
|
let dy = points[last].y;
|
||||||
let a = atan2(dy, dx);
|
let a = atan2(dy, dx);
|
||||||
return points.map(v => {
|
return points.map(v => {
|
||||||
return {
|
return {
|
@@ -1,18 +1,20 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = Bezier.defaultCubic(this);
|
curve = Bezier.defaultCubic(this);
|
||||||
setMovable(this.curve.points);
|
setMovable(curve.points);
|
||||||
|
|
||||||
const inputs = findAll(`input[type=range]`);
|
const inputs = findAll(`input[type=range]`);
|
||||||
if (inputs) {
|
if (inputs) {
|
||||||
const ratios = inputs.map(i => parseFloat(i.value));
|
const ratios = inputs.map(i => parseFloat(i.value));
|
||||||
this.curve.setRatios(ratios);
|
curve.setRatios(ratios);
|
||||||
|
|
||||||
inputs.forEach((input,pos) => {
|
inputs.forEach((input,pos) => {
|
||||||
const span = input.nextSibling;
|
const span = input.nextSibling;
|
||||||
input.listen(`input`, evt => {
|
input.listen(`input`, evt => {
|
||||||
const value = parseFloat(evt.target.value);
|
const value = parseFloat(evt.target.value);
|
||||||
span.textContent = ratios[pos] = value;
|
span.textContent = ratios[pos] = value;
|
||||||
this.curve.update();
|
curve.update();
|
||||||
this.redraw();
|
this.redraw();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -21,12 +23,7 @@ setup() {
|
|||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
clear();
|
clear();
|
||||||
const curve = this.curve;
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
curve.drawPoints();
|
curve.drawPoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = Bezier.defaultQuadratic(this);
|
curve = Bezier.defaultQuadratic(this);
|
||||||
setMovable(this.curve.points);
|
setMovable(curve.points);
|
||||||
setSlider(`.slide-control`, `step`, 25)
|
setSlider(`.slide-control`, `step`, 25)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,28 +18,28 @@ draw() {
|
|||||||
drawBasics() {
|
drawBasics() {
|
||||||
setStroke(`black`);
|
setStroke(`black`);
|
||||||
setFill(`black`);
|
setFill(`black`);
|
||||||
this.curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
text(`First linear interpolation, spaced ${this.step}% (${Math.floor(99/this.step)} steps)`, {x:5, y:15});
|
text(`First linear interpolation, spaced ${this.step}% (${Math.floor(99/this.step)} steps)`, {x:5, y:15});
|
||||||
|
|
||||||
translate(this.height, 0);
|
translate(this.height, 0);
|
||||||
|
|
||||||
line(0, 0, 0, this.height);
|
line(0, 0, 0, this.height);
|
||||||
|
|
||||||
this.curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
text(`Second interpolation, between each generated pair`, {x:5, y:15});
|
text(`Second interpolation, between each generated pair`, {x:5, y:15});
|
||||||
|
|
||||||
translate(this.height, 0);
|
translate(this.height, 0);
|
||||||
|
|
||||||
line(0, 0, 0, this.height);
|
line(0, 0, 0, this.height);
|
||||||
|
|
||||||
this.curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
text(`Curve points generated this way`, {x:5, y:15});
|
text(`Curve points generated this way`, {x:5, y:15});
|
||||||
}
|
}
|
||||||
|
|
||||||
drawPointCurve() {
|
drawPointCurve() {
|
||||||
setStroke(`lightgrey`);
|
setStroke(`lightgrey`);
|
||||||
for(let i=1, e=50, p; i<=e; i++) {
|
for(let i=1, e=50, p; i<=e; i++) {
|
||||||
p = this.curve.get(i/e);
|
p = curve.get(i/e);
|
||||||
circle(p.x, p.y, 1);
|
circle(p.x, p.y, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +48,7 @@ drawInterpolations() {
|
|||||||
for(let i=this.step; i<100; i+=this.step) {
|
for(let i=this.step; i<100; i+=this.step) {
|
||||||
resetTransform();
|
resetTransform();
|
||||||
this.setIterationColor(i);
|
this.setIterationColor(i);
|
||||||
let [np2, np3] = this.drawFirstInterpolation(this.curve.points, i);
|
let [np2, np3] = this.drawFirstInterpolation(curve.points, i);
|
||||||
let np4 = this.drawSecondInterpolation(np2, np3, i);
|
let np4 = this.drawSecondInterpolation(np2, np3, i);
|
||||||
this.drawOnCurve(np4, i);
|
this.drawOnCurve(np4, i);
|
||||||
}
|
}
|
||||||
@@ -94,9 +96,9 @@ drawOnCurve(np4, i) {
|
|||||||
|
|
||||||
drawCurveCoordinates() {
|
drawCurveCoordinates() {
|
||||||
this.resetTransform();
|
this.resetTransform();
|
||||||
this.curve.drawPoints();
|
curve.drawPoints();
|
||||||
translate(this.height, 0);
|
translate(this.height, 0);
|
||||||
this.curve.drawPoints();
|
curve.drawPoints();
|
||||||
translate(this.height, 0);
|
translate(this.height, 0);
|
||||||
this.curve.drawPoints();
|
curve.drawPoints();
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = new Bezier(this, 20, 250, 30, 20, 200, 250, 220, 20);
|
curve = new Bezier(this, 20, 250, 30, 20, 200, 250, 220, 20);
|
||||||
setMovable(this.curve.points);
|
setMovable(curve.points);
|
||||||
setSlider(`.slide-control`, `position`, 0.5);
|
setSlider(`.slide-control`, `position`, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
resetTransform();
|
resetTransform();
|
||||||
clear();
|
clear();
|
||||||
const curve = this.curve;
|
|
||||||
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
@@ -53,9 +54,3 @@ draw() {
|
|||||||
line(0, h-x, w, h-x);
|
line(0, h-x, w, h-x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
if (this.cursor.down) {
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
|
let curve;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.curve = new Bezier(this, 20, 250, 30, 20, 200, 250, 220, 20);
|
curve = new Bezier(this, 20, 250, 30, 20, 200, 250, 220, 20);
|
||||||
setMovable(this.curve.points);
|
setMovable(curve.points);
|
||||||
setSlider(`.slide-control`, `position`, 0.5);
|
setSlider(`.slide-control`, `position`, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
resetTransform();
|
resetTransform();
|
||||||
clear();
|
clear();
|
||||||
const curve = this.curve;
|
|
||||||
|
|
||||||
curve.drawSkeleton();
|
curve.drawSkeleton();
|
||||||
curve.drawCurve();
|
curve.drawCurve();
|
||||||
@@ -19,14 +20,14 @@ draw() {
|
|||||||
|
|
||||||
// prepare our values for root finding:
|
// prepare our values for root finding:
|
||||||
let x = round(bbox.x.min + (bbox.x.max - bbox.x.min) * this.position);
|
let x = round(bbox.x.min + (bbox.x.max - bbox.x.min) * this.position);
|
||||||
let xcoords = this.curve.points.map((p,i) => ({x:i/3, y: p.x - x}));
|
let xcoords = curve.points.map((p,i) => ({x:i/3, y: p.x - x}));
|
||||||
|
|
||||||
// find our root:
|
// find our root:
|
||||||
let roots = Bezier.getUtils().roots(xcoords);
|
let roots = Bezier.getUtils().roots(xcoords);
|
||||||
let t = this.position===0 ? 0 : this.position===1 ? 1 : roots[0];
|
let t = this.position===0 ? 0 : this.position===1 ? 1 : roots[0];
|
||||||
|
|
||||||
// find our answer:
|
// find our answer:
|
||||||
let y = round(this.curve.get(t).y);
|
let y = round(curve.get(t).y);
|
||||||
|
|
||||||
setStroke("red");
|
setStroke("red");
|
||||||
line(x, y, x, h);
|
line(x, y, x, h);
|
||||||
@@ -37,9 +38,3 @@ draw() {
|
|||||||
text(`x=${x}`, x + 5, h - (h-y)/2);
|
text(`x=${x}`, x + 5, h - (h-y)/2);
|
||||||
text(`t=${((t*100)|0)/100}`, x + 15, y);
|
text(`t=${((t*100)|0)/100}`, x + 15, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove() {
|
|
||||||
if (this.cursor.down) {
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
9814
docs/index.html
@@ -100,7 +100,6 @@ class BaseAPI {
|
|||||||
// of its own mouseMove handling.
|
// of its own mouseMove handling.
|
||||||
if (this.movable.length && this.currentPoint && !this.redrawing) {
|
if (this.movable.length && this.currentPoint && !this.redrawing) {
|
||||||
this.redraw();
|
this.redraw();
|
||||||
this.redrawing = false;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -256,6 +255,7 @@ class BaseAPI {
|
|||||||
redraw() {
|
redraw() {
|
||||||
this.redrawing = true;
|
this.redrawing = true;
|
||||||
this.draw();
|
this.draw();
|
||||||
|
this.redrawing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -157,7 +157,7 @@ class GraphicsAPI extends BaseAPI {
|
|||||||
* @param {String} local query selector for the type=range element.
|
* @param {String} local query selector for the type=range element.
|
||||||
* @param {String} propname the name of the property to control.
|
* @param {String} propname the name of the property to control.
|
||||||
* @param {float} initial the initial value for this property.
|
* @param {float} initial the initial value for this property.
|
||||||
* @param {boolean} redraw whether or not to redraw after update the value from the slider.
|
* @param {boolean} redraw whether or not to redraw after updating the value from the slider.
|
||||||
*/
|
*/
|
||||||
setSlider(qs, propname, initial, redraw = true) {
|
setSlider(qs, propname, initial, redraw = true) {
|
||||||
if (typeof this[propname] !== `undefined`) {
|
if (typeof this[propname] !== `undefined`) {
|
||||||
@@ -177,7 +177,9 @@ class GraphicsAPI extends BaseAPI {
|
|||||||
|
|
||||||
slider.listen(`input`, (evt) => {
|
slider.listen(`input`, (evt) => {
|
||||||
this[propname] = parseFloat(evt.target.value);
|
this[propname] = parseFloat(evt.target.value);
|
||||||
if (redraw) this.redraw();
|
if (redraw && !this.redrawing) {
|
||||||
|
this.redraw();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return slider;
|
return slider;
|
||||||
@@ -711,6 +713,12 @@ class GraphicsAPI extends BaseAPI {
|
|||||||
constrain(v, s, e) {
|
constrain(v, s, e) {
|
||||||
return v < s ? s : v > e ? e : v;
|
return v < s ? s : v > e ? e : v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dist(x1, y1, x2, y2) {
|
||||||
|
let dx = x1 - x2;
|
||||||
|
let dy = y1 - y2;
|
||||||
|
return this.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { GraphicsAPI, Bezier, Vector, Matrix, Shape };
|
export { GraphicsAPI, Bezier, Vector, Matrix, Shape };
|
||||||
|
@@ -14,8 +14,8 @@
|
|||||||
"url": "https://github.com/Pomax/BezierInfo-2/issues"
|
"url": "https://github.com/Pomax/BezierInfo-2/issues"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "run-s clean time lint:* build pretty time && rm -f .timing",
|
"start": "run-s clean time lint:* build pretty time clean",
|
||||||
"test": "run-s start -- --pretty&& run-p watch server browser",
|
"test": "run-s start && run-p watch server browser",
|
||||||
"------": "--- note that due to github's naming policy, the public dir is called 'docs' rather than 'public' ---",
|
"------": "--- note that due to github's naming policy, the public dir is called 'docs' rather than 'public' ---",
|
||||||
"browser": "open-cli http://localhost:8000",
|
"browser": "open-cli http://localhost:8000",
|
||||||
"build": "node ./src/build.js",
|
"build": "node ./src/build.js",
|
||||||
|
@@ -64,11 +64,11 @@ async function createIndexPages(locale, localeStrings, chapters) {
|
|||||||
|
|
||||||
let index = nunjucks.render(`index.template.html`, context);
|
let index = nunjucks.render(`index.template.html`, context);
|
||||||
|
|
||||||
if (typeof process !== "undefined") {
|
// if (typeof process !== "undefined") {
|
||||||
if (process.argv.indexOf(`--pretty`) !== 0) {
|
// if (process.argv.indexOf(`--pretty`) !== 0) {
|
||||||
index = prettier.format(index, { parser: `html` });
|
// index = prettier.format(index, { parser: `html` });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Prettification happens as an npm script action
|
// Prettification happens as an npm script action
|
||||||
|
|
||||||
|
@@ -34,8 +34,7 @@ function generateGraphicsModule(chapter, code, width, height, dataset) {
|
|||||||
const globalCode = split.quasiGlobal;
|
const globalCode = split.quasiGlobal;
|
||||||
const classCode = performCodeSurgery(split.classCode);
|
const classCode = performCodeSurgery(split.classCode);
|
||||||
|
|
||||||
return prettier.format(
|
let moduleCode = `
|
||||||
`
|
|
||||||
import CanvasBuilder from 'canvas';
|
import CanvasBuilder from 'canvas';
|
||||||
import { GraphicsAPI, Bezier, Vector, Matrix, Shape } from "${GRAPHICS_API_LOCATION}";
|
import { GraphicsAPI, Bezier, Vector, Matrix, Shape } from "${GRAPHICS_API_LOCATION}";
|
||||||
|
|
||||||
@@ -64,12 +63,11 @@ function generateGraphicsModule(chapter, code, width, height, dataset) {
|
|||||||
const canvas = example.canvas;
|
const canvas = example.canvas;
|
||||||
|
|
||||||
export { canvas };
|
export { canvas };
|
||||||
`,
|
`;
|
||||||
{
|
|
||||||
// I'm not transpiling, I'm assuming Prettier just uses Babel as AST parser/rewriter.
|
// return prettier.format(moduleCode, { parser: `babel` });
|
||||||
parser: `babel`,
|
|
||||||
}
|
return moduleCode;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { generateGraphicsModule };
|
export { generateGraphicsModule };
|
||||||
|