1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-09-24 23:31:50 +02:00
Files
BezierInfo-2/chapters/arcapproximation/handler.js
2020-08-08 16:35:45 -07:00

185 lines
4.7 KiB
JavaScript

var atan2 = Math.atan2, PI = Math.PI, TAU = 2*PI, cos = Math.cos, sin = Math.sin;
module.exports = {
// 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});
}
};