如何使手动计算的轨道路径与Qt的椭圆绘制方法一致?
Posted
技术标签:
【中文标题】如何使手动计算的轨道路径与Qt的椭圆绘制方法一致?【英文标题】:How to make manually calculated orbital paths agree with Qt's ellipse drawing method? 【发布时间】:2014-04-07 16:15:17 【问题描述】:我正在尝试绘制在简化的完美圆形轨道上移动的天体。我还绘制了这些物体将采用的预计轨道路径。但是,问题在于对象所采用的实际路径与放大得足够近时的投影不一致。
演示该问题的视频:https://www.youtube.com/watch?v=ALSVfx48zXw
如果缩小,问题是不存在的,因为偏差太小了。偏差的表观大小似乎主要受到圆的可见曲率的影响——注意卫星的路径如何与它们的运动相一致。如果将卫星的投影路径放大以接近直线,它们将具有与行星显示的相同模式的偏差。
坐标计算方法:
double getX (long int time)
return orbit * cos(offset + time * speed);
double getY (long int time)
return orbit * sin(offset + time * speed);
投影轨道图:
ellipse = scene->addEllipse(system.starX-body.orbit,
system.starY-body.orbit,
body.orbit*2,body.orbit*2,greenPen,transBrush);
在它们实际出现的地方画出天体:
ellipse = scene->addEllipse(-body.radius,
-body.radius,
body.radius*2,body.radius*2,blackPen,greenBrush);
ellipse->setFlag(QGraphicsItem::ItemIgnoresTransformations);
ellipse->setPos(system.starX+body.getX(date2days(game.date)),
system.starY+body.getY(date2days(game.date)));
如何解决这个问题,使天体始终在预测曲线上?
编辑1: 我尝试使用建议的算法来绘制我自己的椭圆。我在这里复制了适用于 Qt 的版本:
QPoint get_point(double a, double b, double theta, QPoint center)
QPoint point;
point.setX(center.x() + a * cos(theta));
point.setY(center.y() + b * sin(theta));
return point;
void draw_ellipse(double a, double b, QPoint center, double zoom_factor, QGraphicsScene * scene, QPen pen)
double d_theta = 1.0d / zoom_factor;
double theta = 0.0d;
int count = 2.0d * 3.14159265358979323846 / d_theta;
QPoint p1, p2;
p1 = get_point(a, b, 0.0f, center);
for (int i = 0; i <= count; i++)
theta += d_theta;
p2 = p1;
p1 = get_point(a, b, theta, center);
scene->addLine(p1.x(),p1.y(),p2.x(),p2.y(),pen);
结果并不令人鼓舞:
除了在 zoom_factor 360 上看起来不漂亮之外,该应用程序运行非常缓慢,使用的资源比以前多得多。
EDIT2: 改进后的版本提供了更好的结果,但仍然很慢。代码如下:
QPointF get_point(qreal a, qreal b, qreal theta, QPointF center)
QPointF point;
point.setX(center.x() + a * cos(theta));
point.setY(center.y() + b * sin(theta));
return point;
void draw_ellipse(qreal a, qreal b, QPointF center, qreal zoom_factor, QGraphicsScene * scene, QPen pen)
qreal d_theta = 1.0d / zoom_factor;
qreal theta = 0.0d;
int count = 2.0d * 3.14159265358979323846 / d_theta;
QPointF p1, p2;
p1 = get_point(a, b, 0.0f, center);
for (int i = 0; i <= count; i++)
theta = i * d_theta;
p2 = p1;
p1 = get_point(a, b, theta, center);
scene->addLine(p1.x(),p1.y(),p2.x(),p2.y(),pen);
【问题讨论】:
您不能在draw_ellipse
和get_point
中使用整数坐标。代替QPoint
,使用QPointF
。
您也不需要显式使用double
或float
,只需使用qreal
来匹配Qt 的浮点几何类型所使用的内容。
将theta
添加到d_theta
是一个错误,因为这样的重复添加不会以2*pi 弧度结束。你应该设置theta = i * d_theta
- 这样的乘法足够便宜。
您也应该只绘制可见角度。您应该首先适当地计算开始和结束角度。这将加快速度。
是的。理想情况下,声明从弧到其近似弦(弧高)相对于半径大小的允许偏差,并选择角度增量以保持该偏差。
【参考方案1】:
Qt 似乎不会自动调整绘图精度或“采样分辨率”。
你可以尝试自己画椭圆,画一个循环线。放大时提高绘图的采样分辨率 - 即使采样点彼此靠近。
取椭圆的参数方程
x = a cos (theta), y = b sin (theta)
其中 a 和 b 是椭圆的半长轴和半短轴,并用它对点进行采样:
(伪 C++ 风格的代码)
point get_point(float theta, point center)
return point(center.x + a * cos(theta), center.y + b * sin(theta));
void draw_ellipse(float a, float b, point center, float zoom_factor)
float d_theta = 1.0f / zoom_factor;
float theta = 0.0f;
int count = 2.0f * PI / d_theta;
point p1, p2;
p1 = get_point(0.0f, center);
for (int i = 0; i < count; i++)
theta += d_theta;
p2 = p1;
p1 = get_point(theta, center);
drawline(p1, p2);
对不起,如果代码看起来很随意(我不熟悉 Qt),但你明白了。
【讨论】:
这与无限精度无关,只是与错误选择的离散化步骤有关。它本质上是Qt中的一个错误。您提供的是一种可能的解决方法。 @KubaOber 嗯,是的,感谢您指出这一点——我的意思是他认为 Qt 会自动调整精度,因此线条不易察觉。 我曾尝试使用它,但结果相当糟糕 - 请参阅上面的第一次编辑。 @AbuDhabi 将您的pen
变量作为引用而不是原始对象的副本传递。也许您应该实现一个剪切/剔除例程,以便您只在屏幕内绘制线条。查找 Sunderland-Hodgman 裁剪算法。
@willywonka_dailyblah Qt 已经为您完成了,而且一支笔的副本很便宜,所以不用出汗。【参考方案2】:
假设您传递给addEllipse
的所有参数都具有足够的分辨率,那么问题似乎在于Qt 如何呈现椭圆。椭圆绘制中使用的离散化不依赖于视图的变换矩阵。
当QGraphicsItem
在视图中呈现时,它的paint
方法当然可以访问绘制设备(在本例中为小部件)。它当然可以根据角度确定适当的离散化步骤。即使图形项使用常规的painter 调用进行渲染,painter 也具有相同的信息,并且paint 设备肯定拥有完整的这些信息。因此,我认为 Qt 没有理由去做它所做的事情。我必须追查这段代码,看看为什么它会失败。
唯一的解决方法是让您实现自己的椭圆项目,并在渲染时根据视口大小选择离散化步骤和开始/结束角度。
qreal
是 double
- 所以这不应该是一个问题,除非 Qt 配置了 -qreal float
。
【讨论】:
会不会是我的代码错误而 Qt 的椭圆方法是正确的?很难说他们中的哪一个在这里“跳跃”。 @AbuDhabi 很容易检查。每次更新行星系统的位置时,都会转储准确度分数。计算方法如下:对于圆形轨道,将与太阳的距离除以行星最外层卫星的半径。对于椭圆轨道,倾倒从轨道焦点到行星的距离总和,如上所示。这将使您能够以最外层卫星轨道直径的可理解单位来衡量行星位置的跳跃性。 “像素”的单位对我来说已经足够了,因为在这种情况下,这是我用于身体属性的单位。无论如何 - 根据这个测试,距离总是一样的。然而,它似乎从我放入 body.orbit 字段的值四舍五入(304.679144385027 与 304.679)。 @AbuDhabi 像素是视图的属性,而不是场景的属性,所以我不太清楚当您查看场景及其项目时如何获得任何特定像素。 我给 body.orbit 赋值。此值是与太阳的距离,根据视图开始时的默认缩放级别以像素为单位。我不确定为什么这会令人困惑。此外,我尝试使用 willywonka_dailyblah 的算法,结果并不令人鼓舞。知道为什么吗?以上是关于如何使手动计算的轨道路径与Qt的椭圆绘制方法一致?的主要内容,如果未能解决你的问题,请参考以下文章
Matlab&Mathematica对三维空间上的点进行椭圆拟合
如何使用canvas绘制椭圆,扩展非chrome浏览器中的ellipse方法