Search code examples
javadata-structuresgame-physicssimulator

Structure for "dynamic" objects in a simulation


I want to create a more or less simple gravity simulation and I'm currently making some decisions about the structure. I've come across one problem, which I don't know how to solve "nicely". Here is a simple class diagram of the problem:

The Problem

On a first look you probably can't see any problem, but let me tell you one additional fact: if a Star reaches a certain density (Schwarzschildradius > Radius), it should form a BlackHole. So how can I tell the Simulation to swap the Star instance with a BlackHole one? I could add a Simulation instance to each body, where they themselves can then modify the bodies array, but there has to be a better way!

Another approach could be, to instead let Planet, Star and BlackHole be subclasses of a new class BodyType, of which Body would then hold an instance.

What do you think? How would you solve this in a nice and clean way? Any help is appreciated!

Edit: for further clarification, here is an illustration of both methods I mentioned. Which one of the two is better? Or is there a better one I didn't mention?


Solution

  • Perhaps this may help; one common method to changing behavior at runtime is the strategy pattern; the idea is to keep things common to all body types in the Body class and separate out different behaviour into different strategies, this way the strategy can be swapped whenever the body changes type. Rather than have BlackHole, Planet and Star extend Body they can implement an interface, say BodyBehavior;

    public interface BodyBehaviour {
        String getType ();
        Double getLuminosity (Body body);
        // anything else you need that's type specific....
    }
    
    public class PlanetBodyBehavior implements BodyBehaviour {
        public static final String TYPE = "Planet"
        @Override
        public String getType() {
            return TYPE;
        }
    
        @Override
        public Double getLuminosity(Body body) {
            // planet specific luminosity calculation - probably 0 
            return calculatedLuminosity;
        }
    }
    

    And your body class would look something like;

    public class Body {
        private Double mass;
    
        // coordinates,radius and other state variables
    
        private BodyBehavior bodyBehaviour;
    
        public void setBodyBehaviour (BodyBehaviour behaviour) {
            this.bodyBehaviour = behaviour;
        }
    
        public String getBodyType () {
            bodyBehaviour.getType();
        }
    
        public Double getLuminosity () {
            bodyBehavior.getLuminosity(this);
        }
    }
    

    And for your simulation you could have something along the lines of;

    // init - note we keep our behaviors stateless so we only need one instance of each
    public class Simulation () {
        BodyBehaviour planetBehavior = new PlanetBodyBehavior();
        BodyBehaviour starBehavior = new StarBodyBehavior()
        BodyBehaviour blackHoleBehavior = new BlackHoleBodyBehavior()
        // Just showing initialisation for a single star...
        Body body = new Body(initilising params....)
        body.setBehaviour (starBehavior);
    
            // iterations....
        while (!finished) {
            // assume we have many bodies and loop over them
            for (Body body : allBodies) {
                // update body positions etc.
                // check it's still a star - don't want to be changing it every iteration...
                if (body.hasBecomeBlackHole() {
                    // and this is the point of it all....
                    body.setBodyBehaviour(blackHoleBehavior);
                }
            }
        }
    }
    

    Happy simulating!

    Note in this case it's the top level simulation loop that invokes the behavior change but you could add this to your BodyBehavior;

    boolean hasBecomeBlackHole (Body body);
    

    and in body you could do something like

    public boolean hasBecomeBlackHole () {
        bodyBehaviour.hasBecomeBlackHole(this);
    }
    

    For StarBodyBehavior;

    public boolean hasBecomeBlackHole (final Body body) {
        return doSomeCalculations(body.getMass(), body.getRadius());
    }
    

    And for planets and blackholes

    public boolean hasBecomeBlackHole (final Body body) {
        return false;
    }