1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-09-01 12:23:19 +02:00
Files
BezierInfo-2/docs/chapters/molding/molding.js
2020-09-26 11:51:00 -07:00

290 lines
8.8 KiB
JavaScript

let curve, utils = Bezier.getUtils();
setup() {
setPanelCount(3);
const type = this.type = this.parameters.type ?? `quadratic`;
curve = type === `quadratic` ? Bezier.defaultQuadratic(this) : Bezier.defaultCubic(this);
this.position = {x:0,y:0};
setMovable(curve.points, [this.position]);
if (this.parameters.interpolated) {
setSlider(`.slide-control`, `falloff`, 100);
}
}
draw() {
clear();
// First and foremost, the curve itself
curve.drawSkeleton();
curve.drawCurve();
curve.drawPoints();
this.drawPosition();
nextPanel();
// then the "as you're moving a point" curve:
curve.drawSkeleton(`lightblue`);
curve.drawCurve(`lightblue`);
curve.points.forEach(p => circle(p.x, p.y, 2));
this.drawMark();
nextPanel();
// And finally, only the result
this.drawResult();
}
drawPosition() {
if (!this.position) return;
setColor(`blue`);
let p = this.position.projection;
if (!this.mark) {
p = this.position.projection = curve.project(
this.position.x,
this.position.y
)
this.position.x = p.x;
this.position.y = p.y;
}
circle(p.x, p.y, 3);
}
drawMark() {
if (!this.mark) return;
if (this.type === `quadratic`) {
this.drawQuadraticMark();
} else {
this.drawCubicMark();
}
}
drawQuadraticMark() {
// This is the function that composes a new quadratic curve
// based on the established start and end points, as well as
// the information it knows about point B that you started
// when when you clicked/tapped the graphic.
let {B, t} = this.mark;
setFill(`black`);
text(`t: ${t.toFixed(5)}`, this.panelWidth/2, 15, CENTER);
let {A, C, S, E} = curve.getABC(t, B);
setColor(`lightblue`);
line(S.x, S.y, E.x, E.y);
line(A.x, A.y, C.x, C.y);
const lbl = [`A`, `B`, `C`];
[A,B,C].forEach((p,i) => {
circle(p.x, p.y, 3);
text(lbl[i], p.x + 10, p.y);
});
if (this.currentPoint) {
let {A,B,C,S,E} = curve.getABC(t, this.position);
setColor(`purple`);
line(A.x, A.y, C.x, C.y);
line(S.x, S.y, A.x, A.y);
line(E.x, E.y, A.x, A.y);
[A,B,C].forEach(p => circle(p.x, p.y, 3));
noFill();
circle(B.x, B.y, 5);
this.molded = new Bezier(this, [S,A,E]);
}
}
drawCubicMark() {
// This is the function that composes a new cubic curve based
// on the established start and end points, as well as the
// information it knows about point B that you started when
// when you clicked/tapped the graphic.
const S = curve.points[0],
E = curve.points[curve.order],
{B, t, e1, e2} = this.mark,
org = curve.getABC(t, B),
nB = this.position,
d1 = { x: e1.x - B.x, y: e1.y - B.y },
d2 = { x: e2.x - B.x, y: e2.y - B.y },
ne1 = { x: nB.x + d1.x, y: nB.y + d1.y },
ne2 = { x: nB.x + d2.x, y: nB.y + d2.y },
{A, C} = curve.getABC(t, nB),
// The cubic case requires us to derive two control points,
// which we'll do in a separate function to keep the code
// at least somewhat manageable.
{v1, v2, C1, C2} = this.deriveControlPoints(S, A, E, ne1, ne2, t);
if (this.parameters.interpolated) {
// For the last example, we need to show what the "ideal" curve
// looks like, in addition to the one we actually get when we
// rely on the B we picked with the `t` value and e1/e2 points
// that point B had...
const ideal = this.getIdealisedCurve(S, nB, E);
this.ideal = new Bezier(this, [ideal.S, ideal.C1, ideal.C2, ideal.E]);
}
setColor(`black`);
text(`t: ${t}`, this.panelWidth/2, 20, CENTER);
setColor(`lightblue`);
line(S.x,S.y,E.x,E.y);
line(org.C.x,org.C.y,org.A.x,org.A.y);
circle(org.A.x, org.A.y, 3);
circle(org.B.x, org.B.y, 3);
circle(org.C.x, org.C.y, 3);
text(`A`, org.A.x + 5, org.A.y);
text(`B`, org.B.x + 5, org.B.y);
text(`C`, org.C.x + 5, org.C.y);
setColor(`purple`);
circle(A.x, A.y, 3);
circle(nB.x, nB.y, 3);
circle(C.x, C.y, 3);
circle(ne1.x, ne1.y, 2);
circle(ne2.x, ne2.y, 2);
line(v1.x, v1.y, A.x, A.y);
line(v2.x, v2.y, A.x, A.y);
line(S.x,S.y,C1.x,C1.y);
line(E.x,E.y,C2.x,C2.y);
line(C2.x,C2.y,C1.x,C1.y);
line(A.x,A.y,C.x,C.y);
line(ne1.x, ne1.y, ne2.x, ne2.y);
noFill();
circle(nB.x, nB.y, 5);
this.molded = new Bezier(this, [S,C1,C2,E]);
}
deriveControlPoints(S, A, E, e1, e2, t) {
// Deriving the control points is effectively "doing what
// we talk about in the section", in code:
const v1 = {
x: A.x - (A.x - e1.x)/(1-t),
y: A.y - (A.y - e1.y)/(1-t)
};
const v2 = {
x: A.x - (A.x - e2.x)/t,
y: A.y - (A.y - e2.y)/t
};
const C1 = {
x: S.x + (v1.x - S.x) / t,
y: S.y + (v1.y - S.y) / t
};
const C2 = {
x: E.x + (v2.x - E.x) / (1-t),
y: E.y + (v2.y - E.y) / (1-t)
};
return {v1, v2, C1, C2};
}
getIdealisedCurve(p1, p2, p3) {
// This "reruns" the curve composition, but with a `t` value
// that is unrelated to the actual point B we picked, instead
// using whatever the appropriate `t` value would be if we were
// trying to fit a circular arc, as per earlier in the section.
const c = utils.getccenter(p1, p2, p3),
d1 = dist(p1.x, p1.y, p2.x, p2.y),
d2 = dist(p3.x, p3.y, p2.x, p2.y),
t = d1 / (d1 + d2),
{ A, B, C, S, E } = Bezier.getABC(3, p1, p2, p3, t),
angle = (atan2(E.y-S.y, E.x-S.x) - atan2(B.y-S.y, B.x-S.x) + TAU) % TAU,
bc = (angle < 0 || angle > PI ? -1 : 1) * dist(S.x, S.y, E.x, E.y)/3,
de1 = t * bc,
de2 = (1-t) * bc,
tangent = [
{ x: B.x - 10 * (B.y-c.y), y: B.y + 10 * (B.x-c.x) },
{ x: B.x + 10 * (B.y-c.y), y: B.y - 10 * (B.x-c.x) }
],
tlength = dist(tangent[0].x, tangent[0].y, tangent[1].x, tangent[1].y),
dx = (tangent[1].x - tangent[0].x)/tlength,
dy = (tangent[1].y - tangent[0].y)/tlength,
e1 = { x: B.x + de1 * dx, y: B.y + de1 * dy},
e2 = { x: B.x - de2 * dx, y: B.y - de2 * dy },
{v1, v2, C1, C2} = this.deriveControlPoints(S, A, E, e1, e2, t);
return {A,B,C,S,E,e1,e2,v1,v2,C1,C2};
}
drawResult() {
let last = curve;
if (this.molded) last = this.molded;
last.drawSkeleton(`lightblue`);
last.drawCurve(this.cursor.down ? `lightblue` : `black`);
last.points.forEach(p => circle(p.x, p.y, 2));
if (this.mark) {
// Did you click/touch somewhere on the curve?
let t = this.mark.t;
let B = last.get(t);
circle(B.x, B.y, 3);
if (this.ideal) {
// Do we want to show the "interpolated" composition?
let d = dist(this.mark.B.x, this.mark.B.y, this.position.x, this.position.y);
let t = min(this.falloff, d) / this.falloff;
this.ideal.drawCurve(`lightblue`);
let iC1 = {
x: (1-t) * last.points[1].x + t * this.ideal.points[1].x,
y: (1-t) * last.points[1].y + t * this.ideal.points[1].y
};
let iC2 = {
x: (1-t) * last.points[2].x + t * this.ideal.points[2].x,
y: (1-t) * last.points[2].y + t * this.ideal.points[2].y
};
this.interpolated = new Bezier(this, [last.points[0], iC1, iC2, last.points[3]]);
this.interpolated.drawCurve();
}
}
}
onMouseDown() {
if (this.currentPoint !== this.position) {
// "moving one of the curve's construction points"
this.mark = false;
this.position.projection = false;
}
else if (this.position.projection) {
// "moving an on-curve point"
let t = this.position.projection.t;
if (this.type === `quadratic`) {
this.mark = {
t, B: this.position.projection,
};
} else {
let struts = curve.getStrutPoints(t);
let m = this.mark = {
t, B: this.position.projection,
e1: struts[7],
e2: struts[8]
};
m.d1 = { x: m.e1.x - m.B.x, y: m.e1.y - m.B.y};
m.d2 = { x: m.e2.x - m.B.x, y: m.e2.y - m.B.y};
}
}
redraw();
}
onMouseMove() {
if (!this.currentPoint && !this.mark) {
this.position.x = this.cursor.x;
this.position.y = this.cursor.y;
}
redraw();
}
onMouseUp() {
this.mark = false;
if (this.molded) {
curve = this.interpolated || this.molded;
resetMovable(curve.points, [this.position]);
this.interpolated = false;
this.molded = false;
}
redraw();
}