aligning + tight bounds
@@ -41,4 +41,4 @@ If we drop all the zero-terms, this gives us:
|
||||
We can see that our original curve definition has been simplified considerably. The following graphics illustrate the result of aligning our example curves to the x-axis, with the cubic case using the coordinates that were just used in the example formulae:
|
||||
|
||||
<graphics-element title="Aligning a quadratic curve" width="550" src="./quadratic.js"></graphics-element>
|
||||
<graphics-element title="Aligning a cubic curve" width="550" src="./cubic.js"></graphics-element>
|
||||
<graphics-element title="Aligning a cubic curve" width="550" src="./cubic.js"></graphics-element>
|
||||
|
@@ -13,27 +13,40 @@ draw() {
|
||||
translate(this.width/2, 0);
|
||||
line(0,0,0,this.height);
|
||||
|
||||
this.drawRTCurve(
|
||||
this.rotatePoints(
|
||||
this.translatePoints(
|
||||
this.curve.points
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
translatePoints(points) {
|
||||
// translate to (0,0)
|
||||
let points = this.curve.points;
|
||||
let m = points[0];
|
||||
points = points.map(v => {
|
||||
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[3].x;
|
||||
let dy = points[3].y;
|
||||
let a = atan2(dy, dx);
|
||||
points = points.map(v => {
|
||||
return points.map(v => {
|
||||
return {
|
||||
x: v.x * cos(-a) - v.y * sin(-a),
|
||||
y: v.x * sin(-a) + v.y * cos(-a)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
drawRTCurve(points) {
|
||||
let ncurve = new Bezier(this, points);
|
||||
translate(60, this.height/2);
|
||||
setStroke(`grey`);
|
||||
|
@@ -1,72 +0,0 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* Setup function for a default quadratic curve.
|
||||
*/
|
||||
setupQuadratic: function(api) {
|
||||
var curve = api.getDefaultQuadratic();
|
||||
api.setCurve(curve);
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup function for a default cubic curve.
|
||||
*/
|
||||
setupCubic: function(api) {
|
||||
var curve = api.getDefaultCubic();
|
||||
api.setCurve(curve);
|
||||
},
|
||||
|
||||
/**
|
||||
* A coordinate rotation function that rotates and
|
||||
* translates the curve, such that the first coordinate
|
||||
* of the curve is (0,0) and the last coordinate is (..., 0)
|
||||
*/
|
||||
align: function(points, line) {
|
||||
var tx = line.p1.x,
|
||||
ty = line.p1.y,
|
||||
// The atan2 function is so important to computing
|
||||
// that most CPUs have a dedicated implementation
|
||||
// at the hardware level for it.
|
||||
a = -Math.atan2(line.p2.y-ty, line.p2.x-tx),
|
||||
cos = Math.cos,
|
||||
sin = Math.sin,
|
||||
d = function(v) {
|
||||
return {
|
||||
x: (v.x-tx)*cos(a) - (v.y-ty)*sin(a),
|
||||
y: (v.x-tx)*sin(a) + (v.y-ty)*cos(a)
|
||||
};
|
||||
};
|
||||
return points.map(d);
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw a curve and its aligned counterpart
|
||||
* side by side across two panels.
|
||||
*/
|
||||
draw: function(api, curve) {
|
||||
api.setPanelCount(2);
|
||||
api.reset();
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
|
||||
var pts = curve.points;
|
||||
var line = {p1: pts[0], p2: pts[pts.length-1]};
|
||||
var apts = this.align(pts, line);
|
||||
var aligned = new api.Bezier(apts);
|
||||
var w = api.getPanelWidth();
|
||||
var h = api.getPanelHeight();
|
||||
|
||||
var offset = {x:w, y:0};
|
||||
api.setColor("black");
|
||||
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
||||
offset.x += w/4;
|
||||
offset.y += h/2;
|
||||
api.setColor("grey");
|
||||
api.drawLine({x:0,y:-h/2}, {x:0,y:h/2}, offset);
|
||||
api.drawLine({x:-w/4,y:0}, {x:w,y:0}, offset);
|
||||
api.setFill("grey");
|
||||
|
||||
api.setColor("black");
|
||||
api.drawSkeleton(aligned, offset);
|
||||
api.drawCurve(aligned, offset);
|
||||
}
|
||||
};
|
@@ -13,27 +13,40 @@ draw() {
|
||||
translate(this.width/2, 0);
|
||||
line(0,0,0,this.height);
|
||||
|
||||
this.drawRTCurve(
|
||||
this.rotatePoints(
|
||||
this.translatePoints(
|
||||
this.curve.points
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
translatePoints(points) {
|
||||
// translate to (0,0)
|
||||
let points = this.curve.points;
|
||||
let m = points[0];
|
||||
points = points.map(v => {
|
||||
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);
|
||||
points = points.map(v => {
|
||||
return points.map(v => {
|
||||
return {
|
||||
x: v.x * cos(-a) - v.y * sin(-a),
|
||||
y: v.x * sin(-a) + v.y * cos(-a)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
drawRTCurve(points) {
|
||||
let ncurve = new Bezier(this, points);
|
||||
translate(10, this.height/2);
|
||||
setStroke(`grey`);
|
||||
|
@@ -28,7 +28,7 @@ draw() {
|
||||
if (p.x > maxx) maxx = p.x;
|
||||
if (p.y < miny) miny = p.y;
|
||||
if (p.y > maxy) maxy = p.y;
|
||||
circle(p.x, p.y, 3);
|
||||
if (t > 0 && t< 1) circle(p.x, p.y, 3);
|
||||
});
|
||||
|
||||
setStroke(`#0F0`);
|
||||
|
@@ -27,7 +27,7 @@ draw() {
|
||||
if (p.x > maxx) maxx = p.x;
|
||||
if (p.y < miny) miny = p.y;
|
||||
if (p.y > maxy) maxy = p.y;
|
||||
circle(p.x, p.y, 3);
|
||||
if (t > 0 && t< 1) circle(p.x, p.y, 3);
|
||||
});
|
||||
|
||||
setStroke(`#0F0`);
|
||||
|
@@ -1,8 +1,10 @@
|
||||
# Tight boxes
|
||||
# Tight bounding boxes
|
||||
|
||||
With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for curves. We first align our curve, recording the translation we performed, "T", and the rotation angle we used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that bounding box back to our original curve by rotating it by -R, and then translating it by -T. We now have nice tight bounding boxes for our curves:
|
||||
With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for curves. We first align our curve, recording the translation we performed, "T", and the rotation angle we used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that bounding box back to our original curve by rotating it by -R, and then translating it by -T.
|
||||
|
||||
<Graphic title="Aligning a quadratic curve" setup={this.setupQuadratic} draw={this.draw} />
|
||||
<Graphic title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />
|
||||
We now have nice tight bounding boxes for our curves:
|
||||
|
||||
These are, strictly speaking, not necessarily the tightest possible bounding boxes. It is possible to compute the optimal bounding box by determining which spanning lines we need to effect a minimal box area, but because of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding precision is often not worth it. If there is high demand for it, I'll add a section on how to precisely compute the best fit bounding box, but the maths is fairly grueling and just not really worth spending time on.
|
||||
<graphics-element title="Aligning a quadratic curve" src="./quadratic.js"></graphics-element>
|
||||
<graphics-element title="Aligning a cubic curve" src="./cubic.js"></graphics-element>
|
||||
|
||||
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.
|
||||
|
79
chapters/tightbounds/cubic.js
Normal file
@@ -0,0 +1,79 @@
|
||||
setup() {
|
||||
const curve = this.curve = Bezier.defaultCubic(this);
|
||||
curve.points[2].x = 210;
|
||||
setMovable(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[3].x;
|
||||
let dy = points[3].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)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
redraw();
|
||||
}
|
@@ -1,71 +0,0 @@
|
||||
module.exports = {
|
||||
setupQuadratic: function(api) {
|
||||
var curve = api.getDefaultQuadratic();
|
||||
api.setCurve(curve);
|
||||
},
|
||||
|
||||
setupCubic: function(api) {
|
||||
var curve = api.getDefaultCubic();
|
||||
api.setCurve(curve);
|
||||
},
|
||||
|
||||
align: function(points, line) {
|
||||
var tx = line.p1.x,
|
||||
ty = line.p1.y,
|
||||
a = -Math.atan2(line.p2.y-ty, line.p2.x-tx),
|
||||
cos = Math.cos,
|
||||
sin = Math.sin,
|
||||
d = function(v) {
|
||||
return {
|
||||
x: (v.x-tx)*cos(a) - (v.y-ty)*sin(a),
|
||||
y: (v.x-tx)*sin(a) + (v.y-ty)*cos(a),
|
||||
a: a
|
||||
};
|
||||
};
|
||||
return points.map(d);
|
||||
},
|
||||
|
||||
// FIXME: I'm not satisfied with needing to turn a bbox[] into a point[],
|
||||
// this needs a bezier.js solution, really, with a call curve.tightbbox()
|
||||
transpose: function(points, angle, offset) {
|
||||
var tx = offset.x,
|
||||
ty = offset.y,
|
||||
cos = Math.cos,
|
||||
sin = Math.sin,
|
||||
v = [points.x.min, points.y.min, points.x.max, points.y.max];
|
||||
return [
|
||||
{x: v[0], y: v[1] },
|
||||
{x: v[2], y: v[1] },
|
||||
{x: v[2], y: v[3] },
|
||||
{x: v[0], y: v[3] }
|
||||
].map(p => {
|
||||
var x=p.x, y=p.y;
|
||||
return {
|
||||
x: x*cos(angle) - y*sin(angle) + tx,
|
||||
y: x*sin(angle) + y*cos(angle) + ty
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
draw: function(api, curve) {
|
||||
api.reset();
|
||||
|
||||
var pts = curve.points;
|
||||
var line = {p1: pts[0], p2: pts[pts.length-1]};
|
||||
var apts = this.align(pts, line);
|
||||
var angle = -apts[0].a;
|
||||
var aligned = new api.Bezier(apts);
|
||||
var bbox = aligned.bbox();
|
||||
var tpts = this.transpose(bbox, angle, pts[0]);
|
||||
|
||||
api.setColor("#00FF00");
|
||||
api.drawLine(tpts[0], tpts[1]);
|
||||
api.drawLine(tpts[1], tpts[2]);
|
||||
api.drawLine(tpts[2], tpts[3]);
|
||||
api.drawLine(tpts[3], tpts[0]);
|
||||
|
||||
api.setColor("black");
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
}
|
||||
};
|
78
chapters/tightbounds/quadratic.js
Normal file
@@ -0,0 +1,78 @@
|
||||
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)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
redraw();
|
||||
}
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
BIN
images/chapters/boundingbox/168229f33086b9919756f4a062ff00bd.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
images/chapters/boundingbox/1f0e2a574995607c61adf48eded66458.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 9.8 KiB |
BIN
images/chapters/tightbounds/40061dae02a2f78fdaaedd37289dc38a.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
images/chapters/tightbounds/95d28739a00d2c9c0c4bbc3c93ce3e05.png
Normal file
After Width: | Height: | Size: 11 KiB |
58
index.html
@@ -111,7 +111,7 @@
|
||||
<li><a href="#extremities">Finding extremities: root finding</a></li>
|
||||
<li><a href="#boundingbox">Bounding boxes</a></li>
|
||||
<li><a href="#aligning">Aligning curves</a></li>
|
||||
<li><a href="#tightbounds">Tight boxes</a></li>
|
||||
<li><a href="#tightbounds">Tight bounding boxes</a></li>
|
||||
<li><a href="#inflections">Curve inflections</a></li>
|
||||
<li><a href="#canonical">Canonical form (for cubic curves)</a></li>
|
||||
<li><a href="#yforx">Finding Y, given X</a></li>
|
||||
@@ -3356,7 +3356,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\boundingbox\7d6e04cb038bc7d8e5cfa28beee46ae5.png"
|
||||
src="images\chapters\boundingbox\1f0e2a574995607c61adf48eded66458.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3372,7 +3372,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\boundingbox\a5595ac771a586fee13b0bf1fd2f4e49.png"
|
||||
src="images\chapters\boundingbox\168229f33086b9919756f4a062ff00bd.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3457,7 +3457,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="550px"
|
||||
height="275px"
|
||||
src="images\chapters\aligning\be2c5ccf3d1136da7f04d39f63e4b94b.png"
|
||||
src="images\chapters\aligning\54238957a61113fae905188b9eb1a582.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3473,7 +3473,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="550px"
|
||||
height="275px"
|
||||
src="images\chapters\aligning\b93cd2d19303c6bcd78b130387cd678a.png"
|
||||
src="images\chapters\aligning\4da87bfcb036722be99f4b1166d5daeb.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3481,7 +3481,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
>
|
||||
</section>
|
||||
<section id="tightbounds">
|
||||
<h1><a href="#tightbounds">Tight boxes</a></h1>
|
||||
<h1><a href="#tightbounds">Tight bounding boxes</a></h1>
|
||||
<p>
|
||||
With our knowledge of bounding boxes, and curve alignment, We can
|
||||
now form the "tight" bounding box for curves. We first align our
|
||||
@@ -3489,18 +3489,41 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
angle we used, "R". We then determine the aligned curve's normal
|
||||
bounding box. Once we have that, we can map that bounding box back
|
||||
to our original curve by rotating it by -R, and then translating it
|
||||
by -T. We now have nice tight bounding boxes for our curves:
|
||||
by -T.
|
||||
</p>
|
||||
<Graphic
|
||||
<p>We now have nice tight bounding boxes for our curves:</p>
|
||||
<graphics-element
|
||||
title="Aligning a quadratic curve"
|
||||
setup="{this.setupQuadratic}"
|
||||
draw="{this.draw}"
|
||||
/>
|
||||
<Graphic
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/tightbounds/quadratic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\tightbounds\95d28739a00d2c9c0c4bbc3c93ce3e05.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
<graphics-element
|
||||
title="Aligning a cubic curve"
|
||||
setup="{this.setupCubic}"
|
||||
draw="{this.draw}"
|
||||
/>
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/tightbounds/cubic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\tightbounds\40061dae02a2f78fdaaedd37289dc38a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
|
||||
<p>
|
||||
These are, strictly speaking, not necessarily the tightest possible
|
||||
@@ -3508,10 +3531,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
by determining which spanning lines we need to effect a minimal box
|
||||
area, but because of the parametric nature of Bézier curves this is
|
||||
actually a rather costly operation, and the gain in bounding
|
||||
precision is often not worth it. If there is high demand for it,
|
||||
I'll add a section on how to precisely compute the best fit bounding
|
||||
box, but the maths is fairly grueling and just not really worth
|
||||
spending time on.
|
||||
precision is often not worth it.
|
||||
</p>
|
||||
</section>
|
||||
<section id="inflections">
|
||||
|
@@ -143,7 +143,9 @@
|
||||
</li>
|
||||
<li><a href="ja-JP/index.html#boundingbox">Bounding boxes</a></li>
|
||||
<li><a href="ja-JP/index.html#aligning">Aligning curves</a></li>
|
||||
<li><a href="ja-JP/index.html#tightbounds">Tight boxes</a></li>
|
||||
<li>
|
||||
<a href="ja-JP/index.html#tightbounds">Tight bounding boxes</a>
|
||||
</li>
|
||||
<li><a href="ja-JP/index.html#inflections">Curve inflections</a></li>
|
||||
<li>
|
||||
<a href="ja-JP/index.html#canonical"
|
||||
@@ -3026,7 +3028,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\boundingbox\7d6e04cb038bc7d8e5cfa28beee46ae5.png"
|
||||
src="images\chapters\boundingbox\1f0e2a574995607c61adf48eded66458.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3042,7 +3044,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\boundingbox\a5595ac771a586fee13b0bf1fd2f4e49.png"
|
||||
src="images\chapters\boundingbox\168229f33086b9919756f4a062ff00bd.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3127,7 +3129,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="550px"
|
||||
height="275px"
|
||||
src="images\chapters\aligning\be2c5ccf3d1136da7f04d39f63e4b94b.png"
|
||||
src="images\chapters\aligning\54238957a61113fae905188b9eb1a582.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3143,7 +3145,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="550px"
|
||||
height="275px"
|
||||
src="images\chapters\aligning\b93cd2d19303c6bcd78b130387cd678a.png"
|
||||
src="images\chapters\aligning\4da87bfcb036722be99f4b1166d5daeb.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3151,7 +3153,9 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
>
|
||||
</section>
|
||||
<section id="tightbounds">
|
||||
<h1><a href="ja-JP/index.html#tightbounds">Tight boxes</a></h1>
|
||||
<h1>
|
||||
<a href="ja-JP/index.html#tightbounds">Tight bounding boxes</a>
|
||||
</h1>
|
||||
<p>
|
||||
With our knowledge of bounding boxes, and curve alignment, We can
|
||||
now form the "tight" bounding box for curves. We first align our
|
||||
@@ -3159,18 +3163,41 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
angle we used, "R". We then determine the aligned curve's normal
|
||||
bounding box. Once we have that, we can map that bounding box back
|
||||
to our original curve by rotating it by -R, and then translating it
|
||||
by -T. We now have nice tight bounding boxes for our curves:
|
||||
by -T.
|
||||
</p>
|
||||
<Graphic
|
||||
<p>We now have nice tight bounding boxes for our curves:</p>
|
||||
<graphics-element
|
||||
title="Aligning a quadratic curve"
|
||||
setup="{this.setupQuadratic}"
|
||||
draw="{this.draw}"
|
||||
/>
|
||||
<Graphic
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/tightbounds/quadratic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\tightbounds\95d28739a00d2c9c0c4bbc3c93ce3e05.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
<graphics-element
|
||||
title="Aligning a cubic curve"
|
||||
setup="{this.setupCubic}"
|
||||
draw="{this.draw}"
|
||||
/>
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/tightbounds/cubic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\tightbounds\40061dae02a2f78fdaaedd37289dc38a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
|
||||
<p>
|
||||
These are, strictly speaking, not necessarily the tightest possible
|
||||
@@ -3178,10 +3205,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
by determining which spanning lines we need to effect a minimal box
|
||||
area, but because of the parametric nature of Bézier curves this is
|
||||
actually a rather costly operation, and the gain in bounding
|
||||
precision is often not worth it. If there is high demand for it,
|
||||
I'll add a section on how to precisely compute the best fit bounding
|
||||
box, but the maths is fairly grueling and just not really worth
|
||||
spending time on.
|
||||
precision is often not worth it.
|
||||
</p>
|
||||
</section>
|
||||
<section id="inflections">
|
||||
|
@@ -386,13 +386,14 @@ class GraphicsAPI extends BaseAPI {
|
||||
/**
|
||||
* A signal to draw the current complex shape
|
||||
*/
|
||||
end() {
|
||||
end(close = false) {
|
||||
this.ctx.beginPath();
|
||||
let { x, y } = this.currentShape.first;
|
||||
this.ctx.moveTo(x, y);
|
||||
this.currentShape.segments.forEach((s) =>
|
||||
this[`draw${s.type}`](s.points, s.factor)
|
||||
);
|
||||
if (close) this.ctx.closePath();
|
||||
this.ctx.fill();
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
@@ -137,7 +137,9 @@
|
||||
</li>
|
||||
<li><a href="zh-CN/index.html#boundingbox">Bounding boxes</a></li>
|
||||
<li><a href="zh-CN/index.html#aligning">Aligning curves</a></li>
|
||||
<li><a href="zh-CN/index.html#tightbounds">Tight boxes</a></li>
|
||||
<li>
|
||||
<a href="zh-CN/index.html#tightbounds">Tight bounding boxes</a>
|
||||
</li>
|
||||
<li><a href="zh-CN/index.html#inflections">Curve inflections</a></li>
|
||||
<li>
|
||||
<a href="zh-CN/index.html#canonical"
|
||||
@@ -3036,7 +3038,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\boundingbox\7d6e04cb038bc7d8e5cfa28beee46ae5.png"
|
||||
src="images\chapters\boundingbox\1f0e2a574995607c61adf48eded66458.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3052,7 +3054,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\boundingbox\a5595ac771a586fee13b0bf1fd2f4e49.png"
|
||||
src="images\chapters\boundingbox\168229f33086b9919756f4a062ff00bd.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3137,7 +3139,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="550px"
|
||||
height="275px"
|
||||
src="images\chapters\aligning\be2c5ccf3d1136da7f04d39f63e4b94b.png"
|
||||
src="images\chapters\aligning\54238957a61113fae905188b9eb1a582.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3153,7 +3155,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
<img
|
||||
width="550px"
|
||||
height="275px"
|
||||
src="images\chapters\aligning\b93cd2d19303c6bcd78b130387cd678a.png"
|
||||
src="images\chapters\aligning\4da87bfcb036722be99f4b1166d5daeb.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
@@ -3161,7 +3163,9 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
>
|
||||
</section>
|
||||
<section id="tightbounds">
|
||||
<h1><a href="zh-CN/index.html#tightbounds">Tight boxes</a></h1>
|
||||
<h1>
|
||||
<a href="zh-CN/index.html#tightbounds">Tight bounding boxes</a>
|
||||
</h1>
|
||||
<p>
|
||||
With our knowledge of bounding boxes, and curve alignment, We can
|
||||
now form the "tight" bounding box for curves. We first align our
|
||||
@@ -3169,18 +3173,41 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
angle we used, "R". We then determine the aligned curve's normal
|
||||
bounding box. Once we have that, we can map that bounding box back
|
||||
to our original curve by rotating it by -R, and then translating it
|
||||
by -T. We now have nice tight bounding boxes for our curves:
|
||||
by -T.
|
||||
</p>
|
||||
<Graphic
|
||||
<p>We now have nice tight bounding boxes for our curves:</p>
|
||||
<graphics-element
|
||||
title="Aligning a quadratic curve"
|
||||
setup="{this.setupQuadratic}"
|
||||
draw="{this.draw}"
|
||||
/>
|
||||
<Graphic
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/tightbounds/quadratic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\tightbounds\95d28739a00d2c9c0c4bbc3c93ce3e05.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
<graphics-element
|
||||
title="Aligning a cubic curve"
|
||||
setup="{this.setupCubic}"
|
||||
draw="{this.draw}"
|
||||
/>
|
||||
width="275"
|
||||
height="275"
|
||||
src="./chapters/tightbounds/cubic.js"
|
||||
>
|
||||
<fallback-image>
|
||||
<img
|
||||
width="275px"
|
||||
height="275px"
|
||||
src="images\chapters\tightbounds\40061dae02a2f78fdaaedd37289dc38a.png"
|
||||
loading="lazy"
|
||||
/>
|
||||
Scripts are disabled. Showing fallback image.
|
||||
</fallback-image></graphics-element
|
||||
>
|
||||
|
||||
<p>
|
||||
These are, strictly speaking, not necessarily the tightest possible
|
||||
@@ -3188,10 +3215,7 @@ function getCubicRoots(pa, pb, pc, pd) {
|
||||
by determining which spanning lines we need to effect a minimal box
|
||||
area, but because of the parametric nature of Bézier curves this is
|
||||
actually a rather costly operation, and the gain in bounding
|
||||
precision is often not worth it. If there is high demand for it,
|
||||
I'll add a section on how to precisely compute the best fit bounding
|
||||
box, but the maths is fairly grueling and just not really worth
|
||||
spending time on.
|
||||
precision is often not worth it.
|
||||
</p>
|
||||
</section>
|
||||
<section id="inflections">
|
||||
|