1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-09-02 21:02:49 +02:00

aligning + tight bounds

This commit is contained in:
Pomax
2020-08-19 16:08:36 -07:00
parent 1c9f2b15fb
commit ddb7a8f063
22 changed files with 326 additions and 215 deletions

View File

@@ -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: 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 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>

View File

@@ -13,27 +13,40 @@ draw() {
translate(this.width/2, 0); translate(this.width/2, 0);
line(0,0,0,this.height); line(0,0,0,this.height);
this.drawRTCurve(
this.rotatePoints(
this.translatePoints(
this.curve.points
)
)
);
}
translatePoints(points) {
// translate to (0,0) // translate to (0,0)
let points = this.curve.points;
let m = points[0]; let m = points[0];
points = points.map(v => { return points.map(v => {
return { return {
x: v.x - m.x, x: v.x - m.x,
y: v.y - m.y y: v.y - m.y
} }
}); });
}
rotatePoints(points) {
// rotate so that last point is (...,0) // rotate so that last point is (...,0)
let dx = points[3].x; let dx = points[3].x;
let dy = points[3].y; let dy = points[3].y;
let a = atan2(dy, dx); let a = atan2(dy, dx);
points = points.map(v => { return points.map(v => {
return { return {
x: v.x * cos(-a) - v.y * sin(-a), x: v.x * cos(-a) - v.y * sin(-a),
y: v.x * sin(-a) + v.y * cos(-a) y: v.x * sin(-a) + v.y * cos(-a)
}; };
}); });
}
drawRTCurve(points) {
let ncurve = new Bezier(this, points); let ncurve = new Bezier(this, points);
translate(60, this.height/2); translate(60, this.height/2);
setStroke(`grey`); setStroke(`grey`);

View File

@@ -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);
}
};

View File

@@ -13,27 +13,40 @@ draw() {
translate(this.width/2, 0); translate(this.width/2, 0);
line(0,0,0,this.height); line(0,0,0,this.height);
this.drawRTCurve(
this.rotatePoints(
this.translatePoints(
this.curve.points
)
)
);
}
translatePoints(points) {
// translate to (0,0) // translate to (0,0)
let points = this.curve.points;
let m = points[0]; let m = points[0];
points = points.map(v => { return points.map(v => {
return { return {
x: v.x - m.x, x: v.x - m.x,
y: v.y - m.y y: v.y - m.y
} }
}); });
}
rotatePoints(points) {
// rotate so that last point is (...,0) // rotate so that last point is (...,0)
let dx = points[2].x; let dx = points[2].x;
let dy = points[2].y; let dy = points[2].y;
let a = atan2(dy, dx); let a = atan2(dy, dx);
points = points.map(v => { return points.map(v => {
return { return {
x: v.x * cos(-a) - v.y * sin(-a), x: v.x * cos(-a) - v.y * sin(-a),
y: v.x * sin(-a) + v.y * cos(-a) y: v.x * sin(-a) + v.y * cos(-a)
}; };
}); });
}
drawRTCurve(points) {
let ncurve = new Bezier(this, points); let ncurve = new Bezier(this, points);
translate(10, this.height/2); translate(10, this.height/2);
setStroke(`grey`); setStroke(`grey`);

View File

@@ -28,7 +28,7 @@ draw() {
if (p.x > maxx) maxx = p.x; if (p.x > maxx) maxx = p.x;
if (p.y < miny) miny = p.y; if (p.y < miny) miny = p.y;
if (p.y > maxy) maxy = 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`); setStroke(`#0F0`);

View File

@@ -27,7 +27,7 @@ draw() {
if (p.x > maxx) maxx = p.x; if (p.x > maxx) maxx = p.x;
if (p.y < miny) miny = p.y; if (p.y < miny) miny = p.y;
if (p.y > maxy) maxy = 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`); setStroke(`#0F0`);

View File

@@ -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} /> We now have nice tight bounding boxes for our curves:
<Graphic title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />
These are, strictly speaking, not necessarily the tightest possible bounding boxes. It is possible to compute the optimal bounding box by determining which spanning lines we need to effect a minimal box area, but because of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding precision is often not worth it. If there is high demand for it, I'll add a section on how to precisely compute the best fit bounding box, but the maths is fairly 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.

View 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();
}

View File

@@ -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);
}
};

