Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ Qt Система рисования. Печать _ Оптимизация отрисовки QGraphicsEllipseItem

Автор: Petr0vi4 14.7.2009, 13:27

Пишу программу для симуляции движения частиц идеального газа в сосуде. У меня есть 4 стенки сосуда (QGraphicsRectItem) и 100 QGraphicsEllipsItem шариков размером 5*5 (типа молекулы).
Каждой молекуле на этапе создание присваивается вектор в виде координат 2х точек, а перемещение происходит по событию таймера вдоль этого вектора. Мне необходимо обработать столкновение между молекулами и стенами. Я все это реализовал, но дело в том, что все это жутко тормозит :(

Код обнаружения столкновений:

for(int i=0;i<Item->collidingItems().count();i++)
    {
        if(Item->collidingItems().at(i)->type() == 4) // столкновение с другой молекулой
        {
            // записываем новый вектор движения в буффер
        }
        else
        {
            if(Item->collidingItems().at(i)->type() == 3) // столкновение со стеной
            {
                // записываем новый вектор движения в буффер
            }
        }
    }

Таймер срабатывает с интервалом в 10 мс.
AllObjects - массив элементов класса Control.
Control - класс наследник QObject, в нем есть QGraphicsEllipsItem* Item (сама молекула) + переменные типа qreal для вектора движения.
Код в событии таймера:
for(int i=0;i<ObjNum;i++) // цикл по всем объектам
    AllObjects[i]->BallCollision(); //обрабатываем столкновения

// применяем координаты и двигаем шары
for(int i=0;i<ObjNum;i++)
{
    if(AllObjects[i]->Changed) // проверяем изменились ли координаты
    {
       AllObjects[i]->ReadFromBuffer(); // пишем из буфера в "рабочий" вектор
    }
    AllObjects[i]->MoveBall();//двигаем
}

Получается за 1 событие таймера я 2 раза выполняю цикл по всем объектам.
Класс для молекулы выбран с рассчетом на то, что в нем уже прописана обработка столкновений.
При количестве шариков 20-30 все работает идеально, при 50-100 уже тормозит :(
Можно ли как то ускорить обработку? Может кто-нибудь предложит какое-то альтернативное решение.

Автор: Litkevich Yuriy 14.7.2009, 14:00

Цитата(Petr0vi4 @ 14.7.2009, 17:27) *
Таймер срабатывает с интервалом в 10 мс.
зачем так часто?

Автор: Petr0vi4 14.7.2009, 14:30

Цитата(Litkevich Yuriy @ 14.7.2009, 22:00) *
зачем так часто?

ну надо чтобы движение плавное было...

Автор: SABROG 14.7.2009, 14:53

Цитата(Petr0vi4 @ 14.7.2009, 15:30) *
Цитата(Litkevich Yuriy @ 14.7.2009, 22:00) *
зачем так часто?

ну надо чтобы движение плавное было...

Это плавное движение равно 1000/10=100 fps, твой глаз не успеет заметить разницы. Выбирай 25-30 fps (50 мс).

Автор: Petr0vi4 14.7.2009, 15:17

На 50 мс слишком медленно движутся почему-то :( Увеличиваю скорость и все равно не то.
Вот код движения, может что не так.
Module(int) - это я беру модуль числа.
Вектор движения это 2 точки: (x1;y1) и (x2;y2).
dx = х2-х1, dy = у2-у1, speed - скорость движения.
Одну координату я увеличиваю на величину скорости, вторую высчитываю по формуле прямая через 2 точки.

if(Module(dx) < Module(dy)) // смотрим по какой координате делать приращение (для равномерного движения)
    {
        if(y2!=y1)
        {
            qreal y = Item->pos().y();
            if(y1<y2) // смотрим в какую сторону двигать
                y = y+speed;
            else
                y = y-speed;
            Item->setPos((((y-y1)*(x2-x1)/(y2-y1))+x1),y);
        }
    }
    else
    {
        if(x2!=x1)
        {
            qreal x = Item->pos().x();
            if(x1<x2) // смотрим в какую сторону двигать
                x = x+speed;
            else
                x = x-speed;
            Item->setPos(x,(((x-x1)*(y2-y1)/(x2-x1))+y1));
        }
    }

Автор: Litkevich Yuriy 14.7.2009, 17:24

Ну, что я могу сказать?
Взял пример examples\graphicsview\collidingmice

Задал кол-во мышей = 40 (вместо 7 исходных)
Всё работает с тойже с коростью, что и раньше. НО стоило развернуть окно на весь экран, как они, мыши, стали ползать как улитки.

Т.е. дело в видимой части представления, как я понимаю.

А вот при кол-ве 100 шт. они уже и при нормальном размере окна еле ползают.

Автор: SABROG 14.7.2009, 17:27

Возможно есть смысл сделать кэш. Например просчитать коллизию на несколько секунд вперед и только менять координаты объектов.

Автор: Kagami 14.7.2009, 17:30

Все дело в процедуре обнаружения столкновений. Это и есть узкое место. Для молекул ее можно упростить. Так как они являются идеальными шарами то столкновения между ними можно определять следующим образом: если расстояние между центрами меньше или равно сумме их радиусов, то они сталкиваются. со стенами еще проще. Смотрим расстояние от центра молекулы до стены, если оно меньше или равно ее радиусу, они сталкиваются. Так как стены перпендекулярны осям, найти это расстояние не будет очень сложно.

Автор: Litkevich Yuriy 14.7.2009, 17:57

Вообще на мой взгляд пример "сталкивающиеся мыши" не рационален.

Т.к. событие таймера генерится для каждой мышки, в каждом таком событии она перерисовывается. А надо генерить событие для сцены и по нему перерисовывать.
Ещё можно в событии таймера для мыши можно её не рисовать а готовить только её положени (поворот и координаты).


П.С. Закоментировал такую строку:

//view.setRenderHint(QPainter::Antialiasing);
скорость почти в двое возрасла без зглаживания.

Добавил в конструктор класса Mouse такую строку:
setCacheMode(QGraphicsItem::ItemCoordinateCache);
сразу всё зашевелилось шустрее.


Проверил, наличе сглаживания в представлении не влияет на скорость, если включен кэш у графического элемента

Автор: Petr0vi4 14.7.2009, 18:13

Цитата
Kagami

Здесь другая сторона медали - мне придется обрабатывать цикл в цикле. Проверять для каждой частицы, сталкивается ли она с остальными девяность девятью! Я думаю это тоже не есть хорошо...

Цитата
SABROG

Кэш с событием collidingItems() не получится, оно ведь срабатывает только при столкновении которое есть сейчас (при данных координатах объектов) :(

Цитата
Litkevich Yuriy

На счет сглаживания спасибо! В конструктор молекулы добавил
setCacheMode(QGraphicsItem::ItemCoordinateCache);

В main функцию
view.setRenderHint(QPainter::Antialiasing);

Стало красиво, но тупит как и раньше.

Автор: Kagami 14.7.2009, 18:17

Я не совсем это имел в виду. Тебе надо переопределить функцию, рассчитывающую столкновения. Она будет сама вызываться при необходимости. В последнем обновлении документации пример Colliding Mice был переведен на русский. Посмотри его внимательней (заодно ошибки поищешь ;))

Автор: Litkevich Yuriy 14.7.2009, 19:25

Petr0vi4, щёлкай по имени автора поста, вместо цитаты. Его имя сразу добавится в форму быстрого ответа.

Автор: Petr0vi4 15.7.2009, 6:26

Kagami, пришлось перелопатить все программу. Сделал теперь класс Control наследником QGraphicsEllipseItem, наследство от QObject убрал за ненадобностью (сначала хотел использовать сигналы/слоты, но потом отпало). Переписал функцию collidesWithItem() для молекул, работает, но как теперь отличить молекулу от стены?

Автор: Kagami 15.7.2009, 17:34

Посмотри в документации по QGraphicsItem::UserType и QGraphicsItem::type(). При определении столкновения смотришь какого типа исследуемый объект. Если это молекула или стена, то ты уже знаешь что с ними делать. В противном случае можно вызывать метод родителя.

Автор: Petr0vi4 16.7.2009, 15:29

Что-то плохо все работает, может все-таки решить проблему как-то по-другому? Вроде программа простая.

Автор: Kagami 16.7.2009, 16:02

Может выложишь исходники программы? Если она, конечно, не секретная ;)

Автор: Petr0vi4 16.7.2009, 16:28

Да, конечно, вот проект.

 PhysX.zip ( 11.45 килобайт ) : 86
 

Автор: Kagami 16.7.2009, 22:07

Мда.. в коде пока особо не копался, но нашел одно узкое место, устранение которого резко повысило скорость. Проблема была в функции Control::BallCollision(). Ты постоянно вызывал функцию collidingItems(), что приводило к постоянному созданию и удалению списка элементов. Правильнее было бы так:

Раскрывающийся текст
void Control::BallCollision() // столкновение
{
    QList<QGraphicsItem *> list = collidingItems();
    for(int i=0;i<list.count();i++)
    {
        if(list.at(i)->type() == 4)// с шаром
        {
            int Num = list.at(i)->data(1).toInt();
            SetVectorToBuffer(pos().x(),pos().y(),AllObjects[Num]->dx,AllObjects[Num]->dy);
            Changed = true;
        }
        else
        {
            if(list.at(i)->type() == 3)// со стеной
            {
                int Num = list.at(i)->data(1).toInt();
                if(Num==0)
                {
                    qreal Y_k = pos().y();
                    qreal X_k = pos().x();
                    SetVector(X_k,Y_k,X_k+dx,Y_k-dy);
                }
                //if(Num==1)
                else
                {
                    qreal X_k = pos().x();
                    qreal Y_k = pos().y();
                    SetVector(X_k,Y_k,X_k-dx,Y_k+dy);
                }
            }
        }
    }
}

Я вызываю эту функцию один раз и сохраняю полученный список в локальной переменной. Впрочем, к работоспособности кода это имеет мало отношения ;) Может у тебя сохранился вариант когда все работало правильно, но медленно?

P.S. Теперь узким местом стала фунция Module(). Поэтому в Control::MoveBall() я заменил ее на qAbs().

Автор: Petr0vi4 17.7.2009, 2:18

Ну вообще-то это и есть правильно, но медленно. :blush:

Автор: Kagami 17.7.2009, 7:25

После исправлений у меня стало работать со 100 элементами довольно шустро

Автор: Litkevich Yuriy 17.7.2009, 8:20

Слушайте у меня шарик улител за пределы стены и у окошка появились линейки прокрутки и стали быстро уменьшатся.
:)

