Как сделать так, чтобы рассчитанные вручную орбитальные пути согласовывались с методом рисования эллипса Qt?

Я пытаюсь нарисовать небесные тела, движущиеся по упрощенным, идеально круговым орбитам. Я также рисую спроецированные орбитальные пути, по которым пойдут эти объекты. Однако проблема в том, что реальный путь, по которому идут объекты, не согласуется с проекцией при достаточном увеличении.

Видео, демонстрирующее проблему: 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)));

Как это исправить, чтобы небесные тела всегда находились на предсказанной кривой?

EDIT1:
Я попытался использовать предложенный алгоритм для рисования собственного эллипса. Версия, адаптированная для использования с 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);
    }
}

person Abu Dhabi    schedule 07.04.2014    source источник
comment
Вы не можете использовать целочисленные координаты в draw_ellipse и get_point. Вместо QPoint используйте QPointF.   -  person Kuba hasn't forgotten Monica    schedule 08.04.2014
comment
Вам также не нужно явно использовать double или float, просто используйте qreal, чтобы соответствовать тому, что используется типами геометрии Qt с плавающей запятой.   -  person Kuba hasn't forgotten Monica    schedule 08.04.2014
comment
Добавление theta к d_theta является ошибкой, поскольку такое многократное добавление не приведет к 2*pi радианам. Вы должны установить theta = i * d_theta - такое умножение достаточно дешево.   -  person Kuba hasn't forgotten Monica    schedule 08.04.2014
comment
Вы также должны рисовать только видимые углы. Сначала вы должны правильно рассчитать начальный и конечный угол. Это ускорит дело.   -  person Kuba hasn't forgotten Monica    schedule 08.04.2014
comment
Это может быть проблемой, потому что что, если все это будет настолько уменьшено, что вы сможете все увидеть? Не сделает ли это его таким медленным? Однако первые три комментария были очень полезными и, безусловно, улучшили внешний вид орбит!   -  person Abu Dhabi    schedule 08.04.2014
comment
Нет, это не сделает его медленным, так как при уменьшении масштаба вам нужно всего несколько сегментов (64 — разумное число).   -  person Kuba hasn't forgotten Monica    schedule 08.04.2014
comment
Итак, мне нужно изменить количество сегментов в соответствии с текущим масштабом?   -  person Abu Dhabi    schedule 08.04.2014
comment
да. В идеале объявите допустимое отклонение от дуги до ее аппроксимирующей хорды (высоты дуги) относительно размера радиуса и выберите приращение угла, чтобы сохранить это отклонение.   -  person Kuba hasn't forgotten Monica    schedule 08.04.2014


Ответы (2)


Похоже, что Qt не регулирует автоматически точность рисования или «разрешение выборки».

Вы можете попробовать нарисовать эллипс самостоятельно, нарисовав петлю из линий. Увеличивайте разрешение образца чертежа при увеличении масштаба, т.е. делайте точки выборки ближе друг к другу.

Возьмем параметрическое уравнение эллипса

x = a cos (тета), y = b sin (тета)

где 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), но суть вы поняли.

