Optimize collision between particles

9

I need to optimize to the maximum the algorithm that makes the collision between the particles, is there anything that can be done for this? And I also want to add a background image, is it possible?

Follow the code canvas.cpp:

canvas::canvas(QWidget *parent) :
QWidget(parent)
{
m_particulas.resize(20);

int n = m_particulas.size();
for(int i = 0 ; i<n ; ++i)
{
    m_particulas[i].init();
}

startTimer(10);
}

void canvas::paintEvent(QPaintEvent * event)
{
(void) event;

QPainter painter(this);
painter.setWindow(0,0,1000,1000);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, 5, Qt::SolidLine, Qt::RoundCap));
painter.setBrush(QBrush(Qt::darkCyan, Qt::SolidPattern));

int n = m_particulas.size();
for(int i = 0 ; i<n ; ++i)
{
    double x = m_particulas[i].x();
    double y = m_particulas[i].y();
    double L = m_particulas[i].r();
    painter.drawEllipse(QPointF(x, y), L, L);
}
}

void canvas::timerEvent(QTimerEvent *event)
{
(void) event;

int n = m_particulas.size();

for(int i = 0 ; i<n ; ++i)
{
    m_particulas[i].andar();
    Particula &pi = m_particulas[i];

    for(int j = 0 ; j < n ; ++j)
    {
        if (i == j) continue;

        Particula &pj = m_particulas[j];

        if (pi.testa_colisao(pj))
        {
            pi.calcula_colisao(pj);
        }
    }
pi.andar();
}
update();
}

particulas.cpp:

Particula::Particula()
{

}

void Particula::init()
{
m_r = 20;

m_x = 900.0*rand()/RAND_MAX + 50;
m_y = 900.0*rand()/RAND_MAX + 50;

m_vx = 2.0 * rand()/RAND_MAX - 1;
m_vy = 2.0 * rand()/RAND_MAX - 1;

double norma = sqrt(m_vx*m_vx + m_vy*m_vy);
m_vx /= norma;
m_vy /= norma;

}

void Particula::normaliza(double &vx, double &vy)
{
double norma = sqrt(vx*vx + vy*vy);
if (norma > 0)
{
    vx /= norma;
    vy /= norma;
}
}

bool Particula::testa_colisao (Particula &p)
{
    double dx = x() - p.x();
    double dy = y() - p.y();
    double dist = sqrt(dx*dx + dy*dy);
    return dist <= r() + p.r();
}

void Particula::calcula_colisao(Particula &p)
{
    double vx = p.x() - x();
    double vy = p.y() - y();
    normaliza(vx,vy);

    p.m_vx += vx;
    p.m_vy += vy;
    normaliza(p.m_vx,p.m_vy);

    m_vx -= vx;
    m_vy -= vy;
    normaliza(m_vx, m_vy);

    while (testa_colisao(p))
    {
        andar();
        p.andar();
    }
}

double Particula::x() const
{
return m_x;
}

double Particula::y() const
{
return m_y;
}

double Particula::r() const
{
return m_r;
}

void Particula::andar()
{
m_x += m_vx;
m_y += m_vy;

if(m_x > 1000-m_r) m_vx *= -1; //inferior - multiplicado por -1 para inverter a direção...
if(m_y > 1000-m_r) m_vy *= -1; //direita
if(m_x < 0+m_r) m_vx *= -1; //esquerda
if(m_y < 0+m_r) m_vy *= -1; //superior
}
    
asked by anonymous 18.05.2014 / 02:00

1 answer

7

For a simple optimization that does not require much code change, I have three suggestions:

  • You are testing all particles against all other particles twice (i.e.% with% of% with% with% with% with% with%). Try them only once:

    for(int i = 0 ; i<n ; ++i)
    {
        m_particulas[i].andar();
        Particula &pi = m_particulas[i];
    
        for(int j = i+1 ; j < n ; ++j)
        {
            //if (i == j) continue;
    
            Particula &pj = m_particulas[j];
    

    For this to be possible, do not make the particle walk after testing the collision. Instead, move the particles first, then start the tests (Q. Why are you moving each particle twice?):

    // Primeiro move
    for (int i = 0 ; i < n ; i++ )
        m_particulas[i].andar();
    
    // Depois testa
    for(int i = 0 ; i<n ; ++i)
    {
        Particula &pi = m_particulas[i];
    
        for(int j = i+1 ; j < n ; ++j) ...
    
  • Calculating the square root is much slower than multiplication. Raise the equation of the entire distance squared so as to avoid this calculation:

    bool Particula::testa_colisao (Particula &p)
    {
        double dx = x() - p.x();
        double dy = y() - p.y();
        double dist2 = dx*dx + dy*dy;
        double somaRaios = r() + p.r()
        return dist2 <= somaRaios * somaRaios;
    }
    
  • In% with%, make the particle walk at least once before testing again for collisions (since the first time it runs, it will always return i ):

    ...
    normaliza(m_vx, m_vy);
    
    do
    {
        andar();
        p.andar();
    } while (testa_colisao(p));
    
  • This should avoid all unnecessary work in the current code. Based on this, we can move on to more complex optimizations, which involve refactoring the program as a whole. This question in SOen gives some promising suggestions:

  • Divide your space into smaller areas, and do the calculation separately for each of these areas. For example, if your scenario is a square of [0,n] , create 9 squares of j (with some intersection between squares, so that a particle very close to the boundary is sure to be tested against the adjacent square) and place each particle in one or more squares. Update this with every move (it should be quick, as it is just a matter of comparing [0,n] and calcula_colisao positions - and if the number of sub-areas is large, you can do a binary search). When testing for collisions, consider only particles that are in the same square.

    • Bonus: This is a good candidate for parallelization - create a separate process / thread to deal with each square. Just be careful that the overhead of managing competition does not nullify the benefit, this technique is only worth it if the number of particles is quite large.
  • Instead of searching for collisions with each simulation frame, store in another data structure the distances between each pair of particles divided by the sum of their velocities. This value corresponds to the minimum number of frames needed for a chance of these particles to collide. If at the time of collision testing this value is greater than or equal to true , decrease it and do not take the test - because it will surely return false. If it is smaller, test it and update the collision prediction (which may have increased, decreased, or stayed the same depending on the direction of the movement).

    • Note: If your frames do not have fixed duration, adaptations may be necessary.

    Whenever a particle collides with any other, its velocity may increase. You could that whole prediction matrix [for this particle] whenever that happened, but I imagine it would be too costly. A reasonable approximation might be to simply reduce all values proportionally (eg, speed increased from% to% with% of%, divide all predictions from collision to% with%).

  • These techniques can be combined (ie divide their space into smaller areas, and make the prediction matrix for each sub-area), and there is

    • If there are many particles in relation to space, so that collisions will be frequent and localized, strategy 1 is more interesting and unnecessary; divide your space into quite a few sub-areas, as it will be rare for a particle to move from one area to another.

    • If there are few particles in relation to space, and they move very fast, strategy 1 is useless (since every hour a particle will change from sub-area) and 2 more interesting; keep the forecast matrix in order, so you do not have to go through it all when you test each particle.

    • Other particular cases will have different impacts on the best strategy.

    18.05.2014 / 11:50