1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-26 17:54:52 +02:00

poly-bezier

This commit is contained in:
Pomax
2020-09-03 22:21:05 -07:00
parent d2992ebd15
commit 83dcab57cb
19 changed files with 367 additions and 290 deletions

View File

@@ -9,8 +9,8 @@ Unless you want sharp corners, of course. Then you don't even need 2.
We'll cover three forms of poly-Bézier curves in this section. First, we'll look at the kind that just follows point 1. where the end point of a segment is the same point as the start point of the next segment. This leads to poly-Béziers that are pretty hard to work with, but they're the easiest to implement:
<Graphic title="Unlinked quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic title="Unlinked cubic poly-Bézier" setup={this.setupCubic} draw={this.draw}/>
<graphics-element title="Unlinked quadratic poly-Bézier" src="./poly.js" data-type="quadratic" data-link="coordinate"></graphics-element>
<graphics-element title="Unlinked cubic poly-Bézier" src="./poly.js" data-type="cubic" data-link="coordinate"></graphics-element>
Dragging the control points around only affects the curve segments that the control point belongs to, and moving an on-curve point leaves the control points where they are, which is not the most useful for practical modelling purposes. So, let's add in the logic we need to make things a little better. We'll start by linking up control points by ensuring that the "incoming" derivative at an on-curve point is the same as it's "outgoing" derivative:
@@ -30,24 +30,24 @@ We can effect this quite easily, because we know that the vector from a curve's
So let's implement that and see what it gets us. The following two graphics show a quadratic and a cubic poly-Bézier curve again, but this time moving the control points around moves others, too. However, you might see something unexpected going on for quadratic curves...
<Graphic title="Connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
<Graphic title="Connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
<graphics-element title="Connected quadratic poly-Bézier" src="./poly.js" data-type="quadratic" data-link="derivative"></graphics-element>
<graphics-element title="Connected cubic poly-Bézier" src="./poly.js" data-type="cubic" data-link="derivative"></graphics-element>
As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point's positions is different depending on whether it's the reflection of its left or right neighbouring control point: we can't even form a proper rule-conforming curve! This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little.
As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point cannot satisfy the constraint that it's the reflection of its two neighbouring control points... This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little.
So: let's relax the requirement a little.
We can change the constraint so that we still preserve the *angle* of the derivatives across sections (so transitions from one section to the next will still look natural), but give up the requirement that they should also have the same *vector length*. Doing so will give us a much more useful kind of poly-Bézier curve:
<Graphic title="Angularly connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDirection}/>
<Graphic title="Angularly connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDirection}/>
<graphics-element title="Angularly connected quadratic poly-Bézier" src="./poly.js" data-type="quadratic" data-link="direction"></graphics-element>
<graphics-element title="Angularly connected cubic poly-Bézier" src="./poly.js" data-type="cubic" data-link="direction"></graphics-element>
Cubic curves are now better behaved when it comes to dragging control points around, but the quadratic poly-Bézier still has the problem that moving one control points will move the control points and may ending up defining "the next" control point in a way that doesn't work. Quadratic curves really aren't very useful to work with...
Finally, we also want to make sure that moving the on-curve coordinates preserves the relative positions of the associated control points. With that, we get to the kind of curve control that you might be familiar with from applications like Photoshop, Inkscape, Blender, etc.
<Graphic title="Standard connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
<Graphic title="Standard connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
<graphics-element title="Standard connected quadratic poly-Bézier" src="./poly.js" data-type="quadratic" data-link="conventional"></graphics-element>
<graphics-element title="Standard connected cubic poly-Bézier" src="./poly.js" data-type="cubic" data-link="conventional"></graphics-element>>
Again, we see that cubic curves are now rather nice to work with, but quadratic curves have a new, very serious problem: we can move an on-curve point in such a way that we can't compute what needs to "happen next". Move the top point down, below the left and right points, for instance. There is no way to preserve correct control points without a kink at the bottom point. Quadratic curves: just not that good...

View File

