Draw 2 bezier curves through 3 points to mimic a circle arc

我们两清 提交于 2019-12-24 11:38:03

问题


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


回答1:


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

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>



回答2:


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.



来源:https://stackoverflow.com/questions/28229695/draw-2-bezier-curves-through-3-points-to-mimic-a-circle-arc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!