mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-02-24 09:33:19 +01:00
4029 lines
101 KiB
JavaScript
4029 lines
101 KiB
JavaScript
window["Bezier Section Handlers"] = {
|
|
"introduction": {
|
|
handler: (function() { return {
|
|
drawQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
drawCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
drawCurve: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"whatis": {
|
|
handler: (function() { return {
|
|
setup: function(api) {
|
|
api.setPanelCount(3);
|
|
var curve = api.getDefaultQuadratic();
|
|
api.setCurve(curve);
|
|
api.step = 25;
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
var dim = api.getPanelWidth(),
|
|
pts = curve.points,
|
|
p1 = pts[0],
|
|
p2=pts[1],
|
|
p3 = pts[2],
|
|
p1e, p2e, m, t, i,
|
|
offset = {x:0, y:0},
|
|
d,v,tvp;
|
|
|
|
api.reset();
|
|
|
|
api.setColor("black");
|
|
api.setFill("black");
|
|
api.drawSkeleton(curve, offset);
|
|
api.text("First linear interpolation at "+api.step+"% steps", {x:5, y:15}, offset);
|
|
|
|
offset.x += dim;
|
|
api.drawLine({x:0, y:0}, {x:0, y:this.dim}, offset);
|
|
api.drawSkeleton(curve, offset);
|
|
api.text("Second interpolation at "+api.step+"% steps", {x:5, y:15}, offset);
|
|
|
|
offset.x += dim;
|
|
api.drawLine({x:0, y:0}, {x:0, y:this.dim}, offset);
|
|
api.drawSkeleton(curve, offset);
|
|
api.text("Curve points generated this way", {x:5, y:15}, offset);
|
|
|
|
api.setColor("lightgrey");
|
|
for(t=1,d=20,v,tvp; t<d; t++) {
|
|
v = t/d;
|
|
tvp = curve.get(v);
|
|
api.drawCircle(tvp,2,offset);
|
|
}
|
|
|
|
for(i = 3*api.step; i>0; i -= api.step) {
|
|
t = i/100;
|
|
if (t>1) continue;
|
|
api.setRandomColor();
|
|
|
|
p1e = {
|
|
x: p1.x + t * (p2.x - p1.x),
|
|
y: p1.y + t * (p2.y - p1.y)
|
|
};
|
|
|
|
p2e = {
|
|
x: p2.x + t * (p3.x - p2.x),
|
|
y: p2.y + t * (p3.y - p2.y)
|
|
};
|
|
|
|
m = {
|
|
x: p1e.x + t * (p2e.x - p1e.x),
|
|
y: p1e.y + t * (p2e.y - p1e.y)
|
|
};
|
|
|
|
offset = {x:0, y:0};
|
|
api.drawCircle(p1e,3, offset);
|
|
api.drawCircle(p2e,3, offset);
|
|
api.setWeight(0.5);
|
|
api.drawLine(p1e, p2e, offset);
|
|
api.setWeight(1.5);
|
|
api.drawLine(p1, p1e, offset);
|
|
api.drawLine(p2, p2e, offset);
|
|
api.setWeight(1);
|
|
|
|
offset.x += dim;
|
|
api.drawCircle(p1e,3, offset);
|
|
api.drawCircle(p2e,3, offset);
|
|
api.setWeight(0.5);
|
|
api.drawLine(p1e, p2e, offset);
|
|
api.setWeight(1.5);
|
|
api.drawLine(p1e, m, offset);
|
|
api.setWeight(1);
|
|
api.drawCircle(m,3,offset);
|
|
|
|
offset.x += dim;
|
|
api.drawCircle(m,3,offset);
|
|
|
|
api.text(i+"%, or t = " + api.utils.round(t,2), {x: m.x + 10 + offset.x, y: m.y + 10 + offset.y});
|
|
}
|
|
},
|
|
|
|
values: {
|
|
"38": 1, // up arrow
|
|
"40": -1 // down arrow
|
|
},
|
|
|
|
onKeyDown: function(e, api) {
|
|
var v = this.values[e.keyCode];
|
|
if(v) {
|
|
e.preventDefault();
|
|
api.step += v;
|
|
if (api.step < 1) {
|
|
api.step = 1;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"explanation": {
|
|
handler: (function() { return {
|
|
statics: {
|
|
keyHandlingOptions: {
|
|
propName: "step",
|
|
values: {
|
|
"38": 0.1, // up arrow
|
|
"40": -0.1 // down arrow
|
|
},
|
|
controller: function(api) {
|
|
if (api.step < 0.1) {
|
|
api.step = 0.1;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
setup: function(api) {
|
|
api.step = 5;
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
var dim = api.getPanelWidth(),
|
|
w = dim,
|
|
h = dim,
|
|
w2 = w/2,
|
|
h2 = h/2,
|
|
w4 = w2/2,
|
|
h4 = h2/2;
|
|
|
|
api.reset();
|
|
api.setColor("black");
|
|
api.drawLine({x:0,y:h2},{x:w,y:h2});
|
|
api.drawLine({x:w2,y:0},{x:w2,y:h});
|
|
|
|
var offset = {x:w2, y:h2};
|
|
for(var t=0, p; t<=api.step; t+=0.1) {
|
|
p = {
|
|
x: w4 * Math.cos(t),
|
|
y: h4 * Math.sin(t)
|
|
};
|
|
api.drawPoint(p, offset);
|
|
var modulo = t % 1;
|
|
if(modulo<0.05 || modulo> 0.95) {
|
|
api.text("t = " + Math.round(t), {
|
|
x: offset.x + 1.25 * w4 * Math.cos(t) - 10,
|
|
y: offset.y + 1.25 * h4 * Math.sin(t) + 5
|
|
});
|
|
api.drawCircle(p, 2, offset);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}()),
|
|
withKeys: true
|
|
},
|
|
"control": {
|
|
handler: (function() { return {
|
|
drawCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
drawCurve: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
},
|
|
|
|
drawFunction: function(api, label, where, generator) {
|
|
api.setRandomColor();
|
|
api.drawFunction(generator);
|
|
api.setFill(api.getColor());
|
|
if (label) api.text(label, where);
|
|
},
|
|
|
|
drawLerpBox: function(api, dim, pad, p) {
|
|
api.noColor();
|
|
api.setFill("rgba(0,0,100,0.2)");
|
|
var p1 = {x: p.x-5, y:pad},
|
|
p2 = {x:p.x + 5, y:dim};
|
|
api.drawRect(p1, p2);
|
|
api.setColor("black");
|
|
},
|
|
|
|
drawLerpPoint: function(api, tf, pad, fwh, p) {
|
|
p.y = pad + tf*fwh;
|
|
api.drawCircle(p, 3);
|
|
api.setFill("black");
|
|
api.text(((tf*10000)|0)/100 + "%", {x:p.x+10, y:p.y+4});
|
|
api.noFill();
|
|
},
|
|
|
|
drawQuadraticLerp: function(api) {
|
|
api.reset();
|
|
|
|
var dim = api.getPanelWidth(),
|
|
pad = 20,
|
|
fwh = dim - pad*2;
|
|
|
|
api.drawAxes(pad, "t",0,1, "S","0%","100%");
|
|
|
|
var p = api.hover;
|
|
if (p && p.x >= pad && p.x <= dim-pad) {
|
|
this.drawLerpBox(api, dim, pad, p);
|
|
var t = (p.x-pad)/fwh;
|
|
this.drawLerpPoint(api, (1-t)*(1-t), pad, fwh, p);
|
|
this.drawLerpPoint(api, 2*(1-t)*(t), pad, fwh, p);
|
|
this.drawLerpPoint(api, (t)*(t), pad, fwh, p);
|
|
}
|
|
|
|
this.drawFunction(api, "first term", {x: pad*2, y: fwh}, function(t) {
|
|
return {
|
|
x: pad + t * fwh,
|
|
y: pad + fwh * (1-t) * (1-t)
|
|
};
|
|
});
|
|
this.drawFunction(api, "second term", {x: dim/2 - 1.5*pad, y: dim/2 + pad}, function(t) {
|
|
return {
|
|
x: pad + t * fwh,
|
|
y: pad + fwh * 2 * (1-t) * (t)
|
|
};
|
|
});
|
|
this.drawFunction(api, "third term", {x: fwh - pad*2.5, y: fwh}, function(t) {
|
|
return {
|
|
x: pad + t * fwh,
|
|
y: pad + fwh * (t) * (t)
|
|
};
|
|
});
|
|
},
|
|
|
|
drawCubicLerp: function(api) {
|
|
api.reset();
|
|
|
|
var dim = api.getPanelWidth(),
|
|
pad = 20,
|
|
fwh = dim - pad*2;
|
|
|
|
api.drawAxes(pad, "t",0,1, "S","0%","100%");
|
|
|
|
var p = api.hover;
|
|
if (p && p.x >= pad && p.x <= dim-pad) {
|
|
this.drawLerpBox(api, dim, pad, p);
|
|
var t = (p.x-pad)/fwh;
|
|
this.drawLerpPoint(api, (1-t)*(1-t)*(1-t), pad, fwh, p);
|
|
this.drawLerpPoint(api, 3*(1-t)*(1-t)*(t), pad, fwh, p);
|
|
this.drawLerpPoint(api, 3*(1-t)*(t)*(t), pad, fwh, p);
|
|
this.drawLerpPoint(api, (t)*(t)*(t), pad, fwh, p);
|
|
}
|
|
|
|
this.drawFunction(api, "first term", {x: pad*2, y: fwh}, function(t) {
|
|
return {
|
|
x: pad + t * fwh,
|
|
y: pad + fwh * (1-t) * (1-t) * (1-t)
|
|
};
|
|
});
|
|
this.drawFunction(api, "second term", {x: dim/2 - 4*pad, y: dim/2 }, function(t) {
|
|
return {
|
|
x: pad + t * fwh,
|
|
y: pad + fwh * 3 * (1-t) * (1-t) * (t)
|
|
};
|
|
});
|
|
this.drawFunction(api, "third term", {x: dim/2 + 2*pad, y: dim/2}, function(t) {
|
|
return {
|
|
x: pad + t * fwh,
|
|
y: pad + fwh * 3 * (1-t) * (t) * (t)
|
|
};
|
|
});
|
|
this.drawFunction(api, "fourth term", {x: fwh - pad*2.5, y: fwh}, function(t) {
|
|
return {
|
|
x: pad + t * fwh,
|
|
y: pad + fwh * (t) * (t) * (t)
|
|
};
|
|
});
|
|
},
|
|
|
|
draw15thLerp: function(api) {
|
|
api.reset();
|
|
|
|
var dim = api.getPanelWidth(),
|
|
pad = 20,
|
|
fwh = dim - pad*2;
|
|
|
|
api.drawAxes(pad, "t",0,1, "S","0%","100%");
|
|
|
|
var factors = [1,15,105,455,1365,3003,5005,6435,6435,5005,3003,1365,455,105,15,1];
|
|
|
|
var p = api.hover, n;
|
|
if (p && p.x >= pad && p.x <= dim-pad) {
|
|
this.drawLerpBox(api, dim, pad, p);
|
|
for(n=0; n<=15; n++) {
|
|
var t = (p.x-pad)/fwh,
|
|
tf = factors[n] * Math.pow(1-t, 15-n) * Math.pow(t, n);
|
|
this.drawLerpPoint(api, tf, pad, fwh, p);
|
|
}
|
|
}
|
|
|
|
for(n=0; n<=15; n++) {
|
|
var label = false, position = false;
|
|
if (n===0) {
|
|
label = "first term";
|
|
position = {x: pad + 5, y: fwh};
|
|
}
|
|
if (n===15) {
|
|
label = "last term";
|
|
position = {x: dim - 3.5*pad, y: fwh};
|
|
}
|
|
this.drawFunction(api, label, position, function(t) {
|
|
return {
|
|
x: pad + t * fwh,
|
|
y: pad + fwh * factors[n] * Math.pow(1-t, 15-n) * Math.pow(t, n)
|
|
};
|
|
});
|
|
}
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"weightcontrol": {
|
|
handler: (function() { var ratios;
|
|
|
|
return {
|
|
drawCubic: function(api) {
|
|
var curve = new api.Bezier(
|
|
120, 160,
|
|
35, 200,
|
|
220, 260,
|
|
220, 40
|
|
);
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
drawCurve: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
},
|
|
|
|
setRatio: function(api, values) {
|
|
ratios = values;
|
|
this.update(api);
|
|
},
|
|
|
|
changeRatio: function(api, value, pos) {
|
|
ratios[pos] = parseFloat(value) || 0.00001;
|
|
this.update(api);
|
|
},
|
|
|
|
update: function(api) {
|
|
api.curve.setRatios(ratios.slice());
|
|
this.drawCurve(api, api.curve);
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"extended": {
|
|
handler: (function() { return {
|
|
setupQuadratic: function(api) {
|
|
var curve = new api.Bezier(70, 155, 20, 110, 100,75);
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve = new api.Bezier(60,105, 75,30, 215,115, 140,160);
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
api.setColor("lightgrey");
|
|
|
|
var t, step=0.05, min=-10;
|
|
var pt = curve.get(min - step), pn;
|
|
for (t=min; t<=step; t+=step) {
|
|
pn = curve.get(t);
|
|
api.drawLine(pt, pn);
|
|
pt = pn;
|
|
}
|
|
|
|
pt = curve.get(1);
|
|
var max = 10;
|
|
for (t=1+step; t<=max; t+=step) {
|
|
pn = curve.get(t);
|
|
api.drawLine(pt, pn);
|
|
pt = pn;
|
|
}
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"decasteljau": {
|
|
handler: (function() { return {
|
|
setup: function(api) {
|
|
var points = [
|
|
{x: 90, y:110},
|
|
{x: 25, y: 40},
|
|
{x:230, y: 40},
|
|
{x:150, y:240}
|
|
];
|
|
api.setCurve(new api.Bezier(points));
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
if (api.hover) {
|
|
api.setColor("rgb(200,100,100)");
|
|
var dim = api.getPanelWidth();
|
|
var t = api.hover.x / dim;
|
|
var hull = api.drawHull(curve, t);
|
|
|
|
for(var i=4; i<=8; i++) {
|
|
api.drawCircle(hull[i],3);
|
|
}
|
|
|
|
var p = curve.get(t);
|
|
api.drawCircle(p, 5);
|
|
api.setFill("black");
|
|
api.drawCircle(p, 3);
|
|
var perc = (t*100)|0;
|
|
t = perc/100;
|
|
api.text("Sequential interpolation for "+perc+"% (t="+t+")", {x: 10, y:15});
|
|
}
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"flattening": {
|
|
handler: (function() { return {
|
|
statics: {
|
|
keyHandlingOptions: {
|
|
propName: "steps",
|
|
values: {
|
|
"38": 1, // up arrow
|
|
"40": -1 // down arrow
|
|
},
|
|
controller: function(api) {
|
|
if (api.steps < 1) {
|
|
api.steps = 1;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
api.setCurve(curve);
|
|
api.steps = 3;
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
api.steps = 5;
|
|
},
|
|
|
|
drawFlattened: function(api, curve) {
|
|
api.reset();
|
|
api.setColor("#DDD");
|
|
api.drawSkeleton(curve);
|
|
api.setColor("#DDD");
|
|
api.drawCurve(curve);
|
|
var step = 1 / api.steps;
|
|
var p0 = curve.points[0], pc;
|
|
for(var t=step; t<1.0+step; t+=step) {
|
|
pc = curve.get(Math.min(t,1));
|
|
api.setColor("red");
|
|
api.drawLine(p0,pc);
|
|
p0 = pc;
|
|
}
|
|
api.setFill("black");
|
|
api.text("Curve approximation using "+api.steps+" segments", {x:10, y:15});
|
|
},
|
|
|
|
values: {
|
|
"38": 1, // up arrow
|
|
"40": -1 // down arrow
|
|
},
|
|
|
|
onKeyDown: function(e, api) {
|
|
var v = this.values[e.keyCode];
|
|
if(v) {
|
|
e.preventDefault();
|
|
api.steps += v;
|
|
if (api.steps < 1) {
|
|
api.steps = 1;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}()),
|
|
withKeys: true
|
|
},
|
|
"splitting": {
|
|
handler: (function() { return {
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
api.forward = true;
|
|
},
|
|
|
|
drawSplit: function(api, curve) {
|
|
api.setPanelCount(2);
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
var offset = {x:0, y:0};
|
|
var t = 0.5;
|
|
var pt = curve.get(0.5);
|
|
var split = curve.split(t);
|
|
api.drawCurve(split.left);
|
|
api.drawCurve(split.right);
|
|
api.setColor("red");
|
|
api.drawCircle(pt,3);
|
|
|
|
api.setColor("black");
|
|
offset.x = api.getPanelWidth();
|
|
api.drawLine({x:0,y:0},{x:0,y:api.getPanelHeight()}, offset);
|
|
|
|
api.setColor("lightgrey");
|
|
api.drawCurve(curve, offset);
|
|
api.drawCircle(pt,4);
|
|
|
|
offset.x -= 20;
|
|
offset.y -= 20;
|
|
api.drawSkeleton(split.left, offset, true);
|
|
api.drawCurve(split.left, offset);
|
|
|
|
offset.x += 40;
|
|
offset.y += 40;
|
|
api.drawSkeleton(split.right, offset, true);
|
|
api.drawCurve(split.right, offset);
|
|
},
|
|
|
|
drawAnimated: function(api, curve) {
|
|
api.setPanelCount(3);
|
|
api.reset();
|
|
|
|
var frame = api.getFrame();
|
|
var interval = 5 * api.getPlayInterval();
|
|
var t = (frame%interval)/interval;
|
|
var forward = (frame%(2*interval)) < interval;
|
|
if (forward) { t = t%1; } else { t = 1 - t%1; }
|
|
var offset = {x:0, y:0};
|
|
|
|
api.setColor("lightblue");
|
|
api.drawHull(curve, t);
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
var pt = curve.get(t);
|
|
api.drawCircle(pt, 4);
|
|
|
|
api.setColor("black");
|
|
offset.x += api.getPanelWidth();
|
|
api.drawLine({x:0,y:0},{x:0,y:api.getPanelHeight()}, offset);
|
|
|
|
var split = curve.split(t);
|
|
|
|
api.setColor("lightgrey");
|
|
api.drawCurve(curve, offset);
|
|
api.drawHull(curve, t, offset);
|
|
api.setColor("black");
|
|
api.drawCurve(split.left, offset);
|
|
api.drawPoints(split.left.points, offset);
|
|
api.setFill("black");
|
|
api.text("Left side of curve split at t = " + (((100*t)|0)/100), {x: 10 + offset.x, y: 15 + offset.y});
|
|
|
|
offset.x += api.getPanelWidth();
|
|
api.drawLine({x:0,y:0},{x:0,y:api.getPanelHeight()}, offset);
|
|
|
|
api.setColor("lightgrey");
|
|
api.drawCurve(curve, offset);
|
|
api.drawHull(curve, t, offset);
|
|
api.setColor("black");
|
|
api.drawCurve(split.right, offset);
|
|
api.drawPoints(split.right.points, offset);
|
|
api.setFill("black");
|
|
api.text("Right side of curve split at t = " + (((100*t)|0)/100), {x: 10 + offset.x, y: 15 + offset.y});
|
|
},
|
|
|
|
togglePlay: function(evt, api) {
|
|
if (api.playing) { api.pause(); } else { api.play(); }
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"reordering": {
|
|
handler: (function() { var invert = require('../../../lib/matrix-invert.js');
|
|
var multiply = require('../../../lib/matrix-multiply.js');
|
|
var transpose = require('../../../lib/matrix-transpose.js');
|
|
|
|
var Reordering = {
|
|
statics: {
|
|
keyHandlingOptions: {
|
|
values: {
|
|
"38": function(api) {
|
|
api.setCurve(api.curve.raise());
|
|
api.redraw();
|
|
},
|
|
"40": function(api) {
|
|
api.setCurve(Reordering.lower(api));
|
|
api.redraw();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// Based on http://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/
|
|
lower: function(api) {
|
|
var curve = api.curve,
|
|
pts = curve.points,
|
|
k = pts.length,
|
|
M = [],
|
|
n = k-1,
|
|
i;
|
|
|
|
// build M, which will be (k) rows by (k-1) columns
|
|
for(i=0; i<k; i++) {
|
|
M[i] = (new Array(k)).fill(0);
|
|
if(i===0) { M[i][0] = 1; }
|
|
else if(i===n) { M[i][i-1] = 1; }
|
|
else {
|
|
M[i][i-1] = i / k;
|
|
M[i][i] = 1 - M[i][i-1];
|
|
}
|
|
}
|
|
|
|
// then, apply our matrix operations:
|
|
var Mt = transpose(M);
|
|
var Mc = multiply(Mt, M);
|
|
var Mi = invert(Mc);
|
|
|
|
if (!Mi) {
|
|
console.error('MtM has no inverse?');
|
|
return curve;
|
|
}
|
|
|
|
var V = multiply(Mi, Mt);
|
|
|
|
// And then we map our k-order list of coordinates
|
|
// to an n-order list of coordinates, instead:
|
|
var x = pts.map(p => [p.x]);
|
|
var nx = multiply(V, x);
|
|
|
|
var y = pts.map(p => [p.y]);
|
|
var ny = multiply(V, y);
|
|
|
|
var npts = nx.map((x,i) => {
|
|
return {
|
|
x: x[0],
|
|
y: ny[i][0]
|
|
};
|
|
});
|
|
|
|
return new api.Bezier(npts);
|
|
},
|
|
|
|
getInitialState: function() {
|
|
return {
|
|
order: 0
|
|
};
|
|
},
|
|
|
|
setup: function(api) {
|
|
var points = [];
|
|
var w = api.getPanelWidth(),
|
|
h = api.getPanelHeight();
|
|
for (var i=0; i<10; i++) {
|
|
points.push({
|
|
x: w/2 + (Math.random() * 20) + Math.cos(Math.PI*2 * i/10) * (w/2 - 40),
|
|
y: h/2 + (Math.random() * 20) + Math.sin(Math.PI*2 * i/10) * (h/2 - 40)
|
|
});
|
|
}
|
|
var curve = new api.Bezier(points);
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
var pts = curve.points;
|
|
|
|
this.setState({
|
|
order: pts.length
|
|
});
|
|
|
|
var p0 = pts[0];
|
|
|
|
// we can't "just draw" this curve, since it'll be an arbitrary order,
|
|
// And the canvas only does 2nd and 3rd - we use de Casteljau's algorithm:
|
|
for(var t=0; t<=1; t+=0.01) {
|
|
var q = JSON.parse(JSON.stringify(pts));
|
|
while(q.length > 1) {
|
|
for (var i=0; i<q.length-1; i++) {
|
|
q[i] = {
|
|
x: q[i].x + (q[i+1].x - q[i].x) * t,
|
|
y: q[i].y + (q[i+1].y - q[i].y) * t
|
|
};
|
|
}
|
|
q.splice(q.length-1, 1);
|
|
}
|
|
api.drawLine(p0, q[0]);
|
|
p0 = q[0];
|
|
}
|
|
|
|
p0 = pts[0];
|
|
api.setColor("black");
|
|
api.drawCircle(p0,3);
|
|
pts.forEach(p => {
|
|
if(p===p0) return;
|
|
api.setColor("#DDD");
|
|
api.drawLine(p0,p);
|
|
api.setColor("black");
|
|
api.drawCircle(p,3);
|
|
p0 = p;
|
|
});
|
|
},
|
|
|
|
getOrder: function() {
|
|
var order = this.state.order;
|
|
if (order%10 === 1 && order !== 11) {
|
|
order += "st";
|
|
} else if (order%10 === 2 && order !== 12) {
|
|
order += "nd";
|
|
} else if (order%10 === 3 && order !== 13) {
|
|
order += "rd";
|
|
} else {
|
|
order += "th";
|
|
}
|
|
return order;
|
|
},
|
|
|
|
onMouseMove: function(evt, api) {
|
|
api.redraw();
|
|
}
|
|
};
|
|
|
|
return Reordering;
|
|
}()),
|
|
withKeys: true
|
|
},
|
|
"pointvectors": {
|
|
handler: (function() { return {
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
|
|
var i,t,p,tg,n,m,nd=20;
|
|
for(i=0; i<=10; i++) {
|
|
t = i/10.0;
|
|
p = curve.get(t);
|
|
tg = curve.derivative(t);
|
|
m = Math.sqrt(tg.x*tg.x + tg.y*tg.y);
|
|
tg = {x:tg.x/m, y:tg.y/m};
|
|
n = curve.normal(t);
|
|
api.setColor("blue");
|
|
api.drawLine(p, {x:p.x+tg.x*nd, y:p.y+tg.y*nd});
|
|
api.setColor("red");
|
|
api.drawLine(p, {x:p.x+n.x*nd, y:p.y+n.y*nd});
|
|
api.setColor("black");
|
|
api.drawCircle(p,3);
|
|
}
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"pointvectors3d": {
|
|
handler: (function() { var vectorOffset;
|
|
var normalsOffset;
|
|
|
|
var SHADOW_ALPHA = 0.2;
|
|
var SHOW_PROJECTIONS = true;
|
|
|
|
function normalize(v) {
|
|
var d = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
|
|
return { x:v.x/d, y:v.y/d, z:v.z/d };
|
|
}
|
|
|
|
function vdot(v1, v2) {
|
|
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
|
|
}
|
|
|
|
function vscale(v1, s) {
|
|
return {
|
|
x: s * v1.x,
|
|
y: s * v1.y,
|
|
z: s * v1.z
|
|
};
|
|
}
|
|
|
|
function vplus(v1, v2) {
|
|
return {
|
|
x: v1.x + v2.x,
|
|
y: v1.y + v2.y,
|
|
z: v1.z + v2.z
|
|
};
|
|
}
|
|
|
|
function vminus(v1, v2) {
|
|
return {
|
|
x: v1.x - v2.x,
|
|
y: v1.y - v2.y,
|
|
z: v1.z - v2.z
|
|
};
|
|
}
|
|
|
|
function vcross(v1, v2) {
|
|
return {
|
|
x: v1.y * v2.z - v1.z * v2.y,
|
|
y: v1.z * v2.x - v1.x * v2.z,
|
|
z: v1.x * v2.y - v1.y * v2.x
|
|
};
|
|
}
|
|
|
|
function vlerp(t, v1, v2) {
|
|
return {
|
|
x: (1-t)*v1.x + t*v2.x,
|
|
y: (1-t)*v1.y + t*v2.y,
|
|
z: (1-t)*v1.z + t*v2.z
|
|
};
|
|
}
|
|
|
|
return {
|
|
setup: function(api) {
|
|
vectorOffset = {
|
|
x: 2 * api.getPanelWidth() / 5,
|
|
y: 4 * api.getPanelHeight() / 5
|
|
};
|
|
api.setSize(1.25 * api.getPanelWidth(),api.getPanelHeight());
|
|
},
|
|
|
|
drawCube: function(api) {
|
|
var prj = p => api.project(p, vectorOffset);
|
|
|
|
var cube = [
|
|
{x:0, y:0, z:0},
|
|
{x:200,y:0, z:0},
|
|
{x:200,y:200,z:0},
|
|
{x:0, y:200,z:0},
|
|
{x:0, y:0, z:200},
|
|
{x:200,y:0, z:200},
|
|
{x:200,y:200,z:200},
|
|
{x:0, y:200,z:200}
|
|
].map(p => prj(p));
|
|
|
|
// "most of the cube"
|
|
api.setColor("grey");
|
|
api.drawLine(cube[1], cube[2]);
|
|
api.drawLine(cube[2], cube[3]);
|
|
api.drawLine(cube[1], cube[5]);
|
|
api.drawLine(cube[2], cube[6]);
|
|
api.drawLine(cube[3], cube[7]);
|
|
api.drawLine(cube[4], cube[5]);
|
|
api.drawLine(cube[5], cube[6]);
|
|
api.drawLine(cube[6], cube[7]);
|
|
api.drawLine(cube[7], cube[4]);
|
|
|
|
// x axis
|
|
api.setColor("blue");
|
|
api.drawLine(cube[0], cube[1]);
|
|
|
|
// y axis
|
|
api.setColor("red");
|
|
api.drawLine(cube[3], cube[0]);
|
|
|
|
// z axis
|
|
api.setColor("green");
|
|
api.drawLine(cube[0], cube[4]);
|
|
},
|
|
|
|
drawCurve(api, curvepoints, project) {
|
|
var prj = p => api.project(p, vectorOffset),
|
|
curve2d = curvepoints.map(p => prj(p)),
|
|
points;
|
|
|
|
if (project) {
|
|
// projections
|
|
api.setColor(`rgba(0,0,0,${SHADOW_ALPHA})`);
|
|
api.drawCurve({ points: curvepoints.map(p => api.projectXY(p, vectorOffset)) });
|
|
api.drawCurve({ points: curvepoints.map(p => api.projectYZ(p, vectorOffset)) });
|
|
api.drawCurve({ points: curvepoints.map(p => api.projectXZ(p, vectorOffset)) });
|
|
}
|
|
|
|
// control lines
|
|
api.setColor("#333");
|
|
api.drawLine(curve2d[0], curve2d[1]);
|
|
api.drawCircle(curve2d[1], 3);
|
|
api.drawCircle(curve2d[2], 3);
|
|
api.drawLine(curve2d[2], curve2d[3]);
|
|
|
|
// main curve
|
|
api.setColor("black");
|
|
api.drawCircle(curve2d[0], 3);
|
|
api.drawCircle(curve2d[3], 3);
|
|
|
|
var curve = new api.Bezier(curve2d);
|
|
api.drawCurve({ points: curve2d });
|
|
},
|
|
|
|
getFrenetVectors: function(t, curve, d1curve) {
|
|
var o = curve.get(t),
|
|
// get the normalized tangent
|
|
dt = d1curve.get(t),
|
|
// and then let's work in the change in tangent
|
|
ddt = d1curve.derivative(t),
|
|
b = normalize(vplus(dt, ddt)),
|
|
// compute the normalized axis of rotation
|
|
r = normalize(vcross(b, dt)),
|
|
// compute the normal
|
|
n = normalize(vcross(r, dt));
|
|
return { o, dt, r, n };
|
|
},
|
|
|
|
lerpVectors(t, v1, v2) {
|
|
var v = {};
|
|
['o', 'dt', 'r', 'n'].forEach(p => {
|
|
v[p] = vlerp(t, v1[p], v2[p]);
|
|
});
|
|
return v;
|
|
},
|
|
|
|
generateRMF: function(curve, d1curve) {
|
|
var frames = [], step = 0.05;
|
|
frames.push(this.getFrenetVectors(0, curve, d1curve));
|
|
for(var t0=0; t0<=1; t0+=step) {
|
|
var x0 = frames.slice(-1)[0],
|
|
t1 = t0 + step,
|
|
x1 = { o: curve.get(t1), dt: d1curve.get(t1) },
|
|
v1 = vminus(x1.o, x0.o),
|
|
c1 = vdot(v1, v1),
|
|
riL = vminus(x0.r, vscale(v1, 2/c1 * vdot(v1, x0.r))),
|
|
tiL = vminus(x0.dt, vscale(v1, 2/c1 * vdot(v1, x0.dt))),
|
|
v2 = vminus(x1.dt, tiL),
|
|
c2 = vdot(v2, v2);
|
|
x1.r = vminus(riL, vscale(v2, 2/c2 * vdot(v2, riL)));
|
|
x1.n = vcross(x1.r, x1.dt);
|
|
frames.push(x1); }
|
|
return frames;
|
|
},
|
|
|
|
getRMF: function(t, curve, d1curve) {
|
|
if (!this.rmf_LUT) {
|
|
this.rmf_LUT = this.generateRMF(curve, d1curve);
|
|
}
|
|
// find integer index
|
|
var l = this.rmf_LUT.length;
|
|
var i = t * l;
|
|
if (i != (i|0)) {
|
|
// no intenger index: interpolate values?
|
|
i = (i|0);
|
|
if (i===l-1) return this.rmf_LUT[i-1];
|
|
var j = i + 1, ti = i/l, tj = j/l;
|
|
t = (t - ti) / (tj - ti);
|
|
return this.lerpVectors(t, this.rmf_LUT[i], this.rmf_LUT[j]);
|
|
}
|
|
return this.rmf_LUT[i];
|
|
},
|
|
|
|
drawVector: function(api, from, to, len, r,g,b) {
|
|
var prj = p => api.project(p, vectorOffset);
|
|
to = normalize(to);
|
|
to = {
|
|
x: from.x + len * to.x,
|
|
y: from.y + len * to.y,
|
|
z: from.z + len * to.z
|
|
};
|
|
api.setColor(`rgba(${r},${g},${b},1)`);
|
|
// draw the actual vector
|
|
api.drawLine(prj(from), prj(to));
|
|
},
|
|
|
|
drawFrenetVectors: function(api) {
|
|
api.reset();
|
|
var prj = p => api.project(p, vectorOffset);
|
|
|
|
this.drawCube(api);
|
|
|
|
var curvepoints = [
|
|
{x:120,y:0,z:0},
|
|
{x:120,y:220,z:0},
|
|
{x:30,y:0,z:30},
|
|
{x:0,y:0,z:200}
|
|
];
|
|
|
|
this.drawCurve(api, curvepoints, SHOW_PROJECTIONS);
|
|
|
|
// let's mark t
|
|
var curve = new api.Bezier(curvepoints);
|
|
var d1curve = new api.Bezier(curve.dpoints[0]);
|
|
var t = Math.max(api.hover.x? api.hover.x / api.getPanelWidth() : 0, 0);
|
|
var mt = curve.get(t);
|
|
api.drawCircle(prj(mt), 3);
|
|
|
|
// draw the tangent, rotational axis, and normal
|
|
var vectors = this.getFrenetVectors(t, curve, d1curve);
|
|
this.drawVector(api, mt, vectors.dt, 40, 0,200,0);
|
|
this.drawVector(api, mt, vectors.r, 40, 0,0,200);
|
|
this.drawVector(api, mt, vectors.n, 40, 200,0,0);
|
|
},
|
|
|
|
drawRMFNormals: function(api) {
|
|
api.reset();
|
|
var prj = p => api.project(p, vectorOffset);
|
|
|
|
this.drawCube(api);
|
|
|
|
var curvepoints = [
|
|
{x:120,y:0,z:0},
|
|
{x:120,y:220,z:0},
|
|
{x:30,y:0,z:30},
|
|
{x:0,y:0,z:200}
|
|
];
|
|
|
|
this.drawCurve(api, curvepoints, SHOW_PROJECTIONS);
|
|
|
|
// let's mark t
|
|
var curve = new api.Bezier(curvepoints);
|
|
var d1curve = new api.Bezier(curve.dpoints[0]);
|
|
var t = Math.max(api.hover.x? api.hover.x / api.getPanelWidth() : 0, 0);
|
|
var mt = curve.get(t);
|
|
api.drawCircle(prj(mt), 3);
|
|
|
|
// draw the tangent, rotational axis, and normal
|
|
var vectors = this.getRMF(t, curve, d1curve);
|
|
this.drawVector(api, mt, vectors.dt, 40, 0,200,0);
|
|
this.drawVector(api, mt, vectors.r, 40, 0,0,200);
|
|
this.drawVector(api, mt, vectors.n, 40, 200,0,0);
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"components": {
|
|
handler: (function() { return {
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
curve.points[2].x = 210;
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.setPanelCount(3);
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
var tf = curve.order,
|
|
pad = 20,
|
|
pts = curve.points,
|
|
w = api.getPanelWidth(),
|
|
wp = w - 2 * pad,
|
|
h = api.getPanelHeight(),
|
|
offset = { x: w, y: 0 };
|
|
|
|
var x_pts = JSON.parse(JSON.stringify(pts)).map((p,t) => {
|
|
return {x:wp*t/tf, y:p.x};
|
|
});
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
api.drawAxes(pad, "t",0,1, "x",0,w, offset);
|
|
offset.x += pad;
|
|
api.drawCurve(new api.Bezier(x_pts), offset);
|
|
|
|
offset.x += w-pad;
|
|
var y_pts = JSON.parse(JSON.stringify(pts)).map((p,t) => {
|
|
return {x:wp*t/tf, y:p.y};
|
|
});
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
api.drawAxes(pad, "t",0,1, "y",0,w, offset);
|
|
offset.x += pad;
|
|
api.drawCurve(new api.Bezier(y_pts), offset);
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"extremities": {
|
|
handler: (function() { return {
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
curve.points[2].x = 210;
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.setPanelCount(3);
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
var tf = curve.order + 1,
|
|
pad = 20,
|
|
pts = curve.points,
|
|
w = api.getPanelWidth(),
|
|
h = api.getPanelHeight(),
|
|
offset = { x: w, y: 0 };
|
|
|
|
var x_pts = JSON.parse(JSON.stringify(pts)).map((p,t) => { return {x:w*t/tf, y:p.x}; });
|
|
api.setColor("black");
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
api.drawAxes(pad, "t",0,1, "x",0,w, offset);
|
|
offset.x += pad;
|
|
var xcurve = new api.Bezier(x_pts);
|
|
api.drawCurve(xcurve, offset);
|
|
api.setColor("red");
|
|
xcurve.extrema().y.forEach(t => {
|
|
var p = xcurve.get(t);
|
|
api.drawCircle(p, 3, offset);
|
|
});
|
|
|
|
offset.x += w-pad;
|
|
var y_pts = JSON.parse(JSON.stringify(pts)).map((p,t) => { return {x:w*t/tf, y:p.y}; });
|
|
api.setColor("black");
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
api.drawAxes(pad, "t",0,1, "y",0,w, offset);
|
|
offset.x += pad;
|
|
var ycurve = new api.Bezier(y_pts);
|
|
api.drawCurve(ycurve, offset);
|
|
api.setColor("red");
|
|
ycurve.extrema().y.forEach(t => {
|
|
var p = ycurve.get(t);
|
|
api.drawCircle(p, 3, offset);
|
|
});
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"boundingbox": {
|
|
handler: (function() { return {
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.setColor("#00FF00");
|
|
api.drawbbox(curve.bbox());
|
|
api.setColor("black");
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
api.setColor("red");
|
|
curve.extrema().values.forEach(t => {
|
|
api.drawCircle(curve.get(t), 3);
|
|
});
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"aligning": {
|
|
handler: (function() { return {
|
|
/**
|
|
* Setup function for a default quadratic curve.
|
|
*/
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
/**
|
|
* Setup function for a default cubic curve.
|
|
*/
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
/**
|
|
* A coordinate rotation function that rotates and
|
|
* translates the curve, such that the first coordinate
|
|
* of the curve is (0,0) and the last coordinate is (..., 0)
|
|
*/
|
|
align: function(points, line) {
|
|
var tx = line.p1.x,
|
|
ty = line.p1.y,
|
|
// The atan2 function is so important to computing
|
|
// that most CPUs have a dedicated implementation
|
|
// at the hardware level for it.
|
|
a = -Math.atan2(line.p2.y-ty, line.p2.x-tx),
|
|
cos = Math.cos,
|
|
sin = Math.sin,
|
|
d = function(v) {
|
|
return {
|
|
x: (v.x-tx)*cos(a) - (v.y-ty)*sin(a),
|
|
y: (v.x-tx)*sin(a) + (v.y-ty)*cos(a)
|
|
};
|
|
};
|
|
return points.map(d);
|
|
},
|
|
|
|
/**
|
|
* Draw a curve and its aligned counterpart
|
|
* side by side across two panels.
|
|
*/
|
|
draw: function(api, curve) {
|
|
api.setPanelCount(2);
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
var pts = curve.points;
|
|
var line = {p1: pts[0], p2: pts[pts.length-1]};
|
|
var apts = this.align(pts, line);
|
|
var aligned = new api.Bezier(apts);
|
|
var w = api.getPanelWidth();
|
|
var h = api.getPanelHeight();
|
|
|
|
var offset = {x:w, y:0};
|
|
api.setColor("black");
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
offset.x += w/4;
|
|
offset.y += h/2;
|
|
api.setColor("grey");
|
|
api.drawLine({x:0,y:-h/2}, {x:0,y:h/2}, offset);
|
|
api.drawLine({x:-w/4,y:0}, {x:w,y:0}, offset);
|
|
api.setFill("grey");
|
|
|
|
api.setColor("black");
|
|
api.drawSkeleton(aligned, offset);
|
|
api.drawCurve(aligned, offset);
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"tightbounds": {
|
|
handler: (function() { return {
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
align: function(points, line) {
|
|
var tx = line.p1.x,
|
|
ty = line.p1.y,
|
|
a = -Math.atan2(line.p2.y-ty, line.p2.x-tx),
|
|
cos = Math.cos,
|
|
sin = Math.sin,
|
|
d = function(v) {
|
|
return {
|
|
x: (v.x-tx)*cos(a) - (v.y-ty)*sin(a),
|
|
y: (v.x-tx)*sin(a) + (v.y-ty)*cos(a),
|
|
a: a
|
|
};
|
|
};
|
|
return points.map(d);
|
|
},
|
|
|
|
// FIXME: I'm not satisfied with needing to turn a bbox[] into a point[],
|
|
// this needs a bezier.js solution, really, with a call curve.tightbbox()
|
|
transpose: function(points, angle, offset) {
|
|
var tx = offset.x,
|
|
ty = offset.y,
|
|
cos = Math.cos,
|
|
sin = Math.sin,
|
|
v = [points.x.min, points.y.min, points.x.max, points.y.max];
|
|
return [
|
|
{x: v[0], y: v[1] },
|
|
{x: v[2], y: v[1] },
|
|
{x: v[2], y: v[3] },
|
|
{x: v[0], y: v[3] }
|
|
].map(p => {
|
|
var x=p.x, y=p.y;
|
|
return {
|
|
x: x*cos(angle) - y*sin(angle) + tx,
|
|
y: x*sin(angle) + y*cos(angle) + ty
|
|
};
|
|
});
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
|
|
var pts = curve.points;
|
|
var line = {p1: pts[0], p2: pts[pts.length-1]};
|
|
var apts = this.align(pts, line);
|
|
var angle = -apts[0].a;
|
|
var aligned = new api.Bezier(apts);
|
|
var bbox = aligned.bbox();
|
|
var tpts = this.transpose(bbox, angle, pts[0]);
|
|
|
|
api.setColor("#00FF00");
|
|
api.drawLine(tpts[0], tpts[1]);
|
|
api.drawLine(tpts[1], tpts[2]);
|
|
api.drawLine(tpts[2], tpts[3]);
|
|
api.drawLine(tpts[3], tpts[0]);
|
|
|
|
api.setColor("black");
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"inflections": {
|
|
handler: (function() { return {
|
|
setupCubic: function(api) {
|
|
var curve = new api.Bezier(135,25, 25, 135, 215,75, 215,240);
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
api.setColor("red");
|
|
curve.inflections().forEach(function(t) {
|
|
api.drawCircle(curve.get(t), 5);
|
|
});
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"canonical": {
|
|
handler: (function() { return {
|
|
setup: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
api.reset();
|
|
api._map_loaded = false;
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
var w = 400,
|
|
h = w,
|
|
unit = this.unit,
|
|
center = {x:w/2, y:h/2};
|
|
|
|
api.setSize(w,h);
|
|
api.setPanelCount(2);
|
|
api.reset();
|
|
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
api.offset.x += 400;
|
|
|
|
if (api._map_loaded) { api.image(api._map_image); }
|
|
else { setTimeout((
|
|
function() {
|
|
this.drawBase(api, curve);
|
|
this.draw(api, curve);
|
|
}
|
|
).bind(this), 100); }
|
|
|
|
api.drawLine({x:0,y:0}, {x:0, y:h});
|
|
|
|
var npts = [
|
|
{x:0, y: 0},
|
|
{x:0, y: unit},
|
|
{x:unit, y: unit},
|
|
this.forwardTransform(curve.points, unit)
|
|
];
|
|
|
|
var canonical = new api.Bezier(npts);
|
|
api.setColor("blue");
|
|
api.drawCurve(canonical, center);
|
|
api.drawCircle(npts[3], 3, center);
|
|
},
|
|
|
|
forwardTransform: function(pts, s) {
|
|
s = s || 1;
|
|
var p1 = pts[0], p2 = pts[1], p3 = pts[2], p4 = pts[3];
|
|
|
|
var xn = -p1.x + p4.x - (-p1.x+p2.x)*(-p1.y+p4.y)/(-p1.y+p2.y);
|
|
var xd = -p1.x + p3.x - (-p1.x+p2.x)*(-p1.y+p3.y)/(-p1.y+p2.y);
|
|
var np4x = s*xn/xd;
|
|
|
|
var yt1 = s*(-p1.y+p4.y) / (-p1.y+p2.y);
|
|
var yt2 = s - (s*(-p1.y+p3.y)/(-p1.y+p2.y));
|
|
var yp = yt2 * xn / xd;
|
|
var np4y = yt1 + yp;
|
|
|
|
return {x:np4x, y:np4y};
|
|
},
|
|
|
|
drawBase: function(api, curve) {
|
|
api.reset();
|
|
|
|
var w = 400,
|
|
h = w,
|
|
unit = this.unit = w/5,
|
|
center = {x:w/2, y:h/2};
|
|
|
|
api.setSize(w,h);
|
|
|
|
// axes + gridlines
|
|
api.setColor("lightgrey");
|
|
for(var x=0; x<w; x+= unit/2) { api.drawLine({x:x, y:0}, {x:x, y:h}); }
|
|
for(var y=0; y<h; y+= unit/2) { api.drawLine({x:0, y:y}, {x:w, y:y}); }
|
|
api.setColor("black");
|
|
api.drawLine({x:w/2,y:0}, {x:w/2, y:h});
|
|
api.drawLine({x:0,y:h/2}, {x:w, y:h/2});
|
|
|
|
// Inflection border:
|
|
api.setColor("green");
|
|
api.drawLine({x:-w/2,y:unit}, {x:w/2,y:unit}, center);
|
|
|
|
// the three stable points
|
|
api.setColor("black");
|
|
api.setFill("black");
|
|
api.drawCircle({x:0, y:0}, 4, center);
|
|
api.text("(0,0)", {x: 5+center.x, y:15+center.y});
|
|
api.drawCircle({x:0, y:unit}, 4, center);
|
|
api.text("(0,1)", {x: 5+center.x, y:unit+15+center.y});
|
|
api.drawCircle({x:unit, y:unit}, 4, center);
|
|
api.text("(1,1)", {x: unit+5+center.x, y:unit+15+center.y});
|
|
|
|
// cusp parabola:
|
|
api.setWeight(1.5);
|
|
api.setColor("#FF0000");
|
|
api.setFill(api.getColor());
|
|
var pts = [];
|
|
var px = 1, py = 1;
|
|
for (x=-10; x<=1; x+=0.01) {
|
|
y = (-x*x + 2*x + 3)/4;
|
|
if (x>-10) {
|
|
pts.push({x:unit*px, y:unit*py});
|
|
api.drawLine({x:unit*px, y:unit*py}, {x:unit*x, y:unit*y}, center);
|
|
}
|
|
px = x;
|
|
py = y;
|
|
}
|
|
pts.push({x:unit*px, y:unit*py});
|
|
api.text("Curve form has cusp →", {x:w/2-unit*2, y: h/2+unit/2.5});
|
|
|
|
// loop/arch transition boundary, elliptical section
|
|
api.setColor("#FF00FF");
|
|
api.setFill(api.getColor());
|
|
var sqrt = Math.sqrt;
|
|
for (x=1; x>=0; x-=0.005) {
|
|
pts.push({x:unit*px, y:unit*py});
|
|
y = 0.5 * (sqrt(3) * sqrt(4*x - x*x) - x);
|
|
api.drawLine({x:unit*px, y:unit*py}, {x:unit*x, y:unit*y}, center);
|
|
px = x;
|
|
py = y;
|
|
}
|
|
pts.push({x:unit*px, y:unit*py});
|
|
api.text("← Curve forms a loop at t = 1", {x:w/2+unit/4, y: h/2+unit/1.5});
|
|
|
|
|
|
// loop/arch transition boundary, parabolic section
|
|
api.setColor("#3300FF");
|
|
api.setFill(api.getColor());
|
|
for (x=0; x>-w; x-=0.01) {
|
|
pts.push({x:unit*px, y:unit*py});
|
|
y = (-x*x + 3*x)/3;
|
|
api.drawLine({x:unit*px, y:unit*py}, {x:unit*x, y:unit*y}, center);
|
|
px = x;
|
|
py = y;
|
|
}
|
|
pts.push({x:unit*px, y:unit*py});
|
|
api.text("← Curve forms a loop at t = 0", {x:w/2-unit+10, y: h/2-unit*1.25});
|
|
|
|
// shape fill
|
|
api.setColor("transparent");
|
|
api.setFill("rgba(255,120,100,0.2)");
|
|
api.drawPath(pts, center);
|
|
pts = [{x:-w/2,y:unit}, {x:w/2,y:unit}, {x:w/2,y:h}, {x:-w/2,y:h}];
|
|
api.setFill("rgba(0,200,0,0.2)");
|
|
api.drawPath(pts, center);
|
|
|
|
// further labels
|
|
api.setColor("black");
|
|
api.setFill(api.getColor());
|
|
api.text("← Curve form has one inflection →", {x:w/2 - unit, y: h/2 + unit*1.75});
|
|
api.text("← Plain curve ↕", {x:w/2 + unit/2, y: h/6});
|
|
api.text("↕ Double inflection", {x:10, y: h/2 - 10});
|
|
|
|
api._map_image = api.toImage();
|
|
api._map_loaded = true;
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"yforx": {
|
|
handler: (function() { var sketch = {
|
|
getCurve: api => {
|
|
if (!sketch.curve) {
|
|
sketch.curve = new api.Bezier(20, 250, 30, 20, 200, 250, 250, 20);
|
|
}
|
|
return sketch.curve;
|
|
},
|
|
|
|
onMouseMove: function(evt, api) {
|
|
api.redraw();
|
|
},
|
|
|
|
tforx: {
|
|
setup: function(api) {
|
|
api.setPanelCount(2);
|
|
api.setCurve(sketch.getCurve(api));
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
let w = api.defaultWidth;
|
|
let h = api.defaultHeight;
|
|
let bbox = curve.bbox();
|
|
let x = api.mx;
|
|
if (bbox.x.min < x && x < bbox.x.max) {
|
|
api.setColor("red");
|
|
api.drawLine({ x: x, y: 0 }, { x: x, y: h });
|
|
api.text(`x=${x | 0}`, { x: x + 5, y: h - 30 });
|
|
}
|
|
|
|
api.setColor("black");
|
|
api.drawLine({ x: w, y: 0 }, { x: w, y: h });
|
|
api.setOffset({ x: w, y: 0 });
|
|
|
|
// draw x = t(x)
|
|
api.drawLine({x:0,y:h-20}, {x:w, y:h-20});
|
|
api.text('0', {x:10,y:h-10});
|
|
api.text('⅓', {x:10 + (w-10)/3,y:h-10});
|
|
api.text('⅔', {x:10 + 2*(w-10)/3,y:h-10});
|
|
api.text('1', {x:w-10,y:h-10});
|
|
let p, s = { x: 0, y: h - curve.get(0).x };
|
|
|
|
for (let step = 0.05, t = step; t < 1 + step; t += step) {
|
|
p = {x: t * w, y: h - curve.get(t).x };
|
|
api.drawLine(s, p);
|
|
s = p;
|
|
}
|
|
|
|
api.setColor("black");
|
|
api.text("↑\nx", {x:10,y:h/2});
|
|
api.text("t →", {x:w/2,y:h-10});
|
|
|
|
if (bbox.x.min < x && x < bbox.x.max) {
|
|
api.setColor("red");
|
|
api.drawLine({ x: 0, y: h-x }, { x: w, y: h-x });
|
|
}
|
|
}
|
|
},
|
|
|
|
yforx: {
|
|
setup: function(api) {
|
|
api.setCurve(sketch.getCurve(api));
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
let w = api.defaultWidth;
|
|
let h = api.defaultHeight;
|
|
let bbox = curve.bbox();
|
|
let x = api.mx;
|
|
if (bbox.x.min < x && x < bbox.x.max) {
|
|
api.setColor("red");
|
|
// The root finder is based on normal x/y coordinates,
|
|
// so we can "trick" it by giving it "t" values as x
|
|
// values, and "x" values as y values. Since it won't
|
|
// even look at the x dimension, we can also just leave it.
|
|
let roots = api.utils.roots(curve.points.map(v => {
|
|
return { x: v.x, y: v.x-x};
|
|
}));
|
|
roots = roots.filter(t => t>=0 && t<=1.0);
|
|
let t = roots[0];
|
|
let p = curve.get(t);
|
|
api.drawLine({ x: p.x, y: p.y }, { x: p.x, y: h });
|
|
api.drawLine({ x: p.x, y: p.y }, { x: 0, y: p.y });
|
|
api.text(`y=${p.y|0}`, { x: p.x/2, y: p.y - 5 });
|
|
api.text(`x=${p.x|0}`, { x: x + 5, y: h - (h-p.y)/2 });
|
|
api.text(`t=${((t*100)|0)/100}`, { x: x + 15, y: p.y });
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
return sketch;
|
|
}())
|
|
},
|
|
"arclength": {
|
|
handler: (function() { var sin = Math.sin;
|
|
var tau = Math.PI*2;
|
|
|
|
return {
|
|
/**
|
|
* Set up a sinusoid generating function,
|
|
* which we'll use to draw the "progressively
|
|
* better looking" integral approximations.
|
|
*/
|
|
setup: function(api) {
|
|
var w = api.getPanelWidth();
|
|
var h = api.getPanelHeight();
|
|
var generator;
|
|
if (!this.generator) {
|
|
generator = ((v,scale) => {
|
|
scale = scale || 1;
|
|
return {
|
|
x: v*w/tau,
|
|
y: scale * sin(v)
|
|
};
|
|
});
|
|
generator.start = 0;
|
|
generator.end = tau;
|
|
generator.step = 0.1;
|
|
generator.scale = h/3;
|
|
this.generator = generator;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Draw the generator's sine function:
|
|
*/
|
|
drawSine: function(api, dheight) {
|
|
var w = api.getPanelWidth();
|
|
var h = api.getPanelHeight();
|
|
var generator = this.generator;
|
|
generator.dheight = dheight;
|
|
|
|
api.setColor("black");
|
|
api.drawLine({x:0,y:h/2}, {x:w,y:h/2});
|
|
api.drawFunction(generator, {x:0, y:h/2});
|
|
},
|
|
|
|
/**
|
|
* Draw the sliced between the sine curve and
|
|
* the x-axis, with a variable number of steps so
|
|
* we can show the approximation becoming better
|
|
* and better as we increase the step count.
|
|
*/
|
|
drawSlices: function(api, steps) {
|
|
var w = api.getPanelWidth();
|
|
var h = api.getPanelHeight();
|
|
var f = w/tau;
|
|
var area = 0;
|
|
var c = steps <= 25 ? 1 : 0;
|
|
api.reset();
|
|
api.setColor("transparent");
|
|
api.setFill("rgba(150,150,255, 0.4)");
|
|
for (var step=tau/steps, i=step/2, v, p1, p2; i<tau+step/2; i+=step) {
|
|
v = this.generator(i);
|
|
|
|
// draw a rectangular strip between the curve and the x-axis:
|
|
p1 = {x:v.x - f*step/2 + c, y: 0};
|
|
p2 = {x:v.x + f*step/2 - c, y: v.y * this.generator.scale};
|
|
|
|
if (!c) { api.setFill("rgba(150,150,255,"+(0.4 + 0.3*Math.random())+")"); }
|
|
api.drawRect(p1, p2, {x:0, y:h/2});
|
|
|
|
// and keep track of the (much simpler to compute) approximated area under the curve so far:
|
|
area += step * Math.abs(v.y * this.generator.scale);
|
|
}
|
|
api.setFill("black");
|
|
var trueArea = ((100 * 4 * h/3)|0)/100;
|
|
var currArea = ((100 * area)|0)/100;
|
|
api.text("Approximating with "+steps+" strips (true area: "+trueArea+"): " + currArea, {x: 10, y: h-15});
|
|
},
|
|
|
|
/**
|
|
* Draw the sine curve, with a 10 slice approximation:
|
|
*/
|
|
drawCoarseIntegral: function(api) {
|
|
api.reset();
|
|
this.drawSlices(api, 10);
|
|
this.drawSine(api);
|
|
},
|
|
|
|
/**
|
|
* Draw the sine curve, with a 24 slice approximation:
|
|
*/
|
|
drawFineIntegral: function(api) {
|
|
api.reset();
|
|
this.drawSlices(api, 24);
|
|
this.drawSine(api);
|
|
},
|
|
|
|
/**
|
|
* Draw the sine curve, with a 99 slice approximation:
|
|
*/
|
|
drawSuperFineIntegral: function(api) {
|
|
api.reset();
|
|
this.drawSlices(api, 99);
|
|
this.drawSine(api);
|
|
},
|
|
|
|
/**
|
|
* Set up a default cubic curve for which we'll be determining
|
|
* its length, using the iterative integral approach:
|
|
*/
|
|
setupCurve: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
/**
|
|
* Draw our curve, and show its computed length:
|
|
*/
|
|
drawCurve: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
var len = curve.length();
|
|
api.setFill("black");
|
|
api.text("Curve length: "+len+" pixels", {x:10, y:15});
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"arclengthapprox": {
|
|
handler: (function() { return {
|
|
// These are functions that can be called "From the page",
|
|
// rather than being internal to the sketch. This is useful
|
|
// for making on-page controls hook into the sketch code.
|
|
statics: {
|
|
keyHandlingOptions: {
|
|
propName: "steps",
|
|
values: {
|
|
"38": 1, // up arrow
|
|
"40": -1 // down arrow
|
|
},
|
|
controller: function(api) {
|
|
if (api.steps < 1) {
|
|
api.steps = 1;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set up the default quadratic curve.
|
|
*/
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
api.setCurve(curve);
|
|
api.steps = 10;
|
|
},
|
|
|
|
/**
|
|
* Set up the default cubic curve.
|
|
*/
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
api.steps = 16;
|
|
},
|
|
|
|
/**
|
|
* Draw a curve and its polygon-approximation,
|
|
* showing the "true" length of the curve vs. the
|
|
* length based on tallying up the polygon sections.
|
|
*/
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
|
|
var pts = curve.getLUT(api.steps);
|
|
|
|
var step = 1 / api.steps;
|
|
var p0 = curve.points[0], pc;
|
|
for(var t=step; t<1.0+step; t+=step) {
|
|
pc = curve.get(Math.min(t,1));
|
|
api.setColor("red");
|
|
api.drawLine(p0,pc);
|
|
p0 = pc;
|
|
}
|
|
|
|
var len = curve.length();
|
|
var alen = 0;
|
|
for(var i=0,p1,dx,dy; i<pts.length-1; i++) {
|
|
p0 = pts[i];
|
|
p1 = pts[i+1];
|
|
dx = p1.x-p0.x;
|
|
dy = p1.y-p0.y;
|
|
alen += Math.sqrt(dx*dx+dy*dy);
|
|
}
|
|
alen = ((100*alen)|0)/100;
|
|
len = ((100*len)|0)/100;
|
|
|
|
api.text("Approximate length, "+api.steps+" steps: "+alen+" (true: "+len+")", {x:10, y: 15});
|
|
}
|
|
};
|
|
}()),
|
|
withKeys: true
|
|
},
|
|
"curvature": {
|
|
handler: (function() { return {
|
|
setup: function(api) {
|
|
let d = api.defaultWidth;
|
|
api.setSize(d*3, api.defaultHeight);
|
|
|
|
// Set up two curves with identical form, but different functions:
|
|
var q = this.q = new api.Bezier(115, 250, 10, 35, 190, 45);
|
|
var c = this.c = q.raise();
|
|
q.points.forEach(p => (p.x += d/2));
|
|
c.points.forEach(p => (p.x += 3*d/2));
|
|
|
|
// And "fake" a master curve that we'll never draw, but which
|
|
// will allow us to move interact with the curve points.
|
|
api.setCurve({
|
|
points: q.points.concat(c.points)
|
|
});
|
|
},
|
|
|
|
updateCurves(api, curve) {
|
|
// update the quadratic and cubic curves by grabbing
|
|
// whatever the points in our "fake" master curve are
|
|
|
|
let q = this.q;
|
|
q.points = curve.points.slice(0,3);
|
|
q.update();
|
|
|
|
let c = this.c;
|
|
c.points = curve.points.slice(3,7);
|
|
c.update();
|
|
},
|
|
|
|
drawCurvature(api, curve, omni) {
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
var s, t, p, n, c, ox, oy;
|
|
for( s=0; s<256; s++) {
|
|
// Draw the curvature as a coloured line at the
|
|
// current point, along the normal.
|
|
api.setColor('rgba(255,127,'+s+',0.6)');
|
|
t = s/255;
|
|
p = curve.get(t);
|
|
n = curve.normal(t);
|
|
c = curve.curvature(t);
|
|
ox = c.k * n.x;
|
|
oy = c.k * n.y;
|
|
api.drawLine(p, { x: p.x + ox, y: p.y + oy });
|
|
|
|
// And if requested, also draw it along the anti-normal.
|
|
if (omni) {
|
|
api.setColor('rgba('+s+',127,255,0.6)');
|
|
api.drawLine(p, { x: p.x - ox, y: p.y - oy });
|
|
}
|
|
}
|
|
},
|
|
|
|
proxyDraw: function(api, curve, omni) {
|
|
api.reset();
|
|
this.updateCurves(api, curve);
|
|
[this.q, this.c].forEach(curve => this.drawCurvature(api, curve, omni));
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
this.proxyDraw(api, curve);
|
|
},
|
|
|
|
drawOmni: function(api, curve) {
|
|
this.proxyDraw(api, curve, true);
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"tracing": {
|
|
handler: (function() { return {
|
|
statics: {
|
|
keyHandlingOptions: {
|
|
propName: "steps",
|
|
values: {
|
|
"38": 1, // up arrow
|
|
"40": -1 // down arrow
|
|
},
|
|
controller: function(api) {
|
|
if (api.steps < 1) {
|
|
api.steps = 1;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
setup: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
api.steps = 8;
|
|
},
|
|
|
|
generate: function(api, curve, offset, pad, fwh) {
|
|
offset.x += pad;
|
|
offset.y += pad;
|
|
var len = curve.length();
|
|
var pts = [{x:0, y:0, d:0}];
|
|
for(var v=1, t, d; v<=100; v++) {
|
|
t = v/100;
|
|
d = curve.split(t).left.length();
|
|
pts.push({
|
|
x: api.utils.map(t, 0,1, 0,fwh),
|
|
y: api.utils.map(d, 0,len, 0,fwh),
|
|
d: d,
|
|
t: t
|
|
});
|
|
}
|
|
return pts;
|
|
},
|
|
|
|
draw: function(api, curve, offset) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
var len = curve.length();
|
|
var w = api.getPanelWidth();
|
|
var h = api.getPanelHeight();
|
|
var pad = 20;
|
|
var fwh = w - 2*pad;
|
|
|
|
offset.x += w;
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
api.drawAxes(pad, "t",0,1, "d",0,len, offset);
|
|
|
|
return this.generate(api, curve, offset, pad, fwh);
|
|
},
|
|
|
|
plotOnly: function(api, curve) {
|
|
api.setPanelCount(2);
|
|
var offset = {x:0, y:0};
|
|
var pts = this.draw(api, curve, offset);
|
|
for(var i=0; i<pts.length-1; i++) {
|
|
api.drawLine(pts[i], pts[i+1], offset);
|
|
}
|
|
},
|
|
|
|
drawColoured: function(api, curve) {
|
|
api.setPanelCount(3);
|
|
var w = api.getPanelWidth();
|
|
var h = api.getPanelHeight();
|
|
var pad = 20;
|
|
var fwh = w - 2*pad;
|
|
|
|
var offset = {x:0, y:0};
|
|
var len = curve.length();
|
|
var pts = this.draw(api, curve, offset);
|
|
var s = api.steps, i, p, ts=[];
|
|
for(i=0; i<=s; i++) {
|
|
var target = (i * len)/s;
|
|
// find the t nearest our target distance
|
|
for (p=0; p<pts.length; p++) {
|
|
if (pts[p].d > target) {
|
|
p--;
|
|
break;
|
|
}
|
|
}
|
|
if(p<0) p=0;
|
|
if(p===pts.length) p=pts.length-1;
|
|
ts.push(pts[p]);
|
|
}
|
|
|
|
for(i=0; i<pts.length-1; i++) {
|
|
api.drawLine(pts[i], pts[i+1], offset);
|
|
}
|
|
|
|
ts.forEach(p => {
|
|
var pt = { x: api.utils.map(p.t,0,1,0,fwh), y: 0 };
|
|
var pd = { x: 0, y: api.utils.map(p.d,0,len,0,fwh) };
|
|
api.setColor("black");
|
|
api.drawCircle(pt, 3, offset);
|
|
api.drawCircle(pd, 3, offset);
|
|
api.setColor("lightgrey");
|
|
api.drawLine(pt, {x:pt.x, y:pd.y}, offset);
|
|
api.drawLine(pd, {x:pt.x, y:pd.y}, offset);
|
|
});
|
|
|
|
offset = {x:2*w, y:0};
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
|
|
var idx=0, colors = ["rgb(240,0,200)", "rgb(0,40,200)"];
|
|
api.setColor(colors[idx]);
|
|
var p0 = curve.get(pts[0].t), p1;
|
|
api.drawCircle(curve.get(0), 4, offset);
|
|
|
|
for (i=1, p1; i<pts.length; i++) {
|
|
p1 = curve.get(pts[i].t);
|
|
api.drawLine(p0, p1, offset);
|
|
if (ts.indexOf(pts[i]) !== -1) {
|
|
api.setColor(colors[++idx % colors.length]);
|
|
api.drawCircle(p1, 4, offset);
|
|
}
|
|
p0 = p1;
|
|
}
|
|
}
|
|
};
|
|
}()),
|
|
withKeys: true
|
|
},
|
|
"intersections": {
|
|
handler: (function() { var min = Math.min, max = Math.max;
|
|
|
|
return {
|
|
setupLines: function(api) {
|
|
var curve1 = new api.Bezier([50,50,150,110]);
|
|
var curve2 = new api.Bezier([50,250,170,170]);
|
|
api.setCurve(curve1, curve2);
|
|
},
|
|
|
|
drawLineIntersection: function(api, curves) {
|
|
api.reset();
|
|
|
|
var lli = api.utils.lli4;
|
|
var p = lli(
|
|
curves[0].points[0],
|
|
curves[0].points[1],
|
|
curves[1].points[0],
|
|
curves[1].points[1]
|
|
);
|
|
|
|
var mark = 0;
|
|
curves.forEach(curve => {
|
|
api.drawSkeleton(curve);
|
|
api.setColor("black");
|
|
if (p) {
|
|
var pts = curve.points,
|
|
mx = min(pts[0].x, pts[1].x),
|
|
my = min(pts[0].y, pts[1].y),
|
|
Mx = max(pts[0].x, pts[1].x),
|
|
My = max(pts[0].y, pts[1].y);
|
|
if (mx <= p.x && my <= p.y && Mx >= p.x && My >= p.y) {
|
|
api.setColor("#00FF00");
|
|
mark++;
|
|
}
|
|
}
|
|
api.drawCurve(curve);
|
|
});
|
|
|
|
if (p) {
|
|
api.setColor(mark < 2 ? "red" : "#00FF00");
|
|
api.drawCircle(p, 3);
|
|
}
|
|
},
|
|
|
|
setupQuadratic: function(api) {
|
|
var curve1 = api.getDefaultQuadratic();
|
|
var curve2 = new api.Bezier([15,250,220,20]);
|
|
api.setCurve(curve1, curve2);
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve1 = new api.Bezier([100,240, 30,60, 210,230, 160,30]);
|
|
var curve2 = new api.Bezier([25,260, 230,20]);
|
|
api.setCurve(curve1, curve2);
|
|
},
|
|
|
|
draw: function(api, curves) {
|
|
api.reset();
|
|
curves.forEach(curve => {
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
});
|
|
|
|
var utils = api.utils;
|
|
var line = { p1: curves[1].points[0], p2: curves[1].points[1] };
|
|
var acpts = utils.align(curves[0].points, line);
|
|
var nB = new api.Bezier(acpts);
|
|
var roots = utils.roots(nB.points);
|
|
roots.forEach(t => {
|
|
var p = curves[0].get(t);
|
|
api.drawCircle(p, 3);
|
|
api.text("t = " + t, {x: p.x + 5, y: p.y + 10});
|
|
});
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"curveintersection": {
|
|
handler: (function() { var abs = Math.abs;
|
|
|
|
return {
|
|
setup: function(api) {
|
|
this.api = api;
|
|
api.setPanelCount(3);
|
|
var curve1 = new api.Bezier(10,100,90,30,40,140,220,220);
|
|
var curve2 = new api.Bezier(5,150,180,20,80,250,210,190);
|
|
api.setCurve(curve1, curve2);
|
|
this.pairReset();
|
|
},
|
|
|
|
pairReset: function() {
|
|
this.prevstep = 0;
|
|
this.step = 0;
|
|
},
|
|
|
|
draw: function(api, curves) {
|
|
api.reset();
|
|
var offset = {x:0, y:0};
|
|
curves.forEach(curve => {
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
});
|
|
|
|
// next panel: iterations
|
|
var w = api.getPanelWidth();
|
|
var h = api.getPanelHeight();
|
|
offset.x += w;
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
|
|
if (this.step === 0) {
|
|
this.pairs = [{c1: curves[0], c2: curves[1]}];
|
|
}
|
|
|
|
if(this.step !== this.prevstep) {
|
|
var pairs = this.pairs;
|
|
this.pairs = [];
|
|
this.finals = [];
|
|
pairs.forEach(p => {
|
|
|
|
if(p.c1.length() < 0.6 && p.c2.length() < 0.6) {
|
|
return this.finals.push(p);
|
|
}
|
|
|
|
var s1 = p.c1.split(0.5);
|
|
api.setColor("black");
|
|
api.drawCurve(p.c1, offset);
|
|
api.setColor("red");
|
|
api.drawbbox(s1.left.bbox(), offset);
|
|
api.drawbbox(s1.right.bbox(), offset);
|
|
|
|
var s2 = p.c2.split(0.5);
|
|
api.setColor("black");
|
|
api.drawCurve(p.c2, offset);
|
|
api.setColor("blue");
|
|
api.drawbbox(s2.left.bbox(), offset);
|
|
api.drawbbox(s2.right.bbox(), offset);
|
|
|
|
if (s1.left.overlaps(s2.left)) { this.pairs.push({c1: s1.left, c2: s2.left}); }
|
|
if (s1.left.overlaps(s2.right)) { this.pairs.push({c1: s1.left, c2: s2.right}); }
|
|
if (s1.right.overlaps(s2.left)) { this.pairs.push({c1: s1.right, c2: s2.left}); }
|
|
if (s1.right.overlaps(s2.right)) { this.pairs.push({c1: s1.right, c2: s2.right}); }
|
|
});
|
|
this.prevstep = this.step;
|
|
} else {
|
|
this.pairs.forEach(p => {
|
|
api.setColor("black");
|
|
api.drawCurve(p.c1, offset);
|
|
api.drawCurve(p.c2, offset);
|
|
api.setColor("red");
|
|
api.drawbbox(p.c1.bbox(), offset);
|
|
api.setColor("blue");
|
|
api.drawbbox(p.c2.bbox(), offset);
|
|
});
|
|
}
|
|
|
|
if (this.pairs.length === 0) {
|
|
this.pairReset();
|
|
this.draw(api, curves);
|
|
}
|
|
|
|
// next panel: results
|
|
offset.x += w;
|
|
api.setColor("black");
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
|
|
// get intersections as coordinates
|
|
var results = curves[0].intersects(curves[1]).map(s => {
|
|
var tvals = s.split('/').map(v => parseFloat(v));
|
|
return {t1: tvals[0], t2: tvals[1]};
|
|
});
|
|
|
|
// filter out likely duplicates
|
|
var curr = results[0], _, i, same = ((a,b) => abs(a.t1-b.t1) < 0.01 && abs(a.t2-b.t2) < 0.01);
|
|
for(i=1; i<results.length; i++) {
|
|
_ = results[i];
|
|
if (same(curr, _)) {
|
|
results.splice(i--,1);
|
|
} else { curr = _; }
|
|
}
|
|
|
|
api.setColor("lightblue");
|
|
api.drawCurve(curves[0], offset);
|
|
api.drawCurve(curves[1], offset);
|
|
|
|
api.setColor("blue");
|
|
results.forEach(tvals => {
|
|
api.drawCircle(curves[0].get(tvals.t1), 3, offset);
|
|
});
|
|
},
|
|
|
|
stepUp: function() {
|
|
this.step++;
|
|
this.api.redraw();
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"abc": {
|
|
handler: (function() { return {
|
|
|
|
// ============== first sketch set =====================
|
|
|
|
/**
|
|
* The entry point for the quadratic curve example
|
|
*/
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
curve.points[0].y -= 10;
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
/**
|
|
* The entry point for the cubic curve example
|
|
*/
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
curve.points[2].y -= 20;
|
|
api.setCurve(curve);
|
|
api.lut = curve.getLUT(100);
|
|
},
|
|
|
|
/**
|
|
* When someone clicks a graphic, find the associated
|
|
* on-curve t value and redraw with that new knowledge.
|
|
*/
|
|
onClick: function(evt, api) {
|
|
api.t = api.curve.on({x: evt.offsetX, y: evt.offsetY},7);
|
|
if (api.t < 0.05 || api.t > 0.95) api.t = false;
|
|
api.redraw();
|
|
},
|
|
|
|
/**
|
|
* The master draw function for the "projection" sketches
|
|
*/
|
|
draw: function(api, curve) {
|
|
// draw the basic curve and curve control points
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
api.setColor("black");
|
|
if (!api.t) return;
|
|
|
|
// draw the user-clicked on-curve point
|
|
api.drawCircle(api.curve.get(api.t),3);
|
|
api.setColor("lightgrey");
|
|
|
|
var utils = api.utils;
|
|
|
|
// find the A/B/C values as described in the section text
|
|
var hull = api.drawHull(curve, api.t);
|
|
var A, B, C;
|
|
if(hull.length === 6) {
|
|
A = curve.points[1];
|
|
B = hull[5];
|
|
C = utils.lli4(A, B, curve.points[0], curve.points[2]);
|
|
api.setColor("lightgrey");
|
|
api.drawLine(curve.points[0], curve.points[2]);
|
|
} else if(hull.length === 10) {
|
|
A = hull[5];
|
|
B = hull[9];
|
|
C = utils.lli4(A, B, curve.points[0], curve.points[3]);
|
|
api.setColor("lightgrey");
|
|
api.drawLine(curve.points[0], curve.points[3]);
|
|
}
|
|
|
|
// show the lines between the A/B/C values
|
|
api.setColor("#00FF00");
|
|
api.drawLine(A,B);
|
|
api.setColor("red");
|
|
api.drawLine(B,C);
|
|
api.setColor("black");
|
|
api.drawCircle(C,3);
|
|
|
|
// with their associated labels
|
|
api.setFill("black");
|
|
api.text("A", {x:10 + A.x, y: A.y});
|
|
api.text("B (t = " + api.utils.round(api.t,2) + ")", {x:10 + B.x, y: B.y});
|
|
api.text("C", {x:10 + C.x, y: C.y});
|
|
|
|
// and show the distance ratio, which we see does not change irrespective of whether A/B/C change.
|
|
var d1 = utils.dist(A, B);
|
|
var d2 = utils.dist(B, C);
|
|
var ratio = d1/d2;
|
|
var h = api.getPanelHeight();
|
|
api.text("d1 (A-B): " + utils.round(d1,2) + ", d2 (B-C): "+ utils.round(d2,2) + ", ratio (d1/d2): " + utils.round(ratio,4), {x:10, y:h-7});
|
|
},
|
|
|
|
// ============== second sketch set =====================
|
|
|
|
/**
|
|
* on mouse move, fix the t value for drawing based on the
|
|
* cursor position over the sketch. All the way on the left
|
|
* is t=0, all the way on the right is t=1, with a linear
|
|
* interpolation for anything in between.
|
|
*/
|
|
setCT: function(evt,api) {
|
|
api.t = evt.offsetX / api.getPanelWidth();
|
|
},
|
|
|
|
/**
|
|
* Draw the quadratic C(t) values
|
|
*/
|
|
drawQCT: function(api) {
|
|
api.u = api.u || function(t) {
|
|
var top = (t-1) * (t-1),
|
|
bottom = 2*t*t - 2*t + 1;
|
|
return top/bottom;
|
|
};
|
|
this.drawCTgraph(api);
|
|
},
|
|
|
|
/**
|
|
* Draw the cubic C(t) values
|
|
*/
|
|
drawCCT: function(api) {
|
|
api.u = api.u || function(t) {
|
|
var top = (1-t) * (1-t) * (1-t),
|
|
bottom = t*t*t + top;
|
|
return top/bottom;
|
|
};
|
|
this.drawCTgraph(api);
|
|
},
|
|
|
|
/**
|
|
* Draw a C(t) curve
|
|
*/
|
|
drawCTgraph: function(api) {
|
|
api.reset();
|
|
var w = api.getPanelWidth();
|
|
var pad = 20;
|
|
var fwh = w - 2*pad;
|
|
|
|
// draw some axes
|
|
api.setColor("black");
|
|
api.drawAxes(pad, "t",0,1, "u",0,1);
|
|
|
|
// draw the C(t) function using an
|
|
// indirection function that takes a
|
|
// t value and spits out the C(t) value
|
|
// as a point coordinate.
|
|
api.setColor("blue");
|
|
var uPoint = function(t) {
|
|
var value = api.u(t),
|
|
res = { x: pad + t*fwh, y: pad + value*fwh };
|
|
return res;
|
|
};
|
|
api.drawFunction(uPoint);
|
|
|
|
// if the cursor is (or was ever) over this
|
|
// graphic, draw the "crosshair" that pinpoints
|
|
// where in the function the associated t/C(t)
|
|
// coordinate is.
|
|
if (api.t) {
|
|
var v = api.u(api.t),
|
|
v1 = api.utils.round(v,3),
|
|
v2 = api.utils.round(1-v,3),
|
|
up = uPoint(api.t);
|
|
api.drawLine({x:up.x,y:pad}, up);
|
|
api.drawLine({x:pad,y:up.y}, up);
|
|
api.drawCircle(up,3);
|
|
|
|
// with some handy text that shows the actual computed values
|
|
api.setFill("blue");
|
|
api.text(" t = " + api.utils.round(api.t,3), {x:up.x+10, y:up.y-7});
|
|
api.text("u(t) = " + api.utils.round(v,3), {x:up.x+10, y:up.y+7});
|
|
api.setFill("black");
|
|
api.text("C = "+v1+" * start + "+v2+" * end", {x:w/2 - pad, y:pad+fwh});
|
|
}
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"moulding": {
|
|
handler: (function() { var abs = Math.abs;
|
|
|
|
return {
|
|
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);
|
|
}
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"pointcurves": {
|
|
handler: (function() { var abs = Math.abs;
|
|
|
|
return {
|
|
setup: function(api) {
|
|
api.lpts = [
|
|
{x:56, y:153},
|
|
{x:144,y:83},
|
|
{x:188,y:185}
|
|
];
|
|
},
|
|
|
|
onClick: function(evt, api) {
|
|
if (api.lpts.length==3) { api.lpts = []; }
|
|
api.lpts.push({
|
|
x: evt.offsetX,
|
|
y: evt.offsetY
|
|
});
|
|
api.redraw();
|
|
},
|
|
|
|
getQRatio: function(t) {
|
|
var t2 = 2*t,
|
|
top = t2*t - t2,
|
|
bottom = top + 1;
|
|
return abs(top/bottom);
|
|
},
|
|
|
|
getCRatio: function(t) {
|
|
var mt = (1-t),
|
|
t3 = t*t*t,
|
|
mt3 = mt*mt*mt,
|
|
bottom = t3 + mt3,
|
|
top = bottom - 1;
|
|
return abs(top/bottom);
|
|
},
|
|
|
|
drawQuadratic: function(api, curve) {
|
|
var labels = ["start","t=0.5","end"];
|
|
|
|
api.reset();
|
|
|
|
api.setColor("lightblue");
|
|
api.drawGrid(10,10);
|
|
|
|
api.setFill("black");
|
|
api.setColor("black");
|
|
api.lpts.forEach((p,i) => {
|
|
api.drawCircle(p,3);
|
|
api.text(labels[i], p, {x:5, y:2});
|
|
});
|
|
|
|
if(api.lpts.length === 3) {
|
|
var S = api.lpts[0],
|
|
E = api.lpts[2],
|
|
B = api.lpts[1],
|
|
C = {
|
|
x: (S.x + E.x)/2,
|
|
y: (S.y + E.y)/2
|
|
};
|
|
api.setColor("blue");
|
|
api.drawLine(S, E);
|
|
api.drawLine(B, C);
|
|
api.drawCircle(C, 3);
|
|
var ratio = this.getQRatio(0.5),
|
|
A = {
|
|
x: B.x + (B.x-C.x)/ratio,
|
|
y: B.y + (B.y-C.y)/ratio
|
|
};
|
|
curve = new api.Bezier([S, A, E]);
|
|
api.setColor("lightgrey");
|
|
api.drawLine(A, B);
|
|
api.drawLine(A, S);
|
|
api.drawLine(A, E);
|
|
api.setColor("black");
|
|
api.drawCircle(A, 1);
|
|
api.drawCurve(curve);
|
|
}
|
|
},
|
|
|
|
drawCubic: function(api, curve) {
|
|
var labels = ["start","t=0.5","end"];
|
|
|
|
api.reset();
|
|
|
|
api.setFill("black");
|
|
api.setColor("black");
|
|
api.lpts.forEach((p,i) => {
|
|
api.drawCircle(p,3);
|
|
api.text(labels[i], p, {x:5, y:2});
|
|
});
|
|
|
|
api.setColor("lightblue");
|
|
api.drawGrid(10,10);
|
|
|
|
if(api.lpts.length === 3) {
|
|
var S = api.lpts[0],
|
|
E = api.lpts[2],
|
|
B = api.lpts[1],
|
|
C = {
|
|
x: (S.x + E.x)/2,
|
|
y: (S.y + E.y)/2
|
|
};
|
|
|
|
api.setColor("blue");
|
|
api.drawLine(S, E);
|
|
api.drawLine(B, C);
|
|
api.drawCircle(C, 1);
|
|
|
|
var ratio = this.getCRatio(0.5),
|
|
A = {
|
|
x: B.x + (B.x-C.x)/ratio,
|
|
y: B.y + (B.y-C.y)/ratio
|
|
},
|
|
selen = api.utils.dist(S,E),
|
|
bclen_min = selen/8,
|
|
bclen = api.utils.dist(B,C),
|
|
aesthetics = 4,
|
|
be12dist = bclen_min + bclen/aesthetics,
|
|
bx = be12dist * (E.x-S.x)/selen,
|
|
by = be12dist * (E.y-S.y)/selen,
|
|
e1 = {
|
|
x: B.x - bx,
|
|
y: B.y - by
|
|
},
|
|
e2 = {
|
|
x: B.x + bx,
|
|
y: B.y + by
|
|
},
|
|
|
|
v1 = {
|
|
x: A.x + (e1.x-A.x)*2,
|
|
y: A.y + (e1.y-A.y)*2
|
|
},
|
|
v2 = {
|
|
x: A.x + (e2.x-A.x)*2,
|
|
y: A.y + (e2.y-A.y)*2
|
|
},
|
|
|
|
nc1 = {
|
|
x: S.x + (v1.x-S.x)*2,
|
|
y: S.y + (v1.y-S.y)*2
|
|
},
|
|
nc2 = {
|
|
x: E.x + (v2.x-E.x)*2,
|
|
y: E.y + (v2.y-E.y)*2
|
|
};
|
|
|
|
curve = new api.Bezier([S, nc1, nc2, E]);
|
|
api.drawLine(e1, e2);
|
|
api.setColor("lightgrey");
|
|
api.drawLine(A, C);
|
|
api.drawLine(A, v1);
|
|
api.drawLine(A, v2);
|
|
api.drawLine(S, nc1);
|
|
api.drawLine(E, nc2);
|
|
api.drawLine(nc1, nc2);
|
|
api.setColor("black");
|
|
api.drawCircle(A, 1);
|
|
api.drawCircle(nc1, 1);
|
|
api.drawCircle(nc2, 1);
|
|
api.drawCurve(curve);
|
|
}
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"curvefitting": {
|
|
handler: (function() { var fit = require('../../../lib/curve-fitter.js');
|
|
|
|
return {
|
|
setup: function(api) {
|
|
this.api = api;
|
|
this.reset();
|
|
},
|
|
|
|
reset: function() {
|
|
this.points = [];
|
|
this.curveset = false;
|
|
this.mode = 0;
|
|
if (this.api) {
|
|
let api = this.api;
|
|
api.setCurve(false);
|
|
api.reset();
|
|
api.redraw();
|
|
}
|
|
},
|
|
|
|
toggle: function() {
|
|
if (this.api) {
|
|
this.customTimeValues = false;
|
|
this.mode = (this.mode + 1) % fit.modes.length;
|
|
this.fitCurve(this.api);
|
|
this.api.redraw();
|
|
}
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.setPanelCount(1);
|
|
api.reset();
|
|
api.setColor('lightgrey');
|
|
api.drawGrid(10,10);
|
|
|
|
api.setColor('black');
|
|
|
|
if (!this.curveset && this.points.length > 2) {
|
|
curve = this.fitCurve(api);
|
|
}
|
|
|
|
if (curve) {
|
|
api.drawCurve(curve);
|
|
api.drawSkeleton(curve);
|
|
}
|
|
|
|
api.drawPoints(this.points);
|
|
|
|
if (!this.customTimeValues) {
|
|
api.setFill(0);
|
|
api.text("using "+fit.modes[this.mode]+" t values", {x: 5, y: 10});
|
|
}
|
|
},
|
|
|
|
processTimeUpdate(sliderid, timeValues) {
|
|
var api = this.api;
|
|
this.customTimeValues = true;
|
|
this.fitCurve(api, timeValues);
|
|
api.redraw();
|
|
},
|
|
|
|
fitCurve(api, timeValues) {
|
|
let bestFitData = fit(this.points, timeValues || this.mode),
|
|
x = bestFitData.C.x,
|
|
y = bestFitData.C.y,
|
|
bpoints = [];
|
|
x.forEach((r,i) => {
|
|
bpoints.push({
|
|
x: r[0],
|
|
y: y[i][0]
|
|
});
|
|
});
|
|
var curve = new api.Bezier(bpoints);
|
|
api.setCurve(curve);
|
|
this.curveset = true;
|
|
this.sliders.setOptions(bestFitData.S);
|
|
return curve;
|
|
},
|
|
|
|
onClick: function(evt, api) {
|
|
this.curveset = false;
|
|
this.points.push({x: api.mx, y: api.my });
|
|
api.redraw();
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"catmullmoulding": {
|
|
handler: (function() { return {
|
|
statics: {
|
|
keyHandlingOptions: {
|
|
propName: "distance",
|
|
values: {
|
|
"38": 1, // up arrow
|
|
"40": -1 // down arrow
|
|
}
|
|
}
|
|
},
|
|
|
|
setup: function(api) {
|
|
api.setPanelCount(3);
|
|
api.lpts = [
|
|
{x:56, y:153},
|
|
{x:144,y:83},
|
|
{x:188,y:185}
|
|
];
|
|
api.distance = 0;
|
|
},
|
|
|
|
convert: function(p1, p2, p3, p4) {
|
|
var t = 0.5;
|
|
return [
|
|
p2, {
|
|
x: p2.x + (p3.x-p1.x)/(6*t),
|
|
y: p2.y + (p3.y-p1.y)/(6*t)
|
|
}, {
|
|
x: p3.x - (p4.x-p2.x)/(6*t),
|
|
y: p3.y - (p4.y-p2.y)/(6*t)
|
|
}, p3
|
|
];
|
|
},
|
|
|
|
draw: function(api) {
|
|
api.reset();
|
|
api.setColor("lightblue");
|
|
api.drawGrid(10,10);
|
|
|
|
var pts = api.lpts;
|
|
api.setColor("black");
|
|
api.setFill("black");
|
|
pts.forEach((p,pos) => {
|
|
api.drawCircle(p, 3);
|
|
api.text("point "+(pos+1), p, {x:10, y:7});
|
|
});
|
|
|
|
var w = api.getPanelWidth();
|
|
var h = api.getPanelHeight();
|
|
var offset = {x:w, y:0};
|
|
api.setColor("lightblue");
|
|
api.drawGrid(10,10,offset);
|
|
api.setColor("black");
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
|
|
pts.forEach((p,pos) => {
|
|
api.drawCircle(p, 3, offset);
|
|
});
|
|
var p1 = pts[0], p2 = pts[1], p3 = pts[2];
|
|
var dx = p3.x - p1.x,
|
|
dy = p3.y - p1.y,
|
|
m = Math.sqrt(dx*dx + dy*dy);
|
|
dx /= m;
|
|
dy /= m;
|
|
api.drawLine(p1, p3, offset);
|
|
|
|
var p0 = {
|
|
x: p1.x + (p3.x - p2.x) - api.distance * dx,
|
|
y: p1.y + (p3.y - p2.y) - api.distance * dy
|
|
};
|
|
var p4 = {
|
|
x: p1.x + (p3.x - p2.x) + api.distance * dx,
|
|
y: p1.y + (p3.y - p2.y) + api.distance * dy
|
|
};
|
|
var center = api.utils.lli4(p1,p3,p2,{
|
|
x: (p0.x + p4.x)/2,
|
|
y: (p0.y + p4.y)/2
|
|
});
|
|
api.setColor("blue");
|
|
api.drawCircle(center, 3, offset);
|
|
api.drawLine(pts[1],center, offset);
|
|
api.setColor("#666");
|
|
api.drawLine(center, p0, offset);
|
|
api.drawLine(center, p4, offset);
|
|
|
|
api.setFill("blue");
|
|
api.text("p0", p0, {x:-20 + offset.x, y:offset.y + 2});
|
|
api.text("p4", p4, {x:+10 + offset.x, y:offset.y + 2});
|
|
|
|
// virtual point p0
|
|
api.setColor("red");
|
|
api.drawCircle(p0, 3, offset);
|
|
api.drawLine(p2, p0, offset);
|
|
api.drawLine(p1, {
|
|
x: p1.x + (p2.x - p0.x)/5,
|
|
y: p1.y + (p2.y - p0.y)/5
|
|
}, offset);
|
|
|
|
// virtual point p4
|
|
api.setColor("#00FF00");
|
|
api.drawCircle(p4, 3, offset);
|
|
api.drawLine(p2, p4, offset);
|
|
api.drawLine(p3, {
|
|
x: p3.x + (p4.x - p2.x)/5,
|
|
y: p3.y + (p4.y - p2.y)/5
|
|
}, offset);
|
|
|
|
// Catmull-Rom curve for p0-p1-p2-p3-p4
|
|
var c1 = new api.Bezier(this.convert(p0,p1,p2,p3)),
|
|
c2 = new api.Bezier(this.convert(p1,p2,p3,p4));
|
|
api.setColor("lightgrey");
|
|
api.drawCurve(c1, offset);
|
|
api.drawCurve(c2, offset);
|
|
|
|
|
|
offset.x += w;
|
|
api.setColor("lightblue");
|
|
api.drawGrid(10,10,offset);
|
|
api.setColor("black");
|
|
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
|
|
|
|
api.drawCurve(c1, offset);
|
|
api.drawCurve(c2, offset);
|
|
api.drawPoints(c1.points, offset);
|
|
api.drawPoints(c2.points, offset);
|
|
api.setColor("lightgrey");
|
|
api.drawLine(c1.points[0], c1.points[1], offset);
|
|
api.drawLine(c1.points[2], c2.points[1], offset);
|
|
api.drawLine(c2.points[2], c2.points[3], offset);
|
|
}
|
|
};
|
|
}()),
|
|
withKeys: true
|
|
},
|
|
"polybezier": {
|
|
handler: (function() { var atan2 = Math.atan2, sqrt = Math.sqrt, sin = Math.sin, cos = Math.cos;
|
|
|
|
return {
|
|
setupQuadratic: function(api) {
|
|
var w = api.getPanelWidth(),
|
|
h = api.getPanelHeight(),
|
|
cx = w/2, cy = h/2, pad = 40,
|
|
pts = [
|
|
// first curve:
|
|
{x:cx,y:pad}, {x:w-pad,y:pad}, {x:w-pad,y:cy},
|
|
// subsequent curve
|
|
{x:w-pad,y:h-pad}, {x:cx,y:h-pad},
|
|
// subsequent curve
|
|
{x:pad,y:h-pad}, {x:pad,y:cy},
|
|
// final curve control point
|
|
{x:pad,y:pad}
|
|
];
|
|
api.lpts = pts;
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var w = api.getPanelWidth(),
|
|
h = api.getPanelHeight(),
|
|
cx = w/2, cy = h/2, pad = 40,
|
|
r = (w - 2*pad)/2,
|
|
k = 0.55228,
|
|
kr = k*r,
|
|
pts = [
|
|
// first curve:
|
|
{x:cx,y:pad}, {x:cx+kr,y:pad}, {x:w-pad,y:cy-kr}, {x:w-pad,y:cy},
|
|
// subsequent curve
|
|
{x:w-pad,y:cy+kr}, {x:cx+kr,y:h-pad}, {x:cx,y:h-pad},
|
|
// subsequent curve
|
|
{x:cx-kr,y:h-pad}, {x:pad,y:cy+kr}, {x:pad,y:cy},
|
|
// final curve control point
|
|
{x:pad,y:cy-kr}, {x:cx-kr,y:pad}
|
|
];
|
|
api.lpts = pts;
|
|
},
|
|
|
|
movePointsQuadraticLD: function(api, i) {
|
|
// ...we need to move _everything_
|
|
var anchor, fixed, toMove;
|
|
for(var p=1; p<4; p++) {
|
|
anchor = i + (2*p - 2) + api.lpts.length;
|
|
anchor = api.lpts[anchor % api.lpts.length];
|
|
fixed = i + (2*p - 1);
|
|
fixed = api.lpts[fixed % api.lpts.length];
|
|
toMove = i + 2*p;
|
|
toMove = api.lpts[toMove % api.lpts.length];
|
|
|
|
toMove.x = fixed.x + (fixed.x - anchor.x);
|
|
toMove.y = fixed.y + (fixed.y - anchor.y);
|
|
}
|
|
// then, the furthest point cannot be computed properly!
|
|
toMove = i + 6;
|
|
toMove = api.lpts[toMove % api.lpts.length];
|
|
api.problem = toMove;
|
|
},
|
|
|
|
movePointsCubicLD: function(api, i) {
|
|
var toMove, fixed;
|
|
if (i%3 === 1) {
|
|
fixed = i-1;
|
|
fixed += (fixed < 0) ? api.lpts.length : 0;
|
|
toMove = i-2;
|
|
toMove += (toMove < 0) ? api.lpts.length : 0;
|
|
} else {
|
|
fixed = (i+1) % api.lpts.length;
|
|
toMove = (i+2) % api.lpts.length;
|
|
}
|
|
fixed = api.lpts[fixed];
|
|
toMove = api.lpts[toMove];
|
|
toMove.x = fixed.x + (fixed.x - api.mp.x);
|
|
toMove.y = fixed.y + (fixed.y - api.mp.y);
|
|
},
|
|
|
|
linkDerivatives: function(evt, api) {
|
|
if (api.mp) {
|
|
var quad = api.lpts.length === 8;
|
|
var i = api.mp_idx;
|
|
if (quad) {
|
|
if (i%2 !== 0) { this.movePointsQuadraticLD(api, i); }
|
|
} else {
|
|
if(i%3 !== 0) { this.movePointsCubicLD(api, i); }
|
|
}
|
|
}
|
|
},
|
|
|
|
movePointsQuadraticDirOnly: function(api, i) {
|
|
// ...we need to move _everything_ ...again
|
|
var anchor, fixed, toMove;
|
|
|
|
// move left and right
|
|
[-1,1].forEach(v => {
|
|
anchor = api.mp;
|
|
fixed = i + v + api.lpts.length;
|
|
fixed = api.lpts[fixed % api.lpts.length];
|
|
toMove = i + 2*v + api.lpts.length;
|
|
toMove = api.lpts[toMove % api.lpts.length];
|
|
var a = atan2(fixed.y - anchor.y, fixed.x - anchor.x),
|
|
dx = toMove.x - fixed.x,
|
|
dy = toMove.y - fixed.y,
|
|
d = sqrt(dx*dx + dy*dy);
|
|
toMove.x = fixed.x + d*cos(a);
|
|
toMove.y = fixed.y + d*sin(a);
|
|
});
|
|
|
|
// then, the furthest point cannot be computed properly!
|
|
toMove = i + 4;
|
|
toMove = api.lpts[toMove % api.lpts.length];
|
|
api.problem = toMove;
|
|
},
|
|
|
|
movePointsCubicDirOnly: function(api, i) {
|
|
var toMove, fixed;
|
|
if (i%3 === 1) {
|
|
fixed = i-1;
|
|
fixed += (fixed < 0) ? api.lpts.length : 0;
|
|
toMove = i-2;
|
|
toMove += (toMove < 0) ? api.lpts.length : 0;
|
|
} else {
|
|
fixed = (i+1) % api.lpts.length;
|
|
toMove = (i+2) % api.lpts.length;
|
|
}
|
|
fixed = api.lpts[fixed];
|
|
toMove = api.lpts[toMove];
|
|
var a = atan2(fixed.y - api.mp.y, fixed.x - api.mp.x),
|
|
dx = toMove.x - fixed.x,
|
|
dy = toMove.y - fixed.y,
|
|
d = sqrt(dx*dx + dy*dy);
|
|
toMove.x = fixed.x + d*cos(a);
|
|
toMove.y = fixed.y + d*sin(a);
|
|
},
|
|
|
|
linkDirection: function(evt, api) {
|
|
if (api.mp) {
|
|
var quad = api.lpts.length === 8;
|
|
var i = api.mp_idx;
|
|
if (quad) {
|
|
if(i%2 !== 0) { this.movePointsQuadraticDirOnly(api, i); }
|
|
} else {
|
|
if(i%3 !== 0) { this.movePointsCubicDirOnly(api, i); }
|
|
}
|
|
}
|
|
},
|
|
|
|
bufferPoints: function(evt, api) {
|
|
api.bpts = JSON.parse(JSON.stringify(api.lpts));
|
|
},
|
|
|
|
moveQuadraticPoint: function(api, i) {
|
|
this.moveCubicPoint(api,i);
|
|
|
|
// then move the other control points
|
|
[-1,1].forEach(v => {
|
|
var anchor = i - v + api.lpts.length;
|
|
anchor = api.lpts[anchor % api.lpts.length];
|
|
var fixed = i - 2*v + api.lpts.length;
|
|
fixed = api.lpts[fixed % api.lpts.length];
|
|
var toMove = i - 3*v + api.lpts.length;
|
|
toMove = api.lpts[toMove % api.lpts.length];
|
|
var a = atan2(fixed.y - anchor.y, fixed.x - anchor.x),
|
|
dx = toMove.x - fixed.x,
|
|
dy = toMove.y - fixed.y,
|
|
d = sqrt(dx*dx + dy*dy);
|
|
toMove.x = fixed.x + d*cos(a);
|
|
toMove.y = fixed.y + d*sin(a);
|
|
});
|
|
|
|
// then signal a problem
|
|
var toMove = i + 4;
|
|
toMove = api.lpts[toMove % api.lpts.length];
|
|
api.problem = toMove;
|
|
},
|
|
|
|
moveCubicPoint: function(api, i) {
|
|
var op = api.bpts[i],
|
|
np = api.lpts[i],
|
|
dx = np.x - op.x,
|
|
dy = np.y - op.y,
|
|
len = api.lpts.length,
|
|
l = i-1+len,
|
|
r = i+1,
|
|
// original left and right
|
|
ol = api.bpts[l % len],
|
|
or = api.bpts[r % len],
|
|
// current left and right
|
|
nl = api.lpts[l % len],
|
|
nr = api.lpts[r % len];
|
|
// update current left
|
|
nl.x = ol.x + dx;
|
|
nl.y = ol.y + dy;
|
|
// update current right
|
|
nr.x = or.x + dx;
|
|
nr.y = or.y + dy;
|
|
return {x:dx, y:dy};
|
|
},
|
|
|
|
modelCurve: function(evt, api) {
|
|
if (api.mp) {
|
|
var quad = api.lpts.length === 8;
|
|
var i = api.mp_idx;
|
|
if (quad) {
|
|
if (i%2 !== 0) { this.movePointsQuadraticDirOnly(api, i); }
|
|
else { this.moveQuadraticPoint(api, i); }
|
|
}
|
|
else {
|
|
if(i%3 !== 0) { this.movePointsCubicDirOnly(api, i); }
|
|
else { this.moveCubicPoint(api, i); }
|
|
}
|
|
}
|
|
},
|
|
|
|
draw: function(api, curves) {
|
|
api.reset();
|
|
var pts = api.lpts;
|
|
var quad = pts.length === 8;
|
|
|
|
var c1 = quad ? new api.Bezier(pts[0],pts[1],pts[2]) : new api.Bezier(pts[0],pts[1],pts[2],pts[3]);
|
|
api.drawSkeleton(c1, false, true);
|
|
api.drawCurve(c1);
|
|
|
|
var c2 = quad ? new api.Bezier(pts[2],pts[3],pts[4]) : new api.Bezier(pts[3],pts[4],pts[5],pts[6]);
|
|
api.drawSkeleton(c2, false, true);
|
|
api.drawCurve(c2);
|
|
|
|
var c3 = quad ? new api.Bezier(pts[4],pts[5],pts[6]) : new api.Bezier(pts[6],pts[7],pts[8],pts[9]);
|
|
api.drawSkeleton(c3, false, true);
|
|
api.drawCurve(c3);
|
|
|
|
var c4 = quad ? new api.Bezier(pts[6],pts[7],pts[0]) : new api.Bezier(pts[9],pts[10],pts[11],pts[0]);
|
|
api.drawSkeleton(c4, false, true);
|
|
api.drawCurve(c4);
|
|
|
|
if (api.problem) {
|
|
api.setColor("red");
|
|
api.drawCircle(api.problem,5);
|
|
}
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"shapes": {
|
|
handler: (function() { var modes;
|
|
|
|
return {
|
|
getInitialState: function() {
|
|
modes = this.modes = ["unite","intersect","exclude","subtract"];
|
|
return {
|
|
mode: modes[0]
|
|
};
|
|
},
|
|
|
|
setMode: function(mode) {
|
|
this.setState({ mode: mode });
|
|
},
|
|
|
|
formPath: function(api, mx, my, w, h) {
|
|
mx = mx || 0;
|
|
my = my || 0;
|
|
var unit = 30;
|
|
var unit2 = unit/2;
|
|
w = w || 8 * unit;
|
|
h = h || 4 * unit;
|
|
var w2 = w/2;
|
|
var h2 = h/2;
|
|
var ow3 = w2/3;
|
|
var oh3 = h2/3;
|
|
|
|
var Paper = api.Paper;
|
|
var Path = Paper.Path;
|
|
var Point = Paper.Point;
|
|
var path = new Path();
|
|
|
|
path.moveTo(
|
|
new Point(mx-w2 + unit*2, my-h2)
|
|
);
|
|
path.cubicCurveTo(
|
|
new Point(mx-w2 + unit2, my-h2 + unit2),
|
|
new Point(mx-w2 + unit2, my+h2 - unit2),
|
|
new Point(mx-w2 + unit*2, my+h2)
|
|
);
|
|
path.cubicCurveTo(
|
|
new Point(mx-ow3, my+oh3),
|
|
new Point(mx+ow3, my+oh3),
|
|
new Point(mx+w2 - unit*2, my+h2)
|
|
);
|
|
path.cubicCurveTo(
|
|
new Point(mx+w2 - unit2, my+h2 - unit2),
|
|
new Point(mx+w2 - unit2, my-h2 + unit2),
|
|
new Point(mx+w2 - unit*2, my-h2)
|
|
);
|
|
path.cubicCurveTo(
|
|
new Point(mx+ow3, my-oh3),
|
|
new Point(mx-ow3, my-oh3),
|
|
new Point(mx-w2 + unit*2, my-h2)
|
|
);
|
|
path.closePath(true);
|
|
path.strokeColor = "rgb(100,100,255)";
|
|
return path;
|
|
},
|
|
|
|
setup: function(api) {
|
|
var dim = api.getPanelWidth();
|
|
var pad = 40;
|
|
var cx = dim/2;
|
|
var cy = dim/2;
|
|
api.c1 = this.formPath(api, cx, cy);
|
|
cx += pad;
|
|
cy += pad;
|
|
api.c2 = this.formPath(api, cx, cy);
|
|
this.state.mode = modes[0];
|
|
},
|
|
|
|
onMouseMove: function(evt, api) {
|
|
var cx = evt.offsetX;
|
|
var cy = evt.offsetY;
|
|
api.c2.position = {x:cx, y:cy};
|
|
},
|
|
|
|
draw: function(api) {
|
|
if (api.c3) { api.c3.remove(); }
|
|
var c1 = api.c1,
|
|
c2 = api.c2,
|
|
fn = c1[this.state.mode].bind(c1),
|
|
c3 = api.c3 = fn(c2);
|
|
|
|
c3.strokeColor = "red";
|
|
c3.fillColor = "rgba(255,100,100,0.4)";
|
|
api.Paper.view.draw();
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"projections": {
|
|
handler: (function() { return {
|
|
setup: function(api) {
|
|
api.setSize(320,320);
|
|
var curve = new api.Bezier([
|
|
{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}
|
|
]);
|
|
api.setCurve(curve);
|
|
api._lut = curve.getLUT();
|
|
},
|
|
|
|
findClosest: function(LUT, p, dist) {
|
|
var i,
|
|
end = LUT.length,
|
|
d,
|
|
dd = dist(LUT[0],p),
|
|
f = 0;
|
|
for(i=1; i<end; i++) {
|
|
d = dist(LUT[i],p);
|
|
if(d<dd) {f = i;dd = d;}
|
|
}
|
|
return f/(end-1);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
if (api.mousePt) {
|
|
api.setColor("red");
|
|
api.setFill("red");
|
|
api.drawCircle(api.mousePt, 3);
|
|
// naive t value
|
|
var t = this.findClosest(api._lut, api.mousePt, api.utils.dist);
|
|
// no real point in refining for illustration purposes
|
|
var p = curve.get(t);
|
|
api.drawLine(p, api.mousePt);
|
|
api.drawCircle(p, 3);
|
|
api.text("t = "+api.utils.round(t,2), p, {x:10, y:3});
|
|
}
|
|
},
|
|
|
|
onMouseMove: function(evt, api) {
|
|
api.mousePt = {x: evt.offsetX, y: evt.offsetY };
|
|
api._lut = api.curve.getLUT();
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"offsetting": {
|
|
handler: (function() { return {
|
|
statics: {
|
|
keyHandlingOptions: {
|
|
propName: "distance",
|
|
values: {
|
|
"38": 1, // up arrow
|
|
"40": -1 // down arrow
|
|
}
|
|
}
|
|
},
|
|
|
|
setup: function(api, curve) {
|
|
api.setCurve(curve);
|
|
api.distance = 20;
|
|
},
|
|
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
this.setup(api, curve);
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
this.setup(api, curve);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
|
|
var reduced = curve.reduce();
|
|
reduced.forEach(c => {
|
|
api.setRandomColor();
|
|
api.drawCurve(c);
|
|
api.drawCircle(c.points[0], 1);
|
|
});
|
|
var last = reduced.slice(-1)[0];
|
|
api.drawPoint(last.points[3] || last.points[2]);
|
|
|
|
api.setColor("red");
|
|
var offset = curve.offset(api.distance);
|
|
offset.forEach(c => {
|
|
api.drawPoint(c.points[0]);
|
|
api.drawCurve(c);
|
|
});
|
|
last = offset.slice(-1)[0];
|
|
api.drawPoint(last.points[3] || last.points[2]);
|
|
|
|
api.setColor("blue");
|
|
offset = curve.offset(-api.distance);
|
|
offset.forEach(c => {
|
|
api.drawPoint(c.points[0]);
|
|
api.drawCurve(c);
|
|
});
|
|
last = offset.slice(-1)[0];
|
|
api.drawPoint(last.points[3] || last.points[2]);
|
|
}
|
|
};
|
|
}()),
|
|
withKeys: true
|
|
},
|
|
"graduatedoffset": {
|
|
handler: (function() { return {
|
|
statics: {
|
|
keyHandlingOptions: {
|
|
propName: "distance",
|
|
values: {
|
|
"38": 1, // up arrow
|
|
"40": -1 // down arrow
|
|
}
|
|
}
|
|
},
|
|
|
|
setup: function(api, curve) {
|
|
api.setCurve(curve);
|
|
api.distance = 20;
|
|
},
|
|
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
this.setup(api, curve);
|
|
},
|
|
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
this.setup(api, curve);
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
|
|
api.setColor("blue");
|
|
var outline = curve.outline(0,0,api.distance,api.distance);
|
|
outline.curves.forEach(c => api.drawCurve(c));
|
|
}
|
|
};
|
|
}()),
|
|
withKeys: true
|
|
},
|
|
"circles": {
|
|
handler: (function() { var sin = Math.sin,
|
|
cos = Math.cos;
|
|
|
|
return {
|
|
setup: function(api) {
|
|
api.w = api.getPanelWidth();
|
|
api.h = api.getPanelHeight();
|
|
api.pad = 20;
|
|
api.r = api.w/2 - api.pad;
|
|
api.mousePt = false;
|
|
api.angle = 0;
|
|
var spt = { x: api.w-api.pad, y: api.h/2 };
|
|
api.setCurve(new api.Bezier(spt, spt, spt));
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
api.setColor("lightgrey");
|
|
api.drawGrid(1,1);
|
|
api.setColor("red");
|
|
api.drawCircle({x:api.w/2,y:api.h/2},api.r);
|
|
api.setColor("transparent");
|
|
api.setFill("rgba(100,255,100,0.4)");
|
|
var p = {
|
|
x: api.w/2,
|
|
y: api.h/2,
|
|
r: api.r,
|
|
s: api.angle < 0 ? api.angle : 0,
|
|
e: api.angle < 0 ? 0 : api.angle
|
|
};
|
|
api.drawArc(p);
|
|
api.setColor("black");
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
},
|
|
|
|
onMouseMove: function(evt, api) {
|
|
var x = evt.offsetX - api.w/2,
|
|
y = evt.offsetY - api.h/2;
|
|
var angle = Math.atan2(y,x);
|
|
var pts = api.curve.points;
|
|
// new control
|
|
var r = api.r,
|
|
b = (cos(angle) - 1) / sin(angle);
|
|
pts[1] = {
|
|
x: api.w/2 + r * (cos(angle) - b * sin(angle)),
|
|
y: api.w/2 + r * (sin(angle) + b * cos(angle))
|
|
};
|
|
// new endpoint
|
|
pts[2] = {
|
|
x: api.w/2 + api.r * cos(angle),
|
|
y: api.w/2 + api.r * sin(angle)
|
|
};
|
|
api.setCurve(new api.Bezier(pts));
|
|
api.angle = angle;
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"circles_cubic": {
|
|
handler: (function() { var sin = Math.sin, cos = Math.cos, tan = Math.tan;
|
|
|
|
return {
|
|
setup: function(api) {
|
|
api.setSize(400,400);
|
|
api.w = api.getPanelWidth();
|
|
api.h = api.getPanelHeight();
|
|
api.pad = 80;
|
|
api.r = api.w/2 - api.pad;
|
|
api.mousePt = false;
|
|
api.angle = 0;
|
|
var spt = { x: api.w-api.pad, y: api.h/2 };
|
|
api.setCurve(new api.Bezier(spt, spt, spt, spt));
|
|
},
|
|
|
|
guessCurve: function(S, B, E) {
|
|
var C = {
|
|
x: (S.x + E.x)/2,
|
|
y: (S.y + E.y)/2
|
|
},
|
|
A = {
|
|
x: B.x + (B.x-C.x)/3, // cubic ratio at t=0.5 is 1/3
|
|
y: B.y + (B.y-C.y)/3
|
|
},
|
|
bx = (E.x-S.x)/4,
|
|
by = (E.y-S.y)/4,
|
|
e1 = {
|
|
x: B.x - bx,
|
|
y: B.y - by
|
|
},
|
|
e2 = {
|
|
x: B.x + bx,
|
|
y: B.y + by
|
|
},
|
|
|
|
v1 = {
|
|
x: A.x + (e1.x-A.x)*2,
|
|
y: A.y + (e1.y-A.y)*2
|
|
},
|
|
v2 = {
|
|
x: A.x + (e2.x-A.x)*2,
|
|
y: A.y + (e2.y-A.y)*2
|
|
},
|
|
|
|
nc1 = {
|
|
x: S.x + (v1.x-S.x)*2,
|
|
y: S.y + (v1.y-S.y)*2
|
|
},
|
|
nc2 = {
|
|
x: E.x + (v2.x-E.x)*2,
|
|
y: E.y + (v2.y-E.y)*2
|
|
};
|
|
return [nc1, nc2];
|
|
},
|
|
|
|
draw: function(api, curve) {
|
|
api.reset();
|
|
|
|
api.setColor("lightgrey");
|
|
api.drawGrid(1,1);
|
|
api.setColor("rgba(255,0,0,0.4)");
|
|
api.drawCircle({x:api.w/2,y:api.h/2},api.r);
|
|
api.setColor("transparent");
|
|
api.setFill("rgba(100,255,100,0.4)");
|
|
var p = {
|
|
x: api.w/2,
|
|
y: api.h/2,
|
|
r: api.r,
|
|
s: api.angle < 0 ? api.angle : 0,
|
|
e: api.angle < 0 ? 0 : api.angle
|
|
};
|
|
api.drawArc(p);
|
|
|
|
// guessed curve
|
|
var B = {
|
|
x: api.w/2 + api.r * cos(api.angle/2),
|
|
y: api.w/2 + api.r * sin(api.angle/2)
|
|
};
|
|
var S = curve.points[0],
|
|
E = curve.points[3],
|
|
nc = this.guessCurve(S,B,E);
|
|
var guess = new api.Bezier([S, nc[0], nc[1], E]);
|
|
api.setColor("rgb(140,140,255)");
|
|
api.drawLine(guess.points[0], guess.points[1]);
|
|
api.drawLine(guess.points[1], guess.points[2]);
|
|
api.drawLine(guess.points[2], guess.points[3]);
|
|
api.setColor("blue");
|
|
api.drawCurve(guess);
|
|
api.drawCircle(guess.points[1], 3);
|
|
api.drawCircle(guess.points[2], 3);
|
|
|
|
// real curve
|
|
api.drawSkeleton(curve);
|
|
api.setColor("black");
|
|
api.drawLine(curve.points[1], curve.points[2]);
|
|
api.drawCurve(curve);
|
|
},
|
|
|
|
onMouseMove: function(evt, api) {
|
|
var x = evt.offsetX - api.w/2,
|
|
y = evt.offsetY - api.h/2;
|
|
if (x>api.w/2) return;
|
|
|
|
var angle = Math.atan2(y,x);
|
|
if (angle < 0) {
|
|
angle = 2*Math.PI + angle;
|
|
}
|
|
var pts = api.curve.points;
|
|
// new control 1
|
|
var r = api.r,
|
|
f = (4 * tan(angle/4)) /3;
|
|
pts[1] = {
|
|
x: api.w/2 + r,
|
|
y: api.w/2 + r * f
|
|
};
|
|
// new control 2
|
|
pts[2] = {
|
|
x: api.w/2 + api.r * (cos(angle) + f*sin(angle)),
|
|
y: api.w/2 + api.r * (sin(angle) - f*cos(angle))
|
|
};
|
|
// new endpoint
|
|
pts[3] = {
|
|
x: api.w/2 + api.r * cos(angle),
|
|
y: api.w/2 + api.r * sin(angle)
|
|
};
|
|
api.setCurve(new api.Bezier(pts));
|
|
api.angle = angle;
|
|
},
|
|
|
|
drawCircle: function(api) {
|
|
api.setSize(325,325);
|
|
api.reset();
|
|
|
|
var w = api.getPanelWidth(),
|
|
h = api.getPanelHeight(),
|
|
pad = 60,
|
|
r = w/2 - pad,
|
|
k = 0.55228,
|
|
offset = {x: -pad/2, y:-pad/4};
|
|
|
|
var curve = new api.Bezier([
|
|
{x:w/2 + r, y:h/2},
|
|
{x:w/2 + r, y:h/2 + k*r},
|
|
{x:w/2 + k*r, y:h/2 + r},
|
|
{x:w/2, y:h/2 + r}
|
|
]);
|
|
|
|
api.setColor("lightgrey");
|
|
api.drawLine({x:0,y:h/2}, {x:w+pad,y:h/2}, offset);
|
|
api.drawLine({x:w/2,y:0}, {x:w/2,y:h+pad}, offset);
|
|
|
|
var pts = curve.points;
|
|
|
|
api.setColor("red");
|
|
api.drawPoint(pts[0], offset);
|
|
api.drawPoint(pts[1], offset);
|
|
api.drawPoint(pts[2], offset);
|
|
api.drawPoint(pts[3], offset);
|
|
api.drawCurve(curve, offset);
|
|
api.setColor("rgb(255,160,160)");
|
|
api.drawLine(pts[0],pts[1],offset);
|
|
api.drawLine(pts[1],pts[2],offset);
|
|
api.drawLine(pts[2],pts[3],offset);
|
|
|
|
api.setFill("red");
|
|
api.text((pts[0].x - w/2) + "," + (pts[0].y - h/2), {x: pts[0].x + 7, y: pts[0].y + 3}, offset);
|
|
api.text((pts[1].x - w/2) + "," + (pts[1].y - h/2), {x: pts[1].x + 7, y: pts[1].y + 3}, offset);
|
|
api.text((pts[2].x - w/2) + "," + (pts[2].y - h/2), {x: pts[2].x + 7, y: pts[2].y + 7}, offset);
|
|
api.text((pts[3].x - w/2) + "," + (pts[3].y - h/2), {x: pts[3].x, y: pts[3].y + 13}, offset);
|
|
|
|
pts.forEach(p => { p.x = -(p.x - w); });
|
|
api.setColor("blue");
|
|
api.drawCurve(curve, offset);
|
|
api.drawLine(pts[2],pts[3],offset);
|
|
api.drawPoint(pts[2],offset);
|
|
api.setFill("blue");
|
|
api.text("reflected", {x: pts[2].x - pad/2, y: pts[2].y + 13}, offset);
|
|
api.setColor("rgb(200,200,255)");
|
|
api.drawLine(pts[1],pts[0],offset);
|
|
api.drawPoint(pts[1],offset);
|
|
|
|
pts.forEach(p => { p.y = -(p.y - h); });
|
|
api.setColor("green");
|
|
api.drawCurve(curve, offset);
|
|
|
|
pts.forEach(p => { p.x = -(p.x - w); });
|
|
api.setColor("purple");
|
|
api.drawCurve(curve, offset);
|
|
api.drawLine(pts[1],pts[0],offset);
|
|
api.drawPoint(pts[1],offset);
|
|
api.setFill("purple");
|
|
api.text("reflected", {x: pts[1].x + 10, y: pts[1].y + 3}, offset);
|
|
api.setColor("rgb(200,200,255)");
|
|
api.drawLine(pts[2],pts[3],offset);
|
|
api.drawPoint(pts[2],offset);
|
|
|
|
|
|
|
|
api.setColor("black");
|
|
api.setFill("black");
|
|
api.drawLine({x:w/2, y:h/2}, {x:w/2 + r -2, y:h/2}, offset);
|
|
api.drawLine({x:w/2, y:h/2}, {x:w/2, y:h/2 + r -2}, offset);
|
|
api.text("r = " + r, {x:w/2 + r/3, y:h/2 + 10}, offset);
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"arcapproximation": {
|
|
handler: (function() { var atan2 = Math.atan2, PI = Math.PI, TAU = 2*PI, cos = Math.cos, sin = Math.sin;
|
|
|
|
return {
|
|
// These are functions that can be called "From the page",
|
|
// rather than being internal to the sketch. This is useful
|
|
// for making on-page controls hook into the sketch code.
|
|
statics: {
|
|
keyHandlingOptions: {
|
|
propName: "error",
|
|
values: {
|
|
"38": 0.1, // up arrow
|
|
"40": -0.1 // down arrow
|
|
},
|
|
controller: function(api) {
|
|
if (api.error < 0.1) {
|
|
api.error = 0.1;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Setup up a skeleton curve that, when using its
|
|
* points for a B-spline, can form a circle.
|
|
*/
|
|
setupCircle: function(api) {
|
|
var curve = new api.Bezier(70,70, 140,40, 240,130);
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
/**
|
|
* Set up the default quadratic curve.
|
|
*/
|
|
setupQuadratic: function(api) {
|
|
var curve = api.getDefaultQuadratic();
|
|
api.setCurve(curve);
|
|
},
|
|
|
|
/**
|
|
* Set up the default cubic curve.
|
|
*/
|
|
setupCubic: function(api) {
|
|
var curve = api.getDefaultCubic();
|
|
api.setCurve(curve);
|
|
api.error = 0.5;
|
|
},
|
|
|
|
/**
|
|
* Given three points, find the (only!) circle
|
|
* that passes through all three points, based
|
|
* on the fact that the perpendiculars of the
|
|
* chords between the points all cross each
|
|
* other at the center of that circle.
|
|
*/
|
|
getCCenter: function(api, p1, p2, p3) {
|
|
// deltas
|
|
var dx1 = (p2.x - p1.x),
|
|
dy1 = (p2.y - p1.y),
|
|
dx2 = (p3.x - p2.x),
|
|
dy2 = (p3.y - p2.y);
|
|
|
|
// perpendiculars (quarter circle turned)
|
|
var dx1p = dx1 * cos(PI/2) - dy1 * sin(PI/2),
|
|
dy1p = dx1 * sin(PI/2) + dy1 * cos(PI/2),
|
|
dx2p = dx2 * cos(PI/2) - dy2 * sin(PI/2),
|
|
dy2p = dx2 * sin(PI/2) + dy2 * cos(PI/2);
|
|
|
|
// chord midpoints
|
|
var mx1 = (p1.x + p2.x)/2,
|
|
my1 = (p1.y + p2.y)/2,
|
|
mx2 = (p2.x + p3.x)/2,
|
|
my2 = (p2.y + p3.y)/2;
|
|
|
|
// midpoint offsets
|
|
var mx1n = mx1 + dx1p,
|
|
my1n = my1 + dy1p,
|
|
mx2n = mx2 + dx2p,
|
|
my2n = my2 + dy2p;
|
|
|
|
// intersection of these lines:
|
|
var i = api.utils.lli8(mx1,my1,mx1n,my1n, mx2,my2,mx2n,my2n);
|
|
var r = api.utils.dist(i,p1);
|
|
|
|
// arc start/end values, over mid point
|
|
var s = atan2(p1.y - i.y, p1.x - i.x),
|
|
m = atan2(p2.y - i.y, p2.x - i.x),
|
|
e = atan2(p3.y - i.y, p3.x - i.x);
|
|
|
|
// determine arc direction (cw/ccw correction)
|
|
var __;
|
|
if (s<e) {
|
|
if (s>m || m>e) { s += TAU; }
|
|
if (s>e) { __=e; e=s; s=__; }
|
|
} else {
|
|
if (e<m && m<s) { __=e; e=s; s=__; } else { e += TAU; }
|
|
}
|
|
|
|
// assign and done.
|
|
i.s = s;
|
|
i.e = e;
|
|
i.r = r;
|
|
return i;
|
|
},
|
|
|
|
/**
|
|
* Draw the circle-computation sketch
|
|
*/
|
|
drawCircle: function(api, curve) {
|
|
api.reset();
|
|
var pts = curve.points;
|
|
|
|
// get center
|
|
var C = this.getCCenter(api, pts[0], pts[1], pts[2]);
|
|
// outer circle
|
|
api.setColor("grey");
|
|
api.drawCircle(C, api.utils.dist(C,pts[0]));
|
|
|
|
// controllable points
|
|
api.setColor("black");
|
|
pts.forEach(p => api.drawCircle(p,3));
|
|
|
|
// chords and perpendicular lines
|
|
var m;
|
|
|
|
api.setColor("blue");
|
|
api.drawLine(pts[0], pts[1]);
|
|
m = {x: (pts[0].x + pts[1].x)/2, y: (pts[0].y + pts[1].y)/2};
|
|
api.drawLine(m, {x:C.x+(C.x-m.x), y:C.y+(C.y-m.y)});
|
|
|
|
api.setColor("red");
|
|
api.drawLine(pts[1], pts[2]);
|
|
m = {x: (pts[1].x + pts[2].x)/2, y: (pts[1].y + pts[2].y)/2};
|
|
api.drawLine(m, {x:C.x+(C.x-m.x), y:C.y+(C.y-m.y)});
|
|
|
|
api.setColor("green");
|
|
api.drawLine(pts[2], pts[0]);
|
|
m = {x: (pts[2].x + pts[0].x)/2, y: (pts[2].y + pts[0].y)/2};
|
|
api.drawLine(m, {x:C.x+(C.x-m.x), y:C.y+(C.y-m.y)});
|
|
|
|
// center
|
|
api.setColor("black");
|
|
api.drawPoint(C);
|
|
api.setFill("black");
|
|
api.text("Intersection point", C, {x:-25, y:10});
|
|
},
|
|
|
|
/**
|
|
* Draw a single arc being fit to a Bezier curve,
|
|
* to show off the general application.
|
|
*/
|
|
drawSingleArc: function(api, curve) {
|
|
api.reset();
|
|
var arcs = curve.arcs(api.error);
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
|
|
var a = arcs[0];
|
|
api.setColor("red");
|
|
api.setFill("rgba(255,0,0,0.2)");
|
|
api.debug = true;
|
|
api.drawArc(a);
|
|
|
|
api.setFill("black");
|
|
api.text("Arc approximation with total error " + api.utils.round(api.error,1), {x:10, y:15});
|
|
},
|
|
|
|
/**
|
|
* Draw an arc approximation for an entire Bezier curve.
|
|
*/
|
|
drawArcs: function(api, curve) {
|
|
api.reset();
|
|
var arcs = curve.arcs(api.error);
|
|
api.drawSkeleton(curve);
|
|
api.drawCurve(curve);
|
|
arcs.forEach(a => {
|
|
api.setRandomColor(0.3);
|
|
api.setFill(api.getColor());
|
|
api.drawArc(a);
|
|
});
|
|
|
|
api.setFill("black");
|
|
api.text("Arc approximation with total error " + api.utils.round(api.error,1) + " per arc segment", {x:10, y:15});
|
|
}
|
|
};
|
|
}()),
|
|
withKeys: true
|
|
},
|
|
"bsplines": {
|
|
handler: (function() { return {
|
|
basicSketch: require('./basic-sketch'),
|
|
interpolationGraph: require('./interpolation-graph'),
|
|
uniformBSpline: require('./uniform-bspline'),
|
|
centerCutBSpline: require('./center-cut-bspline'),
|
|
openUniformBSpline: require('./open-uniform-bspline'),
|
|
rationalUniformBSpline: require('./rational-uniform-bspline'),
|
|
|
|
bindKnots: function(owner, knots, ref) {
|
|
this.refs[ref].bindKnots(owner, knots);
|
|
},
|
|
|
|
bindWeights: function(owner, weights, closed, ref) {
|
|
this.refs[ref].bindWeights(owner, weights, closed);
|
|
}
|
|
};
|
|
}())
|
|
},
|
|
"comments": {
|
|
handler: (function() { /**
|
|
* We REALLY don't want disqus to load unless the user
|
|
* is actually looking at the comments section, because it
|
|
* tacks on 2.5+ MB in network transfers...
|
|
*/
|
|
return {
|
|
componentDidMount() {
|
|
if (typeof document === "undefined") {
|
|
return this.silence();
|
|
}
|
|
this.heading = document.getElementById(this.props.page);
|
|
document.addEventListener("scroll", this.scrollHandler, {passive:true});
|
|
},
|
|
|
|
scrollHandler(evt) {
|
|
var bbox = this.heading.getBoundingClientRect();
|
|
var top = bbox.top;
|
|
var limit = window.innerHeight;
|
|
if (top<limit) { this.loadDisqus(); }
|
|
},
|
|
|
|
loadDisqus() {
|
|
var script = document.createElement("script");
|
|
script.src = "lib/site/disqus.js";
|
|
script.async = true;
|
|
document.head.appendChild(script);
|
|
this.silence();
|
|
this.unlisten();
|
|
},
|
|
|
|
silence() {
|
|
this.loadDisqus = () => {};
|
|
},
|
|
|
|
unlisten() {
|
|
document.removeEventListener("scroll", this.scrollHandler);
|
|
}
|
|
};
|
|
}())
|
|
}
|
|
};
|