View 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();
}

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -111,7 +111,7 @@
<li><a href="#extremities">Finding extremities: root finding</a></li> <li><a href="#extremities">Finding extremities: root finding</a></li>
<li><a href="#boundingbox">Bounding boxes</a></li> <li><a href="#boundingbox">Bounding boxes</a></li>
<li><a href="#aligning">Aligning curves</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="#inflections">Curve inflections</a></li>
<li><a href="#canonical">Canonical form (for cubic curves)</a></li> <li><a href="#canonical">Canonical form (for cubic curves)</a></li>
<li><a href="#yforx">Finding Y, given X</a></li> <li><a href="#yforx">Finding Y, given X</a></li>
@@ -3356,7 +3356,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="275px" width="275px"
height="275px" height="275px"
src="images\chapters\boundingbox\7d6e04cb038bc7d8e5cfa28beee46ae5.png" src="images\chapters\boundingbox\1f0e2a574995607c61adf48eded66458.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3372,7 +3372,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="275px" width="275px"
height="275px" height="275px"
src="images\chapters\boundingbox\a5595ac771a586fee13b0bf1fd2f4e49.png" src="images\chapters\boundingbox\168229f33086b9919756f4a062ff00bd.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3457,7 +3457,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="550px" width="550px"
height="275px" height="275px"
src="images\chapters\aligning\be2c5ccf3d1136da7f04d39f63e4b94b.png" src="images\chapters\aligning\54238957a61113fae905188b9eb1a582.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3473,7 +3473,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="550px" width="550px"
height="275px" height="275px"
src="images\chapters\aligning\b93cd2d19303c6bcd78b130387cd678a.png" src="images\chapters\aligning\4da87bfcb036722be99f4b1166d5daeb.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3481,7 +3481,7 @@ function getCubicRoots(pa, pb, pc, pd) {
> >
</section> </section>
<section id="tightbounds"> <section id="tightbounds">
<h1><a href="#tightbounds">Tight boxes</a></h1> <h1><a href="#tightbounds">Tight bounding boxes</a></h1>
<p> <p>
With our knowledge of bounding boxes, and curve alignment, We can With our knowledge of bounding boxes, and curve alignment, We can
now form the "tight" bounding box for curves. We first align our 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 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 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 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> </p>
<Graphic <p>We now have nice tight bounding boxes for our curves:</p>
<graphics-element
title="Aligning a quadratic curve" title="Aligning a quadratic curve"
setup="{this.setupQuadratic}" width="275"
draw="{this.draw}" height="275"
/> src="./chapters/tightbounds/quadratic.js"
<Graphic >
<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" title="Aligning a cubic curve"
setup="{this.setupCubic}" width="275"
draw="{this.draw}" 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> <p>
These are, strictly speaking, not necessarily the tightest possible 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 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 area, but because of the parametric nature of Bézier curves this is
actually a rather costly operation, and the gain in bounding actually a rather costly operation, and the gain in bounding
precision is often not worth it. If there is high demand for it, precision is often not worth 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.
</p> </p>
</section> </section>
<section id="inflections"> <section id="inflections">

View File

@@ -143,7 +143,9 @@
</li> </li>
<li><a href="ja-JP/index.html#boundingbox">Bounding boxes</a></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#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#inflections">Curve inflections</a></li>
<li> <li>
<a href="ja-JP/index.html#canonical" <a href="ja-JP/index.html#canonical"
@@ -3026,7 +3028,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="275px" width="275px"
height="275px" height="275px"
src="images\chapters\boundingbox\7d6e04cb038bc7d8e5cfa28beee46ae5.png" src="images\chapters\boundingbox\1f0e2a574995607c61adf48eded66458.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3042,7 +3044,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="275px" width="275px"
height="275px" height="275px"
src="images\chapters\boundingbox\a5595ac771a586fee13b0bf1fd2f4e49.png" src="images\chapters\boundingbox\168229f33086b9919756f4a062ff00bd.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3127,7 +3129,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="550px" width="550px"
height="275px" height="275px"
src="images\chapters\aligning\be2c5ccf3d1136da7f04d39f63e4b94b.png" src="images\chapters\aligning\54238957a61113fae905188b9eb1a582.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3143,7 +3145,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="550px" width="550px"
height="275px" height="275px"
src="images\chapters\aligning\b93cd2d19303c6bcd78b130387cd678a.png" src="images\chapters\aligning\4da87bfcb036722be99f4b1166d5daeb.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3151,7 +3153,9 @@ function getCubicRoots(pa, pb, pc, pd) {
> >
</section> </section>
<section id="tightbounds"> <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> <p>
With our knowledge of bounding boxes, and curve alignment, We can With our knowledge of bounding boxes, and curve alignment, We can
now form the "tight" bounding box for curves. We first align our 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 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 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 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> </p>
<Graphic <p>We now have nice tight bounding boxes for our curves:</p>
<graphics-element
title="Aligning a quadratic curve" title="Aligning a quadratic curve"
setup="{this.setupQuadratic}" width="275"
draw="{this.draw}" height="275"
/> src="./chapters/tightbounds/quadratic.js"
<Graphic >
<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" title="Aligning a cubic curve"
setup="{this.setupCubic}" width="275"
draw="{this.draw}" 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> <p>
These are, strictly speaking, not necessarily the tightest possible 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 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 area, but because of the parametric nature of Bézier curves this is
actually a rather costly operation, and the gain in bounding actually a rather costly operation, and the gain in bounding
precision is often not worth it. If there is high demand for it, precision is often not worth 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.
</p> </p>
</section> </section>
<section id="inflections"> <section id="inflections">

