There are some problems with the physics and some problems with the code.
First, the problem with the physics. Assuming that we're not modelling an alternative universe where the laws of physics are different, Newton's law of universal gravitation says that F=G*m1*m2/(r*r). However, force is a vector, not a scalar, so it has both magnitude and direction.
What the code calculates in forceFuncX
is actually the magnitude rather than merely the component of the force parallel to the X axis. forceFuncY
has the same flaw.
Next is the calculation for acceleration. Physics says that this is a=F/m. Mass is a scalar but both acceleration and force are vectors. So to calculate a_x, we could either use F_x/m or we could calculate F*cos(a)/m. Because cos(a) (with a being the angle from one CelesitalObject
to another in 2D space) = dx/r, we can make this a_x = F*dx/(m*r) which is almost but not quite what you've got in your calculation (it's missing r in the divisor).
An alternative approach is to use std::complex
but I'll not show that approach with the assumption that you may wish to extend this model to three dimensions.
That brings us to problems with the code. First, since you're using C++ and writing a simulation of a physical system of discrete objects, it makes sense that you've defined a CelestialObject
class. What makes less sense is that your functions are called by picking out individual pieces of those objects and then calling C-style functions. We can improve the code by using those objects better. To start, since you haven't posted one, here's a CelestialObject
class based on the interface I inferred from your code:
class CelestialObject
{
public:
CelestialObject(std::string name, float mass, float X, float Y,
float VX, float VY)
: myname(name), m(mass), x(X), y(Y), vx(VX), vy(VY) {}
void setPosition(float X, float Y) { x=X; y=Y; }
void setVelocity(float VX, float VY) { vx=VX; vy=VY; }
float getMass() const { return m; }
float getX() const { return x; }
float getY() const { return y; }
float getVX() const { return vx; }
float getVY() const { return vy; }
friend std::ostream& operator<<(std::ostream& out,
const CelestialObject& obj) {
return out << obj.myname << '\t' << obj.x << '\t' << obj.y
<< '\t' << obj.vx << '\t' << obj.vy << std::endl;
}
private:
std::string myname;
float m, x, y;
float vx, vy;
};
Next, a few helper functions:
// returns square of distance between objects
float distance_sq(const CelestialObject &a, const CelestialObject &b)
{
// distance squared is (dy^2 + dx^2)
return pow(a.getY()-b.getY(),2) + pow(a.getX()-b.getX(),2);
}
// returns magnitude of the force between the objects
float force(const CelestialObject &a, const CelestialObject &b)
{
// F=(G * m1 * m1)/(r^2) in the direction a->b and b->a
return G*a.getMass()*b.getMass()/distance_sq(a, b);
}
// returns the angle from a to b
float angle(const CelestialObject &a, const CelestialObject &b)
{
return atan2f(b.getY()-a.getY(),b.getX()-a.getX());
}
Finally the actual verlet:
void updatePosition(CelestialObject &a, CelestialObject &b )
{
float F = force(a,b);
float theta = angle(a,b);
float accela = F/a.getMass();
float accelb = -F/b.getMass();
// now that we have the acceleration of both objects, update positions
// x = x +v *dt + a*dt*dt/2
// = x + dt * (v + a*dt/2)
a.setPosition(
a.getX() + dt * (a.getVX() + accela*cos(theta)*dt/2),
a.getY() + dt * (a.getVY() + accela*sin(theta)*dt/2)
);
b.setPosition(
b.getX() + dt * (b.getVX() + accelb*cos(theta)*dt/2),
b.getY() + dt * (b.getVY() + accelb*sin(theta)*dt/2)
);
// get new acceleration a'
F = force(a,b);
float thetap = angle(a,b);
float accelap = F/a.getMass();
float accelbp = -F/b.getMass();
// and update velocities
// v = v + (a + a')*dt/2
a.setVelocity(
a.getVX() + (accela*cos(theta) + accelap*cos(thetap))*dt/2,
a.getVY() + (accela*sin(theta) + accelap*sin(thetap))*dt/2
);
b.setVelocity(
b.getVX() + (accelb*cos(theta) + accelbp*cos(thetap))*dt/2,
b.getVY() + (accelb*sin(theta) + accelbp*sin(thetap))*dt/2
);
}
And finally some simple test code.
#include <string>
#include <iostream>
#include <vector>
#include <cmath>
const float G(6.67e-11); // N*(m/kg)^2
const float dt(0.1); // s
// all of the other code goes here...
int main()
{
CelestialObject anvil("anvil", 70, 370, 0, 0, 0);
CelestialObject earth("earth", 5.97e+24, -6.378e6, 0, 0, 0);
std::cout << "Initial values:\n" << earth << anvil;
std::cout << "Dropping an anvil from the top of a 370m building...\n"
"It should hit the ground in about 8.7 seconds.\n";
int t;
for (t=0; anvil.getX() > 0; ++t) {
std::cout << dt*t << '\t' << anvil;
updatePosition(anvil, earth);
}
std::cout << "Final values at t = " << dt*t << " seconds:\n"
<< earth << anvil;
return 0;
}
The test code uses a time step of 0.1s which is far too short for your solar system but fine for this quick test which is to see if we get a reasonable result for a known system. In this case, I've chosen a two-body system consisting of the planet Earth and an anvil. This code simulates dropping an anvil from the top of a 370m building which we can easily calculate will hit the ground in about 8.7s if we neglect air resistance. To keep the coordinates simple, I chose to place the origin (0,0) at the surface of the earth, and to consider the top of the building to be at (370,0). When the code is compiled and run, it produces the following:
Initial values:
earth -6.378e+06 0 0 0
anvil 370 0 0 0
Dropping an anvil from the top of a 370m building...
It should hit the ground in about 8.7 seconds.
0 anvil 370 0 0 0
0.1 anvil 369.951 -4.27834e-09 -0.97877 -8.55668e-08
0.2 anvil 369.804 -1.71134e-08 -1.95754 -1.71134e-07
0.3 anvil 369.56 -3.85051e-08 -2.93631 -2.567e-07
...
8.3 anvil 32.8567 -2.9474e-05 -81.2408 -7.1023e-06
8.4 anvil 24.6837 -3.01885e-05 -82.2197 -7.18787e-06
8.5 anvil 16.4127 -3.09116e-05 -83.1985 -7.27345e-06
8.6 anvil 8.04394 -3.16432e-05 -84.1774 -7.35902e-06
Final values at t = 8.7 seconds:
earth -6.378e+06 3.79705e-28 9.98483e-22 8.72901e-29
anvil -0.422744 -3.23834e-05 -85.1563 -7.4446e-06
As you can see, this seems to work, but there are problems. The first problem is that since the objects should only move along the X axis, all Y components should be 0. They are not because this code is not very well-designed from a numerical analysis point of view. Doing additions and subtractions of floating point numbers when one number is large and another is small is one problem. Another is the use of the atan2f
function which returns only a float
and then using that result in cos()
and sin()
. Trigonometric functions are actually best avoided if possible.
Finally, this program currently only works with two objects. Adding a third would be painful with this kind of scheme, so a better design would be to process a std::vector<CelestialObject>
by first calculating the net force on each object by considering the positions and masses of all of the others. I'll leave that to you, but this should at least give you a start in the right direction.