1

I have three points that are not on the same line, originally I want to draw a circle arc through these three points, which I did. But chrome is actually not drawing real circle but using several bezier curves to pretend it is circle, because bezier curves are cheap. If chrome is doing that as a middle man, why ain't I drawing circle-like bezier myself (two bezier, from point 1 to middle point, middle point to point 3)? That would be much cleaner, and cheap ( 2 compared to unknown number of bezier curves browser decides). That is where I am stuck, how? where should the "controlling points" be?

Here is my old draw arc function in javascript

drawArc = function(startPoint, thirdPoint, endPoint){
var ctx = this.ctx;
ctx.lineWidth = this.strokeWidth;
ctx.strokeStyle = this.strokeColor;

var centerObject = circleCenter( new Point(startPoint.x, startPoint.y), 
                                 new Point(thirdPoint.x, thirdPoint.y), 
                                 new Point(endPoint.x, endPoint.y) );
var centerX = centerObject.x;
var centerY = centerObject.y;
var r = centerObject.r

var angle = Math.atan2(centerX-startPoint.x, centerY-startPoint.y);
// console.log(centerObject);
if (!angle){
    ctx.beginPath();
    ctx.moveTo(startPoint.x, startPoint.y);
    ctx.lineTo(endPoint.x, endPoint.y);
} else {
    if( angle > Math.PI/2) {
        ctx.beginPath();
        ctx.arc(centerX, centerY, r, Math.PI * 1.5-angle, Math.PI * 1.5 + angle, true);
    } else {
        ctx.beginPath();
        ctx.arc(centerX, centerY, r, Math.PI * 1.5-angle, Math.PI * 1.5 + angle, false);
    }
}
ctx.globalCompositeOperation = "source-over";
ctx.stroke();

}
var circleCenter = function(startPoint, thirdPoint, endPoint){
var dy1 = thirdPoint.y - startPoint.y;
var dx1 = thirdPoint.x - startPoint.x;
var dy2 = endPoint.y - thirdPoint.y;
var dx2 = endPoint.x - thirdPoint.x;

var aSlope = dy1/dx1;
var bSlope = dy2/dx2;  


var centerX = (aSlope*bSlope*(startPoint.y - endPoint.y) + bSlope*(startPoint.x + thirdPoint.x)
    - aSlope*(thirdPoint.x+endPoint.x) )/( 2* (bSlope-aSlope) );
var centerY = -1*(centerX - (startPoint.x+thirdPoint.x)/2)/aSlope +  (startPoint.y+thirdPoint.y)/2;
var r = dist(centerX, centerY, startPoint.x, startPoint.y)

return {
    x: centerX,
    y: centerY,
    r: r
};
}

can anyone help me rewrite drawArc function to use canvas bezierCurveTo() Method instead arc()?

my code example is here: http://codepen.io/wentin/pen/VYegqq

  • small comment on the "Bezier curves are cheap": compared to circular arcs, Bezier curves are *insanely expensive* to draw. You can find and draw a circular arc through three points several times over in the same time you need to compute and draw a single Bezier segment. The Canvas2d `arc()` instruction is, to the best of my knowledge, a true arc, not a Bezier approximation. – Mike 'Pomax' Kamermans Feb 04 '15 at 00:26

2 Answers2

3

You can approximate a circle using 4 cubic Bezier curves...but it's not a perfect circle ;-)

enter image description here

Example code and a Demo:

You can use the relationships between the radius & constant c to the start, end and control points to calculate your own desired control points. This Bezier approximated circle is drawn around the origin [0,0] so you will, of course, translate to the centerpoint of your specific circle.

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

ctx.lineWidth=3;

// refined from 0.551915024494 thanks to Pomax
var c=0.5522847498307933984022516322796 ;  

var cx=150; // center x
var cy=150; // center y
var r=100;  // radius

drawBezierCircle(cx,cy,r);

function drawBezierCircle(cx,cy,r){
  
    ctx.translate(cx,cy); // translate to centerpoint

    ctx.beginPath();

    ctx.moveTo(0,-r);
    ctx.bezierCurveTo(
      c*r,-r, 
      r,-c*r, 
      r,0
    );
    ctx.strokeStyle='red';
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(r,0);
    ctx.bezierCurveTo(
      r,c*r, 
      c*r,r, 
      0,r
    );
    ctx.strokeStyle='green';
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(0,r);
    ctx.bezierCurveTo(
      -c*r,r, 
      -r,c*r, 
      -r,0
    );
    ctx.strokeStyle='blue';
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(-r,0);
    ctx.bezierCurveTo(
      -r,-c*r, 
      -c*r,-r, 
      0,-r
    );
    ctx.strokeStyle='gold';
    ctx.stroke();
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
markE
  • 102,905
  • 11
  • 164
  • 176
  • 1
    your variable `c` (normally written as `k` to refer to its used-in-maths name, `kappa`) should be 0.5522847498307933984022516322796 (or without rounding: 4*(sqrt(2)-1)/3). See http://pomax.github.io/bezierinfo/#circles_cubic – Mike 'Pomax' Kamermans Feb 03 '15 at 18:21
  • @Mike'Pomax'Kamermans, fair enough... 0.5522847498307933984022516322796 it is! BTW, I enjoyed and learned much from your treatise on Bezier curves--thanks for writing it! http://pomax.github.io/bezierinfo/ – markE Feb 03 '15 at 18:27
  • 1
    always glad to hear it was useful to someone =) – Mike 'Pomax' Kamermans Feb 03 '15 at 18:38
  • It's worth noting that `0.5522847498307933984022516322796` gives you **a worse approximation** than `0.551915024494`, as shown in [this article](http://spencermortensen.com/articles/bezier-circle/) – Ky - Feb 25 '20 at 01:29
1

You can use the following ways to find the control points of a cubic Bezier curve for approximating a circular arc with end points P0, P1, radius R and angular span A:

Denoting the control points as Q0, Q1, Q2 and Q3, then

Q0=P0,
Q3=P1,
Q1=P0 + L * T0
Q2=P1 - L * T1

where T0 and T1 are the unit tangent vector of the circular arc at P0 and P1 and L = (4/3)*tan(A/4).

Please note that the approximation error will grow as the angular span A gets bigger. So, if your circular arc defined by the 3 points has a relatively small angular span, you can even use a single cubic Bezier curve to approximate it with good precision. Similarly, if you always use two Bezier curves (one curve between each two points) to approximate the circular arc, then you might end up with a not-so-good approximation. If Chrome is using several Bezier curves to draw a circular arc, precision might be the reason that the number of Bezier curves is not a fixed value.

fang
  • 3,473
  • 1
  • 13
  • 19