patternjavaModerate
Gravity model for a simulator
Viewed 0 times
modelsimulatorgravityfor
Problem
I am designing a planetary system simulator. The coordinates used are standard for JPanel (0,0 in the upper left corner).
My model is based on a class Body:
Because the force of gravity is the same for any two interacting bodies, I designed the methods to take another body as an argument, so we can call
Distance calculation:
Finally, the method that calculates gravitational force:
```
public void interact(Body other) {
double x = calculateDistX(other);
double y = calculateDistY(other);
double r = calculateDistance(x, y);
double force = (this.getMass() * other.getMass()) / Math.pow(r, 2);
double force_x = force (x / r); // force cos
double force_y = force (y / r); // force sin
/ calculate accelerations for both bodies, set vector orientation /
if (other.getX() > this.getX()) {
this.setAx(force_x / this.getMass());
other.setAx(-force_x / other.getMass());
} else {
this.setAx(-force_x / this.getMass());
other.setAx(force_x / other.getMass());
}
if (other.getY() > this.getY()) {
My model is based on a class Body:
public class Body {
private double x, y; // position
private double w, h; // dimensions for drawing
private double vx = 0, vy = 0; // velocities along x, y axes
private double ax = 0, ay = 0; // accelerations along x, y axes
private double mass = 1; // default = 1
private boolean stationary = false; // stationary body is centered during simulation
/* ... not showing all the setters and getters */Because the force of gravity is the same for any two interacting bodies, I designed the methods to take another body as an argument, so we can call
planet1.interact(planet2)Distance calculation:
public double calculateDistX(Body other) {
double x1 = this.getX();
double x2 = other.getX();
return Math.sqrt(Math.pow(x2 - x1, 2));
}
public double calculateDistY(Body other) {
double y1 = this.getY();
double y2 = other.getY();
return Math.sqrt(Math.pow(y2 - y1, 2));
}
public static double calculateDistance(double dist_x, double dist_y) {
return Math.sqrt(Math.pow(dist_x, 2) + Math.pow(dist_y, 2));
}Finally, the method that calculates gravitational force:
```
public void interact(Body other) {
double x = calculateDistX(other);
double y = calculateDistY(other);
double r = calculateDistance(x, y);
double force = (this.getMass() * other.getMass()) / Math.pow(r, 2);
double force_x = force (x / r); // force cos
double force_y = force (y / r); // force sin
/ calculate accelerations for both bodies, set vector orientation /
if (other.getX() > this.getX()) {
this.setAx(force_x / this.getMass());
other.setAx(-force_x / other.getMass());
} else {
this.setAx(-force_x / this.getMass());
other.setAx(force_x / other.getMass());
}
if (other.getY() > this.getY()) {
Solution
Math.pow(x, 2) -> x*x
A pet peeve of mine is people calling
Shadowing variable names
In
X and Y distances
Look closely at your x/y distance calculations functions:
It actually just does this:
This shorter version is also less prone to roundoff errors. But in fact, I would even remove this function, because it will be more useful to use to preserve the sign of the subtraction than to hide it (read below)
Splitting up interact and update
Your interaction function should do no more than modify the acceleration of the object. Once the body has interacted with all other bodies, its acceleration will have the final acceleration for that timeslice. After that, you should have an update function which takes the acceleration and applies it to the velocity, and then applies the velocity to the position. I'll show an example of that below.
Simplifying the interaction function
Looking at your interaction function, there are a lot of places that can be simplified. First, if you retained the sign of the x/y distances, you wouldn't need all those if statements. Second, a lot of the force calculations are related to each other and can be merged into a smaller set of calculations. In fact, when I rewrote your function, it became this simple:
Further simplifications
If you don't actually need the current velocity of the object other than to update the position (e.g for collisions), you can get rid of the acceleration entirely and operate directly on the velocity.
Edit: Also, I think that multiplications are faster than divisions, so you could also invert r3 so that you only need to do one divide. You can also precompute
A pet peeve of mine is people calling
Math.pow() just to square a number. To me it seems faster and simpler to just multiply the value by itself.Shadowing variable names
In
interact(), you use the variables x and y. I would avoid using variable names that shadow instance variables. I would call these variables dx and dy instead.X and Y distances
Look closely at your x/y distance calculations functions:
public double calculateDistX(Body other) {
double x1 = this.getX();
double x2 = other.getX();
return Math.sqrt(Math.pow(x2 - x1, 2));
}It actually just does this:
public double calculateDistX(Body other) {
return Math.abs(this.getX() - other.getX());
}This shorter version is also less prone to roundoff errors. But in fact, I would even remove this function, because it will be more useful to use to preserve the sign of the subtraction than to hide it (read below)
Splitting up interact and update
Your interaction function should do no more than modify the acceleration of the object. Once the body has interacted with all other bodies, its acceleration will have the final acceleration for that timeslice. After that, you should have an update function which takes the acceleration and applies it to the velocity, and then applies the velocity to the position. I'll show an example of that below.
Simplifying the interaction function
Looking at your interaction function, there are a lot of places that can be simplified. First, if you retained the sign of the x/y distances, you wouldn't need all those if statements. Second, a lot of the force calculations are related to each other and can be merged into a smaller set of calculations. In fact, when I rewrote your function, it became this simple:
public void interact(Body other) {
double dx = other.getX() - x;
double dy = other.getY() - y;
double r = calculateDistance(dx, dy);
double r3 = r * r * r;
/* calculate accelerations for both bodies */
ax += other.getMass() * dx / r3;
ay += other.getMass() * dy / r3;
other.addToAx(mass * -dx / r3);
other.addToAy(mass * -dy / r3);
}
public void update() {
vx += ax;
vy += ay;
ax = 0;
ay = 0;
x += vx;
y += vy;
}Further simplifications
If you don't actually need the current velocity of the object other than to update the position (e.g for collisions), you can get rid of the acceleration entirely and operate directly on the velocity.
Edit: Also, I think that multiplications are faster than divisions, so you could also invert r3 so that you only need to do one divide. You can also precompute
dx/r^3 and dy/r^3 to reduce the number of multiplies:public void interact(Body other) {
double dx = other.getX() - x;
double dy = other.getY() - y;
double r = calculateDistance(dx, dy);
double inv_r3 = 1.0 / (r * r * r);
/* Precalculate force component (1/r^2) times direction (dx/r) = dx / r^3 */
dx *= inv_r3;
dy *= inv_r3;
/* calculate accelerations for both bodies */
vx += other.getMass() * dx;
vy += other.getMass() * dy;
other.addToVx(mass * -dx);
other.addToVy(mass * -dy);
}
public void update() {
x += vx;
y += vy;
}Code Snippets
public double calculateDistX(Body other) {
double x1 = this.getX();
double x2 = other.getX();
return Math.sqrt(Math.pow(x2 - x1, 2));
}public double calculateDistX(Body other) {
return Math.abs(this.getX() - other.getX());
}public void interact(Body other) {
double dx = other.getX() - x;
double dy = other.getY() - y;
double r = calculateDistance(dx, dy);
double r3 = r * r * r;
/* calculate accelerations for both bodies */
ax += other.getMass() * dx / r3;
ay += other.getMass() * dy / r3;
other.addToAx(mass * -dx / r3);
other.addToAy(mass * -dy / r3);
}
public void update() {
vx += ax;
vy += ay;
ax = 0;
ay = 0;
x += vx;
y += vy;
}public void interact(Body other) {
double dx = other.getX() - x;
double dy = other.getY() - y;
double r = calculateDistance(dx, dy);
double inv_r3 = 1.0 / (r * r * r);
/* Precalculate force component (1/r^2) times direction (dx/r) = dx / r^3 */
dx *= inv_r3;
dy *= inv_r3;
/* calculate accelerations for both bodies */
vx += other.getMass() * dx;
vy += other.getMass() * dy;
other.addToVx(mass * -dx);
other.addToVy(mass * -dy);
}
public void update() {
x += vx;
y += vy;
}Context
StackExchange Code Review Q#88566, answer score: 14
Revisions (0)
No revisions yet.