存在 start/end 个角度问题的 OpenGL 椭圆
OpenGL Ellipse with start/end angle issues
我在使用 OpenGL(准确地说是 sharpgl,因为它与 WPF 配合得很好)绘制椭圆对象时遇到问题。
现在我正在使用这段代码(角度以度为单位):
gl.Begin(OpenGL.GL_LINE_STRIP);
gl.Color(colorR, colorG, colorB, alfa);
for (double i = angleStart; i <= angleEnd; ++i)
{
double angleCurrent = Math.PI * i / 180;
double dx = radiusX * Math.Cos(angleCurrent);
double dy = radiusY * Math.Sin(angleCurrent);
gl.Vertex(dx + ellipseMiddleCoordX, dy + ellipseMiddleCoordY, 0);
}
gl.End();
绘制圆(即 radiusX = radiusY
)和角度为 0、90、180、270、360(radiusX != radiusY
)的椭圆时效果 100%。
但是当 radiusX != radiusY
例如 angleStart = 30; angleEnd = 70
.
时,它并不能很好地完成工作
它在上面的示例中做了什么,它首先绘制一个圆,应用角度,然后重新计算 X/Y 半径。这给出了不正确的结果,因为我期望是这样的:
其中红色 = 预期行为,黄色 = 上面的代码,灰色 = 额外的角度线
我完全不知道我应该如何解决这个问题。
P.S.
是的 - 我知道 gl.Begin/gl.End
是过时的函数,但我仍然无法掌握 OGL 的完整概念,而且我的大部分程序只是数学。
那是因为椭圆参量角不是几何角,因为它被每个轴上的不同比例(半径)扭曲了。请参阅这些相关的 QA 以获取一些灵感:
- I need an equation for equal movement along an ellipse
- Algorithm for shape calculation (Ellipse)
解决这个问题的方法是近似失真(但如果偏心率变化很大就会有问题)或搜索正确的角度(简单但缓慢)或分析计算(涉及一些数学但速度很快)。
对于搜索,您可以使用 二进制搜索 为此,请记住几何角度在某些象限上将大于参数角度,而在其他象限上则小于。
这里是小的 C++/OpenGL 示例(简单且未优化以使其尽可能简单):
//---------------------------------------------------------------------------
const float pi2=2.0*M_PI;
const float deg=M_PI/180.0;
//---------------------------------------------------------------------------
float ellipse_angle(float rx,float ry,float ang) // geometrical angle [rad] -> ellipse parametric angle [rad]
{
float da,ea,ga;
ang=fmod(ang,pi2); // normalize to <0,pi2>
ea=ang; // start elliptic angle
ga=atan2(ry*sin(ea),rx*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga<=ang) // ea >= ang
{
da=90.0*deg-fmod(ang,90.0*deg); // range to search
da*=0.5;
for (ea=ang;da>1e-10;da*=0.5) // binary search
{
ea+=da; // test next elliptic angle
ga=atan2(ry*sin(ea),rx*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga>ang) ea-=da; // restore original angle if too big
}
}
else{ // ea <= ang
da=fmod(ang,90.0*deg); // range to search
da*=0.5;
for (ea=ang;da>1e-10;da*=0.5) // binary search
{
ea-=da; // test next elliptic angle
ga=atan2(ry*sin(ea),rx*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga<ang) ea+=da; // restore original angle if too small
}
}
return ea;
}
//---------------------------------------------------------------------------
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glScalef(1.0,float(xs)/float(ys),1.0);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
int i,n=100;
float rx=0.75,ry=0.5,a0=25.0*deg,a1=115.0*deg;
float x,y,a,da,ea0,ea1,dd;
// test whole circumference
for (dd=10.0*deg,a0=0.0,a1=a0+0.75*dd;a1<pi2;a0+=dd,a1+=dd)
{
// get parametric angles
ea0=ellipse_angle(rx,ry,a0);
ea1=ellipse_angle(rx,ry,a1);
// render elliptic arc
glBegin(GL_LINE_STRIP);
glColor3f(0.3,0.3,1.0);
for (a=ea0,da=(ea1-ea0)/float(n-1),i=0;i<n;i++,a+=da)
{
x=rx*cos(a);
y=ry*sin(a);
glVertex2f(x,y);
}
glEnd();
// render geometric axises (for visual check)
glBegin(GL_LINE_STRIP);
glColor3f(0.3,0.3,0.0);
glVertex2f(2.0*cos(a0),2.0*sin(a0));
glVertex2f(0.0,0.0);
glVertex2f(2.0*cos(a1),2.0*sin(a1));
glEnd();
}
glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
其中 xs,ys
是我的 OpenGL 的分辨率 window 并且 M_PI=3.1415926535897932384626433832795
注意所有角度都以弧度为单位。
此处预览:
如您所见,角度在整个圆周上都匹配(蓝色圆弧与黄线对齐)...
函数float ellipse_angle(float rx,float ry,float ang)
将return椭圆参数角对应于轴上的几何角ang
与半轴对齐的椭圆rx,ry
。
有很多东西可以提高速度,比如使用 rx/ry
和只缩放一个轴,预先计算角度常数等。
[Edit1] 稍微好一点的代码
使用宽高比和渲染也进行了更改,以便更好地进行视觉检查
//---------------------------------------------------------------------------
const float pi2=2.0*M_PI;
const float deg=M_PI/180.0;
//---------------------------------------------------------------------------
float ellipse_angle(float rx,float ry,float ang) // geometrical angle [rad] -> ellipse parametric angle [rad]
{
float da,ea,ga,aspect;
aspect=rx/ry; // aspect ratio for speed up
ang=fmod(ang,pi2); // normalize to <0,pi2>
ea=ang; // start elliptic angle
ga=atan2(sin(ea),aspect*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga<=ang) // ea >= ang
{
da=90.0*deg-fmod(ang,90.0*deg); // range to search
da*=0.5;
for (ea=ang;da>1e-10;da*=0.5) // binary search
{
ea+=da; // test next elliptic angle
ga=atan2(sin(ea),aspect*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga>ang) ea-=da; // restore original angle if too big
}
}
else{ // ea <= ang
da=fmod(ang,90.0*deg); // range to search
da*=0.5;
for (ea=ang;da>1e-10;da*=0.5) // binary search
{
ea-=da; // test next elliptic angle
ga=atan2(sin(ea),aspect*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga<ang) ea+=da; // restore original angle if too small
}
}
return ea;
}
//---------------------------------------------------------------------------
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glScalef(1.0,float(xs)/float(ys),1.0);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
int i,n=100;
float rx=0.95,ry=0.35;
float x,y,a,dd,da,a0,a1,ea0,ea1;
// test whole circumference
for (dd=10.0*deg,a0=0.0,a1=a0+0.75*dd;a1<pi2;a0+=dd,a1+=dd)
{
// render geometric angle pies (for visual check)
glBegin(GL_TRIANGLES);
glColor3f(0.1,0.1,0.1);
glVertex2f(cos(a0),sin(a0));
glVertex2f(0.0,0.0);
glVertex2f(cos(a1),sin(a1));
glEnd();
// get parametric angles (rx,ry)
ea0=ellipse_angle(rx,ry,a0);
ea1=ellipse_angle(rx,ry,a1);
// render elliptic arc (rx,ry)
glBegin(GL_LINE_STRIP);
glColor3f(0.3,0.7,1.0);
for (a=ea0,da=(ea1-ea0)/float(n-1),i=0;i<n;i++,a+=da)
{
x=rx*cos(a);
y=ry*sin(a);
glVertex2f(x,y);
}
glEnd();
// get parametric angles (ry,rx)
ea0=ellipse_angle(ry,rx,a0);
ea1=ellipse_angle(ry,rx,a1);
// render elliptic arc (ry,rx)
glBegin(GL_LINE_STRIP);
glColor3f(1.0,0.7,0.3);
for (a=ea0,da=(ea1-ea0)/float(n-1),i=0;i<n;i++,a+=da)
{
x=ry*cos(a);
y=rx*sin(a);
glVertex2f(x,y);
}
glEnd();
}
glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
仍有改进的余地,例如完全摆脱 atan2
并使用叉积代替,就像我在这里所做的那样:
[Edit2]快速解析O(1)
解决方案
我简单地使用了参数线方程:
x(t) = t*cos(ang)
y(t) = t*sin(ang)
和隐式椭圆方程:
(x/rx)^2 + (y/ry)^2 = 1
计算交点(求解 t
并从中计算 x,y
),然后对其应用逆畸变以从椭圆映射到圆形(通过缩放 y*=rx/ry
)和最后用 atan2(y,x)
.
计算它的角度
此处C++代码:
//---------------------------------------------------------------------------
const float pi2=2.0*M_PI;
const float deg=M_PI/180.0;
//---------------------------------------------------------------------------
float ellipse_angle(float rx,float ry,float ang) // geometrical angle [rad] -> ellipse parametric angle [rad]
{
float x,y,t,aa,bb,ea;
x=cos(ang); // axis direction at angle ang
y=sin(ang);
aa=rx*rx; bb=ry*ry; // intersection between ellipse and axis
t=aa*bb/((x*x*bb)+(y*y*aa));
x*=t; y*=t;
y*=rx/ry; // convert to circle
ea=atan2(y,x); // compute elliptic angle
if (ea<0.0) ea+=pi2; // normalize to <0,pi2>
return ea;
}
//---------------------------------------------------------------------------
我在使用 OpenGL(准确地说是 sharpgl,因为它与 WPF 配合得很好)绘制椭圆对象时遇到问题。
现在我正在使用这段代码(角度以度为单位):
gl.Begin(OpenGL.GL_LINE_STRIP);
gl.Color(colorR, colorG, colorB, alfa);
for (double i = angleStart; i <= angleEnd; ++i)
{
double angleCurrent = Math.PI * i / 180;
double dx = radiusX * Math.Cos(angleCurrent);
double dy = radiusY * Math.Sin(angleCurrent);
gl.Vertex(dx + ellipseMiddleCoordX, dy + ellipseMiddleCoordY, 0);
}
gl.End();
绘制圆(即 radiusX = radiusY
)和角度为 0、90、180、270、360(radiusX != radiusY
)的椭圆时效果 100%。
但是当 radiusX != radiusY
例如 angleStart = 30; angleEnd = 70
.
它在上面的示例中做了什么,它首先绘制一个圆,应用角度,然后重新计算 X/Y 半径。这给出了不正确的结果,因为我期望是这样的:
其中红色 = 预期行为,黄色 = 上面的代码,灰色 = 额外的角度线
我完全不知道我应该如何解决这个问题。
P.S.
是的 - 我知道 gl.Begin/gl.End
是过时的函数,但我仍然无法掌握 OGL 的完整概念,而且我的大部分程序只是数学。
那是因为椭圆参量角不是几何角,因为它被每个轴上的不同比例(半径)扭曲了。请参阅这些相关的 QA 以获取一些灵感:
- I need an equation for equal movement along an ellipse
- Algorithm for shape calculation (Ellipse)
解决这个问题的方法是近似失真(但如果偏心率变化很大就会有问题)或搜索正确的角度(简单但缓慢)或分析计算(涉及一些数学但速度很快)。
对于搜索,您可以使用 二进制搜索 为此,请记住几何角度在某些象限上将大于参数角度,而在其他象限上则小于。
这里是小的 C++/OpenGL 示例(简单且未优化以使其尽可能简单):
//---------------------------------------------------------------------------
const float pi2=2.0*M_PI;
const float deg=M_PI/180.0;
//---------------------------------------------------------------------------
float ellipse_angle(float rx,float ry,float ang) // geometrical angle [rad] -> ellipse parametric angle [rad]
{
float da,ea,ga;
ang=fmod(ang,pi2); // normalize to <0,pi2>
ea=ang; // start elliptic angle
ga=atan2(ry*sin(ea),rx*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga<=ang) // ea >= ang
{
da=90.0*deg-fmod(ang,90.0*deg); // range to search
da*=0.5;
for (ea=ang;da>1e-10;da*=0.5) // binary search
{
ea+=da; // test next elliptic angle
ga=atan2(ry*sin(ea),rx*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga>ang) ea-=da; // restore original angle if too big
}
}
else{ // ea <= ang
da=fmod(ang,90.0*deg); // range to search
da*=0.5;
for (ea=ang;da>1e-10;da*=0.5) // binary search
{
ea-=da; // test next elliptic angle
ga=atan2(ry*sin(ea),rx*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga<ang) ea+=da; // restore original angle if too small
}
}
return ea;
}
//---------------------------------------------------------------------------
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glScalef(1.0,float(xs)/float(ys),1.0);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
int i,n=100;
float rx=0.75,ry=0.5,a0=25.0*deg,a1=115.0*deg;
float x,y,a,da,ea0,ea1,dd;
// test whole circumference
for (dd=10.0*deg,a0=0.0,a1=a0+0.75*dd;a1<pi2;a0+=dd,a1+=dd)
{
// get parametric angles
ea0=ellipse_angle(rx,ry,a0);
ea1=ellipse_angle(rx,ry,a1);
// render elliptic arc
glBegin(GL_LINE_STRIP);
glColor3f(0.3,0.3,1.0);
for (a=ea0,da=(ea1-ea0)/float(n-1),i=0;i<n;i++,a+=da)
{
x=rx*cos(a);
y=ry*sin(a);
glVertex2f(x,y);
}
glEnd();
// render geometric axises (for visual check)
glBegin(GL_LINE_STRIP);
glColor3f(0.3,0.3,0.0);
glVertex2f(2.0*cos(a0),2.0*sin(a0));
glVertex2f(0.0,0.0);
glVertex2f(2.0*cos(a1),2.0*sin(a1));
glEnd();
}
glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
其中 xs,ys
是我的 OpenGL 的分辨率 window 并且 M_PI=3.1415926535897932384626433832795
注意所有角度都以弧度为单位。
此处预览:
如您所见,角度在整个圆周上都匹配(蓝色圆弧与黄线对齐)...
函数float ellipse_angle(float rx,float ry,float ang)
将return椭圆参数角对应于轴上的几何角ang
与半轴对齐的椭圆rx,ry
。
有很多东西可以提高速度,比如使用 rx/ry
和只缩放一个轴,预先计算角度常数等。
[Edit1] 稍微好一点的代码
使用宽高比和渲染也进行了更改,以便更好地进行视觉检查
//---------------------------------------------------------------------------
const float pi2=2.0*M_PI;
const float deg=M_PI/180.0;
//---------------------------------------------------------------------------
float ellipse_angle(float rx,float ry,float ang) // geometrical angle [rad] -> ellipse parametric angle [rad]
{
float da,ea,ga,aspect;
aspect=rx/ry; // aspect ratio for speed up
ang=fmod(ang,pi2); // normalize to <0,pi2>
ea=ang; // start elliptic angle
ga=atan2(sin(ea),aspect*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga<=ang) // ea >= ang
{
da=90.0*deg-fmod(ang,90.0*deg); // range to search
da*=0.5;
for (ea=ang;da>1e-10;da*=0.5) // binary search
{
ea+=da; // test next elliptic angle
ga=atan2(sin(ea),aspect*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga>ang) ea-=da; // restore original angle if too big
}
}
else{ // ea <= ang
da=fmod(ang,90.0*deg); // range to search
da*=0.5;
for (ea=ang;da>1e-10;da*=0.5) // binary search
{
ea-=da; // test next elliptic angle
ga=atan2(sin(ea),aspect*cos(ea)); // compute geometrical angle from it
if (ga<0.0) ga+=pi2; // normalize to <0,pi2>
if (ga<ang) ea+=da; // restore original angle if too small
}
}
return ea;
}
//---------------------------------------------------------------------------
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glScalef(1.0,float(xs)/float(ys),1.0);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
int i,n=100;
float rx=0.95,ry=0.35;
float x,y,a,dd,da,a0,a1,ea0,ea1;
// test whole circumference
for (dd=10.0*deg,a0=0.0,a1=a0+0.75*dd;a1<pi2;a0+=dd,a1+=dd)
{
// render geometric angle pies (for visual check)
glBegin(GL_TRIANGLES);
glColor3f(0.1,0.1,0.1);
glVertex2f(cos(a0),sin(a0));
glVertex2f(0.0,0.0);
glVertex2f(cos(a1),sin(a1));
glEnd();
// get parametric angles (rx,ry)
ea0=ellipse_angle(rx,ry,a0);
ea1=ellipse_angle(rx,ry,a1);
// render elliptic arc (rx,ry)
glBegin(GL_LINE_STRIP);
glColor3f(0.3,0.7,1.0);
for (a=ea0,da=(ea1-ea0)/float(n-1),i=0;i<n;i++,a+=da)
{
x=rx*cos(a);
y=ry*sin(a);
glVertex2f(x,y);
}
glEnd();
// get parametric angles (ry,rx)
ea0=ellipse_angle(ry,rx,a0);
ea1=ellipse_angle(ry,rx,a1);
// render elliptic arc (ry,rx)
glBegin(GL_LINE_STRIP);
glColor3f(1.0,0.7,0.3);
for (a=ea0,da=(ea1-ea0)/float(n-1),i=0;i<n;i++,a+=da)
{
x=ry*cos(a);
y=rx*sin(a);
glVertex2f(x,y);
}
glEnd();
}
glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
仍有改进的余地,例如完全摆脱 atan2
并使用叉积代替,就像我在这里所做的那样:
[Edit2]快速解析O(1)
解决方案
我简单地使用了参数线方程:
x(t) = t*cos(ang)
y(t) = t*sin(ang)
和隐式椭圆方程:
(x/rx)^2 + (y/ry)^2 = 1
计算交点(求解 t
并从中计算 x,y
),然后对其应用逆畸变以从椭圆映射到圆形(通过缩放 y*=rx/ry
)和最后用 atan2(y,x)
.
此处C++代码:
//---------------------------------------------------------------------------
const float pi2=2.0*M_PI;
const float deg=M_PI/180.0;
//---------------------------------------------------------------------------
float ellipse_angle(float rx,float ry,float ang) // geometrical angle [rad] -> ellipse parametric angle [rad]
{
float x,y,t,aa,bb,ea;
x=cos(ang); // axis direction at angle ang
y=sin(ang);
aa=rx*rx; bb=ry*ry; // intersection between ellipse and axis
t=aa*bb/((x*x*bb)+(y*y*aa));
x*=t; y*=t;
y*=rx/ry; // convert to circle
ea=atan2(y,x); // compute elliptic angle
if (ea<0.0) ea+=pi2; // normalize to <0,pi2>
return ea;
}
//---------------------------------------------------------------------------