diff --git a/docs/chapters/polybezier/content.en-GB.md b/docs/chapters/polybezier/content.en-GB.md index 6184a7f4..1a79c52a 100644 --- a/docs/chapters/polybezier/content.en-GB.md +++ b/docs/chapters/polybezier/content.en-GB.md @@ -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: - - + + 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... - - + + -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: - - + + 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. - - + +> 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... diff --git a/docs/chapters/polybezier/handler.js b/docs/chapters/polybezier/handler.js deleted file mode 100644 index 125d6d67..00000000 --- a/docs/chapters/polybezier/handler.js +++ /dev/null @@ -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); - } - } -}; diff --git a/docs/chapters/polybezier/poly.js b/docs/chapters/polybezier/poly.js new file mode 100644 index 00000000..a174f3c1 --- /dev/null +++ b/docs/chapters/polybezier/poly.js @@ -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(); + } +} diff --git a/docs/images/chapters/polybezier/04bc7b98ba019a4a90bedce6e10eaf08.png b/docs/images/chapters/polybezier/04bc7b98ba019a4a90bedce6e10eaf08.png new file mode 100644 index 00000000..3d02bdbb Binary files /dev/null and b/docs/images/chapters/polybezier/04bc7b98ba019a4a90bedce6e10eaf08.png differ diff --git a/docs/images/chapters/polybezier/0553872bf00ebc557f4b11d69d634fc6.png b/docs/images/chapters/polybezier/0553872bf00ebc557f4b11d69d634fc6.png new file mode 100644 index 00000000..f9579b11 Binary files /dev/null and b/docs/images/chapters/polybezier/0553872bf00ebc557f4b11d69d634fc6.png differ diff --git a/docs/images/chapters/polybezier/1a3c6b24bea874d32334d62110507fcb.png b/docs/images/chapters/polybezier/1a3c6b24bea874d32334d62110507fcb.png new file mode 100644 index 00000000..3d02bdbb Binary files /dev/null and b/docs/images/chapters/polybezier/1a3c6b24bea874d32334d62110507fcb.png differ diff --git a/docs/images/chapters/polybezier/1aeb331a5170c203c31c55ae8d55b809.png b/docs/images/chapters/polybezier/1aeb331a5170c203c31c55ae8d55b809.png new file mode 100644 index 00000000..f9579b11 Binary files /dev/null and b/docs/images/chapters/polybezier/1aeb331a5170c203c31c55ae8d55b809.png differ diff --git a/docs/images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg b/docs/images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg index 4f06b843..a2e31452 100644 --- a/docs/images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg +++ b/docs/images/chapters/polybezier/408dd95905a5f001179c4da6051e49c5.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/images/chapters/polybezier/60adbd6fe091a72e1ed37355e49a506e.png b/docs/images/chapters/polybezier/60adbd6fe091a72e1ed37355e49a506e.png new file mode 100644 index 00000000..3d02bdbb Binary files /dev/null and b/docs/images/chapters/polybezier/60adbd6fe091a72e1ed37355e49a506e.png differ diff --git a/docs/images/chapters/polybezier/7afd119dd93a581ec161e2cbfc3c1e63.png b/docs/images/chapters/polybezier/7afd119dd93a581ec161e2cbfc3c1e63.png new file mode 100644 index 00000000..f9579b11 Binary files /dev/null and b/docs/images/chapters/polybezier/7afd119dd93a581ec161e2cbfc3c1e63.png differ diff --git a/docs/images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg b/docs/images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg index dd042e94..341af24b 100644 --- a/docs/images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg +++ b/docs/images/chapters/polybezier/8c1b570b3efdfbbc39ddedb4adcaaff6.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/images/chapters/polybezier/acb8f004751017eaac4aae0b039c94cf.png b/docs/images/chapters/polybezier/acb8f004751017eaac4aae0b039c94cf.png new file mode 100644 index 00000000..f9579b11 Binary files /dev/null and b/docs/images/chapters/polybezier/acb8f004751017eaac4aae0b039c94cf.png differ diff --git a/docs/images/chapters/polybezier/cfbfbbc56365ba547dc7e82b329c4007.png b/docs/images/chapters/polybezier/cfbfbbc56365ba547dc7e82b329c4007.png new file mode 100644 index 00000000..3d02bdbb Binary files /dev/null and b/docs/images/chapters/polybezier/cfbfbbc56365ba547dc7e82b329c4007.png differ diff --git a/docs/index.html b/docs/index.html index 42983cc4..83ef8e1f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1990,27 +1990,67 @@ for p = 1 to points.length-3 (inclusive):

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:

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + +

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:

- +

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:

- +

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...

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + + -

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:

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + +

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.

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + + >

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...

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.

diff --git a/docs/ja-JP/index.html b/docs/ja-JP/index.html index 0a7dae7e..d57f26c8 100644 --- a/docs/ja-JP/index.html +++ b/docs/ja-JP/index.html @@ -1986,27 +1986,67 @@ for p = 1 to points.length-3 (inclusive):

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:

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + +

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:

- +

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:

- +

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...

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + + -

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:

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + +

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.

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + + >

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...

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.

diff --git a/docs/js/custom-element/api/graphics-api.js b/docs/js/custom-element/api/graphics-api.js index 6853145e..8fa69e0e 100644 --- a/docs/js/custom-element/api/graphics-api.js +++ b/docs/js/custom-element/api/graphics-api.js @@ -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; } diff --git a/docs/js/custom-element/api/types/bezier.js b/docs/js/custom-element/api/types/bezier.js index 4ffc1dc4..3b6c75bb 100644 --- a/docs/js/custom-element/api/types/bezier.js +++ b/docs/js/custom-element/api/types/bezier.js @@ -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; diff --git a/docs/js/custom-element/api/types/matrix.js b/docs/js/custom-element/api/types/matrix.js index cc1e9963..cca93d33 100644 --- a/docs/js/custom-element/api/types/matrix.js +++ b/docs/js/custom-element/api/types/matrix.js @@ -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 diff --git a/docs/zh-CN/index.html b/docs/zh-CN/index.html index a8d82090..28e8a087 100644 --- a/docs/zh-CN/index.html +++ b/docs/zh-CN/index.html @@ -1980,27 +1980,67 @@ for p = 1 to points.length-3 (inclusive):

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:

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + +

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:

- +

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:

- +

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...

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + + -

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:

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + +

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.

- - + + + Scripts are disabled. Showing fallback image. + + + + + + Scripts are disabled. Showing fallback image. + + + >

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...

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.