SVG эллиптические дуги очень сложно, и мне потребовалось некоторое время, чтобы осуществить это (даже после SVG спецификаций). Я в конечном итоге с чем-то вроде этого в C++:
//---------------------------------------------------------------------------
class svg_usek // virtual class for svg_line types
{
public:
int pat; // svg::pat[] index
virtual void reset(){};
virtual double getl (double mx,double my){ return 1.0; };
virtual double getdt(double dl,double mx,double my){ return 0.1; };
virtual void getpnt(double &x,double &y,double t){};
virtual void compute(){};
virtual void getcfg(AnsiString &nam,AnsiString &dtp,AnsiString &val){};
virtual void setcfg(AnsiString &nam,AnsiString &dtp,AnsiString &val,int &an,int &ad,int &av){};
};
//---------------------------------------------------------------------------
class svg_ela:public svg_usek // sweep = 0 arc goes from line p0->p1 CW
{ // sweep = 1 arc goes from line p0->p1 CCW
public: // larc is unused if |da|=PI
double x0,y0,x1,y1,a,b,alfa; int sweep,larc;
double sx,sy,a0,a1,da,ang; // sx,sy rotated center by ang
double cx,cy; // real center
void reset() { x0=0; y0=0; x1=0; y1=0; a=0; b=0; alfa=0; sweep=false; larc=false; compute(); }
double getl (double mx,double my);
// double getdt(double dl,double mx,double my);
double getdt(double dl,double mx,double my) { int n; double dt; dt=divide(dl,getl(mx,my)); n=floor(divide(1.0,dt)); if (n<1) n=1; return divide(1.0,n); }
void getpnt(double &x,double &y,double t);
void compute();
void getcfg(AnsiString &nam,AnsiString &dtp,AnsiString &val);
void setcfg(AnsiString &nam,AnsiString &dtp,AnsiString &val,int &an,int &ad,int &av);
svg_ela() {}
svg_ela(svg_ela& a) { *this=a; }
~svg_ela() {}
svg_ela* operator = (const svg_ela *a) { *this=*a; return this; }
//svg_ela* operator = (const svg_ela &a) { ...copy... return this; }
};
//---------------------------------------------------------------------------
void svg_ela::getpnt(double &x,double &y,double t)
{
double c,s,xx,yy;
t=a0+(da*t);
xx=sx+a*cos(t);
yy=sy+b*sin(t);
c=cos(-ang);
s=sin(-ang);
x=xx*c-yy*s;
y=xx*s+yy*c;
}
//---------------------------------------------------------------------------
void svg_ela::compute()
{
double ax,ay,bx,by; // body
double vx,vy,l,db;
int _sweep;
double c,s,e;
ang=pi-alfa;
_sweep=sweep;
if (larc) _sweep=!_sweep;
e=divide(a,b);
c=cos(ang);
s=sin(ang);
ax=x0*c-y0*s;
ay=x0*s+y0*c;
bx=x1*c-y1*s;
by=x1*s+y1*c;
ay*=e; // transform to circle
by*=e;
sx=0.5*(ax+bx); // mid point between A,B
sy=0.5*(ay+by);
vx=(ay-by);
vy=(bx-ax);
l=divide(a*a,(vx*vx)+(vy*vy))-0.25;
if (l<0) l=0;
l=sqrt(l);
vx*=l;
vy*=l;
if (_sweep)
{
sx+=vx;
sy+=vy;
}
else{
sx-=vx;
sy-=vy;
}
a0=atanxy(ax-sx,ay-sy);
a1=atanxy(bx-sx,by-sy);
// ay=divide(ay,e);
// by=divide(by,e);
sy=divide(sy,e);
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;
// realny stred
c=cos(+ang);
s=sin(+ang);
cx=sx*c-sy*s;
cy=sx*s+sy*c;
}
//---------------------------------------------------------------------------
atanxy(x,y)
такая же, как atan2(y,x)
. Вы можете игнорировать класс svg_usek
. Использование svg_ela
просто первый корм параметры SVG к нему:
x0,y0
является начальной точкой (от предыдущего <path>
элемента)
x1,y1
является конечной точкой (x0+dx,y0+dy
)
a,b
являются как ваш rx,ry
alfa
угол поворота [rad]
, поэтому вам необходимо преобразовать из градусов ...
sweep,larc
являются вашими.
И затем вызывается svg_ela::compute();
, который будет вычислять все переменные, необходимые для интерполяции. Когда эта инициализация выполняется, чтобы получить любую точку от дуги, просто вызовите svg_ela::getpnt(x,y,t);
, где x,y
- это возвращаемая координата, а t=<0,1>
- входной параметр. Все другие методы не важны для вас. Для того, чтобы сделать вашу ARC просто сделать это:
svg_ela arc; // your initialized arc here
int e; double x,y,t;
arc.getpnt(x,y,0.0);
Canvas->MoveTo(x,y);
for (e=1,t=0.0;e;t+=0.02)
{
if (t>=1.0) { t=1.0; e=0; }
arc.getpnt(x,y,t);
Canvas->LineTo(x,y);
}
Не забывайте, что SVG<g>
и <path>
может иметь преобразование матрицы, поэтому вы должны применять их после каждого svg_ela::getpnt(x,y,t)
вызова.
Если вас интересует, как материал работает compute()
просто:
- вращает пространство так Эллипс полуприцепы осях выровнены по оси.
- шкала пространство поэтому эллипс становится круг.
вычислительный центр точка окружности
центр лежит на линии, перпендикулярной к линии (x0,y0),(x1,y1)
, а также лежит на его середине.Расстояние вычисляется Pytagoras и направление от sweep
и larc
.
масштаб обратно к эллипсу
- повернуть назад
Теперь мы имеем реальное положение центра, так и вычислить реальный конечной углы по отношению к нему. Теперь для каждой точки на эллипсе достаточно вычислить ее стандартным параметрическим уравнением эллипса и повернуть в нужное положение, что и делает getpnt(x,y,t)
.
Надеюсь, это поможет.
https://www.w3.org/TR/SVG/implnote.html#ArcConversionCenterToEndpoint –