HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaModerate

Gravity model for a simulator

Submitted by: @import:stackexchange-codereview··
0
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:

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 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.