@@ -1,241 +0,0 @@
var atan2 = Math.atan2, sqrt = Math.sqrt, sin = Math.sin, cos = Math.cos;
module.exports = {
setupQuadratic: function(api) {
var w = api.getPanelWidth(),
h = api.getPanelHeight(),
cx = w/2, cy = h/2, pad = 40,
pts = [
// first curve:
{x:cx,y:pad}, {x:w-pad,y:pad}, {x:w-pad,y:cy},
// subsequent curve
{x:w-pad,y:h-pad}, {x:cx,y:h-pad},
// subsequent curve
{x:pad,y:h-pad}, {x:pad,y:cy},
// final curve control point
{x:pad,y:pad}
];
api.lpts = pts;
},
setupCubic: function(api) {
var w = api.getPanelWidth(),
h = api.getPanelHeight(),
cx = w/2, cy = h/2, pad = 40,
r = (w - 2*pad)/2,
k = 0.55228,
kr = k*r,
pts = [
// first curve:
{x:cx,y:pad}, {x:cx+kr,y:pad}, {x:w-pad,y:cy-kr}, {x:w-pad,y:cy},
// subsequent curve
{x:w-pad,y:cy+kr}, {x:cx+kr,y:h-pad}, {x:cx,y:h-pad},
// subsequent curve
{x:cx-kr,y:h-pad}, {x:pad,y:cy+kr}, {x:pad,y:cy},
// final curve control point
{x:pad,y:cy-kr}, {x:cx-kr,y:pad}
];
api.lpts = pts;
},
movePointsQuadraticLD: function(api, i) {
// ...we need to move _everything_
var anchor, fixed, toMove;
for(var p=1; p<4; p++) {
anchor = i + (2*p - 2) + api.lpts.length;
anchor = api.lpts[anchor % api.lpts.length];
fixed = i + (2*p - 1);
fixed = api.lpts[fixed % api.lpts.length];
toMove = i + 2*p;
toMove = api.lpts[toMove % api.lpts.length];
toMove.x = fixed.x + (fixed.x - anchor.x);
toMove.y = fixed.y + (fixed.y - anchor.y);
}
// then, the furthest point cannot be computed properly!
toMove = i + 6;
toMove = api.lpts[toMove % api.lpts.length];
api.problem = toMove;
},
movePointsCubicLD: function(api, i) {
var toMove, fixed;
if (i%3 === 1) {
fixed = i-1;
fixed += (fixed < 0) ? api.lpts.length : 0;
toMove = i-2;
toMove += (toMove < 0) ? api.lpts.length : 0;
} else {
fixed = (i+1) % api.lpts.length;
toMove = (i+2) % api.lpts.length;
}
fixed = api.lpts[fixed];
toMove = api.lpts[toMove];
toMove.x = fixed.x + (fixed.x - api.mp.x);
toMove.y = fixed.y + (fixed.y - api.mp.y);
},
linkDerivatives: function(evt, api) {
if (api.mp) {
var quad = api.lpts.length === 8;
var i = api.mp_idx;
if (quad) {
if (i%2 !== 0) { this.movePointsQuadraticLD(api, i); }
} else {
if(i%3 !== 0) { this.movePointsCubicLD(api, i); }
}
}
},
movePointsQuadraticDirOnly: function(api, i) {
// ...we need to move _everything_ ...again
var anchor, fixed, toMove;
// move left and right
[-1,1].forEach(v => {
anchor = api.mp;
fixed = i + v + api.lpts.length;
fixed = api.lpts[fixed % api.lpts.length];
toMove = i + 2*v + api.lpts.length;
toMove = api.lpts[toMove % api.lpts.length];
var a = atan2(fixed.y - anchor.y, fixed.x - anchor.x),
dx = toMove.x - fixed.x,
dy = toMove.y - fixed.y,
d = sqrt(dx*dx + dy*dy);
toMove.x = fixed.x + d*cos(a);
toMove.y = fixed.y + d*sin(a);
});
// then, the furthest point cannot be computed properly!
toMove = i + 4;
toMove = api.lpts[toMove % api.lpts.length];
api.problem = toMove;
},
movePointsCubicDirOnly: function(api, i) {
var toMove, fixed;
if (i%3 === 1) {
fixed = i-1;
fixed += (fixed < 0) ? api.lpts.length : 0;
toMove = i-2;
toMove += (toMove < 0) ? api.lpts.length : 0;
} else {
fixed = (i+1) % api.lpts.length;
toMove = (i+2) % api.lpts.length;
}
fixed = api.lpts[fixed];
toMove = api.lpts[toMove];
var a = atan2(fixed.y - api.mp.y, fixed.x - api.mp.x),
dx = toMove.x - fixed.x,
dy = toMove.y - fixed.y,
d = sqrt(dx*dx + dy*dy);
toMove.x = fixed.x + d*cos(a);
toMove.y = fixed.y + d*sin(a);
},
linkDirection: function(evt, api) {
if (api.mp) {
var quad = api.lpts.length === 8;
var i = api.mp_idx;
if (quad) {
if(i%2 !== 0) { this.movePointsQuadraticDirOnly(api, i); }
} else {
if(i%3 !== 0) { this.movePointsCubicDirOnly(api, i); }
}
}
},
bufferPoints: function(evt, api) {
api.bpts = JSON.parse(JSON.stringify(api.lpts));
},
moveQuadraticPoint: function(api, i) {
this.moveCubicPoint(api,i);
// then move the other control points
[-1,1].forEach(v => {
var anchor = i - v + api.lpts.length;
anchor = api.lpts[anchor % api.lpts.length];
var fixed = i - 2*v + api.lpts.length;
fixed = api.lpts[fixed % api.lpts.length];
var toMove = i - 3*v + api.lpts.length;
toMove = api.lpts[toMove % api.lpts.length];
var a = atan2(fixed.y - anchor.y, fixed.x - anchor.x),
dx = toMove.x - fixed.x,
dy = toMove.y - fixed.y,
d = sqrt(dx*dx + dy*dy);
toMove.x = fixed.x + d*cos(a);
toMove.y = fixed.y + d*sin(a);
});
// then signal a problem
var toMove = i + 4;
toMove = api.lpts[toMove % api.lpts.length];
api.problem = toMove;
},
moveCubicPoint: function(api, i) {
var op = api.bpts[i],
np = api.lpts[i],
dx = np.x - op.x,
dy = np.y - op.y,
len = api.lpts.length,
l = i-1+len,
r = i+1,
// original left and right
ol = api.bpts[l % len],
or = api.bpts[r % len],
// current left and right
nl = api.lpts[l % len],
nr = api.lpts[r % len];
// update current left
nl.x = ol.x + dx;
nl.y = ol.y + dy;
// update current right
nr.x = or.x + dx;
nr.y = or.y + dy;
return {x:dx, y:dy};
},
modelCurve: function(evt, api) {
if (api.mp) {
var quad = api.lpts.length === 8;
var i = api.mp_idx;
if (quad) {
if (i%2 !== 0) { this.movePointsQuadraticDirOnly(api, i); }
else { this.moveQuadraticPoint(api, i); }
}
else {
if(i%3 !== 0) { this.movePointsCubicDirOnly(api, i); }
else { this.moveCubicPoint(api, i); }
}
}
},
draw: function(api, curves) {
api.reset();
var pts = api.lpts;
var quad = pts.length === 8;
var c1 = quad ? new api.Bezier(pts[0],pts[1],pts[2]) : new api.Bezier(pts[0],pts[1],pts[2],pts[3]);
api.drawSkeleton(c1, false, true);
api.drawCurve(c1);
var c2 = quad ? new api.Bezier(pts[2],pts[3],pts[4]) : new api.Bezier(pts[3],pts[4],pts[5],pts[6]);
api.drawSkeleton(c2, false, true);
api.drawCurve(c2);
var c3 = quad ? new api.Bezier(pts[4],pts[5],pts[6]) : new api.Bezier(pts[6],pts[7],pts[8],pts[9]);
api.drawSkeleton(c3, false, true);
api.drawCurve(c3);
var c4 = quad ? new api.Bezier(pts[6],pts[7],pts[0]) : new api.Bezier(pts[9],pts[10],pts[11],pts[0]);
api.drawSkeleton(c4, false, true);
api.drawCurve(c4);
if (api.problem) {
api.setColor("red");
api.drawCircle(api.problem,5);
}
}
};

View File