person Community    schedule 07.04.2014
comment
Это не имеет ничего общего с бесконечной точностью, просто с неправильно выбранным шагом дискретизации. По сути, это ошибка в Qt. То, что вы предлагаете, является возможным обходным путем. - person Kuba hasn't forgotten Monica; 07.04.2014
comment
@KubaOber, да, спасибо, что указали на это - я имел в виду, что он думал, что Qt автоматически регулирует точность, поэтому линии становятся незаметными. - person ; 07.04.2014
comment
Я пытался использовать это с довольно плачевными результатами - см. мое первое редактирование выше. - person Abu Dhabi; 07.04.2014
comment
@AbuDhabi передает вашу переменную pen как ссылку, а не как копию исходного объекта. Возможно, вам следует реализовать процедуру отсечения/отбраковки, чтобы вы рисовали только линии внутри экрана. Посмотрите алгоритм отсечения Сандерленда-Ходжмана. - person ; 08.04.2014
comment
@willywonka_dailyblah Qt уже делает это за вас, а копия ручки дешевая, так что не парьтесь. - person Kuba hasn't forgotten Monica; 08.04.2014
comment
@KubaOber хм, ладно ... возможно, есть и другие способы ускорить это, например, использование таблиц поиска sin / cos и квадратичная / кубическая интерполяция между точками ... но, вероятно, слишком много сложностей для его цели ... хе-хе ... - person ; 08.04.2014
comment
Таблицы поиска sin/cos здесь не очень полезны, равно как и интерполяция. Важно нарисовать только небольшое количество видимых сегментов. Это то, что Qt делает внутри. - person Kuba hasn't forgotten Monica; 08.04.2014
comment
@KubaOber, но если Qt сделает свою работу как можно лучше, то единственным возможным узким местом будут вычисления с плавающей запятой, такие как sin/cos - person ; 08.04.2014
comment
Qt внутренне ничего не вычисляет для невидимых сегментов эллипса. Когда вы повторно реализуете рендеринг эллипса, чтобы получить ту же производительность, вам нужно выполнить аналогичные оптимизации :) - person Kuba hasn't forgotten Monica; 08.04.2014

Предполагая, что все параметры, которые вы передаете addEllipse, имеют достаточное разрешение, проблема, похоже, связана с тем, как Qt отображает эллипсы. Дискретизация, используемая при рисовании эллипса, не зависит от матрицы преобразования вида.

Когда QGraphicsItem визуализируется в представлении, его метод paint, безусловно, имеет доступ к устройству рисования (в данном случае: к виджету). Это, безусловно, могло бы определить правильный шаг дискретизации с точки зрения угла. Даже если бы графический элемент отображался с использованием обычных вызовов рисовальщика, рисовальщик имеет ту же информацию, а устройство рисования наверняка имеет эту информацию в полном объеме. Таким образом, я думаю, у Qt нет причин делать то, что он делает. Мне нужно проследить этот код и понять, почему он так плохо работает.

Единственное исправление заключается в том, чтобы вы реализовали свой собственный элемент эллипса и выбрали шаг дискретизации и начальный/конечный углы в соответствии с размером области просмотра во время рендеринга.

qreal - это double, так что это не должно быть проблемой, если Qt не настроен с -qreal float.

person Kuba hasn't forgotten Monica    schedule 07.04.2014
comment
С другой стороны, может ли быть так, что мой код неверен, а метод эллипса Qt верен? Трудно сказать, кто из них здесь нервничает. - person Abu Dhabi; 07.04.2014
comment
@AbuDhabi Это легко проверить. Каждый раз, когда вы обновляете местоположение планетарной системы, сбрасывайте показатель точности. Вот как это рассчитать: Для круговых орбит рассчитайте расстояние от солнца, деленное на радиус самой удаленной луны планеты. Для эллиптических орбит рассчитайте сумму расстояний от орбитальных фокусов до планеты, разделенную, как указано выше. Это позволит вам оценить изменчивость положения планеты в понятных единицах диаметра орбиты самой удаленной луны. - person Kuba hasn't forgotten Monica; 07.04.2014
comment
Единицы пикселей мне вполне подходят, так как в данном случае я использую их для атрибутов тела. Во всяком случае, согласно этому тесту, расстояние всегда одинаково. Однако значение, которое я ввел в поле body.orbit, кажется округленным (304,679144385027 против 304,679). - person Abu Dhabi; 07.04.2014
comment
@AbuDhabi Пиксели - это атрибуты вида, а не сцены, поэтому я не совсем понимаю, как можно получить какие-то конкретные пиксели, когда вы смотрите на сцену и ее элементы. - person Kuba hasn't forgotten Monica; 07.04.2014
comment
Я присваиваю body.orbit значение. Это значение представляет собой расстояние от солнца в пикселях в соответствии с уровнем масштабирования по умолчанию, с которого начинается просмотр. Я не уверен, почему это сбивает с толку. Кроме того, я попытался использовать алгоритм willywonka_dailyblah, и результаты не обнадеживают. Есть идеи, почему? - person Abu Dhabi; 07.04.2014