I am trying to build a near-perfect circle with quadratic curves in canvas. I have this function for setting up points around a circle and connecting them with quadratic curves:
function calcPointsCircle(cx, cy, radius, dashLength) {
var n = radius / dashLength,
alpha = Math.PI * 2 / n,
i = -1;
while (i < n) {
var theta = alpha * i,
theta2 = alpha * (i + 1);
points.push({
x : (Math.cos(theta) * radius) + cx,
y : (Math.sin(theta) * radius) + cy,
ex : (Math.cos(theta2) * radius) + cx,
ey : (Math.sin(theta2) * radius) + cy,
py : (Math.sin(theta) * radius) + cy
});
i+=2;
}
}
for (i = 0; i < points.length; i++) {
var p = points[i];
ctx.strokeStyle = '#fff';
ctx.quadraticCurveTo(p.x, p.py, p.x, p.y);
ctx.stroke();
}
It works, but the lines are currently straight (which is obvious, since I am using the points x and y coordinates for the control point):
I am looking for a way to automatically calculate the poisitions for the control points based on the circle radius and the number of points... All help is more then welcome
Here's how to calculate the controls points of a set of quadratic curves which approximate a circle circumscribing a regular polygon.
Given:
A centerpoint, radius & sidecount.
For each side of the polygon, calculate:
3 points on a circumscribing circumference and then calculate the quadratic curve control point that causes the curve to pass through those 3 points:
The 2 points of the polygon side are 2 of the 3 points
Calculate the sweep angle between the 2 points of a side (var sweep)
Bisect the sweep angle (sweep/2)
Use trigonometry to calculate the point on the circumference midway between the 2 points of the side.
Calculate the middle control point:
// calc middle control point
var cpX=2*x1-x0/2-x2/2;
var cpY=2*y1-y0/2-y2/2;
Example code and a Demo:
// change sideCount to the # of poly sides desired
//
var sideCount=5;
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.lineWidth=2;
ctx.fillStyle=randomColor();
// save PI*2
var PI2=Math.PI*2;
// functions to calc a point on circumference of circle
var xx=function(a){return(cx+radius*Math.cos(a));}
var yy=function(a){return(cy+radius*Math.sin(a));}
// general interpolation function
var lerp=function(a,b,x){ return(a+x*(b-a)); }
// define the regular polygon
var cx=150;
var cy=150;
var radius=100;
// calc qCurve controls points and put in sides[] array
var sides=[];
for(var i=0;i<sideCount;i++){
sides.push(makeSide(i,sideCount));
}
// drawing and animating stuff
var percent=0;
var percentDirection=0.50;
$("#toShape").click(function(){
percentDirection=-0.50;
})
$("#toCircle").click(function(){
percentDirection=0.50;
})
animate();
// functions
function animate(){
requestAnimationFrame(animate);
drawSides(percent);
percent+=percentDirection;
if(percent>100){percent=100;}
if(percent<0){percent=0;}
}
function drawSides(pct,color){
ctx.clearRect(0,0,canvas.width,canvas.height);
if(pct==100){
ctx.beginPath();
ctx.arc(cx,cy,radius,0,PI2);
ctx.closePath();
ctx.fill();
}else{
ctx.beginPath();
ctx.moveTo(sides[0].x0,sides[0].y0);
for(var i=0;i<sideCount;i++){
var side=sides[i];
var cpx=lerp(side.midX,side.cpX,pct/100);
var cpy=lerp(side.midY,side.cpY,pct/100);
ctx.quadraticCurveTo(cpx,cpy,side.x2,side.y2);
}
ctx.fill();
}
}
// given a side of a regular polygon,
// calc a qCurve that approximates a circle
function makeSide(n,sideCount){
// starting & ending angles vs centerpoint
var sweep=PI2/sideCount;
var sAngle=sweep*(n-1);
var eAngle=sweep*n;
// given start & end points,
// calc the point on circumference at middle of sweep angle
var x0=xx(sAngle);
var y0=yy(sAngle);
var x1=xx((eAngle+sAngle)/2);
var y1=yy((eAngle+sAngle)/2);
var x2=xx(eAngle);
var y2=yy(eAngle);
// calc the control points to pass a qCurve
// through the 3 points
var dx=x2-x1;
var dy=y2-y1;
var a=Math.atan2(dy,dx);
var midX=lerp(x0,x2,0.50);
var midY=lerp(y0,y2,0.50);
// calc middle control point
var cpX=2*x1-x0/2-x2/2;
var cpY=2*y1-y0/2-y2/2;
return({
x0:x0, y0:y0,
x2:x2, y2:y2,
midX:midX, midY:midY,
cpX:cpX, cpY:cpY,
color:randomColor()
});
}
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="toShape">Animate to Shape</button>
<button id="toCircle">Animate to Circle</button><br>
<canvas id="canvas" width=300 height=300></canvas>