Автор: Petr0vi4 17.7.2009, 8:31

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

Litkevich Yuriy,
да, бывают косяки иногда, но в основном работает же.
А на счет увеличения окна: я не знаю почему так :)
какая то особенноть Qt...

Автор: Kagami 17.7.2009, 11:36

На ста шариках такие "побеги" случаются чаще :) Это надо править механизм обнаружения столкновений. А увеличивается потому что сцена автоматически расширяется чтобы вмещать все объекты.

Автор: Litkevich Yuriy 17.7.2009, 16:29

Petr0vi4, и ещё есть явный недочёт в вычислении соударений. Ситуация:
Два шарика (наблюдал максимум 4) двжутся по одной линии (траектории) ударяясь друг об друга чуть-чуть отлетают (почти без зазора) и снова летят на встречу друг-другу (хотя повода для этого нет). Другими словами, они колеблятся вокруг воображаемой линии, которой всегда касаются.

Автор: Petr0vi4 17.7.2009, 18:59

Litkevich Yuriy, принцип соударения заключается в обмене векторами движения. Если 2 шара сталкнутся все работает на ура, а если 3 одновременно - тогда не знаю:) Результат крайне непредсказуем.

Автор: Kagami 17.7.2009, 20:26

Эх.. Видимо надо вспоминать школьную физику, а именно про импульс. Если просуммировать импульсы всех столкнувшихся тел, то можно получить вполне адекватные результаты. Плюс нельзя забывать про ограничения в виде стен. Это особый случай, так как молекула не должна их пересекать. Если считать столкновение абсолютно упругим, то угол падения молекулы на стену будет равен углу отскока нее.