@@ -0,0 +1,173 @@
let points;
setup() {
var w = this.width,
h = this.height,
cx = w/2,
cy = h/2,
pad = 40,
type = this.type = this.parameters.type ?? `quadratic`;
if (type === `quadratic`) {
points = [
// first curve:
{x:cx,y:pad}, {x:w-pad,y:pad}, {x:w-pad,y:cy},
// subsequent curve
{x:w-pad,y:h-pad}, {x:cx,y:h-pad},
// subsequent curve
{x:pad,y:h-pad}, {x:pad,y:cy},
// final curve control point
{x:pad,y:pad}
];
} else {
let r = (w - 2*pad)/2,
k = 0.55228,
kr = k*r;
points = [
// first curve:
{x:cx,y:pad}, {x:cx+kr,y:pad}, {x:w-pad,y:cy-kr}, {x:w-pad,y:cy},
// subsequent curve
{x:w-pad,y:cy+kr}, {x:cx+kr,y:h-pad}, {x:cx,y:h-pad},
// subsequent curve
{x:cx-kr,y:h-pad}, {x:pad,y:cy+kr}, {x:pad,y:cy},
// final curve control point
{x:pad,y:cy-kr}, {x:cx-kr,y:pad}
];
}
setMovable(points);
}
draw() {
clear()
var pts = points;
var quad = pts.length === 8;
var c1 = quad ? new Bezier(this, pts[0],pts[1],pts[2]) : new Bezier(this, pts[0],pts[1],pts[2],pts[3]);
c1.drawSkeleton();
c1.drawCurve();
c1.drawPoints(false);
var c2 = quad ? new Bezier(this, pts[2],pts[3],pts[4]) : new Bezier(this, pts[3],pts[4],pts[5],pts[6]);
c2.drawSkeleton();
c2.drawCurve();
c2.drawPoints(false);
var c3 = quad ? new Bezier(this, pts[4],pts[5],pts[6]) : new Bezier(this, pts[6],pts[7],pts[8],pts[9]);
c3.drawSkeleton();
c3.drawCurve();
c3.drawPoints(false);
var c4 = quad ? new Bezier(this, pts[6],pts[7],pts[0]) : new Bezier(this, pts[9],pts[10],pts[11],pts[0]);
c4.drawSkeleton();
c4.drawCurve();
c4.drawPoints(false);
if (this.problem) {
noFill();
setStroke(`red`);
circle(this.problem.x, this.problem.y, 7);
}
}
movePointsQuadratic(i, link) {
const l = points.length;
if (link === `conventional` && i%2 === 0) {
let ppl = points[(l+i-3)%l];
let pl = points[(l+i-1)%l];
let pr = points[(l+i+1)%l];
let ppr = points[(l+i+3)%l];
pl.x += this.currentPoint.diff.x;
pl.y += this.currentPoint.diff.y;
pr.x += this.currentPoint.diff.x;
pr.y += this.currentPoint.diff.y;
ppl.x -= this.currentPoint.diff.x;
ppl.y -= this.currentPoint.diff.y;
ppr.x -= this.currentPoint.diff.x;
ppr.y -= this.currentPoint.diff.y;
this.problem = points[(l+i+4)%l];
if (ppr.y === this.problem.y) {
this.problem = false;
}
}
if (i%2 === 1) {
let c1 = points[(l+i-2)%l];
let p1 = points[(l+i-1)%l];
let p2 = points[(l+i+1)%l];
let c2 = points[(l+i+2)%l];
let p3 = points[(l+i+3)%l];
let c3 = points[(l+i+4)%l];
let p4 = points[(l+i+5)%l];
if (link === `derivative`) {
c1.x = p1.x + (p1.x - this.currentPoint.x)
c1.y = p1.y + (p1.y - this.currentPoint.y)
c2.x = p2.x + (p2.x - this.currentPoint.x)
c2.y = p2.y + (p2.y - this.currentPoint.y)
c3.x = p3.x + (p3.x - c2.x)
c3.y = p3.y + (p3.y - c2.y)
this.problem = false;
if (c3.x !== p4.x + (p4.x - c1.x) || c3.y !== p4.y + (p4.y - c1.y)) {
this.problem = c3;
}
}
if (link === `direction` || link === `conventional`) {
let a1 = atan2(this.currentPoint.y-p1.y,this.currentPoint.x-p1.x) + PI;
let d1 = dist(c1.x, c1.y, p1.x, p1.y);
c1.x = p1.x + d1 * cos(a1);
c1.y = p1.y + d1 * sin(a1);
let a2 = atan2(this.currentPoint.y-p2.y,this.currentPoint.x-p2.x) + PI;
let d2 = dist(c2.x, c2.y, p2.x, p2.y);
c2.x = p2.x + d2 * cos(a2);
c2.y = p2.y + d2 * sin(a2);
this.problem = c3;
}
}
}
movePointsCubic(i, link) {
const l = points.length;
if (link === `conventional` && i%3 === 0) {
let left = points[(l+i-1)%l];
left.x += this.currentPoint.diff.x;
left.y += this.currentPoint.diff.y;
let right = points[(l+i+1)%l];
right.x += this.currentPoint.diff.x;
right.y += this.currentPoint.diff.y;
}
if (i%3 > 0) {
let c, p;
if (i%3 === 1) {
c = points[(l+i-2)%l];
p = points[(l+i-1)%l];
}
if (i%3 === 2) {
c = points[(l+i+2)%l];
p = points[(l+i+1)%l];
}
if (link === `derivative`) {
c.x = p.x + (p.x - this.currentPoint.x)
c.y = p.y + (p.y - this.currentPoint.y)
}
if (link === `direction` || link === `conventional`) {
let a = atan2(this.currentPoint.y-p.y,this.currentPoint.x-p.x) + PI;
let d = dist(c.x, c.y, p.x, p.y);
c.x = p.x + d * cos(a);
c.y = p.y + d * sin(a);
}
}
}
onMouseMove() {
if (this.currentPoint) {
const position = points.indexOf(this.currentPoint);
const link = this.parameters.link;
if (this.type === `quadratic`) {
this.movePointsQuadratic(position, link);
} else {
this.movePointsCubic(position, link);
}
redraw();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="124" height="17px" viewBox="0 0 93 13"><defs><symbol overflow="visible" id="a"><path d="M6.813-6.844c0-.984-1.094-1.687-2.016-1.687-.594 0-1.203.031-1.797.031l-1.938-.047-.062.61.797.03c.281 0 .266-.03.266.188 0 .172-.047.547-.11.907L.922-.782C.89-.577.875-.624.25-.531L.14.048A21.28 21.28 0 011.407 0c.64 0 1.266.031 1.922.031.563 0 1.219-.156 1.781-.5.954-.562 1.516-1.469 1.516-2.375 0-.515-.203-1-.531-1.265-.313-.25-.766-.391-1.594-.516v.266c.688-.25 1.063-.438 1.453-.75.531-.485.86-1.125.86-1.735zM5.296-2.656c0 1.234-.734 1.984-1.922 1.984-.25 0-.594-.015-.984-.047-.079-.015-.22-.015-.25-.031l.562-3.313h.906c1.235 0 1.688.375 1.688 1.407zm.219-3.985c0 1.266-.75 1.891-2.235 1.891h-.453l.531-3.016c.22 0 .532-.062.844-.062.969 0 1.313.297 1.313 1.187zm0 0"/></symbol><symbol overflow="visible" id="c"><path d="M4.203-7.828a.735.735 0 01-.187-.14c-.063-.063-.11-.126-.22-.329-1.593 1.61-2.5 3.266-2.5 4.781v.797c0 1.516.907 3.172 2.5 4.781.11-.203.157-.265.22-.328.062-.062.125-.109.312-.203C2.875.063 2.281-1.344 2.281-2.719v-.797c0-1.39.594-2.78 2.047-4.25zm0 0"/></symbol><symbol overflow="visible" id="d"><path d="M5.125-.094v-.484l-.75-.047c-.656-.031-.64-.031-.64-.656v-7.172l-.313-.125c-.875.469-1.61.781-2.86 1.219l.125.718h.235l1.547-.687.031-.016c.063 0-.047-.015-.047.266v5.797c0 .625.016.625-.64.656L1-.578v.625L3.125 0l2 .047zm0 0"/></symbol><symbol overflow="visible" id="e"><path d="M3.766-2.719v-.797c0-1.515-.907-3.171-2.516-4.78-.11.202-.156.265-.203.327-.063.063-.125.11-.313.203 1.438 1.47 2.032 2.86 2.032 4.25v.797c0 1.375-.594 2.781-2.032 4.25.188.094.25.14.313.203.047.063.094.125.203.329C2.86.452 3.766-1.204 3.766-2.72zm0 0"/></symbol><symbol overflow="visible" id="g"><path d="M8.266-4.078a1.419 1.419 0 01-.047-.36c0-.109.015-.234.062-.484h-7.5c.063.25.063.375.063.484 0 .125 0 .235-.063.5h7.5zm0 2.625a1.332 1.332 0 01-.047-.36c0-.109.015-.234.062-.484h-7.5c.063.25.063.375.063.485 0 .125 0 .25-.063.5h7.5zm0 0"/></symbol><symbol overflow="visible" id="h"><path d="M5.688-4.5c0-2.625-.907-4-2.547-4C1.297-8.5.203-6.89.203-4c0 1.39.281 2.703.703 3.281.422.594 1.203.953 1.938.953 1.812 0 2.844-1.687 2.844-4.734zm-1.282.594C4.406-1.36 4.094-.437 3-.437c-1.156 0-1.516-1.079-1.516-4 0-2.516.313-3.375 1.438-3.375 1.172 0 1.484 1.03 1.484 3.906zm0 0"/></symbol><symbol overflow="visible" id="b"><path d="M2.594-4.672l-.203-.187-1.094.39-.89 3.36.312.234.5-.25 1.437-3.484zm0 0"/></symbol><symbol overflow="visible" id="f"><path d="M5.14-.984l-.156-.25-.406.25c-.281.156-.375.218-.469.218-.062 0 .016.063.016-.046 0-.032-.031-.032.031-.157l.625-2.453c.078-.25.11-.5.11-.656 0-.203-.25-.453-.438-.453-.406 0-1.187.375-1.766.86-.359.312-.64.608-1.124 1.25l.265.108.36-1.421c.046-.172.062-.266.062-.375 0-.188-.234-.422-.375-.422-.203 0-.703.234-1.39.687l-.344.235.172.421.5-.28c.343-.22.265-.188.343-.188.094 0 .016-.047.016.078 0 .453-.406 2.156-.813 3.453l.172.219c.313-.078.531-.125.875-.188.203-1.218.375-1.703.797-2.312.531-.735 1.14-1.25 1.578-1.25.094 0 0-.063 0 .078 0 .156-.015.344-.11.625l-.5 1.875c-.077.328-.124.531-.124.672 0 .219.265.484.453.484.25 0 .703-.219 1.734-.922zm0 0"/></symbol><symbol overflow="visible" id="i"><path d="M6.156-2.078c-.015-.094-.015-.172-.015-.266 0-.094 0-.172.046-.39H3.781v-2.438c-.25.063-.328.063-.422.063-.093 0-.171 0-.421-.063v2.438H.515c.046.218.062.296.062.39 0 .094-.015.172-.062.39h2.421V.485c.25-.062.329-.062.422-.062.094 0 .172 0 .422.062v-2.437h2.406zm0 0"/></symbol><symbol overflow="visible" id="j"><path d="M4.266-.11V-.5l-.657-.031c-.515-.031-.468.015-.468-.453v-5.329l-.282-.093c-.625.297-1.312.562-2.375.906l.11.594h.234l1.25-.516.031-.016c.047 0-.078-.046-.078.172v4.282c0 .468.047.421-.469.453L.86-.5v.531L2.594 0l1.672.031zm0 0"/></symbol></defs><use xlink:href="#a" x=".25" y="9.082"/><use xlink:href="#b" x="7.555" y="4.849"/><use xlink:href="#c" x="11.123" y="9.082"/><use xlink:href="#d" x="16.192" y="9.082"/><use xlink:href="#e" x="22.17" y="9.082"/><use xlink:href="#f" x="27.239" y="11.856"/><use xlink:href="#g" x="36.596" y="9.082"/><use xlink:href="#a" x="48.994" y="9.082"/><use xlink:href="#b" x="56.308" y="4.849"/><g><use xlink:href="#c" x="59.876" y="9.082"/><use xlink:href="#h" x="64.945" y="9.082"/><use xlink:href="#e" x="70.923" y="9.082"/></g><g><use xlink:href="#f" x="75.991" y="11.856"/><use xlink:href="#i" x="81.529" y="11.856"/><use xlink:href="#j" x="88.253" y="11.856"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129px" height="17px" viewBox="0 0 97 13"><defs><symbol overflow="visible" id="a"><path d="M9.234-6.64c0-.891-.984-1.782-2.421-1.782H2.796c-.235 0-.547.125-.547.36 0 .14.313.265.531.265l.14-.031s.11.031.313.062c.22.016.125-.093.125.063 0 .047-.015.078-.046.219l-1.61 6.421c-.11.47.063.422-.875.422-.203 0-.531.141-.531.375 0 .141.312.266.531.266h4.266c1.89 0 3.484-1.547 3.484-2.719 0-.86-.875-1.687-2.047-1.812v.265c1.25-.234 2.703-1.25 2.703-2.375zM7.75-6.689c0 1.047-.828 2.047-2.281 2.047H3.937l.72-2.828c.093-.422-.063-.328.452-.328h1.532c1.062 0 1.109.578 1.109 1.11zm-.672 3.844c0 1.188-.86 2.203-2.266 2.203h-1.89c-.125-.015.031.11.031 0 0-.03 0-.046.063-.265l.78-3.203h2.11c1.14 0 1.172.75 1.172 1.265zm0 0"/></symbol><symbol overflow="visible" id="c"><path d="M4.156 2.719c0-.047-.078-.172-.11-.203-1.234-.922-2-3.22-2-5.141v-1c0-1.906.767-4.203 2-5.14a.457.457 0 00.11-.188c0-.063-.25-.25-.312-.25a1.01 1.01 0 00-.203.062C2.312-8.156 1-5.67 1-3.625v1C1-.578 2.313 1.906 3.64 2.906c.016.016.188.063.204.063.062 0 .312-.203.312-.25zm0 0"/></symbol><symbol overflow="visible" id="d"><path d="M5.203-.125v-.516h-.578c-1.078 0-.922 0-.922-.437v-6.703c0-.282-.187-.438-.578-.438-.766.797-1.672.766-2.266.766v.625c.438 0 1.282-.031 1.563-.172v5.922c0 .437.172.437-.906.437H.937v.672C1.547-.03 2.595-.03 3.079-.03c.469 0 1.516 0 2.125.062zm0 0"/></symbol><symbol overflow="visible" id="e"><path d="M3.64-2.625v-1c0-2.047-1.328-4.531-2.64-5.516a.774.774 0 00-.203-.062c-.063 0-.313.187-.313.25 0 .031.079.172.094.187 1.25.938 2.016 3.235 2.016 5.141v1c0 1.922-.766 4.219-2.016 5.14-.015.032-.094.157-.094.204 0 .047.25.25.313.25A.774.774 0 001 2.906c1.313-1 2.64-3.484 2.64-5.531zm0 0"/></symbol><symbol overflow="visible" id="g"><path d="M8.828-4.281c0-.125-.312-.375-.437-.375H.906c-.125 0-.437.25-.437.375 0 .14.312.375.437.375h7.485c.125 0 .437-.235.437-.375zm0 2.328c0-.14-.312-.375-.437-.375H.906c-.125 0-.437.234-.437.375 0 .125.312.36.437.36h7.485c.125 0 .437-.235.437-.36zm0 0"/></symbol><symbol overflow="visible" id="h"><path d="M5.688-3.953c0-.953-.063-1.922-.47-2.797-.562-1.156-1.734-1.469-2.234-1.469-.718 0-1.796.438-2.28 1.547-.376.828-.438 1.766-.438 2.719 0 .89.109 2.062.593 2.969.516.968 1.532 1.25 2.11 1.25.656 0 1.75-.391 2.281-1.516.375-.828.438-1.766.438-2.703zm-1.391-.14c0 .89 0 1.702-.125 2.468C4-.485 3.516-.265 2.969-.265 2.516-.266 2-.438 1.78-1.579c-.125-.719-.125-1.813-.125-2.516 0-.765 0-1.562.094-2.203.219-1.422.922-1.406 1.219-1.406.406 0 .984.094 1.219 1.266.109.671.109 1.578.109 2.343zm0 0"/></symbol><symbol overflow="visible" id="b"><path d="M3.063-4.313c0-.25-.422-.609-.672-.609-.172 0-.563.281-.61.406l-1.5 3.72H1L2.922-3.97c.047-.062.14-.265.14-.344zm0 0"/></symbol><symbol overflow="visible" id="f"><path d="M5.719-1.36c0-.109-.313-.265-.344-.265-.11 0-.328.203-.36.297-.202.672-.343.86-.687.86-.172 0 0 .046 0-.141 0-.188.047-.297.188-.672.109-.266.453-1.14.453-1.61 0-.812-.86-1.125-1.297-1.125-.703 0-1.313.485-1.563.813l.36.11c-.063-.579-.766-.923-1.11-.923-.359 0-.703.313-.812.5-.188.313-.36.907-.36.938 0 .11.329.281.344.281.125 0 .344-.187.39-.422.141-.484.079-.734.407-.734.219 0 .078.015.078.25 0 .156-.078.469-.14.687l-.188.75L.812-.687c-.03.109-.078.312-.078.343 0 .188.36.422.485.422.156 0 .437-.14.468-.219.047-.078.188-.468.22-.64l.187-.75.14-.563c.094-.344.047-.297.282-.64.234-.329.484-.72 1.125-.72.484 0 .28.267.28.423 0 .5-.358 1.437-.5 1.781-.093.234-.124.313-.124.453 0 .438.578.875 1.015.875.829 0 1.407-1.312 1.407-1.437zm0 0"/></symbol><symbol overflow="visible" id="i"><path d="M6.25-2.25c0-.094-.281-.328-.375-.328H3.641v-2.297c0-.094-.297-.328-.375-.328-.094 0-.391.234-.391.328v2.297H.641c-.094 0-.375.234-.375.328s.28.328.375.328h2.234V.375c0 .078.297.313.39.313.079 0 .376-.235.376-.313v-2.297h2.234c.094 0 .375-.234.375-.328zm0 0"/></symbol><symbol overflow="visible" id="j"><path d="M4.172-.156v-.469H3.64c-.829 0-.625.063-.625-.219v-4.64c0-.235-.235-.391-.563-.391-.578.578-1.203.531-1.75.531v.625c.406 0 1.11-.047 1.219-.11v3.985c0 .281.203.219-.625.219H.766v.64L2.469-.03l1.703.047zm0 0"/></symbol></defs><use xlink:href="#a" x=".43" y="9.082"/><use xlink:href="#b" x="9.803" y="4.742"/><use xlink:href="#c" x="13.707" y="9.082"/><use xlink:href="#d" x="18.358" y="9.082"/><use xlink:href="#e" x="24.335" y="9.082"/><use xlink:href="#f" x="28.986" y="12.035"/><use xlink:href="#g" x="38.713" y="9.082"/><use xlink:href="#a" x="51.326" y="9.082"/><use xlink:href="#b" x="60.708" y="4.742"/><g><use xlink:href="#c" x="64.613" y="9.082"/><use xlink:href="#h" x="69.264" y="9.082"/><use xlink:href="#e" x="75.241" y="9.082"/></g><g><use xlink:href="#f" x="79.891" y="12.035"/><use xlink:href="#i" x="85.799" y="12.035"/><use xlink:href="#j" x="92.31" y="12.035"/></g></svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1990,27 +1990,67 @@ for p = 1 to points.length-3 (inclusive):
</ol>
<p>Unless you want sharp corners, of course. Then you don't even need 2.</p>
<p>We'll cover three forms of poly-Bézier curves in this section. First, we'll look at the kind that just follows point 1. where the end point of a segment is the same point as the start point of the next segment. This leads to poly-Béziers that are pretty hard to work with, but they're the easiest to implement:</p>
<Graphic title="Unlinked quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic title="Unlinked cubic poly-Bézier" setup={this.setupCubic} draw={this.draw}/>
<graphics-element title="Unlinked quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="coordinate">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\0553872bf00ebc557f4b11d69d634fc6.png">
<label>Unlinked quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Unlinked cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="coordinate">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\60adbd6fe091a72e1ed37355e49a506e.png">
<label>Unlinked cubic poly-Bézier</label>
</fallback-image></graphics-element>
<p>Dragging the control points around only affects the curve segments that the control point belongs to, and moving an on-curve point leaves the control points where they are, which is not the most useful for practical modelling purposes. So, let's add in the logic we need to make things a little better. We'll start by linking up control points by ensuring that the "incoming" derivative at an on-curve point is the same as it's "outgoing" derivative:</p>
<img class="LaTeX SVG" src="./images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg" width="124px" height="17px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg" width="129px" height="17px" loading="lazy">
<p>We can effect this quite easily, because we know that the vector from a curve's last control point to its last on-curve point is equal to the derivative vector. If we want to ensure that the first control point of the next curve matches that, all we have to do is mirror that last control point through the last on-curve point. And mirroring any point A through any point B is really simple:</p>
<img class="LaTeX SVG" src="./images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg" width="304px" height="40px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg" width="320px" height="40px" loading="lazy">
<p>So let's implement that and see what it gets us. The following two graphics show a quadratic and a cubic poly-Bézier curve again, but this time moving the control points around moves others, too. However, you might see something unexpected going on for quadratic curves...</p>
<Graphic title="Connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
<Graphic title="Connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
<graphics-element title="Connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="derivative">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\1aeb331a5170c203c31c55ae8d55b809.png">
<label>Connected quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="derivative">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\04bc7b98ba019a4a90bedce6e10eaf08.png">
<label>Connected cubic poly-Bézier</label>
</fallback-image></graphics-element>
<p>As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point's positions is different depending on whether it's the reflection of its left or right neighbouring control point: we can't even form a proper rule-conforming curve! This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little.</p>
<p>As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point cannot satisfy the constraint that it's the reflection of its two neighbouring control points... This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little.</p>
<p>So: let's relax the requirement a little.</p>
<p>We can change the constraint so that we still preserve the <em>angle</em> of the derivatives across sections (so transitions from one section to the next will still look natural), but give up the requirement that they should also have the same <em>vector length</em>. Doing so will give us a much more useful kind of poly-Bézier curve:</p>
<Graphic title="Angularly connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDirection}/>
<Graphic title="Angularly connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDirection}/>
<graphics-element title="Angularly connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="direction">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\acb8f004751017eaac4aae0b039c94cf.png">
<label>Angularly connected quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Angularly connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="direction">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\1a3c6b24bea874d32334d62110507fcb.png">
<label>Angularly connected cubic poly-Bézier</label>
</fallback-image></graphics-element>
<p>Cubic curves are now better behaved when it comes to dragging control points around, but the quadratic poly-Bézier still has the problem that moving one control points will move the control points and may ending up defining "the next" control point in a way that doesn't work. Quadratic curves really aren't very useful to work with...</p>
<p>Finally, we also want to make sure that moving the on-curve coordinates preserves the relative positions of the associated control points. With that, we get to the kind of curve control that you might be familiar with from applications like Photoshop, Inkscape, Blender, etc.</p>
<Graphic title="Standard connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
<Graphic title="Standard connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
<graphics-element title="Standard connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="conventional">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\7afd119dd93a581ec161e2cbfc3c1e63.png">
<label>Standard connected quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Standard connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="conventional">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\cfbfbbc56365ba547dc7e82b329c4007.png">
<label>Standard connected cubic poly-Bézier</label>
</fallback-image></graphics-element>>
<p>Again, we see that cubic curves are now rather nice to work with, but quadratic curves have a new, very serious problem: we can move an on-curve point in such a way that we can't compute what needs to "happen next". Move the top point down, below the left and right points, for instance. There is no way to preserve correct control points without a kink at the bottom point. Quadratic curves: just not that good...</p>
<p>A final improvement is to offer fine-level control over which points behave which, so that you can have "kinks" or individually controlled segments when you need them, with nicely well-behaved curves for the rest of the path. Implementing that, is left as an exercise for the reader.</p>

View File

@@ -1986,27 +1986,67 @@ for p = 1 to points.length-3 (inclusive):
</ol>
<p>Unless you want sharp corners, of course. Then you don't even need 2.</p>
<p>We'll cover three forms of poly-Bézier curves in this section. First, we'll look at the kind that just follows point 1. where the end point of a segment is the same point as the start point of the next segment. This leads to poly-Béziers that are pretty hard to work with, but they're the easiest to implement:</p>
<Graphic title="Unlinked quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic title="Unlinked cubic poly-Bézier" setup={this.setupCubic} draw={this.draw}/>
<graphics-element title="Unlinked quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="coordinate">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\0553872bf00ebc557f4b11d69d634fc6.png">
<label>Unlinked quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Unlinked cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="coordinate">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\60adbd6fe091a72e1ed37355e49a506e.png">
<label>Unlinked cubic poly-Bézier</label>
</fallback-image></graphics-element>
<p>Dragging the control points around only affects the curve segments that the control point belongs to, and moving an on-curve point leaves the control points where they are, which is not the most useful for practical modelling purposes. So, let's add in the logic we need to make things a little better. We'll start by linking up control points by ensuring that the "incoming" derivative at an on-curve point is the same as it's "outgoing" derivative:</p>
<img class="LaTeX SVG" src="./images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg" width="124px" height="17px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg" width="129px" height="17px" loading="lazy">
<p>We can effect this quite easily, because we know that the vector from a curve's last control point to its last on-curve point is equal to the derivative vector. If we want to ensure that the first control point of the next curve matches that, all we have to do is mirror that last control point through the last on-curve point. And mirroring any point A through any point B is really simple:</p>
<img class="LaTeX SVG" src="./images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg" width="304px" height="40px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg" width="320px" height="40px" loading="lazy">
<p>So let's implement that and see what it gets us. The following two graphics show a quadratic and a cubic poly-Bézier curve again, but this time moving the control points around moves others, too. However, you might see something unexpected going on for quadratic curves...</p>
<Graphic title="Connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
<Graphic title="Connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
<graphics-element title="Connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="derivative">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\1aeb331a5170c203c31c55ae8d55b809.png">
<label>Connected quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="derivative">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\04bc7b98ba019a4a90bedce6e10eaf08.png">
<label>Connected cubic poly-Bézier</label>
</fallback-image></graphics-element>
<p>As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point's positions is different depending on whether it's the reflection of its left or right neighbouring control point: we can't even form a proper rule-conforming curve! This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little.</p>
<p>As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point cannot satisfy the constraint that it's the reflection of its two neighbouring control points... This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little.</p>
<p>So: let's relax the requirement a little.</p>
<p>We can change the constraint so that we still preserve the <em>angle</em> of the derivatives across sections (so transitions from one section to the next will still look natural), but give up the requirement that they should also have the same <em>vector length</em>. Doing so will give us a much more useful kind of poly-Bézier curve:</p>
<Graphic title="Angularly connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDirection}/>
<Graphic title="Angularly connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDirection}/>
<graphics-element title="Angularly connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="direction">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\acb8f004751017eaac4aae0b039c94cf.png">
<label>Angularly connected quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Angularly connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="direction">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\1a3c6b24bea874d32334d62110507fcb.png">
<label>Angularly connected cubic poly-Bézier</label>
</fallback-image></graphics-element>
<p>Cubic curves are now better behaved when it comes to dragging control points around, but the quadratic poly-Bézier still has the problem that moving one control points will move the control points and may ending up defining "the next" control point in a way that doesn't work. Quadratic curves really aren't very useful to work with...</p>
<p>Finally, we also want to make sure that moving the on-curve coordinates preserves the relative positions of the associated control points. With that, we get to the kind of curve control that you might be familiar with from applications like Photoshop, Inkscape, Blender, etc.</p>
<Graphic title="Standard connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
<Graphic title="Standard connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
<graphics-element title="Standard connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="conventional">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\7afd119dd93a581ec161e2cbfc3c1e63.png">
<label>Standard connected quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Standard connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="conventional">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\cfbfbbc56365ba547dc7e82b329c4007.png">
<label>Standard connected cubic poly-Bézier</label>
</fallback-image></graphics-element>>
<p>Again, we see that cubic curves are now rather nice to work with, but quadratic curves have a new, very serious problem: we can move an on-curve point in such a way that we can't compute what needs to "happen next". Move the top point down, below the left and right points, for instance. There is no way to preserve correct control points without a kink at the bottom point. Quadratic curves: just not that good...</p>
<p>A final improvement is to offer fine-level control over which points behave which, so that you can have "kinks" or individually controlled segments when you need them, with nicely well-behaved curves for the rest of the path. Implementing that, is left as an exercise for the reader.</p>

View File

@@ -97,6 +97,8 @@ class GraphicsAPI extends BaseAPI {
d = new Vector(p).dist(this.cursor);
if (d <= cdist) {
this.currentPoint = p;
this.currentPoint.mark = { x: p.x, y: p.y };
this.currentPoint.last = { x: p.x, y: p.y };
break;
}
}
@@ -105,8 +107,20 @@ class GraphicsAPI extends BaseAPI {
onMouseMove(evt) {
super.onMouseMove(evt);
if (this.currentPoint) {
this.currentPoint.last = {
x: this.currentPoint.x,
y: this.currentPoint.y,
};
this.currentPoint.x = this.cursor.x;
this.currentPoint.y = this.cursor.y;
this.currentPoint.diff = {
x: this.cursor.x - this.currentPoint.last.x,
y: this.cursor.y - this.currentPoint.last.y,
total: {
x: this.cursor.x - this.currentPoint.mark.x,
y: this.cursor.y - this.currentPoint.mark.y,
},
};
} else {
for (let i = 0, e = this.movable.length, p; i < e; i++) {
p = this.movable[i];
@@ -121,6 +135,9 @@ class GraphicsAPI extends BaseAPI {
onMouseUp(evt) {
super.onMouseUp(evt);
delete this.currentPoint.mark;
delete this.currentPoint.last;
delete this.currentPoint.diff;
this.currentPoint = false;
}

View File

@@ -92,7 +92,15 @@ class Bezier extends Original {
}
drawPoints(labels = true) {
const colors = [`red`, `green`, `blue`, `yellow`];
const colors = [
`red`,
`green`,
`blue`,
`yellow`,
`orange`,
`cyan`,
`magenta`,
];
const api = this.api;
const ctx = this.ctx;

View File

@@ -1,11 +1,11 @@
// Copied from http://blog.acipo.com/matrix-inversion-in-javascript/
function invert(M) {
// I use Guassian Elimination to calculate the inverse:
// Copied from http://blog.acipo.com/matrix-inversion-in-javascript/
// With permission, http://blog.acipo.com/matrix-inversion-in-javascript/#comment-5057289889
// (1) 'augment' the matrix (left) by the identity (on the right)
// (2) Turn the matrix on the left into the identity by elemetry row ops
// (3) The matrix on the right is the inverse (was the identity matrix)
// There are 3 elemtary row ops: (I combine b and c in my code)
// There are 3 elemtary row ops:
// (a) Swap 2 rows
// (b) Multiply a row by a scalar
// (c) Add 2 rows

View File

@@ -1980,27 +1980,67 @@ for p = 1 to points.length-3 (inclusive):
</ol>
<p>Unless you want sharp corners, of course. Then you don't even need 2.</p>
<p>We'll cover three forms of poly-Bézier curves in this section. First, we'll look at the kind that just follows point 1. where the end point of a segment is the same point as the start point of the next segment. This leads to poly-Béziers that are pretty hard to work with, but they're the easiest to implement:</p>
<Graphic title="Unlinked quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic title="Unlinked cubic poly-Bézier" setup={this.setupCubic} draw={this.draw}/>
<graphics-element title="Unlinked quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="coordinate">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\0553872bf00ebc557f4b11d69d634fc6.png">
<label>Unlinked quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Unlinked cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="coordinate">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\60adbd6fe091a72e1ed37355e49a506e.png">
<label>Unlinked cubic poly-Bézier</label>
</fallback-image></graphics-element>
<p>Dragging the control points around only affects the curve segments that the control point belongs to, and moving an on-curve point leaves the control points where they are, which is not the most useful for practical modelling purposes. So, let's add in the logic we need to make things a little better. We'll start by linking up control points by ensuring that the "incoming" derivative at an on-curve point is the same as it's "outgoing" derivative:</p>
<img class="LaTeX SVG" src="./images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg" width="124px" height="17px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg" width="129px" height="17px" loading="lazy">
<p>We can effect this quite easily, because we know that the vector from a curve's last control point to its last on-curve point is equal to the derivative vector. If we want to ensure that the first control point of the next curve matches that, all we have to do is mirror that last control point through the last on-curve point. And mirroring any point A through any point B is really simple:</p>
<img class="LaTeX SVG" src="./images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg" width="304px" height="40px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg" width="320px" height="40px" loading="lazy">
<p>So let's implement that and see what it gets us. The following two graphics show a quadratic and a cubic poly-Bézier curve again, but this time moving the control points around moves others, too. However, you might see something unexpected going on for quadratic curves...</p>
<Graphic title="Connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
<Graphic title="Connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDerivatives}/>
<graphics-element title="Connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="derivative">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\1aeb331a5170c203c31c55ae8d55b809.png">
<label>Connected quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="derivative">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\04bc7b98ba019a4a90bedce6e10eaf08.png">
<label>Connected cubic poly-Bézier</label>
</fallback-image></graphics-element>
<p>As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point's positions is different depending on whether it's the reflection of its left or right neighbouring control point: we can't even form a proper rule-conforming curve! This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little.</p>
<p>As you can see, quadratic curves are particularly ill-suited for poly-Bézier curves, as all the control points are effectively linked. Move one of them, and you move all of them. Not only that, but if we move the on-curve points, it's possible to get a situation where a control point cannot satisfy the constraint that it's the reflection of its two neighbouring control points... This means that we cannot use quadratic poly-Béziers for anything other than really, really simple shapes. And even then, they're probably the wrong choice. Cubic curves are pretty decent, but the fact that the derivatives are linked means we can't manipulate curves as well as we might if we relaxed the constraints a little.</p>
<p>So: let's relax the requirement a little.</p>
<p>We can change the constraint so that we still preserve the <em>angle</em> of the derivatives across sections (so transitions from one section to the next will still look natural), but give up the requirement that they should also have the same <em>vector length</em>. Doing so will give us a much more useful kind of poly-Bézier curve:</p>
<Graphic title="Angularly connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseMove={this.linkDirection}/>
<Graphic title="Angularly connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseMove={this.linkDirection}/>
<graphics-element title="Angularly connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="direction">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\acb8f004751017eaac4aae0b039c94cf.png">
<label>Angularly connected quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Angularly connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="direction">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\1a3c6b24bea874d32334d62110507fcb.png">
<label>Angularly connected cubic poly-Bézier</label>
</fallback-image></graphics-element>
<p>Cubic curves are now better behaved when it comes to dragging control points around, but the quadratic poly-Bézier still has the problem that moving one control points will move the control points and may ending up defining "the next" control point in a way that doesn't work. Quadratic curves really aren't very useful to work with...</p>
<p>Finally, we also want to make sure that moving the on-curve coordinates preserves the relative positions of the associated control points. With that, we get to the kind of curve control that you might be familiar with from applications like Photoshop, Inkscape, Blender, etc.</p>
<Graphic title="Standard connected quadratic poly-Bézier" setup={this.setupQuadratic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
<Graphic title="Standard connected cubic poly-Bézier" setup={this.setupCubic} draw={this.draw} onMouseDown={this.bufferPoints} onMouseMove={this.modelCurve}/>
<graphics-element title="Standard connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="conventional">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\7afd119dd93a581ec161e2cbfc3c1e63.png">
<label>Standard connected quadratic poly-Bézier</label>
</fallback-image></graphics-element>
<graphics-element title="Standard connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="conventional">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\polybezier\cfbfbbc56365ba547dc7e82b329c4007.png">
<label>Standard connected cubic poly-Bézier</label>
</fallback-image></graphics-element>>
<p>Again, we see that cubic curves are now rather nice to work with, but quadratic curves have a new, very serious problem: we can move an on-curve point in such a way that we can't compute what needs to "happen next". Move the top point down, below the left and right points, for instance. There is no way to preserve correct control points without a kink at the bottom point. Quadratic curves: just not that good...</p>
<p>A final improvement is to offer fine-level control over which points behave which, so that you can have "kinks" or individually controlled segments when you need them, with nicely well-behaved curves for the rest of the path. Implementing that, is left as an exercise for the reader.</p>