[first cut at interactive euler spline editor raph.levien@gmail.com**20070520010334] { addfile ./spiro.html hunk ./spiro.html 1 + + + + + + + +

Message

+ + hunk ./spiro.js 1 -function draw_spiro(ctx, k0, k1, k2, k3) { - if (!this.x) { this.x = .5 } - this.x += .1; - ctx.moveTo(-.5, 0); - ctx.lineTo(.5, 0); -} -function show_xy_evt(evt, style) { - var canvas = document.getElementById("canvas"); - var ctx = canvas.getContext("2d"); - var x = evt.offsetX != undefined ? evt.offsetX : evt.pageX - canvas.offsetLeft; - var y = evt.offsetY != undefined ? evt.offsetY : evt.pageY - canvas.offsetTop; - ctx.beginPath(); - ctx.fillStyle = style; - ctx.moveTo(x-1, y-1); - ctx.lineTo(x+1, y-1); - ctx.lineTo(x+1, y+1); - ctx.lineTo(x-1, y+1); - ctx.fill(); -} -function show_evt(evt) { - var str = "["; - for (x in evt) { - str += x + ": " - try { - str += evt[x] + ", "; - } catch (e) { - str += ""; - } - } - str += "]"; - var msgel = document.getElementById("msg"); - msgel.appendChild(document.createTextNode(str)); -} -function hook_events(canvas) { - show_evt(canvas); - canvas.onmouseover = function(evt) { show_evt(evt, "red"); evt.preventDefault(); }; - canvas.onmouseup = function(evt) { show_evt(evt, "red"); evt.preventDefault(); }; - canvas.onmousedown = function(evt) { show_xy_evt(evt, "red"); evt.preventDefault(); }; - //canvas.onmousemove = function(evt) { show_xy_evt(evt, "black"); evt.preventDefault(); }; - canvas.ondblclick = function() { alert("varclick") }; - window.addEventListener("keypress", function(evt){ - show_evt(evt); - evt.preventDefault(); - }, false); -} - hunk ./spiro.js 214 +function fit_euler_ks(th0, th1, chord) { + var p = fit_euler(th0, th1); + var sc = p.chord / chord; + p.k0 = (p.ks[0] - .5 * p.ks[1]) * sc; + p.k1 = (p.ks[0] + .5 * p.ks[1]) * sc; + return p; +} + hunk ./spiro.js 238 +function Spline(segs, nodes) { + this.segs = segs; + this.nodes = nodes; +} + +Spline.prototype.show_in_shell = function () { + showobj(this.segs); + showobj(this.nodes); +} + hunk ./spiro.js 303 - return {segs: segs, nodes: nodes}; + return new Spline(segs, nodes); +} + +function get_jacobian_g2(node) { + var save_dth = node.dth; + var delta = 1e-6; + node.dth += delta; + + var ths = node.left.get_ths(); + var lparms = fit_euler_ks(ths[0], ths[1], node.left.chord); + + ths = node.right.get_ths(); + var rparms = fit_euler_ks(ths[0], ths[1], node.right.chord); + + node.dth = save_dth; + + return [(lparms.k0 - node.left.params.k0) / delta, + (rparms.k0 - node.right.params.k0 - lparms.k1 + node.left.params.k1) / delta, + (-rparms.k1 + node.right.params.k1) / delta]; hunk ./spiro.js 325 + var maxerr = 0; hunk ./spiro.js 331 - seg.params = fit_cloth(ths[0], ths[1]); + seg.params = fit_euler_ks(ths[0], ths[1], seg.chord); hunk ./spiro.js 333 + var dks = []; + var mat = []; hunk ./spiro.js 338 - var lparams = node.left.params; - kleft = (lparams.ks[0] + .5 * lparams.ks[1]) * lparams.chord / node.left.chord; - var rparams = node.right.params; - kright = (rparams.ks[0] - .5 * rparams.ks[1]) * rparams.chord / node.right.chord; - print('kleft = ' + String(kleft) + ', kright = ' + String(kright)); + var kerr = node.right.params.k0 - node.left.params.k1; + dks.push(kerr); + if (Math.abs(kerr) > maxerr) maxerr = Math.abs(kerr); + mat.push({a: get_jacobian_g2(node)}); hunk ./spiro.js 344 + if (mat.length == 0) return 0; + bandec(mat, mat.length, 1); + banbks(mat, dks, mat.length, 1); + j = 0; + for (i = 0; i < nodes.length; i++) { + var node = nodes[i]; + if (node.left && node.right) { + node.dth -= dks[j]; + j += 1; + } + } + return maxerr; hunk ./spiro.js 386 +// UI stuff follows + +function SpiroUi(canvas) { + this.canvas = canvas; + this.path = []; + this.hit = null; + var self = this; + canvas.onmousedown = function(evt) { self.down(evt); }; + canvas.onmousemove = function(evt) { self.move(evt); }; + canvas.onmouseup = function(evt) { self.up(evt); }; +} + +SpiroUi.prototype.paint = function() { + var ctx = this.canvas.getContext("2d"); + ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + var spline = setup_solver(this.path); + while (refine_euler(spline) > 1e-6); + var nodes = spline.nodes; + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + ctx.beginPath(); + ctx.arc(node.xy[0], node.xy[1], 2, 0, 2 * Math.PI, 0); + ctx.fill(); + } + ctx.beginPath(); + ctx.moveTo(nodes[0].xy[0], nodes[0].xy[1]); + var segs = spline.segs; + for (var i = 0; i < segs.length; i++) { + var seg = segs[i]; + var ths = seg.get_ths(); + var ks = fit_euler(ths[0], ths[1]).ks; + ks.push(0); ks.push(0); + seg_to_bez(ctx, ks, seg.left.xy[0], seg.left.xy[1], seg.right.xy[0], seg.right.xy[1]); + } + ctx.stroke(); +} + + + +SpiroUi.prototype.down = function(evt) { + var canvas = this.canvas; + var x = evt.offsetX != undefined ? evt.offsetX : evt.pageX - canvas.offsetLeft; + var y = evt.offsetY != undefined ? evt.offsetY : evt.pageY - canvas.offsetTop; + var hit = null; + var hitr2 = null; + for (var i = 0; i < this.path.length; i++) { + var xy = this.path[i]; + var r2 = (xy[0] - x) * (xy[0] - x) + (xy[1] - y) * (xy[1] - y); + if (r2 < (hitr2 == null ? 10 : hitr2)) { + hit = i; + hitr2 = r2; + } + } + if (hit == null) { + this.path.push([x, y]); + hit = this.path.length - 1; + } + this.hit = hit; + evt.preventDefault(); +} + +SpiroUi.prototype.move = function(evt) { + var canvas = this.canvas; + var x = evt.offsetX != undefined ? evt.offsetX : evt.pageX - canvas.offsetLeft; + var y = evt.offsetY != undefined ? evt.offsetY : evt.pageY - canvas.offsetTop; + if (this.hit != null) { + this.path[this.hit] = [x, y]; + this.paint(); + } + evt.preventDefault(); +} + +SpiroUi.prototype.up = function(evt) { + this.hit = null; + evt.preventDefault(); +} + hunk ./test.html 5 +function show_xy_evt(evt, style) { + var canvas = document.getElementById("canvas"); + var ctx = canvas.getContext("2d"); + var x = evt.offsetX != undefined ? evt.offsetX : evt.pageX - canvas.offsetLeft; + var y = evt.offsetY != undefined ? evt.offsetY : evt.pageY - canvas.offsetTop; + ctx.beginPath(); + ctx.fillStyle = style; + ctx.moveTo(x-1, y-1); + ctx.lineTo(x+1, y-1); + ctx.lineTo(x+1, y+1); + ctx.lineTo(x-1, y+1); + ctx.fill(); +} +function show_evt(evt) { + var str = "["; + for (x in evt) { + str += x + ": " + try { + str += evt[x] + ", "; + } catch (e) { + str += ""; + } + } + str += "]"; + var msgel = document.getElementById("msg"); + msgel.appendChild(document.createTextNode(str)); +} +function hook_events(canvas) { + show_evt(canvas); + canvas.onmouseover = function(evt) { show_evt(evt, "red"); evt.preventDefault(); }; + canvas.onmouseup = function(evt) { show_evt(evt, "red"); evt.preventDefault(); }; + canvas.onmousedown = function(evt) { show_xy_evt(evt, "red"); evt.preventDefault(); }; + //canvas.onmousemove = function(evt) { show_xy_evt(evt, "black"); evt.preventDefault(); }; + canvas.ondblclick = function() { alert("varclick") }; + window.addEventListener("keypress", function(evt){ + show_evt(evt); + evt.preventDefault(); + }, false); +} + hunk ./test.html 63 +function print(s) { +} hunk ./test.html 73 - spline.nodes[2].dth = -.2; + while (refine_euler(spline) > 1e-6); }