Автор: Petr0vi4 18.7.2009, 2:59

Цитата(Kagami @ 18.7.2009, 3:26) *
угол падения молекулы на стену будет равен углу отскока нее
так и есть!
При абсолютно упругом столкновении и получается, что частицы меняются импульсами (то есть векторами движения)....Но при трех частицах как найти направления результирующих векторов? Я так и не нашел.

Автор: Kagami 18.7.2009, 8:46

Цитата(Petr0vi4 @ 18.7.2009, 3:59) *
При абсолютно упругом столкновении и получается, что частицы меняются импульсами (то есть векторами движения)....Но при трех частицах как найти направления результирующих векторов? Я так и не нашел.

Векторами меняются при центровом ударении. В общем случае, в соответствии с третьим законом Ньютона, молекулы будут действовать друг с другом с силой, направленной вдоль оси, соединяющей их центры. При столкновении с несколькими молекулами таких сил будет несколько. Каждая из них будет давать определенный импульс молекуле. Сложив эти импульсы можно получить результирующий новый импульс тела. Это достаточно приближенно, но в целом адекватно.

P.S. Теперь пойду вспоминать как считать сумму векторов ;) Эх.. как давно была школа

Автор: Litkevich Yuriy 18.7.2009, 12:00

Цитата(Kagami @ 18.7.2009, 12:46) *
Теперь пойду вспоминать как считать сумму векторов
как и сложение коплексных чисел.

надо взять исходники https://sourceforge.net/search/?type_of_search=soft&words=billiards
там уже всё решено.
:)

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)