diff --git a/docs/chapters/arclength/draw-slices.js b/docs/chapters/arclength/draw-slices.js index aec3ccb2..ac1c0bc9 100644 --- a/docs/chapters/arclength/draw-slices.js +++ b/docs/chapters/arclength/draw-slices.js @@ -24,7 +24,7 @@ draw() { end(); setFill(`black`); - text(`Approximating with ${this.steps} strips: ${computedArea} (true area: ${trueArea}`, w/2, h-10, CENTER); + text(`Approximating with ${this.steps} strips : ${computedArea} (true area: ${trueArea})`, w/2, h-10, CENTER); } drawSlices(w, h, a, n) { diff --git a/docs/chapters/arclengthapprox/content.en-GB.md b/docs/chapters/arclengthapprox/content.en-GB.md index ace9e2d4..604885e7 100644 --- a/docs/chapters/arclengthapprox/content.en-GB.md +++ b/docs/chapters/arclengthapprox/content.en-GB.md @@ -7,11 +7,11 @@ If we combine the work done in the previous sections on curve flattening and arc
- + - +
diff --git a/docs/chapters/bsplines/basic.js b/docs/chapters/bsplines/basic.js index 8251fc19..000fe549 100644 --- a/docs/chapters/bsplines/basic.js +++ b/docs/chapters/bsplines/basic.js @@ -19,7 +19,7 @@ draw() { setStroke(`lightgrey`); drawGrid(20); - setStroke(`#CC00CC99`); + setStroke(this.parameters.showCurves ? `transparent` : `#CC00CC99`); for (let i=0, e=points.length-1, p, n; i circle(p.x, p.y, 3)); + points.forEach((p,i) => { + circle(p.x, p.y, 3) + text(`${i+1}`, p.x+5, p.y+5); + }); this.drawSplineData(); } @@ -36,10 +39,17 @@ drawSplineData() { // we'll need at least 4 points if (points.length <= 3) return; + if (this.parameters.showCurves) { + for(let i=0; i vertex(p.x, p.y)); end(); diff --git a/docs/chapters/bsplines/content.en-GB.md b/docs/chapters/bsplines/content.en-GB.md index ccf52a68..98b099fb 100644 --- a/docs/chapters/bsplines/content.en-GB.md +++ b/docs/chapters/bsplines/content.en-GB.md @@ -15,6 +15,10 @@ Consider the difference to be this: - for Bézier curves, the curve is defined as an interpolation of points, but: - for B-Splines, the curve is defined as an interpolation of *curves*. +In fact, let's look at that again, but this time with the base curves shown, too. Each consecutive four points defined one curve: + + + In order to make this interpolation of curves work, the maths is necessarily more complex than the maths for Bézier curves, so let's have a look at how things work. diff --git a/docs/chapters/bsplines/interpolation.js b/docs/chapters/bsplines/interpolation.js index 48eed69d..5954d465 100644 --- a/docs/chapters/bsplines/interpolation.js +++ b/docs/chapters/bsplines/interpolation.js @@ -31,7 +31,7 @@ setup() { knots = new BSpline(this, points).formKnots(); let min = 0, max = knots.length-1; knots.forEach((_,i) => { - addSlider(`slide-control`, false, min, max, 0.01, i, (v) => this.setKnotValue(i,v)); + addSlider(`slide-control`, `!knot ${i+1}`, min, max, 0.01, i, (v) => this.setKnotValue(i,v)); }); } diff --git a/docs/chapters/bsplines/rational-uniform.js b/docs/chapters/bsplines/rational-uniform.js index 195ed98a..6d15395a 100644 --- a/docs/chapters/bsplines/rational-uniform.js +++ b/docs/chapters/bsplines/rational-uniform.js @@ -12,7 +12,7 @@ setup() { weights = new BSpline(this, points, !!this.parameters.open).formWeights(); points.forEach((_,i) => { - addSlider(`slide-control`, false, 0, 10, 0.1, i%2===1? 2 : 6, v => this.setWeight(i, v)); + addSlider(`slide-control`, `!weight ${i+1}`, 0, 10, 0.1, i%2===1? 2 : 6, v => this.setWeight(i, v)); }); points = points.concat(points.slice(0,3)); diff --git a/docs/chapters/bsplines/reduced.js b/docs/chapters/bsplines/reduced.js index d9835f33..b69f439a 100644 --- a/docs/chapters/bsplines/reduced.js +++ b/docs/chapters/bsplines/reduced.js @@ -20,7 +20,7 @@ setup() { let min=0, max=knots.length-1; knots.forEach((_,i) => { - addSlider(`slide-control`, false, min, max, 0.01, knots[i], v => this.setKnotValue(i, v)); + addSlider(`slide-control`, `!knot ${i+1}`, min, max, 0.01, knots[i], v => this.setKnotValue(i, v)); }); } diff --git a/docs/chapters/bsplines/uniform.js b/docs/chapters/bsplines/uniform.js index 578408f3..2b39520c 100644 --- a/docs/chapters/bsplines/uniform.js +++ b/docs/chapters/bsplines/uniform.js @@ -12,7 +12,7 @@ setup() { knots = new BSpline(this, points).formKnots(!!this.parameters.open); let min=0, max=knots.length-1; knots.forEach((_,i) => { - addSlider(`slide-control`, false, min, max, 0.01, knots[i], v => this.setKnotValue(i, v)); + addSlider(`slide-control`, `!knot ${i+1}`, min, max, 0.01, knots[i], v => this.setKnotValue(i, v)); }); } diff --git a/docs/chapters/catmullconv/catmull-rom.js b/docs/chapters/catmullconv/catmull-rom.js index e377fa72..fab34c05 100644 --- a/docs/chapters/catmullconv/catmull-rom.js +++ b/docs/chapters/catmullconv/catmull-rom.js @@ -21,6 +21,8 @@ transformTension(v) { draw() { clear(); + setStroke(`lightgrey`); + drawGrid(20); const p = points, n = points.length; for (let i=1, e=n-2; i -
+ You'll note there is a convenient "toggle" buttons that lets you toggle between equidistant `t` values, and distance ratio along the polygon formed by the points. Arguably more interesting is that once you have points to abstract a curve, you also get direct control over the time values through sliders for each, because if the time values are our degree of freedom, you should be able to freely manipulate them and see what the effect on your curve is. diff --git a/docs/chapters/curvefitting/curve-fitting.js b/docs/chapters/curvefitting/curve-fitting.js index 6078b57a..c2e37f65 100644 --- a/docs/chapters/curvefitting/curve-fitting.js +++ b/docs/chapters/curvefitting/curve-fitting.js @@ -110,7 +110,7 @@ updateSliders() { const l = points.length-1; if (l >= 2) { points.forEach((_,i) => { - addSlider(`slide-control`, false, 0, 1, 0.01, i/l, v => this.setTvalue(i, v)); + addSlider(`slide-control`, `!t${i}`, 0, 1, 0.01, i/l, v => this.setTvalue(i, v)); }); } this.label = `Using equidistant t values`; diff --git a/docs/chapters/curveintersection/content.en-GB.md b/docs/chapters/curveintersection/content.en-GB.md index 20902dd4..44d10f4b 100644 --- a/docs/chapters/curveintersection/content.en-GB.md +++ b/docs/chapters/curveintersection/content.en-GB.md @@ -17,9 +17,9 @@ The following graphic applies this algorithm to a pair of cubic curves, one step (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!) - + Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at `t=0.5` first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points. diff --git a/docs/chapters/curveintersection/curve-curve.js b/docs/chapters/curveintersection/curve-curve.js index 4a95ebeb..253793a3 100644 --- a/docs/chapters/curveintersection/curve-curve.js +++ b/docs/chapters/curveintersection/curve-curve.js @@ -4,7 +4,7 @@ setup() { setPanelCount(3); this.pairReset(); this.setupEventListening(); - setSlider(`.slide-control`, `epsilon`, 1.0, v => this.reset(v)); + setSlider(`.slide-control`, `threshold`, 1.0, v => this.reset(v)); } pairReset() { @@ -52,7 +52,7 @@ draw() { this.drawIteration(); setFill(`black`); - let information = `Initial curves, threshold = ${this.epsilon}px` + let information = `Initial curves, threshold = ${this.threshold}px` if (this.step) { information = `Curve collection at iteration ${this.step}`; } @@ -72,7 +72,7 @@ drawIteration() { const pairs = this.pairs; this.pairs = []; pairs.forEach(pair => { - if(pair[0].length() < this.epsilon && pair[1].length() < this.epsilon) + if(pair[0].length() < this.threshold && pair[1].length() < this.threshold) return this.finals.push(pair); // split two curves into four curves diff --git a/docs/chapters/pointvectors/content.en-GB.md b/docs/chapters/pointvectors/content.en-GB.md index 8be2cf24..ad74fa2b 100644 --- a/docs/chapters/pointvectors/content.en-GB.md +++ b/docs/chapters/pointvectors/content.en-GB.md @@ -73,6 +73,6 @@ treated as a sequence of three (elementary) shear operations. When we combine th The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as *t*-intervals, not spaced equidistant).
- - + +
diff --git a/docs/chapters/pointvectors/cubic.js b/docs/chapters/pointvectors/pointvectors.js similarity index 58% rename from docs/chapters/pointvectors/cubic.js rename to docs/chapters/pointvectors/pointvectors.js index 150369ae..f80499e8 100644 --- a/docs/chapters/pointvectors/cubic.js +++ b/docs/chapters/pointvectors/pointvectors.js @@ -1,7 +1,16 @@ let curve; setup() { - curve = Bezier.defaultCubic(this); + const type = this.type = this.parameters.type ?? `quadratic`; + if (type === `quadratic`) { + curve = Bezier.defaultQuadratic(this); + } else { + curve = Bezier.defaultCubic(this); + curve.points[0].x = 30; + curve.points[0].y = 230; + curve.points[1].x = 75; + curve.points[1].y = 50; + } setMovable(curve.points); } @@ -15,7 +24,7 @@ draw() { for(let i=0; i<=10; i++) { let t = i/10.0; let p = curve.get(t); - let d = this.getDerivative(t, pts); + let d = this.type === `quadratic` ? this.getQuadraticDerivative(t, pts) : this.getCubicDerivative(t, pts); let m = sqrt(d.x*d.x + d.y*d.y); d = { x: d.x/m, y: d.y/m }; @@ -27,9 +36,6 @@ draw() { setStroke(`red`); line(p.x, p.y, p.x + n.x*f, p.y + n.y*f); - setStroke(`purple`); - line(p.x, p.y, p.x - n.x*f, p.y - n.y*f); - setStroke(`black`); circle(p.x, p.y, 3); } @@ -37,7 +43,25 @@ draw() { curve.drawPoints(); } -getDerivative(t, points) { +getQuadraticDerivative(t, points) { + let mt = (1 - t), d = [ + { + x: 2 * (points[1].x - points[0].x), + y: 2 * (points[1].y - points[0].y) + }, + { + x: 2 * (points[2].x - points[1].x), + y: 2 * (points[2].y - points[1].y) + } + ]; + + return { + x: mt * d[0].x + t * d[1].x, + y: mt * d[0].y + t * d[1].y + }; +} + +getCubicDerivative(t, points) { let mt = (1 - t), a = mt*mt, b = mt*t, c = t*t, d = [ { x: 3 * (points[1].x - points[0].x), diff --git a/docs/chapters/pointvectors/quadratic.js b/docs/chapters/pointvectors/quadratic.js deleted file mode 100644 index 4c8b64d4..00000000 --- a/docs/chapters/pointvectors/quadratic.js +++ /dev/null @@ -1,61 +0,0 @@ -let curve; - -setup() { - curve = Bezier.defaultQuadratic(this); - setMovable(curve.points); -} - -draw() { - clear(); - curve.drawSkeleton(); - - const pts = curve.points; - const f = 15; - - for(let i=0; i<=10; i++) { - let t = i/10.0; - let p = curve.get(t); - let d = this.getDerivative(t, pts); - - let m = sqrt(d.x*d.x + d.y*d.y); - d = { x: d.x/m, y: d.y/m }; - let n = this.getNormal(t, d); - - setStroke(`blue`); - line(p.x, p.y, p.x + d.x*f, p.y + d.y*f); - - setStroke(`red`); - line(p.x, p.y, p.x + n.x*f, p.y + n.y*f); - - setStroke(`purple`); - line(p.x, p.y, p.x - n.x*f, p.y - n.y*f); - - setStroke(`black`); - circle(p.x, p.y, 3); - } - - curve.drawPoints(); -} - -getDerivative(t, points) { - let mt = (1 - t), d = [ - { - x: 2 * (points[1].x - points[0].x), - y: 2 * (points[1].y - points[0].y) - }, - { - x: 2 * (points[2].x - points[1].x), - y: 2 * (points[2].y - points[1].y) - } - ]; - - return { - x: mt * d[0].x + t * d[1].x, - y: mt * d[0].y + t * d[1].y - }; -} - -getNormal(t, d) { - const q = sqrt(d.x * d.x + d.y * d.y); - return { x: -d.y / q, y: d.x / q }; -} diff --git a/docs/chapters/pointvectors3d/content.en-GB.md b/docs/chapters/pointvectors3d/content.en-GB.md index 46aa1514..4b52497b 100644 --- a/docs/chapters/pointvectors3d/content.en-GB.md +++ b/docs/chapters/pointvectors3d/content.en-GB.md @@ -28,7 +28,7 @@ And then we're done, we found "the" normal vector for a 3D curve. Let's see what -However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around" around between t=0.5 and t=0.9... Why is doing that? +However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around the curve" between t=0.65 and t=0.75... Why is it doing that? As it turns out, it's doing that because that's how the maths works, and that's the problem with Frenet normals: while they are "mathematically correct", they are "practically problematic", and so for any kind of graphics work what we really want is a way to compute normals that just... look good. diff --git a/docs/chapters/polybezier/poly.js b/docs/chapters/polybezier/poly.js index a174f3c1..eb37c9ab 100644 --- a/docs/chapters/polybezier/poly.js +++ b/docs/chapters/polybezier/poly.js @@ -76,14 +76,14 @@ movePointsQuadratic(i, link) { 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; + pl.x += this.cursor.diff.x; + pl.y += this.cursor.diff.y; + pr.x += this.cursor.diff.x; + pr.y += this.cursor.diff.y; + ppl.x -= this.cursor.diff.x; + ppl.y -= this.cursor.diff.y; + ppr.x -= this.cursor.diff.x; + ppr.y -= this.cursor.diff.y; this.problem = points[(l+i+4)%l]; if (ppr.y === this.problem.y) { @@ -129,11 +129,12 @@ 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; + left.x += this.cursor.diff.x; + left.y += this.cursor.diff.y; + let right = points[(l+i+1)%l]; - right.x += this.currentPoint.diff.x; - right.y += this.currentPoint.diff.y; + right.x += this.cursor.diff.x; + right.y += this.cursor.diff.y; } if (i%3 > 0) { diff --git a/docs/chapters/projections/content.en-GB.md b/docs/chapters/projections/content.en-GB.md index 7b8ad96c..8d181ea8 100644 --- a/docs/chapters/projections/content.en-GB.md +++ b/docs/chapters/projections/content.en-GB.md @@ -24,4 +24,4 @@ This makes the interval we check smaller and smaller at each iteration, and we c 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/chapters/projections/project.js b/docs/chapters/projections/project.js index f2d71fe4..ba659ef3 100644 --- a/docs/chapters/projections/project.js +++ b/docs/chapters/projections/project.js @@ -2,16 +2,16 @@ let curve; setup() { curve = new Bezier(this, [ - {x:248,y:188}, - {x:218,y:294}, - {x:45,y:290}, - {x:12,y:236}, - {x:14,y:82}, - {x:186,y:177}, - {x:221,y:90}, - {x:18,y:156}, - {x:34,y:57}, - {x:198,y:18} + {x:288,y:218}, + {x:258,y:334}, + {x:85,y:330}, + {x:52,y:276}, + {x:54,y:122}, + {x:216,y:217}, + {x:261,y:130}, + {x:58,y:196}, + {x:84,y:97}, + {x:238,y:58} ]); this.cursor.x = 280; diff --git a/docs/chapters/reordering/reorder.js b/docs/chapters/reordering/reorder.js index e2e12dec..3bf5c265 100644 --- a/docs/chapters/reordering/reorder.js +++ b/docs/chapters/reordering/reorder.js @@ -23,6 +23,8 @@ bindButtons() { draw() { clear(); + setStroke(`lightgrey`); + drawGrid(20); this.drawCurve(); } @@ -31,6 +33,7 @@ drawCurve() { // And the canvas only does 2nd and 3rd - we use de Casteljau's algorithm: start(); noFill(); + setStroke(`black`); for(let t=0; t<=1; t+=0.01) { let q = JSON.parse(JSON.stringify(points)); while(q.length > 1) { @@ -113,9 +116,9 @@ lower() { // And then we map our k-order list of coordinates // to an n-order list of coordinates, instead: const V = Mi.multiply(Mt); - const x = new Matrix(pts.map(p => [p.x])); + const x = new Matrix(points.map(p => [p.x])); const nx = V.multiply(x); - const y = new Matrix(pts.map(p => [p.y])); + const y = new Matrix(points.map(p => [p.y])); const ny = V.multiply(y); points = nx.data.map((x,i) => ({ diff --git a/docs/chapters/weightcontrol/content.en-GB.md b/docs/chapters/weightcontrol/content.en-GB.md index 18c8cce6..f7c41dd5 100644 --- a/docs/chapters/weightcontrol/content.en-GB.md +++ b/docs/chapters/weightcontrol/content.en-GB.md @@ -23,10 +23,10 @@ This does something unexpected: it turns our polynomial into something that _isn But the best way to show what this does is to do literally that: let's look at the effect of "rationalising" our Bézier curves using an interactive graphic for a rationalised curves. The following graphic shows the Bézier curve from the previous section, "enriched" with ratio factors for each coordinate. The closer to zero we set one or more terms, the less relative influence the associated coordinate exerts on the curve (and of course the higher we set them, the more influence they have). Try to change the values and see how it affects what gets drawn: - ratio 1 1.0
- ratio 2 1.0
- ratio 3 1.0
- ratio 4 1.0 + + + +
You can think of the ratio values as each coordinate's "gravity": the higher the gravity, the closer to that coordinate the curve will want to be. You'll also notice that if you simply increase or decrease all the ratios by the same amount, nothing changes... much like with gravity, if the relative strengths stay the same, nothing really changes. The values define each coordinate's influence _relative to all other points_. diff --git a/docs/chapters/weightcontrol/rational.js b/docs/chapters/weightcontrol/rational.js index 23a2f30e..3814c902 100644 --- a/docs/chapters/weightcontrol/rational.js +++ b/docs/chapters/weightcontrol/rational.js @@ -1,24 +1,17 @@ -let curve; +let curve, ratios=[1, 1, 1, 1]; setup() { curve = Bezier.defaultCubic(this); setMovable(curve.points); + curve.points.forEach((p,i) => { + setSlider(`.ratio-${i+1}`, `!ratio-${i+1}`, 1, v => this.setRatio(i,v)) + }); +} - const inputs = findAll(`input[type=range]`); - if (inputs) { - const ratios = inputs.map(i => parseFloat(i.value)); - curve.setRatios(ratios); - - inputs.forEach((input,pos) => { - const span = input.nextSibling; - input.listen(`input`, evt => { - const value = parseFloat(evt.target.value); - span.textContent = ratios[pos] = value; - curve.update(); - this.redraw(); - }); - }) - } +setRatio(i, v) { + ratios[i] = v; + curve.setRatios(ratios); + redraw(); } draw() { diff --git a/docs/images/chapters/arclength/0d7138b99f5986332a050a8479eefa57.png b/docs/images/chapters/arclength/0d7138b99f5986332a050a8479eefa57.png deleted file mode 100644 index 124e2dbd..00000000 Binary files a/docs/images/chapters/arclength/0d7138b99f5986332a050a8479eefa57.png and /dev/null differ diff --git a/docs/images/chapters/arclength/475547c773a7279dc037c9ced2c8c6dc.png b/docs/images/chapters/arclength/475547c773a7279dc037c9ced2c8c6dc.png deleted file mode 100644 index f3cff42a..00000000 Binary files a/docs/images/chapters/arclength/475547c773a7279dc037c9ced2c8c6dc.png and /dev/null differ diff --git a/docs/images/chapters/arclength/4b5d220d02b08f6c9aa19389255ef8bb.png b/docs/images/chapters/arclength/4b5d220d02b08f6c9aa19389255ef8bb.png new file mode 100644 index 00000000..48b1078d Binary files /dev/null and b/docs/images/chapters/arclength/4b5d220d02b08f6c9aa19389255ef8bb.png differ diff --git a/docs/images/chapters/arclength/4bffba7dda2a3556cf5b2ae7392083c6.png b/docs/images/chapters/arclength/4bffba7dda2a3556cf5b2ae7392083c6.png new file mode 100644 index 00000000..a672359d Binary files /dev/null and b/docs/images/chapters/arclength/4bffba7dda2a3556cf5b2ae7392083c6.png differ diff --git a/docs/images/chapters/arclength/580c33f599b70de44b17c546098508aa.png b/docs/images/chapters/arclength/580c33f599b70de44b17c546098508aa.png deleted file mode 100644 index 5064860a..00000000 Binary files a/docs/images/chapters/arclength/580c33f599b70de44b17c546098508aa.png and /dev/null differ diff --git a/docs/images/chapters/arclength/dc74a2f2da19470b8d721ece5f3ce268.png b/docs/images/chapters/arclength/dc74a2f2da19470b8d721ece5f3ce268.png new file mode 100644 index 00000000..ec30941d Binary files /dev/null and b/docs/images/chapters/arclength/dc74a2f2da19470b8d721ece5f3ce268.png differ diff --git a/docs/images/chapters/bsplines/7ac6e46280de14dce141fb073777dc8e.png b/docs/images/chapters/bsplines/3cfaf7bf5e950072437cfe70391155fa.png similarity index 100% rename from docs/images/chapters/bsplines/7ac6e46280de14dce141fb073777dc8e.png rename to docs/images/chapters/bsplines/3cfaf7bf5e950072437cfe70391155fa.png diff --git a/docs/images/chapters/bsplines/3f66c97dcdd56d201c2acda3bd403bbf.png b/docs/images/chapters/bsplines/3f66c97dcdd56d201c2acda3bd403bbf.png new file mode 100644 index 00000000..5e1e92a3 Binary files /dev/null and b/docs/images/chapters/bsplines/3f66c97dcdd56d201c2acda3bd403bbf.png differ diff --git a/docs/images/chapters/bsplines/610232b8f7ce7ef3f0f012d55e385c6d.png b/docs/images/chapters/bsplines/610232b8f7ce7ef3f0f012d55e385c6d.png deleted file mode 100644 index 8c1e929f..00000000 Binary files a/docs/images/chapters/bsplines/610232b8f7ce7ef3f0f012d55e385c6d.png and /dev/null differ diff --git a/docs/images/chapters/bsplines/984c86b3b357c799aaab5a5fbd48d599.png b/docs/images/chapters/bsplines/6ba59877389e2c856234403ecbd953f9.png similarity index 100% rename from docs/images/chapters/bsplines/984c86b3b357c799aaab5a5fbd48d599.png rename to docs/images/chapters/bsplines/6ba59877389e2c856234403ecbd953f9.png diff --git a/docs/images/chapters/bsplines/4c71f82901edc1a0acae31cf5be12c65.png b/docs/images/chapters/bsplines/9c4bbb753918e3cb8cbc5a770a2af9ee.png similarity index 100% rename from docs/images/chapters/bsplines/4c71f82901edc1a0acae31cf5be12c65.png rename to docs/images/chapters/bsplines/9c4bbb753918e3cb8cbc5a770a2af9ee.png diff --git a/docs/images/chapters/bsplines/84bd623b9d3d8bf01a656f49c17079b8.png b/docs/images/chapters/bsplines/bd03680e4661ae7f4d687b2f229d2495.png similarity index 100% rename from docs/images/chapters/bsplines/84bd623b9d3d8bf01a656f49c17079b8.png rename to docs/images/chapters/bsplines/bd03680e4661ae7f4d687b2f229d2495.png diff --git a/docs/images/chapters/bsplines/d9a99344a5de4b5be77824f8f8caa707.png b/docs/images/chapters/bsplines/d9a99344a5de4b5be77824f8f8caa707.png new file mode 100644 index 00000000..c955ec20 Binary files /dev/null and b/docs/images/chapters/bsplines/d9a99344a5de4b5be77824f8f8caa707.png differ diff --git a/docs/images/chapters/bsplines/9915d887443459415a7bfb57bea1b898.png b/docs/images/chapters/bsplines/dda4911d5148032e005a02ef33b78978.png similarity index 100% rename from docs/images/chapters/bsplines/9915d887443459415a7bfb57bea1b898.png rename to docs/images/chapters/bsplines/dda4911d5148032e005a02ef33b78978.png diff --git a/docs/images/chapters/catmullconv/aa46749b9469341d9249ca452390d875.png b/docs/images/chapters/catmullconv/aa46749b9469341d9249ca452390d875.png deleted file mode 100644 index cb6a47d3..00000000 Binary files a/docs/images/chapters/catmullconv/aa46749b9469341d9249ca452390d875.png and /dev/null differ diff --git a/docs/images/chapters/catmullconv/e1e640b29dd07f905c1555438511cad9.png b/docs/images/chapters/catmullconv/e1e640b29dd07f905c1555438511cad9.png new file mode 100644 index 00000000..8080e357 Binary files /dev/null and b/docs/images/chapters/catmullconv/e1e640b29dd07f905c1555438511cad9.png differ diff --git a/docs/images/chapters/curvefitting/78d32beb061391c47217611128446146.png b/docs/images/chapters/curvefitting/1307361f152242ab0263d81a469cf43b.png similarity index 100% rename from docs/images/chapters/curvefitting/78d32beb061391c47217611128446146.png rename to docs/images/chapters/curvefitting/1307361f152242ab0263d81a469cf43b.png diff --git a/docs/images/chapters/curveintersection/9540ecc84c3fc63a07f5372ab031c99e.png b/docs/images/chapters/curveintersection/390fe022c4e290a7a9d3814ae2936b77.png similarity index 100% rename from docs/images/chapters/curveintersection/9540ecc84c3fc63a07f5372ab031c99e.png rename to docs/images/chapters/curveintersection/390fe022c4e290a7a9d3814ae2936b77.png diff --git a/docs/images/chapters/pointvectors/2724b6c42f36b4dbea6962cac844d2c3.png b/docs/images/chapters/pointvectors/2724b6c42f36b4dbea6962cac844d2c3.png new file mode 100644 index 00000000..5c30dd39 Binary files /dev/null and b/docs/images/chapters/pointvectors/2724b6c42f36b4dbea6962cac844d2c3.png differ diff --git a/docs/images/chapters/pointvectors/44d454f380ae84dbe4661bd67c406edc.png b/docs/images/chapters/pointvectors/44d454f380ae84dbe4661bd67c406edc.png deleted file mode 100644 index 074d9bbb..00000000 Binary files a/docs/images/chapters/pointvectors/44d454f380ae84dbe4661bd67c406edc.png and /dev/null differ diff --git a/docs/images/chapters/pointvectors/58719bde12c1cbd535d5d8af1bef9c2b.png b/docs/images/chapters/pointvectors/58719bde12c1cbd535d5d8af1bef9c2b.png deleted file mode 100644 index be068f3c..00000000 Binary files a/docs/images/chapters/pointvectors/58719bde12c1cbd535d5d8af1bef9c2b.png and /dev/null differ diff --git a/docs/images/chapters/pointvectors/f4d72cc162d4cbcc6d6a459fced01cfb.png b/docs/images/chapters/pointvectors/f4d72cc162d4cbcc6d6a459fced01cfb.png new file mode 100644 index 00000000..de58b6e3 Binary files /dev/null and b/docs/images/chapters/pointvectors/f4d72cc162d4cbcc6d6a459fced01cfb.png differ diff --git a/docs/images/chapters/polybezier/0553872bf00ebc557f4b11d69d634fc6.png b/docs/images/chapters/polybezier/044f65dd588b0210499add16ea50a23d.png similarity index 100% rename from docs/images/chapters/polybezier/0553872bf00ebc557f4b11d69d634fc6.png rename to docs/images/chapters/polybezier/044f65dd588b0210499add16ea50a23d.png diff --git a/docs/images/chapters/polybezier/1aeb331a5170c203c31c55ae8d55b809.png b/docs/images/chapters/polybezier/17a6ffbfffaa9046ad165ca880d9030d.png similarity index 100% rename from docs/images/chapters/polybezier/1aeb331a5170c203c31c55ae8d55b809.png rename to docs/images/chapters/polybezier/17a6ffbfffaa9046ad165ca880d9030d.png diff --git a/docs/images/chapters/polybezier/7afd119dd93a581ec161e2cbfc3c1e63.png b/docs/images/chapters/polybezier/a1a3aabd22c0221beb38403a4532ea86.png similarity index 100% rename from docs/images/chapters/polybezier/7afd119dd93a581ec161e2cbfc3c1e63.png rename to docs/images/chapters/polybezier/a1a3aabd22c0221beb38403a4532ea86.png diff --git a/docs/images/chapters/polybezier/04bc7b98ba019a4a90bedce6e10eaf08.png b/docs/images/chapters/polybezier/adc3b55c9956849ec86ecffcd8864d8a.png similarity index 100% rename from docs/images/chapters/polybezier/04bc7b98ba019a4a90bedce6e10eaf08.png rename to docs/images/chapters/polybezier/adc3b55c9956849ec86ecffcd8864d8a.png diff --git a/docs/images/chapters/polybezier/acb8f004751017eaac4aae0b039c94cf.png b/docs/images/chapters/polybezier/b492c486c25f17a95c690e235b8ad483.png similarity index 100% rename from docs/images/chapters/polybezier/acb8f004751017eaac4aae0b039c94cf.png rename to docs/images/chapters/polybezier/b492c486c25f17a95c690e235b8ad483.png diff --git a/docs/images/chapters/polybezier/1a3c6b24bea874d32334d62110507fcb.png b/docs/images/chapters/polybezier/b7dfe772ac90d762f48772b691a9070f.png similarity index 100% rename from docs/images/chapters/polybezier/1a3c6b24bea874d32334d62110507fcb.png rename to docs/images/chapters/polybezier/b7dfe772ac90d762f48772b691a9070f.png diff --git a/docs/images/chapters/polybezier/60adbd6fe091a72e1ed37355e49a506e.png b/docs/images/chapters/polybezier/b810f02639a79cf7f8ae416d7185614d.png similarity index 100% rename from docs/images/chapters/polybezier/60adbd6fe091a72e1ed37355e49a506e.png rename to docs/images/chapters/polybezier/b810f02639a79cf7f8ae416d7185614d.png diff --git a/docs/images/chapters/polybezier/cfbfbbc56365ba547dc7e82b329c4007.png b/docs/images/chapters/polybezier/babb27083b805c7c77bb93cfecbefb2b.png similarity index 100% rename from docs/images/chapters/polybezier/cfbfbbc56365ba547dc7e82b329c4007.png rename to docs/images/chapters/polybezier/babb27083b805c7c77bb93cfecbefb2b.png diff --git a/docs/images/chapters/projections/3cc334d0ebc01cc5352e23ed47bc5414.png b/docs/images/chapters/projections/3cc334d0ebc01cc5352e23ed47bc5414.png new file mode 100644 index 00000000..c7d2f1fc Binary files /dev/null and b/docs/images/chapters/projections/3cc334d0ebc01cc5352e23ed47bc5414.png differ diff --git a/docs/images/chapters/projections/c40ab9e3f3d1f53872dff30a7bcdb003.png b/docs/images/chapters/projections/c40ab9e3f3d1f53872dff30a7bcdb003.png deleted file mode 100644 index 9fc7c5a9..00000000 Binary files a/docs/images/chapters/projections/c40ab9e3f3d1f53872dff30a7bcdb003.png and /dev/null differ diff --git a/docs/images/chapters/reordering/387f931043aabd6c467985c568482636.png b/docs/images/chapters/reordering/387f931043aabd6c467985c568482636.png deleted file mode 100644 index c25bc276..00000000 Binary files a/docs/images/chapters/reordering/387f931043aabd6c467985c568482636.png and /dev/null differ diff --git a/docs/images/chapters/reordering/81e559a69298ecc24c8edebe59e79647.png b/docs/images/chapters/reordering/81e559a69298ecc24c8edebe59e79647.png new file mode 100644 index 00000000..6b905500 Binary files /dev/null and b/docs/images/chapters/reordering/81e559a69298ecc24c8edebe59e79647.png differ diff --git a/docs/images/chapters/weightcontrol/0ef06657f0540938686b9b35b249a22b.png b/docs/images/chapters/weightcontrol/be18e8119472af796329f3e2159bdf94.png similarity index 100% rename from docs/images/chapters/weightcontrol/0ef06657f0540938686b9b35b249a22b.png rename to docs/images/chapters/weightcontrol/be18e8119472af796329f3e2159bdf94.png diff --git a/docs/images/favicon.png b/docs/images/favicon.png deleted file mode 100644 index 7c2353e8..00000000 Binary files a/docs/images/favicon.png and /dev/null differ diff --git a/docs/images/og-image.png b/docs/images/og-image.png deleted file mode 100644 index ed5a6ecd..00000000 Binary files a/docs/images/og-image.png and /dev/null differ diff --git a/docs/index.html b/docs/index.html index 344e5b67..1f7a90a3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -412,13 +412,13 @@ function Bezier(3,t,w[]): Scripts are disabled. Showing fallback image. - + - ratio 1 1.0
- ratio 2 1.0
- ratio 3 1.0
- ratio 4 1.0 + + + +

You can think of the ratio values as each coordinate's "gravity": the higher the gravity, the closer to that coordinate the curve will want to be. You'll also notice that if you simply increase or decrease all the ratios by the same amount, nothing changes... much like with gravity, if the relative strengths stay the same, nothing really changes. The values define each coordinate's influence relative to all other points.

@@ -742,7 +742,7 @@ function drawCurve(points[], t): Scripts are disabled. Showing fallback image. - + @@ -815,16 +815,16 @@ treated as a sequence of three (elementary) shear operations. When we combine th

The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as t-intervals, not spaced equidistant).

- + Scripts are disabled. Showing fallback image. - + - + Scripts are disabled. Showing fallback image. - +
@@ -860,7 +860,7 @@ treated as a sequence of three (elementary) shear operations. When we combine th
-

However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around" around between t=0.5 and t=0.9... Why is doing that?

+

However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around the curve" between t=0.65 and t=0.75... Why is it doing that?

As it turns out, it's doing that because that's how the maths works, and that's the problem with Frenet normals: while they are "mathematically correct", they are "practically problematic", and so for any kind of graphics work what we really want is a way to compute normals that just... look good.

Thankfully, Frenet normals are not our only option.

Another option is to take a slightly more algorithmic approach and compute a form of Rotation Minimising Frame (also known as "parallel transport frame" or "Bishop frame") instead, where a "frame" is a set made up of the tangent, the rotational axis, and the normal vector, centered on an on-curve point.

@@ -1359,19 +1359,19 @@ y = curve.get(t).y Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -1412,7 +1412,7 @@ y = curve.get(t).y - + @@ -1421,7 +1421,7 @@ y = curve.get(t).y - + @@ -1588,12 +1588,12 @@ lli = function(line1, line2): Scripts are disabled. Showing fallback image. - + - +

Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at t=0.5 first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.

@@ -1724,10 +1724,10 @@ for (coordinate, index) in LUT:

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 Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.

- + Scripts are disabled. Showing fallback image. - + @@ -1847,11 +1847,11 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + -
+

You'll note there is a convenient "toggle" buttons that lets you toggle between equidistant t values, and distance ratio along the polygon formed by the points. Arguably more interesting is that once you have points to abstract a curve, you also get direct control over the time values through sliders for each, because if the time values are our degree of freedom, you should be able to freely manipulate them and see what the effect on your curve is.

@@ -1864,7 +1864,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1992,13 +1992,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -2010,13 +2010,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -2026,13 +2026,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -2041,13 +2041,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + > @@ -2336,7 +2336,7 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + @@ -2346,6 +2346,14 @@ for p = 1 to points.length-3 (inclusive):
  • for Bézier curves, the curve is defined as an interpolation of points, but:
  • for B-Splines, the curve is defined as an interpolation of curves.
  • +

    In fact, let's look at that again, but this time with the base curves shown, too. Each consecutive four points defined one curve:

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

    In order to make this interpolation of curves work, the maths is necessarily more complex than the maths for Bézier curves, so let's have a look at how things work.

    How to compute a B-Spline curve: some maths

    Given a B-Spline of degree d and thus order k=d+1 (so a quadratic B-Spline is degree 2 and order 3, a cubic B-Spline is degree 3 and order 4, etc) and n control points P0 through Pn-1, we can compute a point on the curve for some value t in the interval [0,1] (where 0 is the start of the curve, and 1 the end, just like for Bézier curves), by evaluating the following function:

    @@ -2379,7 +2387,7 @@ Doing so for a degree d B-Spline with n control point Scripts are disabled. Showing fallback image. - + @@ -2421,7 +2429,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + @@ -2434,7 +2442,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + @@ -2447,7 +2455,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + @@ -2461,7 +2469,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + diff --git a/docs/ja-JP/index.html b/docs/ja-JP/index.html index a128be1c..2eaf155d 100644 --- a/docs/ja-JP/index.html +++ b/docs/ja-JP/index.html @@ -409,13 +409,13 @@ function Bezier(3,t,w[]): Scripts are disabled. Showing fallback image. - + - ratio 1 1.0
    - ratio 2 1.0
    - ratio 3 1.0
    - ratio 4 1.0 + + + +

    You can think of the ratio values as each coordinate's "gravity": the higher the gravity, the closer to that coordinate the curve will want to be. You'll also notice that if you simply increase or decrease all the ratios by the same amount, nothing changes... much like with gravity, if the relative strengths stay the same, nothing really changes. The values define each coordinate's influence relative to all other points.

    @@ -738,7 +738,7 @@ function drawCurve(points[], t): Scripts are disabled. Showing fallback image. - + @@ -811,16 +811,16 @@ treated as a sequence of three (elementary) shear operations. When we combine th

    The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as t-intervals, not spaced equidistant).

    - + Scripts are disabled. Showing fallback image. - + - + Scripts are disabled. Showing fallback image. - +
    @@ -856,7 +856,7 @@ treated as a sequence of three (elementary) shear operations. When we combine th
    -

    However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around" around between t=0.5 and t=0.9... Why is doing that?

    +

    However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around the curve" between t=0.65 and t=0.75... Why is it doing that?

    As it turns out, it's doing that because that's how the maths works, and that's the problem with Frenet normals: while they are "mathematically correct", they are "practically problematic", and so for any kind of graphics work what we really want is a way to compute normals that just... look good.

    Thankfully, Frenet normals are not our only option.

    Another option is to take a slightly more algorithmic approach and compute a form of Rotation Minimising Frame (also known as "parallel transport frame" or "Bishop frame") instead, where a "frame" is a set made up of the tangent, the rotational axis, and the normal vector, centered on an on-curve point.

    @@ -1355,19 +1355,19 @@ y = curve.get(t).y Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -1408,7 +1408,7 @@ y = curve.get(t).y - +
    @@ -1417,7 +1417,7 @@ y = curve.get(t).y - + @@ -1584,12 +1584,12 @@ lli = function(line1, line2): Scripts are disabled. Showing fallback image. - + - +

    Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at t=0.5 first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.

    @@ -1720,10 +1720,10 @@ for (coordinate, index) in LUT:

    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 Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.

    - + Scripts are disabled. Showing fallback image. - + @@ -1843,11 +1843,11 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + -
    +

    You'll note there is a convenient "toggle" buttons that lets you toggle between equidistant t values, and distance ratio along the polygon formed by the points. Arguably more interesting is that once you have points to abstract a curve, you also get direct control over the time values through sliders for each, because if the time values are our degree of freedom, you should be able to freely manipulate them and see what the effect on your curve is.

    @@ -1860,7 +1860,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1988,13 +1988,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -2006,13 +2006,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -2022,13 +2022,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -2037,13 +2037,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + > @@ -2332,7 +2332,7 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + @@ -2342,6 +2342,14 @@ for p = 1 to points.length-3 (inclusive):
  • for Bézier curves, the curve is defined as an interpolation of points, but:
  • for B-Splines, the curve is defined as an interpolation of curves.
  • +

    In fact, let's look at that again, but this time with the base curves shown, too. Each consecutive four points defined one curve:

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

    In order to make this interpolation of curves work, the maths is necessarily more complex than the maths for Bézier curves, so let's have a look at how things work.

    How to compute a B-Spline curve: some maths

    Given a B-Spline of degree d and thus order k=d+1 (so a quadratic B-Spline is degree 2 and order 3, a cubic B-Spline is degree 3 and order 4, etc) and n control points P0 through Pn-1, we can compute a point on the curve for some value t in the interval [0,1] (where 0 is the start of the curve, and 1 the end, just like for Bézier curves), by evaluating the following function:

    @@ -2375,7 +2383,7 @@ Doing so for a degree d B-Spline with n control point Scripts are disabled. Showing fallback image. - + @@ -2417,7 +2425,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + @@ -2430,7 +2438,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + @@ -2443,7 +2451,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + @@ -2457,7 +2465,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + diff --git a/docs/js/custom-element/api/graphics-api.js b/docs/js/custom-element/api/graphics-api.js index 49060fec..7f1d7ca9 100644 --- a/docs/js/custom-element/api/graphics-api.js +++ b/docs/js/custom-element/api/graphics-api.js @@ -207,8 +207,32 @@ class GraphicsAPI extends BaseAPI { throw new Error(`this.${propname} already exists: cannot bind slider.`); } + let propLabel = propname.replace(`!`, ``); + propname = propLabel === propname ? propname : false; + let slider = typeof qs === `string` ? this.find(qs) : qs; + // relocate this slider + let ui = (() => { + if (!this.element) { + return { update: (v) => {} }; + } + let wrapper = create(`div`); + wrapper.classList.add(`slider-wrapper`); + let label = create(`label`); + label.classList.add(`slider-label`); + label.innerHTML = propLabel; + wrapper.append(label); + slider.parentNode.replaceChild(wrapper, slider); + slider.setAttribute(`class`, `slider`); + wrapper.append(slider); + let valueField = create(`label`); + valueField.classList.add(`slider-value`); + valueField.textContent; + wrapper.append(valueField); + return { update: (v) => (valueField.textContent = v) }; + })(); + if (!slider) { console.warn(`Warning: no slider found for query selector "${qs}"`); if (propname) this[propname] = initial; @@ -217,6 +241,7 @@ class GraphicsAPI extends BaseAPI { const updateProperty = (evt) => { let value = parseFloat(slider.value); + ui.update(value); try { let checked = transform ? transform(value) ?? value : value; if (propname) this[propname] = checked; @@ -225,6 +250,7 @@ class GraphicsAPI extends BaseAPI { evt.preventDefault(); evt.stopPropagation(); } + ui.update(e.value); slider.value = e.value; slider.setAttribute(`value`, e.value); } @@ -242,7 +268,7 @@ class GraphicsAPI extends BaseAPI { * remove all sliders from this element */ removeSliders() { - this.findAll(`input[type=range]`).forEach((s) => { + this.findAll(`.slider-wrapper`).forEach((s) => { s.parentNode.removeChild(s); }); } diff --git a/docs/js/custom-element/lib/create.js b/docs/js/custom-element/lib/create.js index b08bf864..48a6162e 100644 --- a/docs/js/custom-element/lib/create.js +++ b/docs/js/custom-element/lib/create.js @@ -1,14 +1,27 @@ import { enrich } from "./enrich.js"; -function create(element) { +const noop = () => {}; + +function create(tag) { if (typeof document !== `undefined`) { - return enrich(document.createElement(element)); + return enrich(document.createElement(tag)); } - return { - name: element, - tag: element.toUpperCase(), + const element = { + name: tag, + tag: tag.toUpperCase(), + append: noop, + appendChild: noop, + replaceChild: noop, + removeChild: noop, + classList: { + add: noop, + remove: noop, + }, + children: [], }; + + return element; } export { create }; diff --git a/docs/placeholder-style.css b/docs/placeholder-style.css index 71d4f6cf..762cb841 100644 --- a/docs/placeholder-style.css +++ b/docs/placeholder-style.css @@ -1,153 +1,193 @@ :root[lang="en-GB"] { - font-family: 'Museo', 'Helvetica Neue', 'Helvetica', Arial, sans-serif; - font-size: 18px; + font-family: "Museo", "Helvetica Neue", "Helvetica", Arial, sans-serif; + font-size: 18px; } :root[lang="ja-JP"] { - font-family: 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', 'Osaka', 'メイリオ', 'Meiryo', 'MS Pゴシック', 'MS PGothic', 'Helvetica Neue', 'Helvetica', Arial, sans-serif; - font-size: 16px; + font-family: "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", "Osaka", + "メイリオ", "Meiryo", "MS Pゴシック", "MS PGothic", "Helvetica Neue", + "Helvetica", Arial, sans-serif; + font-size: 16px; } :root[lang="zh-CN"] { - font-family: '华文细黑', 'STXihei', 'PingFang TC', '微软雅黑体', 'Microsoft YaHei New', '微软雅黑', 'Microsoft Yahei', '宋体', 'SimSun', 'Helvetica Neue', 'Helvetica', Arial, sans-serif; - font-size: 17.2px; + font-family: "华文细黑", "STXihei", "PingFang TC", "微软雅黑体", + "Microsoft YaHei New", "微软雅黑", "Microsoft Yahei", "宋体", "SimSun", + "Helvetica Neue", "Helvetica", Arial, sans-serif; + font-size: 17.2px; } h1 { - background: black; - color: white; - padding: 0.2em 0.5em; + background: black; + color: white; + padding: 0.2em 0.5em; } label[for="changelogtoggle"] { - display: inline-block; - cursor: pointer; - color: #2929b3; - margin-left: 2em; + display: inline-block; + cursor: pointer; + color: #2929b3; + margin-left: 2em; } #changelogtoggle { - display: none; + display: none; } #changelogtoggle:not(:checked) ~ * { - display: none; + display: none; } - #chapters { - counter-reset: section; + counter-reset: section; } #chapters section h1:before { - counter-increment: section; - content: "§" counter(section); - margin-right: 1em; + counter-increment: section; + content: "§" counter(section); + margin-right: 1em; } #chapters section > h1 > a { - text-decoration: none; - color: inherit; + text-decoration: none; + color: inherit; } graphics-element { - background: #e8e8e8; + background: #e8e8e8; } div.print { - opacity: 0.2; - padding-left: 2em; + opacity: 0.2; + padding-left: 2em; } div.print:before { - content: "PRINT-ONLY:"; - position: relative; - left: -2em; + content: "PRINT-ONLY:"; + position: relative; + left: -2em; } div.print:after { - content: "END-PRINT-ONLY"; - position: relative; - left: -2em; + content: "END-PRINT-ONLY"; + position: relative; + left: -2em; } div.howtocode { - position: relative; - border: 1px solid black; - background: rgb(246, 255, 255); - margin: 1em; - padding: 1em 1em 0 1em; + position: relative; + border: 1px solid black; + background: rgb(246, 255, 255); + margin: 1em; + padding: 1em 1em 0 1em; } div.howtocode h3 { - box-sizing: border-box; - position: relative; - width: calc(100% + 2em - 4px); - left: calc(-1em + 2px); - background: black; - color: white; - padding: 0.2em; - margin: 0 auto; - margin-top: -1em; + box-sizing: border-box; + position: relative; + width: calc(100% + 2em - 4px); + left: calc(-1em + 2px); + background: black; + color: white; + padding: 0.2em; + margin: 0 auto; + margin-top: -1em; } div.note { - position: relative; - border: 1px solid black; - background: rgb(255, 255, 246); - margin: 1em; - padding: 1em 1em 0 1em; + position: relative; + border: 1px solid black; + background: rgb(255, 255, 246); + margin: 1em; + padding: 1em 1em 0 1em; } div.note:before { - display: inline-block; - content: "Note"; - border: 1px solid black; - border-radius: 0.5em; - background: orange; - padding: 0.2em 0.5em; - position: absolute; - top: -0.5em; - left: -1em; + display: inline-block; + content: "Note"; + border: 1px solid black; + border-radius: 0.5em; + background: orange; + padding: 0.2em 0.5em; + position: absolute; + top: -0.5em; + left: -1em; } img.LaTeX.SVG { - margin-left: 2em; + margin-left: 2em; } img.LaTeX.SVG + img.LaTeX.SVG { - margin-top: 1em; - margin-left: 1em; + margin-top: 1em; + margin-left: 1em; } pre { - display: inline-block; - margin-left: 1em; - background-color: lightyellow; - padding: 0.5em; - border: 1px dotted gray; + display: inline-block; + margin-left: 1em; + background-color: lightyellow; + padding: 0.5em; + border: 1px dotted gray; } code { - position: relative; - font-family: monospace; - font-size: 0.9rem; + position: relative; + font-family: monospace; + font-size: 0.9rem; } p code { - color: #ff6000; - font-weight: bold; - font-family: monospace; - font-size: 1.2em; - letter-spacing: -2px; + color: #ff6000; + font-weight: bold; + font-family: monospace; + font-size: 1.2em; + letter-spacing: -2px; } -.slide-control { - display: block; - width: 100%; +.slider-wrapper { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + font-size: 90%; + background: white; + margin: -0.5em; + margin-left: -0.25em; + margin-right: -0.25em; + border: 1px solid black; + border-width: 0 1px; +} + +graphics-element .slider-wrapper:first-of-type { + clear: both; + border-top: 1px solid black; + margin-top: 0.1em; + padding-top: 2px; +} + +graphics-element .slider-wrapper:last-of-type { + border-bottom: 1px solid black; + margin-bottom: -0.25em; +} + +.slider-wrapper .slider-label { + flex: 0 0 auto; + font-family: Arial; + padding: 0 0.5em; +} + +.slider-wrapper .slider-value { + flex: 0 0 auto; + min-width: 3em; + padding-right: 0.25em; + text-align-last: center; +} + +.slider-wrapper .slider { + flex: 1 1 auto; } .grid { - display: flex; - flex-wrap: wrap; + display: flex; + flex-wrap: wrap; } diff --git a/docs/zh-CN/index.html b/docs/zh-CN/index.html index dad0d8d8..5c9b8582 100644 --- a/docs/zh-CN/index.html +++ b/docs/zh-CN/index.html @@ -403,13 +403,13 @@ function Bezier(3,t,w[]): Scripts are disabled. Showing fallback image. - + - ratio 1 1.0
    - ratio 2 1.0
    - ratio 3 1.0
    - ratio 4 1.0 + + + +

    You can think of the ratio values as each coordinate's "gravity": the higher the gravity, the closer to that coordinate the curve will want to be. You'll also notice that if you simply increase or decrease all the ratios by the same amount, nothing changes... much like with gravity, if the relative strengths stay the same, nothing really changes. The values define each coordinate's influence relative to all other points.

    @@ -732,7 +732,7 @@ function drawCurve(points[], t): Scripts are disabled. Showing fallback image. - + @@ -805,16 +805,16 @@ treated as a sequence of three (elementary) shear operations. When we combine th

    The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as t-intervals, not spaced equidistant).

    - + Scripts are disabled. Showing fallback image. - + - + Scripts are disabled. Showing fallback image. - +
    @@ -850,7 +850,7 @@ treated as a sequence of three (elementary) shear operations. When we combine th
    -

    However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around" around between t=0.5 and t=0.9... Why is doing that?

    +

    However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around the curve" between t=0.65 and t=0.75... Why is it doing that?

    As it turns out, it's doing that because that's how the maths works, and that's the problem with Frenet normals: while they are "mathematically correct", they are "practically problematic", and so for any kind of graphics work what we really want is a way to compute normals that just... look good.

    Thankfully, Frenet normals are not our only option.

    Another option is to take a slightly more algorithmic approach and compute a form of Rotation Minimising Frame (also known as "parallel transport frame" or "Bishop frame") instead, where a "frame" is a set made up of the tangent, the rotational axis, and the normal vector, centered on an on-curve point.

    @@ -1349,19 +1349,19 @@ y = curve.get(t).y Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -1402,7 +1402,7 @@ y = curve.get(t).y - +
    @@ -1411,7 +1411,7 @@ y = curve.get(t).y - + @@ -1578,12 +1578,12 @@ lli = function(line1, line2): Scripts are disabled. Showing fallback image. - + - +

    Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at t=0.5 first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.

    @@ -1714,10 +1714,10 @@ for (coordinate, index) in LUT:

    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 Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.

    - + Scripts are disabled. Showing fallback image. - + @@ -1837,11 +1837,11 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + -
    +

    You'll note there is a convenient "toggle" buttons that lets you toggle between equidistant t values, and distance ratio along the polygon formed by the points. Arguably more interesting is that once you have points to abstract a curve, you also get direct control over the time values through sliders for each, because if the time values are our degree of freedom, you should be able to freely manipulate them and see what the effect on your curve is.

    @@ -1854,7 +1854,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1982,13 +1982,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -2000,13 +2000,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -2016,13 +2016,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + @@ -2031,13 +2031,13 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + Scripts are disabled. Showing fallback image. - + > @@ -2326,7 +2326,7 @@ for p = 1 to points.length-3 (inclusive): Scripts are disabled. Showing fallback image. - + @@ -2336,6 +2336,14 @@ for p = 1 to points.length-3 (inclusive):
  • for Bézier curves, the curve is defined as an interpolation of points, but:
  • for B-Splines, the curve is defined as an interpolation of curves.
  • +

    In fact, let's look at that again, but this time with the base curves shown, too. Each consecutive four points defined one curve:

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

    In order to make this interpolation of curves work, the maths is necessarily more complex than the maths for Bézier curves, so let's have a look at how things work.

    How to compute a B-Spline curve: some maths

    Given a B-Spline of degree d and thus order k=d+1 (so a quadratic B-Spline is degree 2 and order 3, a cubic B-Spline is degree 3 and order 4, etc) and n control points P0 through Pn-1, we can compute a point on the curve for some value t in the interval [0,1] (where 0 is the start of the curve, and 1 the end, just like for Bézier curves), by evaluating the following function:

    @@ -2369,7 +2377,7 @@ Doing so for a degree d B-Spline with n control point Scripts are disabled. Showing fallback image. - + @@ -2411,7 +2419,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + @@ -2424,7 +2432,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + @@ -2437,7 +2445,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - + @@ -2451,7 +2459,7 @@ for(let L = 1; L <= order; L++) { Scripts are disabled. Showing fallback image. - +