Search code examples
javaphysicscalculusprojectile

Why do I get inaccuracies when working with large scale/speed projectiles, is it due to Eulerian Integration?


So I am trying to make a Physics Simulation, which aims to simulate a missile going off a long distance away, (for my example I have used 1.6km) and I have a AA weapon at this 1.6km distance; when the missile is launched, due to maths I have worked out when I need to release the AA weapon to hit the missile. So cool the maths all checks out, and I am 99% certain mathematically this should work; but the error seems to occur in the animation stage, and I feel this is something to do with how I am handling gravity.

The issue is instead of the flight path of the missile following a lovely parabola, it is instead taking a path consisting of many different linear functions (hence the Eulerian integration speculation(Source: http://natureofcode.com/book/chapter-5-physics-libraries/)); and I believe the reason for this is to do with my scale. I am using a scale of 1 pixel = 1 meter. The image updates every 60 seconds, so I am saying every cycle, take the vertical velocity - (9.81 / 60) and then update the position by (Vertical velocity / 60). The problem is, it only wants to round to the nearest pixel, so in the first 2 seconds or so, it only wants to have the vertical position change at 4 pixels per cycle, and then it goes to 5, then 6... This causes the flight path (when the missile was launched at 200m/s at 50 degrees and the AA launched at 70degrees with the same speed) to look like: Simulation Test

If anyone knows how I can fix this issue to turn the inaccurate linear representation into a nice parabolic one that was still accurate to the time, (the time after launch is about 3 seconds for this). If anyone has any suggestions and/or solutions, or if you are able to explain why this is happening and don't mind spending a little time explaining it to me to get a better understanding of the problem, that would be greatly appreciated! If you require any more information to help you help me, then just leave a comment and I will provide you with any info. The relevant pieces of code are:

public void gravity(){
    for (int i = 0; i < rockets.size(); i++){

        if(timeToStart <= milliSecondTimer){
            rockets.get(1).fired = true;
            //Trail of other rocket
            if (milliSecondTimer > 0.1 * count){
                rockets.add(new Ball(rockets.get(1).x - 20 ,rockets.get(1).y - HEIGHT + 100,5,0,0,false));
            }

        }

        if(rockets.get(i).fired){
            rockets.get(i).vSpeed -= 9.81 / 60;
            rockets.get(i).move(0, (int) (rockets.get(i).vSpeed / 60));
            rockets.get(i).move(1, (int) (rockets.get(i).hSpeed / 60));
        } else if (timeToStart==1110){
            //function to work out the time displacment
            derr(1);
        }

        //For the trail
        if (milliSecondTimer > 0.1 * count){
            rockets.add(new Ball(rockets.get(0).x - 20 ,rockets.get(0).y - HEIGHT + 100,5,0,0,false));
            count++;
        }

    }
}   
public static void main(String[] args) throws InterruptedException{
    JFrame frame = new JFrame("App Name");
    Rockets app = new Rockets();
    frame.setSize((int)(WIDTH * SCALER),(int)(HEIGHT * SCALER));
    frame.add(app);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setResizable(false);
    frame.requestFocus();
    long lastLoopTime = System.nanoTime();
    int fps = 0, lastFpsTime = 0, count = 1;
    final int TARGET_FPS = 60;
    final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
    //Game Loop
    while(true){
        long now = System.nanoTime();
        long updateLength = now - lastLoopTime;
        lastLoopTime = now;
        delta = updateLength / ((double)OPTIMAL_TIME);
        lastFpsTime += updateLength;
        fps++;
        if (lastFpsTime > 100000000 * count){
           milliSecondTimer += 0.1;
           count++;
        }
        if (lastFpsTime >= 1000000000){
            System.out.println("(FPS: "+fps+")");
            lastFpsTime = 0;
            fps = 0;
            count = 1;
        }
        loopsGone++;
        app.repaint();
        Thread.sleep( (lastLoopTime-System.nanoTime() + OPTIMAL_TIME)/1000000 );
    }
}

Thank you,

Sam


Solution

  • Perhaps you can change your x and y rocket coordinates to a float (I'm assuming they are int right now). And then when you are drawing, that is when you add the (int) cast. For instance,

    rockets.get(i).move(0, (int) (rockets.get(i).vSpeed / 60));
    

    Should not have an int cast.

    Also, you're going to want to change

    (rockets.get(i).vSpeed / 60));
    

    to

    (rockets.get(i).vSpeed / 60.0)); 
    

    You want your position to retain precision, which it's not doing currently with the int cast. This is what using a float would achieve.

    It doesn't look like scale is the issue. It would be an issue if you are trying to draw a parabola on an image that is, say, 10 x 10 pixels.

    Could you post your rocket class? I'd like to run this, looks interesting.

    Thanks for accepting my answer. Here is another issue I saw.

    For much better precision in position, you will want to update your position function much more frequent, meaning more than once every time you repaint. This is how my game loop looks.

    public void run() {
            long lastTime=System.nanoTime();
            final double amountOfTicks=60.0;
            double ns=1000000000/amountOfTicks;
            double delta=0; 
    
            int updates=0;
            int frames=0;
            long timer=System.currentTimeMillis();
    
            while (running) {
                long now=System.nanoTime();
                delta +=(now-lastTime) / ns;
                lastTime=now;
                if (delta>1) {      
                    update();     <--- this is where your gravity() method belongs
                    delta--;
                    updates++;
                }
                render();     <---- this will be your repaint method
                frames++;
                if (System.currentTimeMillis()-timer>1000) {
                    timer+=1000;
                    currUpdates=updates;
                    currFrames=frames;
                    updates=0;
                    frames=0;
                }
            }
        }
    

    What it does is updates spaceship positions once every millisecond, but only renders in 60 fps.