Mercurial > hg > xsplines
changeset 5:a9ffd0bcc292
implement everything
author | Jordi Gutiérrez Hermoso <jordi@ecometrica.com> |
---|---|
date | Sun, 26 Aug 2018 00:19:57 -0400 |
parents | 64bf53f0264d |
children | d3a25d39a6e2 |
files | splines.html splines.js |
diffstat | 2 files changed, 188 insertions(+), 15 deletions(-) [+] |
line wrap: on
line diff
--- a/splines.html +++ b/splines.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <head> <meta charset="utf-8"> - <link rel="stylesheet" href="d3.slider.css" /> + <link rel="stylesheet" href="d3.slider.css" /> <title>Spline Editor</title> <style> body { @@ -30,6 +30,12 @@ stroke-width: 1.5px; } + .xspline { + fill: none; + stroke: red; + stroke-width: 1.5px; + } + circle { fill: #fff; fill-opacity: .2; @@ -52,12 +58,27 @@ </head> <body> + <input name="approximateButton" + type="button" + value="Approximate all" + onclick="approximate_all()" /> + <input name="sharplyInterpolateButton" + type="button" + value="Sharply interpolate all" + onclick="sharply_interpolate_all()" /> + <input name="interpolateButton" + type="button" + value="Interpolate all" + onclick="interpolate_all()" /> + <form> - <label for="interpolate">Interpolate:</label> + <label for="interpolate">SVG spline type:</label> <select id="interpolate"></select><br> </form> <div id="splines"></div> <div id="slider"></div> + + <script src="splines.js"></script> </body>
--- a/splines.js +++ b/splines.js @@ -1,21 +1,21 @@ var width = 960, height = 500; -var points = d3.range(1, 5).map(function(i) { +var nodes = d3.range(1, 5).map(function(i) { return [i * width / 5, 50 + Math.random() * (height - 100), 1]; }); + var dragged = null, - selected = points[0]; + selected = nodes[0]; var slider = d3.slider() .axis(true) .min(-1) .max(1) + .step(0.01) .value(1) - .on("slide", function(evt, value){ - selected[2] = value; - }); + .on("slide", schange); var line = d3.svg.line(); @@ -31,7 +31,7 @@ .on("mousedown", mousedown); svg.append("path") - .datum(points) + .datum(nodes) .attr("class", "line") .call(redraw); @@ -63,15 +63,33 @@ d3.select("#slider").call(slider); + +// How many points to plot between nodes +var tstep = 20; +// Parameter space, "time", one "second" per node +var tvec = linspace(0, nodes.length-1, nodes.length*tstep); + +var x = d3.scale.linear().range([0,width]).domain([0,tvec.length]); + +var xspline = d3.svg.line() + .x(function(d,i){return xspline_at_t(d, 0);}) + .y(function(d,i){return xspline_at_t(d, 1);}); + +svg.append('path') + .datum(tvec) + .attr('class','xspline') + .call(redraw); + function redraw() { - svg.select("path").attr("d", line); + svg.select("path.line").attr("d", line); + svg.select("path.xspline").attr("d", xspline); var circle = svg.selectAll("circle") - .data(points, function(d) { return d; }); + .data(nodes, function(d) { return d; }); circle.enter().append("circle") .attr("r", 1e-6) - .on("mousedown", function(d, i) { + .on("mousedown", function(d, i) { selected = dragged = d; slider.value(d[2]); redraw(); @@ -99,11 +117,18 @@ redraw(); } +function schange(evt, value){ + selected[2] = value; + svg.select("path.xspline").attr("d", xspline); +} + function mousedown() { var newcirc = d3.mouse(svg.node()); newcirc.push(1); slider.value(1); - points.push(selected = dragged = newcirc); + nodes.push(selected = dragged = newcirc); + tvec = linspace(0, nodes.length-1, nodes.length*tstep); + svg.select("path.xspline").datum(tvec); redraw(); } @@ -126,13 +151,140 @@ switch (d3.event.keyCode) { case 8: // backspace case 46: { // delete - var i = points.indexOf(selected); - points.splice(i, 1); - selected = points.length ? points[i > 0 ? i - 1 : 0] : null; + var i = nodes.indexOf(selected); + nodes.splice(i, 1); + selected = nodes.length ? nodes[i > 0 ? i - 1 : 0] : null; + tvec = linspace(0, nodes.length-1, nodes.length*tstep); + svg.select("path.xspline").datum(tvec); redraw(); break; } } } +// Xspline stuff starts here +function linspace(a, b, n) { + let stepsize = (b - a)/(n - 1); + return [...Array(n).keys()].map(x => a + x*stepsize); +} + +// Basic polynomials +function f(p, u) { + return u*u*u*(10-p + u*(2*p-15 + u*(6-p))); +} + +function g(q, u) { + return u*(q + u*(2*q + u*((8 - 12*q) + u*(14*q - 11 + u*(4 - 5*q))))); +} + +function h(q, u) { + return q*u*(1 + u*(2 - u*u*(2 + u))); +} + +// Non-normalised blending function at node k +function fk(k, t) { + // No blending functions outside the range + if(k >= nodes.length || k < 0) { + return 0; + } + + // Special case the endpoints + if ( (t == 0 && k == 0) || (k == nodes.length-1 && t == nodes.length-1)) { + return 1; + } + + var s, tboundary; + // Determine if we're doing the left or right side + if (t < k) + { + s = nodes[k-1][2]; + tboundary = k - 1 - s; + } + else { + s = nodes[k+1][2]; + tboundary = k + 1 + s; + } + + // Approximation or sharp interpolation at this node + if (s >= 0) { + // Check if it's outside the support of the function + if ((t > k && t > tboundary) || (t < k && t < tboundary)) { + return 0; + } + + var p = 2*(1+s)*(1+s); + return f(p, (t-tboundary)/(k-tboundary)); + } + + // Else, s < 0, and we're smoothly interpolating at this node + var q = -s/2; + + //Outside of support + if (t < k-2) { + return 0; + } + //left negative + else if (t < k-1) { + return h(q, t - k + 1); + } + //left positive + else if (t < k) { + return g(q, t - k + 1); + } + //right positive + else if (t < k+1) { + return g(q, k + 1 -t); + } + // right negative + else if (t < k+ 2){ + return h(q, k + 1 - t); + } + + // Outside of support + return 0; +} + + +function xspline_at_t(t, XorY) { + // Determine which blending functions have influence at this point. + var k1 = Math.floor(t) - 1, k2 = Math.floor(t), k3 = Math.ceil(t), k4 = Math.ceil(t) + 1; + + var f1t = fk(k1, t), + f2t = fk(k2, t), + f3t = fk(k3, t), + f4t = fk(k4, t); + + // normalisation factor + var denom = f1t + f2t + f3t + f4t; + + var p1 = k1 >= 0 ? nodes[k1] : [0, 0], + p2 = k2 >= 0 && k2 < nodes.length ? nodes[k2] : [0, 0], + p3 = k3 >= 0 && k3 < nodes.length ? nodes[k3] : [0, 0], + p4 = k4 < nodes.length ? nodes[k4] : [0, 0]; + + var at_t = (f1t*p1[XorY] + f2t*p2[XorY] + f3t*p3[XorY] + f4t*p4[XorY])/denom; + + return at_t; +} + +function approximate_all() { + for(var i = 0; i < nodes.length; i++) { + nodes[i][2] = 1; + } + redraw(); +} + +function sharply_interpolate_all() { + for(var i = 0; i < nodes.length; i++) { + nodes[i][2] = 0; + } + redraw(); +} + +function interpolate_all() { + for(var i = 0; i < nodes.length; i++) { + nodes[i][2] = -1; + } + redraw(); +}