see Converting an svg arc to lines
It will compute any point on the SVG elliptic arc by parameter so you can create as many control points as you want.
use interpolation cubics
take a look at:
especially the last link from there:
as it converts the interpolation cubic control points directly to BEZIER cubic control points.
So divide your arc into n
points. Form 4 point cubic patches and convert them to BEZIERs ...
Beware you need at least 4 cubics for whole ellipse but 8 is better so you do not have too big deviation from original shape. So based on the angular size of the arc decide how many cubics you need 1..8
for 0..360 deg
Do not forget to handle the edges of the elliptic curve by extrapolating 1st and last control point slightly outside the angle range of the arc so the 1st derivation is not screwed ...
[Edit1] example ...
Let us consider this simple SVG:
<svg width="512" height="512" viewBox="3.621934 13.621934 90.255485 62.818094" fill="none" stroke="none" stroke-width="1px" transform="matrix(1,0,0,1,0,0" >
<g>
<path id=" " stroke="magenta" d="M 10 70 a 133.591805 50 12.97728 0 0 70 -50 "/>
</g>
</svg>
So (no)/unit matrix, single arc path looking like this:
After rendering the precomputed values using:
_test_ellarc(10,70,133.591806,50.0,12.97728,0,0,80,20);
source is below... Will give:
With some added explanations:
(x0,y0) = (10,70) // last point before 'a'
a = 133.591805
b = 50
ang = 12.97728 deg
sweep = 0
larc = 0
(x1,y1) = (80,20) // lower case 'a' means relative coordinates to x0,y0
Now I created simplified C++ example that computes everything and render overlay with GL in my SVG editor engine:
//---------------------------------------------------------------------------
void svg2scr(double *p,double x,double y) // SVG(x,y) -> OpenGL(p[3])
{
p[0]=x;
p[1]=y;
p[2]=0.0;
win_SVGEditor->edit.scl2g_svg2ogl.l2g(p,p);
}
void draw_line(double x0,double y0,double x1,double y1,double r,double g,double b)
{
double p0[3],p1[3];
glBegin(GL_LINES);
glColor3f(r,g,b);
svg2scr(p0,x0,y0); glVertex2dv(p0);
svg2scr(p1,x1,y1); glVertex2dv(p1);
glEnd();
}
//---------------------------------------------------------------------------
void _test_ellarc(double x0,double y0,double a,double b,double ang,bool larc,bool sweep,double x1,double y1)
{
// ang [deg]
// x0,y0,x1,y1 are absolute !!!
// (ignore) init for rendering
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
// -----------------------------------------
// [SVG elliptic arc to parametric ellipse]
// -----------------------------------------
// draw_line(x0,y0,x1,y1,1.0,0.0,0.0); // raw start-end point line (red)
// precomputed constants
double sx,sy,a0,a1,da; // sx,sy rotated center by ang
double cx,cy; // real center
// helper variables
double ax,ay,bx,by;
double vx,vy,l,db;
int _sweep;
double c,s,e;
ang=M_PI-(ang*M_PI/180.0); // [deg] -> [rad] and offset to match my coordinate system
_sweep=sweep;
if (larc) _sweep=!_sweep;
e=divide(a,b);
c=cos(+ang);
s=sin(+ang);
ax=x0*c-y0*s; // (ax,ay) = unrotated (x0,y0)
ay=x0*s+y0*c;
bx=x1*c-y1*s; // (bx,by) = unrotated (x1,y1)
by=x1*s+y1*c;
ay*=e; // transform ellipse to circle by scaling y axis
by*=e;
sx=0.5*(ax+bx); // mid point between A,B
sy=0.5*(ay+by);
vx=(ay-by); // perpendicular direction vector to AB of size |AB|
vy=(bx-ax);
/* pythagoras:
|v|=|b-a|
(|v|/2)^2 + l^2 = r^2
l^2 = r^2 - (|v|/2)^2
l^2 = r^2 - |v|^2 * 0.25
l^2/|v|^2 = r^2/|v|^2 - 0.25
*/
l=divide(a*a,(vx*vx)+(vy*vy))-0.25; // compute distance of center to (sx,sy) from pythagoras
if (l<0) l=0; // handle if start/end points out of range (not on ellipse) center is in mid of the line
l=sqrt(l);
vx*=l; // rescale v to distance from id point to center
vy*=l;
// (ignore) perpendicular line going through both centers (dark GREEN)
// draw_line(sx-vx,sy-vy,sx+vx,sy+vy,0.0,0.3,0.0);
if (_sweep) // pick the center side
{
sx+=vx;
sy+=vy;
}
else{
sx-=vx;
sy-=vy;
}
a0=atanxy(ax-sx,ay-sy); // compute unrotated angle range
a1=atanxy(bx-sx,by-sy);
/*
// (ignore) unrotated scaled to circle center and start-end points (GREEN)
draw_line(ax,ay,bx,by,0.0,0.7,0.0);
draw_line(ax,ay,sx,sy,0.0,0.7,0.0);
draw_line(bx,by,sx,sy,0.0,0.7,0.0);
// (ignore) unrotated scaled to circle circle arc a0..a1 (GREEN)
glBegin(GL_LINE_STRIP);
glColor3f(0.0,0.7,0.0);
for (double aaa=a0,daa=(a1-a0)*0.05,p[3],i=0;i<=20;aaa+=daa,i++)
{ svg2scr(p,sx+a*cos(aaa),sy+a*sin(aaa)); glVertex2dv(p); }
glEnd();
*/
ay=divide(ay,e);
by=divide(by,e);
sy=divide(sy,e); // scale center back to ellipse
/*
// (ignore) unrotated ellipse center and start-end points (BLUE)
draw_line(ax,ay,bx,by,0.0,0.0,0.7);
draw_line(ax,ay,sx,sy,0.0,0.0,0.7);
draw_line(bx,by,sx,sy,0.0,0.0,0.7);
// (ignore) unrotated ellipse arc a0..a1 (BLUE)
glBegin(GL_LINE_STRIP);
glColor3f(0.0,0.0,0.7);
for (double aaa=a0,daa=(a1-a0)*0.05,p[3],i=0;i<=20;aaa+=daa,i++)
{ svg2scr(p,sx+a*cos(aaa),sy+b*sin(aaa)); glVertex2dv(p); }
glEnd();
*/
// pick angle range
da=a1-a0;
if (fabs(fabs(da)-pi)<=_acc_zero_ang) // half arc is without larc and sweep is not working instead change a0,a1
{
db=(0.5*(a0+a1))-atanxy(bx-ax,by-ay);
while (db<-pi) db+=pi2; // db<0 CCW ... sweep=1
while (db>+pi) db-=pi2; // db>0 CW ... sweep=0
_sweep=0;
if ((db<0.0)&&(!sweep)) _sweep=1;
if ((db>0.0)&&( sweep)) _sweep=1;
if (_sweep)
{
// a=0; b=0;
if (da>=0.0) a1-=pi2;
if (da< 0.0) a0-=pi2;
}
}
else if (larc) // big arc
{
if ((da< pi)&&(da>=0.0)) a1-=pi2;
if ((da>-pi)&&(da< 0.0)) a0-=pi2;
}
else{ // small arc
if (da>+pi) a1-=pi2;
if (da<-pi) a0-=pi2;
}
da=a1-a0;
// rotated center
c=cos(-ang);
s=sin(-ang);
cx=sx*c-sy*s;
cy=sx*s+sy*c;
/*
// (ignore) rotated center and start-end point (RED)
draw_line(x0,y0,x1,y1,1.0,0.0,0.0);
draw_line(x0,y0,cx,cy,1.0,0.0,0.0);
draw_line(x1,y1,cx,cy,1.0,0.0,0.0);
*/
// -----------------------------------------
// [parametric ellipse to BEZIER cubics]
// -----------------------------------------
int i,n;
const int N=16; // cubics per whole ellipse
double t,dt;
double px[N+3],py[N+3]; // all interpolation cubics control points
double w=2.5; // rendered cross size
// arclength 0..2*PI -> cubics count 1..8
n=fabs(double(N)*da)/(2.0*M_PI);
if (n<1) n=1;
if (n>N) n=N;
dt=da/double(n);
// get n+3 points on ellipse (with edges uniformly outside a0,a1)
for (t=a0-dt,i=0;i<n+3;i++,t+=dt)
{
double c,s,xx,yy;
// point on axis aligned ellipse
xx=sx+a*cos(t);
yy=sy+b*sin(t);
// rotate by ang
c=cos(-ang);
s=sin(-ang);
px[i]=xx*c-yy*s;
py[i]=xx*s+yy*c;
// render
draw_line(px[i]-w,py[i]+w,px[i]+w,py[i]-w,0.5,0.2,0.7);
draw_line(px[i]-w,py[i]-w,px[i]+w,py[i]+w,0.5,0.2,0.7);
}
// process cubics
AnsiString txt="";
for (i=0;i<n;i++)
{
const double m=1.0/6.0;
double x0,y0,x1,y1,x2,y2,x3,y3;
// convert to interpolation cubic control points to BEZIER
x0 = px[i+1]; y0 = py[i+1];
x1 = px[i+1]-(px[i+0]-px[i+2])*m; y1 = py[i+1]-(py[i+0]-py[i+2])*m;
x2 = px[i+2]+(px[i+1]-px[i+3])*m; y2 = py[i+2]+(py[i+1]-py[i+3])*m;
x3 = px[i+2]; y3 = py[i+2];
// render
if (!i) txt+=AnsiString().sprintf("M%.6lf %.6lf",x0,y0);
txt+=AnsiString().sprintf(" C%.6lf %.6lf %.6lf %.6lf %.6lf %.6lf",x1,y1,x2,y2,x3,y3);
}
// here save the txt into your SVG path
// (ignore) exit from rendering
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
//---------------------------------------------------------------------------
where svg2scr
converts from SVG units into my GL view coordinates and draw_line
render debug output so you can ignore them. The _acc_zero_ang=0.000001*M_PI/180.0
is just accuracy constant. The unimportant stuff is taged with (ignore)
comment and can be deleted.
Now magenta is the SVG rendered elliptic arc.
The start end point is unrotated by angle (blue line not going to center).
That makes the ellipse axis aligned so scaling its y axis by a/b
will turn it into circle with radius a
(red line not going to center). From its mid point is cast a perpendicular line (which side depends on sweep/larc). Which must hit the circle center along the way somewhere.
The circle center/midpoint/start or end point form a right angle triangle so using Pythagoras I compute the distance from mid point to center. Converted to scale 'l' of the vx,vy
vector.
Once you got the center unrotated circle sx,sy
you can compute edge angles a0,a1
of the arc using atan2
Now scale back to ellipse by scaling y axis by b/a
(blue)
Now rotate the (sx,sy)
center back by ang
getting (cx,cy)
is all you need (red)
Now we can finally obtain any point on the ellipse so we can convert to BEZIER cubics. Here overlay of original ellipse (magenta) and new BEZIER (red) paths.
Beware they do not match precisely here zoom: