0
\$\begingroup\$

I'm making a simple planetary system simulator. I've run into some problems with physics model and simulation speed and I'd like to learn how to fix them.

My main class is Body:

public abstract 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

...
}

Then, in this class, the method I'm using to calculate the force of gravity. I decided to have another body as a parameter, so when I call the method it updates both of the bodies. If that's not the best way to do it, please tell me. I also know that using "this" inside a public method is not necessary, but it makes the code more clear for me.

public void interact(Body b2) {
    double x = calculateDistX(b2);
    double y = calculateDistY(b2);
    double r = calculateDistance(x, y);

    double f = (this.getMass() * b2.getMass()) / Math.pow(r, 2); // gravitational
                                                                    // force

    double fx = f * (x / r); // x component of the force - f times cosine of
                                // the angle
    double fy = f * (y / r); // x component of the force - f times sine of
                                // the angle

    /* calculate accelerations for both bodies, set vector orientation */
    if (b2.getX() > this.getX()) {
        this.setAx(fx / this.getMass());
        b2.setAx(-fx / b2.getMass());
    } else {
        this.setAx(-fx / this.getMass());
        b2.setAx(fx / b2.getMass());
    }

    if (b2.getY() > this.getY()) {
        this.setAy(fy / this.getMass());
        b2.setAy(-fy / b2.getMass());
    } else {
        this.setAy(-fy / this.getMass());
        b2.setAy(fy / b2.getMass());
    }

    /* calculate velocities for both bodies */
    this.setVx(this.getVx() + this.getAx());
    this.setVy(this.getVy() + this.getAy());

    b2.setVx(b2.getVx() + b2.getAx());
    b2.setVy(b2.getVy() + b2.getAy());

    /* calculate positions for both bodies */
    this.setX(this.getX() + this.getVx());
    this.setY(this.getY() + this.getVy());

    b2.setX(b2.getX() + b2.getVx());
    b2.setY(b2.getY() + b2.getVy());
}

Now, when I run the simulation it tends to blow up when bodies get too close. I will try to implement the Runge-Kutta or Verlet algorithm in the interact() method to change this, but how do I implement timestep? Should there be some timer that counts time and is used to calculate derivatives? Right now I'm using a Timer in my drawing method:

class DrawingPanel extends JPanel {
DrawingPanel() {
    int step = 15; // milliseconds
    ActionListener taskPerformer = new ActionListener() {
        public void actionPerformed(ActionEvent evt) {
            repaint();
            }
    };
    new Timer(step, taskPerformer).start();
    Body b0 = new Star();
    addBody(b0);
    Body b1 = new Planet();
    addBody(b1);
    Body b2 = new Planet();
    addBody(b2);
}

List<Body> bodies = new ArrayList<>();

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    bodies.get(2).interact(bodies.get(1));
    bodies.get(0).interact(bodies.get(1));
    bodies.get(0).interact(bodies.get(2));

    for (Body b : bodies) {
        f.draw(b);
    }
}
public void addBody(Body b) {
    bodies.add(b);
}
}

I feel like calling the interact() method in my paintComponent() method is not the right solution, but I don't know how to do it. Also, how to make the simulation slower? Right now changing the "step" to bigger value slows the simulation down, but it ends up being just low FPS, because it's just the separation between two drawing events. How should I implement the timer to control the simulation and preserve its smoothness?

\$\endgroup\$
  • \$\begingroup\$ Before you go much further, please do some research on barycenters. They will help you simplify many of the calcs. Specifically, the "Center of gravity" section. Also, toward the bottom under "Astronomy", there is an animation showing a barycenter. \$\endgroup\$ – Jon Apr 20 '15 at 20:41
  • \$\begingroup\$ You should try an isolate your different problems/questions and post them in different questions, this would probably yield better help for you! \$\endgroup\$ – Vaillancourt Apr 20 '15 at 23:14
1
\$\begingroup\$

Edit: Moved from comments; didn't originally feel like a complete answer. May not be still...

Time-step:
A typical problem with collisions is when bodies move beyond each other, instantly, in the same frame; i.e. "teleport". No collision is detected because the objects moved past, not through, the other. Floating-point error is unavoidable and will always cause your model to deviate from "ideal".

It's important that the centers of bodies not be "teleported" into the center of other bodies during Update(). Calculating the pull due to G, between two "planets" whose volumes are overlapping, will result in Sun-like values, because they are impossibly close. You need to keep things like that in mind when selecting a time-step for the "universe".

Adding additional logic and/or dimensions to the tree-like structure described below, can give you the added flexibility to run physics in more, smaller, chunks on an object-by-object, or case-by-case, basis. Two near-and-heavy objects should take smaller steps until they pass the "danger-zone". For example, a quick check of diminishing distances between near-and-heavy bodies that suddenly start increasing is a probable indicator of a chance-of-collision expiring. They were getting closer, and now they aren't, so there's no more collisions to check between them for awhile. To state the obvious, objects can only collide when they are approaching each other, and the tree structure presented will contain references to enough information to detect this hint and re-optimize on the fly.

Monitor how long the "universe" is taking to Physics() and reduce the overall aggressiveness/LOD, if necessary, to catch up and/or stay caught-up on laggy machines.

Barycenters:
Before you go much further, please do some research on barycenters. They will help you simplify many of the calculations. Specifically, the "Center of gravity" section. Also, toward the bottom under "Astronomy", there is an animation showing a barycenter.

The resulting barycenter class will very-loosely perform the function of spatial partitioning. The barycenter contains everything needed to calculate gravitational attraction between all of its' children. To calculate the force on an individual child, you just exclude the child from the pre-computed total, then calculate gravity as usual.

You can also test objects outside of the barycenter against the entire barycenter, including other barycenters. In this way, entire "galaxies" can affect each other realistically, without having to iterate every particle in both systems.

The tree-ish-like functionality has added benefits such as a barycenter being a convenient "bucket" for the bodies within it. They collect Render(), Update(), Gravity(), etc. into logically partitioned chunks, somewhat like a quad-tree. The player is in a ship (barycenter), which is in a solar system (bc), which is in a... Testing any/all against each other becomes trivial.

How to implement the Barycenter class effectively? There can be many of them, i.e. in star + planet and moon system, there will be one in the planet-moon system and one between star and orbiting planet. Some of the barycenters will move during the simulation. How do I calculate them correctly?

For a minimal number of bodies, you just need the one. For an example "object" (a ship), you could calculate the pull from the moon and affect the ship. Then, calculate the planet, and affect the ship. Then, the star, and affect the ship. Instead, pre-calculate the entire system as one point-mass (barycenter). Subtract the ship's influence from the point-mass, then calculate gravity against the entire rest of the system in one calculation. After an entire frame of gravity has been Update()'d, the barycenter will update itself, using all of the new (MG/D^2)'s.

Using the barycenter, the end-result will be the same force vector you would calculate any other way. It will point toward the nearest-and-heaviest body, weighted by MG/D^2.

You can ask it "whats the pull from everything except X" then apply that directly to X. You get a precise, one-shot answer, regardless of what is being tested, in-, or out-side of the system. When you start needing to test against a second star, you start needing a "quadrant/universe barycenter". The effect of a distant star on a ship near the earth is negligible, but the distant star does pull on the barycenter of Sol. Technically, the distant star pulls on every object within Sol, individually; but we tend to behave as a unit.

Real-world:
The center of our solar system's barycenter is nearer to the surface of the Sun, than its' center. The vector between those two points represents the pull on the sun, from everything except the sun.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.