View File

@@ -386,13 +386,14 @@ class GraphicsAPI extends BaseAPI {
/** /**
* A signal to draw the current complex shape * A signal to draw the current complex shape
*/ */
end() { end(close = false) {
this.ctx.beginPath(); this.ctx.beginPath();
let { x, y } = this.currentShape.first; let { x, y } = this.currentShape.first;
this.ctx.moveTo(x, y); this.ctx.moveTo(x, y);
this.currentShape.segments.forEach((s) => this.currentShape.segments.forEach((s) =>
this[`draw${s.type}`](s.points, s.factor) this[`draw${s.type}`](s.points, s.factor)
); );
if (close) this.ctx.closePath();
this.ctx.fill(); this.ctx.fill();
this.ctx.stroke(); this.ctx.stroke();
} }

View File

@@ -137,7 +137,9 @@
</li> </li>
<li><a href="zh-CN/index.html#boundingbox">Bounding boxes</a></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#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#inflections">Curve inflections</a></li>
<li> <li>
<a href="zh-CN/index.html#canonical" <a href="zh-CN/index.html#canonical"
@@ -3036,7 +3038,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="275px" width="275px"
height="275px" height="275px"
src="images\chapters\boundingbox\7d6e04cb038bc7d8e5cfa28beee46ae5.png" src="images\chapters\boundingbox\1f0e2a574995607c61adf48eded66458.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3052,7 +3054,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="275px" width="275px"
height="275px" height="275px"
src="images\chapters\boundingbox\a5595ac771a586fee13b0bf1fd2f4e49.png" src="images\chapters\boundingbox\168229f33086b9919756f4a062ff00bd.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3137,7 +3139,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="550px" width="550px"
height="275px" height="275px"
src="images\chapters\aligning\be2c5ccf3d1136da7f04d39f63e4b94b.png" src="images\chapters\aligning\54238957a61113fae905188b9eb1a582.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3153,7 +3155,7 @@ function getCubicRoots(pa, pb, pc, pd) {
<img <img
width="550px" width="550px"
height="275px" height="275px"
src="images\chapters\aligning\b93cd2d19303c6bcd78b130387cd678a.png" src="images\chapters\aligning\4da87bfcb036722be99f4b1166d5daeb.png"
loading="lazy" loading="lazy"
/> />
Scripts are disabled. Showing fallback image. Scripts are disabled. Showing fallback image.
@@ -3161,7 +3163,9 @@ function getCubicRoots(pa, pb, pc, pd) {
> >
</section> </section>
<section id="tightbounds"> <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> <p>
With our knowledge of bounding boxes, and curve alignment, We can With our knowledge of bounding boxes, and curve alignment, We can
now form the "tight" bounding box for curves. We first align our 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 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 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 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> </p>
<Graphic <p>We now have nice tight bounding boxes for our curves:</p>
<graphics-element
title="Aligning a quadratic curve" title="Aligning a quadratic curve"
setup="{this.setupQuadratic}" width="275"
draw="{this.draw}" height="275"
/> src="./chapters/tightbounds/quadratic.js"
<Graphic >
<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" title="Aligning a cubic curve"
setup="{this.setupCubic}" width="275"
draw="{this.draw}" 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> <p>
These are, strictly speaking, not necessarily the tightest possible 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 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 area, but because of the parametric nature of Bézier curves this is
actually a rather costly operation, and the gain in bounding actually a rather costly operation, and the gain in bounding
precision is often not worth it. If there is high demand for it, precision is often not worth 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.
</p> </p>
</section> </section>
<section id="inflections"> <section id="inflections">