I am writing the code for my EPQ project and am aiming to produce a graphic showing the collapse of a particle cloud with around 10^5/6 particles.
My current code will spawn a specified number of particles in a uniform distribution across a specified area. It will then calculate the acceleration of the particles using a Barnes-Hutt tree and integrate this to find the new position of each particle. I currently have no graphics but the program will print to the console upon each movement.
Unfortunately, each iteration takes around half a minute (running with 35000 particles) which is way too slow for a graphic. So i am looking for a way to improve my algorithm(s) to make it faster.
Here is some of my code:
Tree class:
class tree
{
public:
obj* body; // obj class stores the mass, velocity, position ans acceleration of an object
bool parent = false;
double total_mass;
region R; // region class defines boundaaries and can check if an object is in them
vect moment;
tree* nw, *ne, *sw, *se; // the children nodes of the tree
tree(vect corner, double length) : R(length, corner), total_mass(0), moment(vect(0, 0)), body(nullptr) {}
tree(cloud& a) : R(a.size + 1, vect(0,0)), total_mass(0), moment(vect(0, 0)), body(nullptr)
{
for (int i = 0; i < a.chest.size(); i++)
{
insert(a.chest[i]); // this constructer id called for the root node only
}
}
~tree()
{
delete nw;
delete sw;
delete ne;
delete se;
}
void insert(obj* i)
{
if (!R.in(i->pos)) // cant insert into a region its not in
{
return;
}
else
{
if (body == nullptr) // no other bodies in this region
{
if (!parent)
{
body = i; // region is not divides so you can insert particle here
}
else
{
nw->insert(i); //region is divided so try to insert into children nodes
ne->insert(i);
sw->insert(i);
se->insert(i);
}
}
else // trying to have more than one particle in a node
{
divide(); // splits node into its childrem
nw->insert(i);
ne->insert(i);
sw->insert(i);
se->insert(i);
nw->insert(body); // insert current object and the object that was previouly in the parent node into the children nodes
ne->insert(body);
sw->insert(body);
se->insert(body);
body = nullptr; // no longer bodies in the node
}
total_mass += i->mass;
moment += (i->pos) * (i->mass);
}
}
void divide()
{
double l = R.length / 2;
nw = new tree(R.point + vect(0, l), l);
ne = new tree(R.point + vect(l, l), l);
sw = new tree(R.point + vect(0, 0), l);
se = new tree(R.point + vect(l, 0), l);
parent = true;
}
vect COM()
{
return moment / total_mass;
}
};
Accelerator:
constexpr double theta = 0.5; //theta criterion
double G = 1 * pow(10,-11); // gravitational constant
void accelerate(obj& i, tree& t)
{
vect r = t.COM() - i.pos; // vector between the position of the particle and the center of mass of the node
if (!t.parent) //checks if node is undivided
{
i.a += (t.body == nullptr || t.R.in(i.pos)) ? vect(0, 0) : r.norm() * G * t.total_mass / r.mag2();
}//if there are also no bodys or the object being accelerated is in the node then there is no effect on the particle
else
{
if (t.R.in(i.pos) || t.R.length / r.mag() > theta)
{
accelerate(i, *t.nw); //object is in the node or the node does not meet the theta criterion so try the nodes children
accelerate(i, *t.ne);
accelerate(i, *t.sw);
accelerate(i, *t.se);
}
else
{
i.a += r.norm() * G * t.total_mass / r.mag2();
}
}
}
RK4:
void move(cloud& a) // cloud class stores an array of pointers to objects
{
tree* t1 = new tree(a); //adds objects in cloud to a new tree
for (obj* i : a.chest) //chest is the array of pointer to objects
{
accelerate(*i, *t1); //uses tree to refresh the accelration of the particle
i->tpos = i->ppos = i->pos; // tpos/v/a stores the value of the new pos/v/a, ppos stores the value from the previous itteration
i->tv = i->pv = i->v;
i->ta = i->pa = i->a;
vect dr1 = i->v * h;
vect dv1 = i->a * h;
i->pos = i->ppos + dr1 / 2;
i->v = i->pv + dv1 / 2;
i->tpos += dr1 / 6;
i->tv += dv1 / 6;
}
delete t1;
tree* t2 = new tree(a); // deletes previous tree and creates a new one to culculate the new acceleration
for (obj* i : a.chest)
{
accelerate(*i, *t2);
vect dr2 = i->v * h;
vect dv2 = i->a * h;
i->pos = i->ppos + dr2 / 2;
i->v = i->pv + dv2 / 2;
i->tpos += dr2 / 3;
i->tv += dv2 / 3;
}
delete t2;
tree* t3 = new tree(a);
for (obj* i : a.chest)
{
accelerate(*i, *t3);
vect dr3 = i->v * h;
vect dv3 = i->a * h;
i->pos = i->ppos + dr3;
i->v = i->pv + dv3;
i->tpos += dr3 / 3;
i->tv += dv3 / 3;
}
delete t3;
tree* t4 = new tree(a);
for (obj* i : a.chest)
{
accelerate(*i, *t4);
vect dr4 = i->v * h;
vect dv4 = i->a * h;
i->tpos += dr4 / 6;
i->tv += dv4 / 6;
i->pos = i->tpos;
i->v = i->tv;
i->a = i->pa;
}
delete t4;
}
Would half a minute be normal when simulating this many particles? If not how could I improve this code to make it run faster?
Would half a minute be normal when simulating [10^5…6] particles?
\$\endgroup\$Would half a minute be normal when simulating this many particles?
No. The target framers for most games is 60 FPS. I'm not sure how most particles systems achieve that though I suspect an object pool could be the answer. Be sure to head over to Game Development and see if they've seen this before. I bet they have. \$\endgroup\$cloud
class and amain
with some test data so we can run the code. Incomplete code snippets are hard to review, and make it impossible for us to help you improve the code. \$\endgroup\$