mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-08-31 20:11:59 +02:00
image regeneration + circles
This commit is contained in:
85
docs/chapters/circles_cubic/arc-approximation.js
Normal file
85
docs/chapters/circles_cubic/arc-approximation.js
Normal file
@@ -0,0 +1,85 @@
|
||||
let guess, w, h, pad = 75, r;
|
||||
|
||||
setup() {
|
||||
w = this.width;
|
||||
h = this.height;
|
||||
r = w/2 - pad;
|
||||
guess = new Bezier(this,
|
||||
{ x: w - pad, y: h/2},
|
||||
{ x: w - pad, y: h/2},
|
||||
{ x: w - pad, y: h/2},
|
||||
{ x: w - pad, y: h/2}
|
||||
);
|
||||
setSlider(`.slide-control`, `angle`, -PI/4, v => this.updateCurve(v));
|
||||
}
|
||||
|
||||
draw() {
|
||||
clear();
|
||||
setColor(`lightgrey`);
|
||||
line(0, h/2, w, h/2);
|
||||
line(w/2, 0, w/2, h);
|
||||
|
||||
noFill();
|
||||
setStroke(`red`);
|
||||
circle(w/2, h/2, r);
|
||||
|
||||
noStroke();
|
||||
setFill(`rgba(100,255,100,0.4)`);
|
||||
let a = this.angle;
|
||||
arc(w/2, h/2, r, a < 0 ? a : 0, a < 0 ? 0 : a, w/2, h/2);
|
||||
|
||||
guess.drawSkeleton(`lightblue`);
|
||||
guess.drawCurve(`lightblue`);
|
||||
|
||||
let real = this.getRealCurve(guess.points[0], guess.points[3], this.angle);
|
||||
real.drawSkeleton();
|
||||
real.drawCurve();
|
||||
|
||||
setColor(`black`);
|
||||
real.points.forEach(p => {
|
||||
circle(p.x, p.y, 2);
|
||||
text(`(${p.x|0},${p.y|0})`, p.x+5, p.y);
|
||||
});
|
||||
}
|
||||
|
||||
updateCurve(a) {
|
||||
let angle = -a;
|
||||
|
||||
const S = guess.points[0],
|
||||
B = {
|
||||
x: w/2 + r * cos(angle/2),
|
||||
y: h/2 + r * sin(angle/2)
|
||||
},
|
||||
E = guess.points[3] = {
|
||||
x: w/2 + r * cos(angle),
|
||||
y: h/2 + r * sin(angle)
|
||||
};
|
||||
|
||||
guess = this.guessCurve(S,B,E);
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
guessCurve(S, B, E) {
|
||||
const C = { x: (S.x + E.x)/2, y: (S.y + E.y)/2 }, // we know we're working with t=0.5
|
||||
A = { x: B.x + (B.x-C.x)/3, y: B.y + (B.y-C.y)/3 }, // cubic ratio at t=0.5 is 1/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 },
|
||||
C1 = { x: S.x + (v1.x-S.x)*2, y: S.y + (v1.y-S.y)*2 },
|
||||
C2 = { x: E.x + (v2.x-E.x)*2, y: E.y + (v2.y-E.y)*2 };
|
||||
return new Bezier(this, [S, C1, C2, E]);
|
||||
}
|
||||
|
||||
getRealCurve(S, E, angle) {
|
||||
const f = 4/3 * tan(angle/4);
|
||||
const C1 = { x: w/2 + r, y: h/2 + r * f };
|
||||
const C2 = {
|
||||
x: w/2 + r * ( cos(angle) + f * sin(angle) ),
|
||||
y: h/2 + r * ( sin(angle) - f * cos(angle) )
|
||||
};
|
||||
return new Bezier(this, [S, C1, C2, E]);
|
||||
}
|
64
docs/chapters/circles_cubic/circle.js
Normal file
64
docs/chapters/circles_cubic/circle.js
Normal file
@@ -0,0 +1,64 @@
|
||||
let curve, r;
|
||||
|
||||
setup() {
|
||||
r = (this.width/4) | 0;
|
||||
curve = new Bezier(this, [
|
||||
{ x: r, y: 0 },
|
||||
{ x: r, y: 0.55228 * r },
|
||||
{ x: 0.55228 * r, y: r},
|
||||
{ x: 0, y: r }
|
||||
]);
|
||||
}
|
||||
|
||||
draw() {
|
||||
clear();
|
||||
translate(this.width/2, this.height/2);
|
||||
const w = this.width, h = this.height;
|
||||
|
||||
setStroke(`lightgrey`);
|
||||
line(0,-h,0,h);
|
||||
line(-w,0,w,0);
|
||||
|
||||
setStroke(`black`);
|
||||
line(-r,0,r,0);
|
||||
line(0,-r,0,r);
|
||||
|
||||
setColor(`black`);
|
||||
text(`r = ${r}`, r/2, 15, CENTER);
|
||||
|
||||
setColor(`red`);
|
||||
curve.drawSkeleton(`red`);
|
||||
curve.points.forEach(p => {
|
||||
circle(p.x, p.y, 2);
|
||||
text(`(${p.x},${p.y})`, p.x+5, p.y+15);
|
||||
});
|
||||
curve.drawCurve();
|
||||
|
||||
curve.points.forEach(p => p.y = -p.y);
|
||||
curve.drawCurve(`#CC00CC40`);
|
||||
|
||||
setColor(`#CC00CC`);
|
||||
line(r, 0, r, -0.55228 * r);
|
||||
circle(r, -0.55228 * r, 2);
|
||||
text(`reflected`, r + 7, -0.55228 * r + 3, LEFT);
|
||||
|
||||
setColor(`#CC00CC40`);
|
||||
line(0, -r, 0.55228 * r, -r);
|
||||
|
||||
curve.points.forEach(p => {
|
||||
p.x = -p.x;
|
||||
p.y = -p.y;
|
||||
});
|
||||
curve.drawCurve(`#0000CC40`);
|
||||
|
||||
setColor(`#0000CC`);
|
||||
line(0, r, -0.55228 * r, r);
|
||||
circle(-0.55228 * r, r, 2);
|
||||
text(`reflected`, -0.55228 * r - 5, r + 3, RIGHT);
|
||||
|
||||
setColor(`#0000CC40`);
|
||||
line(-r, 0, -r, 0.55228 * r);
|
||||
|
||||
curve.points.forEach(p => p.y = -p.y);
|
||||
curve.drawCurve(`#00000040`);
|
||||
}
|
@@ -8,7 +8,9 @@ The first thing we can do is "guess" what the curve should look like, based on t
|
||||
|
||||
So have a graphical look at a "bad" guess versus the true fit, where we'll be using the bad guess and the description in the second paragraph to derive the maths for the true fit:
|
||||
|
||||
<Graphic title="Cubic Bézier arc approximation" setup={this.setup} draw={this.draw} onMouseMove={this.onMouseMove}/>
|
||||
<graphics-element title="Cubic Bézier arc approximation" width="400" height="400" src="./arc-approximation.js">
|
||||
<input type="range" min="-3.1415" max="3.1415" step="0.01" value="-0.7854" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
We see two curves here; in blue, our "guessed" curve and its control points, and in grey/black, the true curve fit, with proper control points that were shifted in, along line between our guessed control points, such that the derivatives at the start and end points are correct.
|
||||
|
||||
@@ -177,4 +179,4 @@ Which, in decimal values, rounded to six significant digits, is:
|
||||
|
||||
Of course, this is for a circle with radius 1, so if you have a different radius circle, simply multiply the coordinate by the radius you need. And then finally, forming a full curve is now a simple a matter of mirroring these coordinates about the origin:
|
||||
|
||||
<Graphic title="Cubic Bézier circle approximation" draw={this.drawCircle} static={true}/>
|
||||
<graphics-element title="Cubic Bézier circle approximation" width="400" height="400" src="./circle.js"></graphics-element>
|
||||
|
@@ -1,205 +0,0 @@
|
||||
var sin = Math.sin, cos = Math.cos, tan = Math.tan;
|
||||
|
||||
module.exports = {
|
||||
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);
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user