diff --git a/docs/chapters/curveintersection/curve-curve.js b/docs/chapters/curveintersection/curve-curve.js index ae7eb601..6a0a0d0b 100644 --- a/docs/chapters/curveintersection/curve-curve.js +++ b/docs/chapters/curveintersection/curve-curve.js @@ -48,8 +48,7 @@ draw() { // panel 2: the current iteration step nextPanel(); - setStroke(`black`); - line(0,0,0,this.height); + this.drawIteration(); setFill(`black`); let information = `Initial curves, threshold = ${this.epsilon}px` @@ -64,8 +63,6 @@ draw() { // panel 3: intersections nextPanel(); - setStroke(`black`); - line(0,0,0,this.height); this.drawIntersections(); } diff --git a/docs/chapters/moulding/content.en-GB.md b/docs/chapters/moulding/content.en-GB.md index 4ac43422..01e8f446 100644 --- a/docs/chapters/moulding/content.en-GB.md +++ b/docs/chapters/moulding/content.en-GB.md @@ -2,36 +2,47 @@ Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the t-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points. - + -**Click-dragging the curve itself** shows what we're using to compute the new coordinates: while dragging you will see the original point B and its corresponding t-value, the original point C for that t-value, as well as the new point B' based on the mouse cursor. Since we know the t-value for this configuration, we can compute the ABC ratio for this configuration, and we know that our new point A' should like at a distance: +Click-dragging a point on the curve shows what we're using to compute the new coordinates: while dragging you will see the original point `B` and its corresponding t-value, and the original points `A` and `C` for that t-value, in light coloring, as well as the new `A'`, `B'`, and `C'` (although of course the `C` coordinates are the same ones, because that's the defining feature of point `C`) based on where you're dragging point `B` to, in purple. + +Since we know the new point `B'`, and the "new" point `C'` as well as the `t` value, we know our new point A' has to be: \[ - A' = B' - \frac{C - B'}{ratio} = B' + \frac{B' - C}{ratio} + A' = B' - \frac{C - B'}{ratio(t)} = B' + \frac{B' - C}{ratio(t)} \] -For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point. For cubic curves, we need to do a little more work: +For quadratic curves, this means we're done, since the new point `A'` is equivalent to the new quadratic control point. - +For cubic curves, we need to do a little more work, because while computing a new `A'` is exactly the same as before, we're not quite done once we've done so. For cubic curves, `B` has not just an associated `t` value, but also two associated "side" values. Let's revisit the graphic from the chapter on de Casteljau's algorithm, to see what we mean: -To help understand what's going on, the cubic graphic shows the full de Casteljau construction "hull" when repositioning point B. We compute A' in exactly the same way as before, but we also record the final strut line that forms B in the original curve. Given A', B', and the endpoints e1 and e2 of the strut line relative to B', we can now compute where the new control points should be. Remember that B' lies on line e1--e2 at a distance t, because that's how Bézier curves work. In the same manner, we know the distance A--e1 is only line-interval [0,t] of the full segment, and A--e2 is only line-interval [t,1], so constructing the new control points is fairly easy. + + + -First, we construct the one-level-of-de-Casteljau-up points: +In addition to the `A`, `B`, and `C` values, we also see the points `e1` and `e2`, without which constructing our de Casteljau "strut lines" becomes very difficult indeed; as well as the points `v1` and `v2`, which we can construct when we know our ABC values enriched with `e1` and `e2`: \[ \left \{ \begin{aligned} - v1 &= e1 - \frac{e1 - A'}{t} \\ - v2 &= e2 + \frac{e2 - A'}{1 - t} + v1 &= A' - \frac{A' - e1}{1 - t} \\ + v2 &= A' - \frac{A' - e2}{t} \end{aligned} \right . \] -And then we can compute the new control points: +After which computing the new control points is straight-forward: \[ \left \{ \begin{aligned} - C1' &= v1 + \frac{v1 - start}{t} \\ - C2' &= v2 + \frac{v2 - end}{1 - t} + C1' &= start + \frac{v1 - start}{t} \\ + C2' &= end + \frac{v2 - end}{1 - t} \end{aligned} \right . \] -And that's cubic curve manipulation. +So let's put that into practice: + + + +So that looks pretty good, but you may not like having `e1` and `e2` stay the same distances away from `B'` while moving the point around. An alternative is to scale the distances of `e1` and `e2` to `B'` to match the scaling that `A`--`C` undergoes as `A'`--`C`' - whether this looks better or not depends somewhat on your intention as programmer or user, of course, so the following graphic applies this scaling, but it's up to you to decide whether or not that looks better (or, more appropriately, under which circumstances you might want to apply this scaling vs. when you might not): + + + diff --git a/docs/chapters/moulding/decasteljau.js b/docs/chapters/moulding/decasteljau.js new file mode 100644 index 00000000..632c5b5c --- /dev/null +++ b/docs/chapters/moulding/decasteljau.js @@ -0,0 +1,34 @@ +let curve; + +setup() { + curve = Bezier.defaultCubic(this); + setMovable(curve.points); + setSlider(`.slide-control`, `position`, 0.5); +} + +draw() { + clear(); + const blue = `lightblue`; + const t = this.position; + const {A,B,C,S,E} = curve.getABC(t); + const struts = curve.drawStruts(t, blue, false); + curve.drawSkeleton(blue); + curve.drawCurve(`black`); + setStroke(blue); + line(S.x, S.y, E.x, E.y); + line(A.x, A.y, C.x, C.y); + curve.drawPoints(); + + [A,B,C].forEach(p => circle(p.x, p.y, 3)); + setFill(`black`); + text(`A`, A.x+10, A.y); + text(`B`, B.x+10, B.y); + text(`C`, C.x+10, C.y); + setStroke(`purple`); + + const lbl = [`v1`, `v2`, `e1`, `e2`]; + [struts[4], struts[6], struts[7], struts[8]].forEach((p,i) => { + circle(p.x, p.y, 2); + text(lbl[i], p.x+10, p.y); + }); +} diff --git a/docs/chapters/moulding/handler.js b/docs/chapters/moulding/handler.js deleted file mode 100644 index b3514949..00000000 --- a/docs/chapters/moulding/handler.js +++ /dev/null @@ -1,203 +0,0 @@ -var abs = Math.abs; - -module.exports = { - setupQuadratic: function(api) { - api.setPanelCount(3); - var curve = api.getDefaultQuadratic(); - curve.points[2].x -= 30; - api.setCurve(curve); - }, - - setupCubic: function(api) { - api.setPanelCount(3); - var curve = new api.Bezier([100,230, 30,160, 200,50, 210,160]); - curve.points[2].y -= 20; - api.setCurve(curve); - api.lut = curve.getLUT(100); - }, - - saveCurve: function(evt, api) { - if (!api.t) return; - if (!api.newcurve) return; - api.setCurve(api.newcurve); - api.t = false; - api.redraw(); - }, - - findTValue: function(evt, api) { - var t = api.curve.on({x: evt.offsetX, y: evt.offsetY},7); - if (t < 0.05 || t > 0.95) return false; - return t; - }, - - markQB: function(evt, api) { - api.t = this.findTValue(evt, api); - if(api.t) { - var t = api.t, - t2 = 2*t, - top = t2*t - t2, - bottom = top + 1, - ratio = abs(top/bottom), - curve = api.curve, - A = api.A = curve.points[1], - B = api.B = curve.get(t); - api.C = api.utils.lli4(A, B, curve.points[0], curve.points[2]); - api.ratio = ratio; - this.dragQB(evt, api); - } - }, - - markCB: function(evt, api) { - api.t = this.findTValue(evt, api); - if(api.t) { - var t = api.t, - mt = (1-t), - t3 = t*t*t, - mt3 = mt*mt*mt, - bottom = t3 + mt3, - top = bottom - 1, - ratio = abs(top/bottom), - curve = api.curve, - hull = curve.hull(t), - A = api.A = hull[5], - B = api.B = curve.get(t); - api.db = curve.derivative(t); - api.C = api.utils.lli4(A, B, curve.points[0], curve.points[3]); - api.ratio = ratio; - this.dragCB(evt, api); - } - }, - - drag: function(evt, api) { - if (!api.t) return; - - var newB = api.newB = { - x: evt.offsetX, - y: evt.offsetY - }; - - // now that we know A, B, C and the AB:BC ratio, we can compute the new A' based on the desired B' - api.newA = { - x: newB.x - (api.C.x - newB.x) / api.ratio, - y: newB.y - (api.C.y - newB.y) / api.ratio - }; - }, - - dragQB: function(evt, api) { - if (!api.t) return; - this.drag(evt, api); - api.update = [api.newA]; - }, - - dragCB: function(evt, api) { - if (!api.t) return; - this.drag(evt,api); - - // preserve struts for B when repositioning - var curve = api.curve, - hull = curve.hull(api.t), - B = api.B, - Bl = hull[7], - Br = hull[8], - dbl = { x: Bl.x - B.x, y: Bl.y - B.y }, - dbr = { x: Br.x - B.x, y: Br.y - B.y }, - pts = curve.points, - // find new point on s--c1 - p1 = {x: api.newB.x + dbl.x, y: api.newB.y + dbl.y}, - sc1 = { - x: api.newA.x - (api.newA.x - p1.x)/(1-api.t), - y: api.newA.y - (api.newA.y - p1.y)/(1-api.t) - }, - // find new point on c2--e - p2 = {x: api.newB.x + dbr.x, y: api.newB.y + dbr.y}, - sc2 = { - x: api.newA.x + (p2.x - api.newA.x)/(api.t), - y: api.newA.y + (p2.y - api.newA.y)/(api.t) - }, - // construct new c1` based on the fact that s--sc1 is s--c1 * t - nc1 = { - x: pts[0].x + (sc1.x - pts[0].x)/(api.t), - y: pts[0].y + (sc1.y - pts[0].y)/(api.t) - }, - // construct new c2` based on the fact that e--sc2 is e--c2 * (1-t) - nc2 = { - x: pts[3].x - (pts[3].x - sc2.x)/(1-api.t), - y: pts[3].y - (pts[3].y - sc2.y)/(1-api.t) - }; - - api.p1 = p1; - api.p2 = p2; - api.sc1 = sc1; - api.sc2 = sc2; - api.nc1 = nc1; - api.nc2 = nc2; - - api.update = [nc1, nc2]; - }, - - drawMould: function(api, curve) { - api.reset(); - - api.drawSkeleton(curve); - api.drawCurve(curve); - - var w = api.getPanelWidth(), - h = api.getPanelHeight(), - offset = {x:w, y:0}, - round = api.utils.round; - - api.setColor("black"); - api.drawLine({x:0,y:0},{x:0,y:h}, offset); - api.drawLine({x:w,y:0},{x:w,y:h}, offset); - - if (api.t && api.update) { - api.drawCircle(curve.get(api.t),3); - api.npts = [curve.points[0]].concat(api.update).concat([curve.points.slice(-1)[0]]); - api.newcurve = new api.Bezier(api.npts); - - api.setColor("lightgrey"); - api.drawCurve(api.newcurve); - var newhull = api.drawHull(api.newcurve, api.t, offset); - api.drawLine(api.npts[0], api.npts.slice(-1)[0], offset); - api.drawLine(api.newA, api.newB, offset); - - api.setColor("grey"); - api.drawCircle(api.newA, 3, offset); - api.setColor("blue"); - api.drawCircle(api.B, 3, offset); - api.drawCircle(api.C, 3, offset); - api.drawCircle(api.newB, 3, offset); - api.drawLine(api.B, api.C, offset); - api.drawLine(api.newB, api.C, offset); - - api.setFill("black"); - api.text("A'", api.newA, {x:offset.x + 7, y:offset.y + 1}); - api.text("start", curve.get(0), {x:offset.x + 7, y:offset.y + 1}); - api.text("end", curve.get(1), {x:offset.x + 7, y:offset.y + 1}); - api.setFill("blue"); - api.text("B'", api.newB, {x:offset.x + 7, y:offset.y + 1}); - api.text("B, at t = "+round(api.t,2), api.B, {x:offset.x + 7, y:offset.y + 1}); - api.text("C", api.C, {x:offset.x + 7, y:offset.y + 1}); - - if(curve.order === 3) { - var hull = curve.hull(api.t); - api.drawLine(hull[7], hull[8], offset); - api.drawLine(newhull[7], newhull[8], offset); - api.drawCircle(newhull[7], 3, offset); - api.drawCircle(newhull[8], 3, offset); - api.text("e1", newhull[7], {x:offset.x + 7, y:offset.y + 1}); - api.text("e2", newhull[8], {x:offset.x + 7, y:offset.y + 1}); - } - - offset.x += w; - - api.setColor("lightgrey"); - api.drawSkeleton(api.newcurve, offset); - api.setColor("black"); - api.drawCurve(api.newcurve, offset); - } else { - offset.x += w; - api.drawCurve(curve, offset); - } - } -}; diff --git a/docs/chapters/moulding/mould-quadratic.js b/docs/chapters/moulding/mould-quadratic.js deleted file mode 100644 index 6e79417e..00000000 --- a/docs/chapters/moulding/mould-quadratic.js +++ /dev/null @@ -1,110 +0,0 @@ -let curve; - -setup() { - setPanelCount(3); - curve = Bezier.defaultQuadratic(this); - this.position = {x:0,y:0}; - setMovable(curve.points, [this.position]); -} - -draw() { - clear(); - - curve.drawSkeleton(); - curve.drawCurve(); - curve.drawPoints(); - - if (this.position) { - setColor(`blue`); - let p = this.position.projection; - if (!this.mark) { - p = this.position.projection = curve.project( - this.position.x, - this.position.y - ) - this.position.x = p.x; - this.position.y = p.y; - } - circle(p.x, p.y, 3); - } - - nextPanel(); - setStroke(`black`); - line(0,0,0,this.height); - - curve.drawSkeleton(`lightblue`); - curve.drawCurve(`lightblue`); - curve.points.forEach(p => circle(p.x, p.y, 2)); - - if (this.mark) { - let B = this.mark.B; - setFill(`black`); - text(`t = ${this.mark.t.toFixed(2)}`, B.x + 5, B.y + 10); - - let {A, C, S, E} = curve.getABC(this.mark.t, B); - setColor(`lightblue`); - line(S.x, S.y, E.x, E.y); - line(A.x, A.y, C.x, C.y); - circle(A.x, A.y, 3); - circle(B.x, B.y, 3); - circle(C.x, C.y, 3); - - if (this.currentPoint) { - let {A,B,C,S,E} = curve.getABC(this.mark.t, this.position); - setColor(`purple`); - line(A.x, A.y, C.x, C.y); - line(S.x, S.y, A.x, A.y); - line(E.x, E.y, A.x, A.y); - circle(A.x, A.y, 3); - circle(B.x, B.y, 3); - circle(C.x, C.y, 3); - - this.moulded = new Bezier(this, [S,A,E]); - } - } - - nextPanel(); - setStroke(`black`); - line(0,0,0,this.height); - - if (this.moulded) { - this.moulded.drawSkeleton(`lightblue`); - this.moulded.drawCurve(`black`); - this.moulded.points.forEach(p => circle(p.x, p.y, 2)); - } else { - curve.drawSkeleton(`lightblue`); - curve.drawCurve(`black`); - curve.points.forEach(p => circle(p.x, p.y, 2)); - } -} - -onMouseDown() { - if (this.currentPoint !== this.position) { - this.mark = false; - this.position.projection = false; - } - else if (this.position.projection) { - this.mark = { - B: this.position.projection, - t: this.position.projection.t - }; - } - redraw(); -} - -onMouseMove() { - if (!this.currentPoint && !this.mark) { - this.position.x = this.cursor.x; - this.position.y = this.cursor.y; - } - redraw(); -} - -onMouseUp() { - this.mark = false; - if (this.moulded) { - curve = this.moulded; - resetMovable(curve.points, [this.position]); - } - redraw(); -} diff --git a/docs/chapters/moulding/moulding.js b/docs/chapters/moulding/moulding.js new file mode 100644 index 00000000..2810082d --- /dev/null +++ b/docs/chapters/moulding/moulding.js @@ -0,0 +1,199 @@ +let curve; + +setup() { + setPanelCount(3); + const type = this.type = this.parameters.type ?? `quadratic`; + curve = type === `quadratic` ? Bezier.defaultQuadratic(this) : Bezier.defaultCubic(this); + this.position = {x:0,y:0}; + setMovable(curve.points, [this.position]); +} + +draw() { + clear(); + + curve.drawSkeleton(); + curve.drawCurve(); + curve.drawPoints(); + this.drawPosition(); + + nextPanel(); + + curve.drawSkeleton(`lightblue`); + curve.drawCurve(`lightblue`); + curve.points.forEach(p => circle(p.x, p.y, 2)); + this.drawMark(); + + nextPanel(); + + this.drawResult(); +} + +drawPosition() { + if (!this.position) return; + + setColor(`blue`); + let p = this.position.projection; + if (!this.mark) { + p = this.position.projection = curve.project( + this.position.x, + this.position.y + ) + this.position.x = p.x; + this.position.y = p.y; + } + circle(p.x, p.y, 3); +} + +drawMark() { + if (!this.mark) return; + if (this.type === `quadratic`) { + this.drawQuadraticMark(); + } else { + this.drawCubicMark(); + } +} + +drawQuadraticMark() { + let {B, t} = this.mark; + setFill(`black`); + text(`t = ${t.toFixed(2)}`, B.x + 5, B.y + 10); + + let {A, C, S, E} = curve.getABC(t, B); + setColor(`lightblue`); + line(S.x, S.y, E.x, E.y); + line(A.x, A.y, C.x, C.y); + + const lbl = [`A`, `B`, `C`]; + [A,B,C].forEach((p,i) => { + circle(p.x, p.y, 3); + text(lbl[i], p.x + 10, p.y); + }); + + if (this.currentPoint) { + let {A,B,C,S,E} = curve.getABC(t, this.position); + setColor(`purple`); + line(A.x, A.y, C.x, C.y); + line(S.x, S.y, A.x, A.y); + line(E.x, E.y, A.x, A.y); + [A,B,C].forEach(p => circle(p.x, p.y, 3)); + + noFill(); + circle(B.x, B.y, 5); + this.moulded = new Bezier(this, [S,A,E]); + } +} + +drawCubicMark() { + let {B, t, e1, e2} = this.mark; + let d1 = { x: e1.x - B.x, y: e1.y - B.y}; + let d2 = { x: e2.x - B.x, y: e2.y - B.y}; + setFill(`black`); + text(`t = ${t.toFixed(2)}`, B.x + 5, B.y + 10); + + let {A, C, S, E} = curve.getABC(this.mark.t, B); + let olen = dist(A.x, A.y, C.x, C.y); + setColor(`lightblue`); + line(S.x, S.y, E.x, E.y); + line(A.x, A.y, C.x, C.y); + + const lbl = [`A`, `B`, `C`, `e1`, `e2`]; + [A,B,C,e1,e2].forEach((p,i) => { + circle(p.x, p.y, 3); + text(lbl[i], p.x + 10, p.y); + }); + + if (this.currentPoint) { + let {A,B,C,S,E} = curve.getABC(this.mark.t, this.position); + + let nlen = dist(A.x, A.y, C.x, C.y); + let f = this.parameters.scaling ? nlen/olen : 1; + let e1 = { x: B.x + f * d1.x, y: B.y + f * d1.y }; + let e2 = { x: B.x + f * d2.x, y: B.y + f * d2.y }; + + setColor(`purple`); + line(A.x, A.y, C.x, C.y); + line(e1.x, e1.y, e2.x, e2.y); + + let v1 = { + x: A.x - (A.x - e1.x)/(1-t), + y: A.y - (A.y - e1.y)/(1-t) + }; + + let v2 = { + x: A.x - (A.x - e2.x)/t, + y: A.y - (A.y - e2.y)/t + }; + + let C1 = { + x: S.x + (v1.x - S.x) / t, + y: S.y + (v1.y - S.y) / t + }; + let C2 = { + x: E.x + (v2.x - E.x) / (1-t), + y: E.y + (v2.y - E.y) / (1-t) + }; + + [A,B,C,e1,e2,v1,v2,C1,C2].forEach(p => circle(p.x, p.y, 3)); + + noFill(); + circle(B.x, B.y, 5); + this.moulded = new Bezier(this, [S,C1,C2,E]); + } +} + +drawResult() { + let last = curve; + if (this.moulded) last = this.moulded; + + last.drawSkeleton(`lightblue`); + last.drawCurve(`black`); + last.points.forEach(p => circle(p.x, p.y, 2)); + + if (this.mark) { + let t = this.mark.t; + let B = last.get(t); + circle(B.x, B.y, 3); + setFill(`black`); + text(`t = ${this.mark.t.toFixed(2)}`, B.x + 5, B.y + 10); + } +} + +onMouseDown() { + if (this.currentPoint !== this.position) { + this.mark = false; + this.position.projection = false; + } + else if (this.position.projection) { + let t = this.position.projection.t; + if (this.type === `quadratic`) { + this.mark = { + t, B: this.position.projection, + }; + } else { + let struts = curve.getStrutPoints(t); + this.mark = { + t, B: this.position.projection, + e1: struts[7], + e2: struts[8] + }; + } + } + redraw(); +} + +onMouseMove() { + if (!this.currentPoint && !this.mark) { + this.position.x = this.cursor.x; + this.position.y = this.cursor.y; + } + redraw(); +} + +onMouseUp() { + this.mark = false; + if (this.moulded) { + curve = this.moulded; + resetMovable(curve.points, [this.position]); + } + redraw(); +} diff --git a/docs/chapters/projections/content.en-GB.md b/docs/chapters/projections/content.en-GB.md index 73f42928..0e7c544f 100644 --- a/docs/chapters/projections/content.en-GB.md +++ b/docs/chapters/projections/content.en-GB.md @@ -22,6 +22,6 @@ After this runs, we know that `LUT[i]` is the coordinate on the curve _in our LU This makes the interval we check smaller and smaller at each iteration, and we can keep running the three steps until the interval becomes so small as to lead to distances that are, for all intents and purposes, the same for all points. -So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bezier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds. +So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds. diff --git a/docs/images/chapters/curveintersection/02d70e27ba678db49a883afcc6264c9a.png b/docs/images/chapters/curveintersection/02d70e27ba678db49a883afcc6264c9a.png deleted file mode 100644 index 4dc1ed9c..00000000 Binary files a/docs/images/chapters/curveintersection/02d70e27ba678db49a883afcc6264c9a.png and /dev/null differ diff --git a/docs/images/chapters/curveintersection/3689ed0c15eace45a1f6ae03909ad8ed.png b/docs/images/chapters/curveintersection/3689ed0c15eace45a1f6ae03909ad8ed.png new file mode 100644 index 00000000..c48f987f Binary files /dev/null and b/docs/images/chapters/curveintersection/3689ed0c15eace45a1f6ae03909ad8ed.png differ diff --git a/docs/images/chapters/moulding/524206c49f317d27d8e07a310b24a7a3.svg b/docs/images/chapters/moulding/524206c49f317d27d8e07a310b24a7a3.svg deleted file mode 100644 index e577df1f..00000000 --- a/docs/images/chapters/moulding/524206c49f317d27d8e07a310b24a7a3.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/images/chapters/moulding/53cf83a9d9f2173d3212ca87f380f071.png b/docs/images/chapters/moulding/53cf83a9d9f2173d3212ca87f380f071.png new file mode 100644 index 00000000..0da8d01e Binary files /dev/null and b/docs/images/chapters/moulding/53cf83a9d9f2173d3212ca87f380f071.png differ diff --git a/docs/images/chapters/moulding/7bba0a4fd605e023cda922de125b3e32.svg b/docs/images/chapters/moulding/7159e1e6eeab38c5aba225fe7553dbe6.svg similarity index 63% rename from docs/images/chapters/moulding/7bba0a4fd605e023cda922de125b3e32.svg rename to docs/images/chapters/moulding/7159e1e6eeab38c5aba225fe7553dbe6.svg index a89f561a..529e0673 100644 --- a/docs/images/chapters/moulding/7bba0a4fd605e023cda922de125b3e32.svg +++ b/docs/images/chapters/moulding/7159e1e6eeab38c5aba225fe7553dbe6.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/images/chapters/moulding/7f080cfc5764282db126164d6705d83d.png b/docs/images/chapters/moulding/7f080cfc5764282db126164d6705d83d.png deleted file mode 100644 index 9be106b1..00000000 Binary files a/docs/images/chapters/moulding/7f080cfc5764282db126164d6705d83d.png and /dev/null differ diff --git a/docs/images/chapters/moulding/8e99a48e3227c97233c4933b5adcb080.svg b/docs/images/chapters/moulding/8e99a48e3227c97233c4933b5adcb080.svg new file mode 100644 index 00000000..db1e1d53 --- /dev/null +++ b/docs/images/chapters/moulding/8e99a48e3227c97233c4933b5adcb080.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/chapters/moulding/94f61d17f896aebddcf5a7c676aee7d1.svg b/docs/images/chapters/moulding/94f61d17f896aebddcf5a7c676aee7d1.svg deleted file mode 100644 index 1d1c2eb2..00000000 --- a/docs/images/chapters/moulding/94f61d17f896aebddcf5a7c676aee7d1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/images/chapters/moulding/9de1da4f061d324415321a5ef688f067.png b/docs/images/chapters/moulding/9de1da4f061d324415321a5ef688f067.png new file mode 100644 index 00000000..07b52bf4 Binary files /dev/null and b/docs/images/chapters/moulding/9de1da4f061d324415321a5ef688f067.png differ diff --git a/docs/images/chapters/moulding/a0df70324a97e780df772a837c5f1c31.png b/docs/images/chapters/moulding/a0df70324a97e780df772a837c5f1c31.png new file mode 100644 index 00000000..07b52bf4 Binary files /dev/null and b/docs/images/chapters/moulding/a0df70324a97e780df772a837c5f1c31.png differ diff --git a/docs/images/chapters/moulding/e787b4456ccf03c0481f1731c5c32add.svg b/docs/images/chapters/moulding/e787b4456ccf03c0481f1731c5c32add.svg new file mode 100644 index 00000000..777cef71 --- /dev/null +++ b/docs/images/chapters/moulding/e787b4456ccf03c0481f1731c5c32add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/chapters/moulding/fbc7d78dd768eec4314eb4b5320f2ce5.png b/docs/images/chapters/moulding/fbc7d78dd768eec4314eb4b5320f2ce5.png new file mode 100644 index 00000000..1c013908 Binary files /dev/null and b/docs/images/chapters/moulding/fbc7d78dd768eec4314eb4b5320f2ce5.png differ diff --git a/docs/index.html b/docs/index.html index 89470acf..7a4c1adc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1540,7 +1540,7 @@ lli = function(line1, line2):

(can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)

- + Scripts are disabled. Showing fallback image. @@ -1619,7 +1619,7 @@ for (coordinate, index) in LUT:
  • we then check which of these five points is the closest to our original point p, and then repeat step 1 with the points before and after the closest point we just found.
  • This makes the interval we check smaller and smaller at each iteration, and we can keep running the three steps until the interval becomes so small as to lead to distances that are, for all intents and purposes, the same for all points.

    -

    So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bezier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.

    +

    So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.

    @@ -1630,23 +1630,43 @@ for (coordinate, index) in LUT:

    Manipulating a curve

    Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the t-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.

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

    Click-dragging the curve itself shows what we're using to compute the new coordinates: while dragging you will see the original point B and its corresponding t-value, the original point C for that t-value, as well as the new point B' based on the mouse cursor. Since we know the t-value for this configuration, we can compute the ABC ratio for this configuration, and we know that our new point A' should like at a distance:

    - -

    For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point. For cubic curves, we need to do a little more work:

    - +

    Click-dragging a point on the curve shows what we're using to compute the new coordinates: while dragging you will see the original point B and its corresponding t-value, and the original points A and C for that t-value, in light coloring, as well as the new A', B', and C' (although of course the C coordinates are the same ones, because that's the defining feature of point C) based on where you're dragging point B to, in purple.

    +

    Since we know the new point B', and the "new" point C' as well as the t value, we know our new point A' has to be:

    + +

    For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point.

    +

    For cubic curves, we need to do a little more work, because while computing a new A' is exactly the same as before, we're not quite done once we've done so. For cubic curves, B has not just an associated t value, but also two associated "side" values. Let's revisit the graphic from the chapter on de Casteljau's algorithm, to see what we mean:

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

    In addition to the A, B, and C values, we also see the points e1 and e2, without which constructing our de Casteljau "strut lines" becomes very difficult indeed; as well as the points v1 and v2, which we can construct when we know our ABC values enriched with e1 and e2:

    + +

    After which computing the new control points is straight-forward:

    + +

    So let's put that into practice:

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

    So that looks pretty good, but you may not like having e1 and e2 stay the same distances away from B' while moving the point around. An alternative is to scale the distances of e1 and e2 to B' to match the scaling that A--C undergoes as A'--C' - whether this looks better or not depends somewhat on your intention as programmer or user, of course, so the following graphic applies this scaling, but it's up to you to decide whether or not that looks better (or, more appropriately, under which circumstances you might want to apply this scaling vs. when you might not):

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

    To help understand what's going on, the cubic graphic shows the full de Casteljau construction "hull" when repositioning point B. We compute A' in exactly the same way as before, but we also record the final strut line that forms B in the original curve. Given A', B', and the endpoints e1 and e2 of the strut line relative to B', we can now compute where the new control points should be. Remember that B' lies on line e1--e2 at a distance t, because that's how Bézier curves work. In the same manner, we know the distance A--e1 is only line-interval [0,t] of the full segment, and A--e2 is only line-interval [t,1], so constructing the new control points is fairly easy.

    -

    First, we construct the one-level-of-de-Casteljau-up points:

    - -

    And then we can compute the new control points:

    - -

    And that's cubic curve manipulation.

    diff --git a/docs/ja-JP/index.html b/docs/ja-JP/index.html index a50667df..c31691cc 100644 --- a/docs/ja-JP/index.html +++ b/docs/ja-JP/index.html @@ -1537,7 +1537,7 @@ lli = function(line1, line2):

    (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)

    - + Scripts are disabled. Showing fallback image. @@ -1616,7 +1616,7 @@ for (coordinate, index) in LUT:
  • we then check which of these five points is the closest to our original point p, and then repeat step 1 with the points before and after the closest point we just found.
  • This makes the interval we check smaller and smaller at each iteration, and we can keep running the three steps until the interval becomes so small as to lead to distances that are, for all intents and purposes, the same for all points.

    -

    So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bezier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.

    +

    So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.

    @@ -1627,23 +1627,43 @@ for (coordinate, index) in LUT:

    Manipulating a curve

    Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the t-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.

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

    Click-dragging the curve itself shows what we're using to compute the new coordinates: while dragging you will see the original point B and its corresponding t-value, the original point C for that t-value, as well as the new point B' based on the mouse cursor. Since we know the t-value for this configuration, we can compute the ABC ratio for this configuration, and we know that our new point A' should like at a distance:

    - -

    For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point. For cubic curves, we need to do a little more work:

    - +

    Click-dragging a point on the curve shows what we're using to compute the new coordinates: while dragging you will see the original point B and its corresponding t-value, and the original points A and C for that t-value, in light coloring, as well as the new A', B', and C' (although of course the C coordinates are the same ones, because that's the defining feature of point C) based on where you're dragging point B to, in purple.

    +

    Since we know the new point B', and the "new" point C' as well as the t value, we know our new point A' has to be:

    + +

    For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point.

    +

    For cubic curves, we need to do a little more work, because while computing a new A' is exactly the same as before, we're not quite done once we've done so. For cubic curves, B has not just an associated t value, but also two associated "side" values. Let's revisit the graphic from the chapter on de Casteljau's algorithm, to see what we mean:

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

    In addition to the A, B, and C values, we also see the points e1 and e2, without which constructing our de Casteljau "strut lines" becomes very difficult indeed; as well as the points v1 and v2, which we can construct when we know our ABC values enriched with e1 and e2:

    + +

    After which computing the new control points is straight-forward:

    + +

    So let's put that into practice:

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

    So that looks pretty good, but you may not like having e1 and e2 stay the same distances away from B' while moving the point around. An alternative is to scale the distances of e1 and e2 to B' to match the scaling that A--C undergoes as A'--C' - whether this looks better or not depends somewhat on your intention as programmer or user, of course, so the following graphic applies this scaling, but it's up to you to decide whether or not that looks better (or, more appropriately, under which circumstances you might want to apply this scaling vs. when you might not):

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

    To help understand what's going on, the cubic graphic shows the full de Casteljau construction "hull" when repositioning point B. We compute A' in exactly the same way as before, but we also record the final strut line that forms B in the original curve. Given A', B', and the endpoints e1 and e2 of the strut line relative to B', we can now compute where the new control points should be. Remember that B' lies on line e1--e2 at a distance t, because that's how Bézier curves work. In the same manner, we know the distance A--e1 is only line-interval [0,t] of the full segment, and A--e2 is only line-interval [t,1], so constructing the new control points is fairly easy.

    -

    First, we construct the one-level-of-de-Casteljau-up points:

    - -

    And then we can compute the new control points:

    - -

    And that's cubic curve manipulation.

    diff --git a/docs/js/custom-element/api/graphics-api.js b/docs/js/custom-element/api/graphics-api.js index bfa9a4f8..57007b89 100644 --- a/docs/js/custom-element/api/graphics-api.js +++ b/docs/js/custom-element/api/graphics-api.js @@ -142,8 +142,10 @@ class GraphicsAPI extends BaseAPI { /** * Multi-panel graphics: set up (0,0) to the next panel's start */ - nextPanel(c) { + nextPanel(color = `black`) { this.translate(this.panelWidth, 0); + this.setStroke(color); + this.line(0, 0, 0, this.height); } /** diff --git a/docs/js/custom-element/api/types/bezier.js b/docs/js/custom-element/api/types/bezier.js index ff3165c0..be10c47f 100644 --- a/docs/js/custom-element/api/types/bezier.js +++ b/docs/js/custom-element/api/types/bezier.js @@ -118,7 +118,7 @@ class Bezier extends Original { return p; } - drawStruts(t, color = `black`) { + drawStruts(t, color = `black`, showpoints = true) { const p = t.forEach ? t : this.getStrutPoints(t); const api = this.api; @@ -134,7 +134,7 @@ class Bezier extends Original { for (let i = 0; i < n; i++) { let pt = p[s + i]; api.vertex(pt.x, pt.y); - api.circle(pt.x, pt.y, 5); + if (showpoints) api.circle(pt.x, pt.y, 5); } api.end(); s += n; diff --git a/docs/zh-CN/index.html b/docs/zh-CN/index.html index b17ada6e..e8862b3b 100644 --- a/docs/zh-CN/index.html +++ b/docs/zh-CN/index.html @@ -1531,7 +1531,7 @@ lli = function(line1, line2):

    (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)

    - + Scripts are disabled. Showing fallback image. @@ -1610,7 +1610,7 @@ for (coordinate, index) in LUT:
  • we then check which of these five points is the closest to our original point p, and then repeat step 1 with the points before and after the closest point we just found.
  • This makes the interval we check smaller and smaller at each iteration, and we can keep running the three steps until the interval becomes so small as to lead to distances that are, for all intents and purposes, the same for all points.

    -

    So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bezier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.

    +

    So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.

    @@ -1621,23 +1621,43 @@ for (coordinate, index) in LUT:

    Manipulating a curve

    Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the t-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.

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

    Click-dragging the curve itself shows what we're using to compute the new coordinates: while dragging you will see the original point B and its corresponding t-value, the original point C for that t-value, as well as the new point B' based on the mouse cursor. Since we know the t-value for this configuration, we can compute the ABC ratio for this configuration, and we know that our new point A' should like at a distance:

    - -

    For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point. For cubic curves, we need to do a little more work:

    - +

    Click-dragging a point on the curve shows what we're using to compute the new coordinates: while dragging you will see the original point B and its corresponding t-value, and the original points A and C for that t-value, in light coloring, as well as the new A', B', and C' (although of course the C coordinates are the same ones, because that's the defining feature of point C) based on where you're dragging point B to, in purple.

    +

    Since we know the new point B', and the "new" point C' as well as the t value, we know our new point A' has to be:

    + +

    For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point.

    +

    For cubic curves, we need to do a little more work, because while computing a new A' is exactly the same as before, we're not quite done once we've done so. For cubic curves, B has not just an associated t value, but also two associated "side" values. Let's revisit the graphic from the chapter on de Casteljau's algorithm, to see what we mean:

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

    In addition to the A, B, and C values, we also see the points e1 and e2, without which constructing our de Casteljau "strut lines" becomes very difficult indeed; as well as the points v1 and v2, which we can construct when we know our ABC values enriched with e1 and e2:

    + +

    After which computing the new control points is straight-forward:

    + +

    So let's put that into practice:

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

    So that looks pretty good, but you may not like having e1 and e2 stay the same distances away from B' while moving the point around. An alternative is to scale the distances of e1 and e2 to B' to match the scaling that A--C undergoes as A'--C' - whether this looks better or not depends somewhat on your intention as programmer or user, of course, so the following graphic applies this scaling, but it's up to you to decide whether or not that looks better (or, more appropriately, under which circumstances you might want to apply this scaling vs. when you might not):

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

    To help understand what's going on, the cubic graphic shows the full de Casteljau construction "hull" when repositioning point B. We compute A' in exactly the same way as before, but we also record the final strut line that forms B in the original curve. Given A', B', and the endpoints e1 and e2 of the strut line relative to B', we can now compute where the new control points should be. Remember that B' lies on line e1--e2 at a distance t, because that's how Bézier curves work. In the same manner, we know the distance A--e1 is only line-interval [0,t] of the full segment, and A--e2 is only line-interval [t,1], so constructing the new control points is fairly easy.

    -

    First, we construct the one-level-of-de-Casteljau-up points:

    - -

    And then we can compute the new control points:

    - -

    And that's cubic curve manipulation.