n-body模拟 - 随机发生的IndexOutOfBoundsException

时间:2015-03-12 17:16:24

标签: java

让我简短一点。我正在为学校项目创建一个n体仿真。我遇到了一个问题:我有时只在运行模拟时收到错误IndexOutOfBoundsException。我收到错误时似乎没有任何关联。我假设我正在摧毁尸体(通过碰撞检测方法)太快而无法注册,并且它试图访问不再存在的索引。我想

if(bodies.get(i)!=null&&bodies.get(n)!= null) 
更新方法中的

会修复它,但它没有。有人可以看看代码并告诉我可能导致错误的原因是什么?要重新创建错误:只需按“' o'在模拟过程中,或随机产生大量的物体,其中包括' m'直到它发生。请记住,我只是在高中,我没有很多编程经验。

MAIN:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Main extends JPanel implements Runnable, KeyListener{
    public static int width = 1400;
    public static int height = (width/16) * 9;
    public String title = "Orbital Mechanics Simulator -";
    public Dimension dim = new Dimension(width, height);
    public boolean running;

    Color bgColor = new Color(0x000000);
    public static long timeScale = (long) Math.pow(10,7.5); //how many simulation-seconds one IRL-second represents.
    public static long distanceScale = ((Physics.astUnit*10L) / width); //how many meters does one pixel represent

    ArrayList<Body> bodies = new ArrayList<Body>();
    long initFpsTime;
    long secondFpsTime;
    long nowTime;
    long lastTime;
    long timeElapsed;
    float deltaTime;

    int fps = 200; 

    public Main(){
        this.setPreferredSize(dim);
        this.setBackground(bgColor);
        this.start();
    }

    public void start(){
        running = true;
        Thread program = new Thread(this, "update");
        program.start();
    }

    public void run(){      
        lastTime = System.nanoTime();
        while(running){
            nowTime = System.nanoTime();
            deltaTime = nowTime - lastTime;
            update(deltaTime);
            initFpsTime = System.currentTimeMillis();
            if((initFpsTime - secondFpsTime) > Math.pow(10,3) / fps){
                render();
                secondFpsTime = System.currentTimeMillis();
            }
        }
    }

    public void update(float deltaTime){
        for(int i=0; i<bodies.size();i++){

            resetForces();

            bodies.get(i).update((float)(deltaTime / Math.pow(10,9))*timeScale);
            lastTime = System.nanoTime();

            //sets the forces for all bodies
            for(int n=0; n<bodies.size();n++){
                if(bodies.get(i)!=bodies.get(n)){
                    if(bodies.get(i)!=null&&bodies.get(n)!= null)
                        bodies.get(i).setForce(Physics.getFx(bodies.get(i), bodies.get(n)), Physics.getFy(bodies.get(i), bodies.get(n)));

                        //collision detection
                        if(Physics.getDistanceBetween(bodies.get(i), bodies.get(n)) < (bodies.get(i).radius + bodies.get(n).radius)*distanceScale){
                            collision(bodies.get(i),bodies.get(n)); 
                        }
                }
            }
        }

    }

    //DIFFERENT SYSTEMS
    public void solarSystem(){
        Body sun;
        Body earth;
        Body mars;

        sun = new Body("Sun", Physics.massSun, 20, 0,0, new Color(0xffff00), (float)0, (float)0);
        earth = new Body("earth", Physics.massEarth, 10, Physics.astUnit/distanceScale,0, new Color(0x0000ff), (float)0, (float)0);
        mars = new Body("Mars", Physics.massEarth, 10, (long)(1.5*Physics.astUnit/distanceScale) ,0, new Color(0x00ff00), (float)0, (float)0);

        earth.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(earth, sun), sun));
        mars.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(mars, sun), sun));

        bodies.add(sun);
        bodies.add(earth);
        bodies.add(mars);
    }

    public void twoBodies(){


        bodies.add(new Body("Sun", Physics.massSun, 20, 0,0, new Color(0xffff00), (float)0, (float)0));
        bodies.add(new Body("earth", Physics.massEarth, 10, Physics.astUnit/distanceScale,0, new Color(0x0000ff), (float)0, (float)0));
        bodies.add(new Body("Mars", Physics.massMars, 10, (long)(1.5*Physics.astUnit/(distanceScale)),0 ,new Color(0x00ff00), (float)0, (float)0));

        //earth.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(earth, sun), sun));
        //mars.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(mars, sun), sun));

    }

    public void createRandomBody(){

        bodies.add(new Body("randomBody",Physics.massSun,10, Physics.randomXPos(), Physics.randomYPos(), Physics.randomColor(),(float)0,(float)0));

    }

    public void createMassiveBody(){
        bodies.add(new Body("Sun",Physics.massSun,10, Physics.randomXPos(), Physics.randomYPos(), Physics.randomColor(),(float)0,(float)0));
    }

    public void createSmallBody(){
        bodies.add(new Body("Earth",Physics.massEarth,10, 0, 0, Physics.randomColor(), (float)0, (float)0));

    }

    public void createSystem(){

        for(int i=0; i<20;i++){
            for(int n=0; n<20;n++){
                bodies.add(new Body("Random", Physics.massSun, 4, -width/2 + n*20 , height/2 - i*20 , Color.WHITE, (float)0, (float)0 ));
            }
        }
    }

    public void resetForces(){
        if(bodies.get(0) != null);
            for(int i=0;i<bodies.size();i++){
                if(i<=0 && i < bodies.size()){
                    bodies.get(i).resetForce();
                }
        }
    }

    public void collision(Body a, Body b){

        Body newBody = new Body("newBody", a.mass+b.mass, 20, (a.xPos+b.xPos)/2, (a.yPos+b.yPos)/2, Physics.randomColor(), (a.mass*a.vx + b.mass*b.vx)/(a.mass+b.mass), (a.mass*a.vy+b.mass*b.vy)/(a.mass+b.mass));
        bodies.remove(a);
        bodies.remove(b);
        bodies.add(newBody);

    }

    public void render(){
        repaint();
    }

    public void keyPressed(KeyEvent event) {
        switch(event.getKeyChar()){
            case 'r': createRandomBody();
                break;
            case 'f': twoBodies();
                break;
            case 'm':
                createMassiveBody();
                break;
            case 'z':
                createSmallBody();
                break;
            case 'o': 
                createSystem();
                break;
            case 's': 
                solarSystem();
                break;
        }

    }

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D)g;

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //quick vs quality - preferring quality 

        for(int i=0;i<bodies.size();i++){
            bodies.get(i).displayPlanet(g2d);

        }
    }

    public static void gui(){
        Main main = new Main();
        JFrame frame = new JFrame();
        frame.setResizable(false);
        frame.setTitle(main.title);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(main);
        frame.addKeyListener(main);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String args[]){
        SwingUtilities.invokeLater(new Runnable(){ //Event dispatching thread
            public void run(){
                gui();
            }
        });
    }

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub

    }
}

BODY

import java.awt.Color;
import java.awt.Graphics;

public class Body {

    public static int xOrigo = Main.width / 2;
    public static int yOrigo = ((Main.width * 9) / 16) / 2;
    public static int numOfBodies; // how many bodies the program contains.
    public static float distanceScale = Main.distanceScale;

    public String name;
    public float mass, fx, fy, accX, accY, vx, vy, initVx, initVy, deltaX, deltaY;
    public long xDisplay, yDisplay;
    public long xPos, yPos;
    public int radius; 
    public Color color;


    public Body(String name, float mass, int radius, long xPos, long yPos, Color color, float initVx, float initVy){
        this.name = name;
        this.mass = mass;
        this.radius = radius;
        this.color = color;

        numOfBodies++;

        setX(xPos);
        setY(yPos);
        setVelocity(initVx, initVy);

    }

    public void update(float deltaTime){

        this.deltaX += this.vx * deltaTime;

        if(this.deltaX >= distanceScale){
            this.incX();
            deltaX = 0;
        }else if(this.deltaX <= -distanceScale){
            this.decX();
            deltaX = 0;
        }

        this.deltaY += this.vy * deltaTime;

        if(this.deltaY >= distanceScale){
            this.incY();
            deltaY = 0;
        }else if(this.deltaY <= -distanceScale){
            this.decY();
            deltaY = 0;
        }

        this.vx += (this.accX * deltaTime);
        this.vy += (this.accY * deltaTime);

    }

    public void setX(long xPos){
        this.xPos = xPos;
        this.xDisplay = xOrigo + xPos - radius;
    }

    public void setY(long yPos){
        this.yPos = yPos;
        this.yDisplay = yOrigo - yPos - radius;
    }

    public void incX(){
        this.xPos++;
        this.xDisplay = xOrigo + xPos - radius;
    }

    public void decX(){
        this.xPos--;
        this.xDisplay = xOrigo + xPos - radius;
    }


    public void incY(){
        this.yPos++;
        this.yDisplay = yOrigo - yPos - radius;
    }

    public void decY(){
        this.yPos--;
        this.yDisplay = yOrigo - yPos - radius;
    }

    public void setForce(float fx, float fy){
        this.fx += fx;
        this.accX = fx / this.mass;

        this.fy += fy;
        this.accY = fy / this.mass;
    }

    public void setVelocity(float vx, float vy){
        this.vx += vx;
        this.vy += vy;
    }


    public void displayPlanet(Graphics g){
        g.setColor(this.color);
        g.fillOval((int )this.xDisplay, (int)this.yDisplay, this.radius*2, this.radius*2); //temporary fix
    }

    public long getXPos(){
        return this.xPos;
    }

    public long getYPos(){
        return this.yPos;
    }

    public void resetForce(){
        this.fx = 0;
        this.fy = 0;
    }

}

物理学:

import java.awt.Color;
import java.util.Random;


public class Physics {
    public static Random rand = new Random();

    public static final double G = 6.67384*(Math.pow(10, -11));
    public static long astUnit = 149597871000L; //L IS TO INDICATE IT'S A LONG VALUE, otherwise neg value
    public static float massEarth = (float)(5.97219*Math.pow(10,24));
    public static float massSun = (float)(1.9891*Math.pow(10,30));
    public static float massMars = (float) (6.41693*(Math.pow(10,23)));

    public static float randomMass(){
        return (float) Math.pow(((rand.nextDouble()*(massSun-massEarth))),rand.nextDouble())+massEarth;
    }

    public static double randInitV(){
        return (double) rand.nextDouble()*Math.pow(10,4);
    }

    public static int randomXPos(){
        return rand.nextInt(Main.width)-Main.width/2;
    }

    public static int randomYPos(){
        return rand.nextInt(Main.height)-Main.height/2;
    }

    public static Color randomColor(){
        return new Color(rand.nextInt(0xffffff));
    }

    public static int randomRadius(){
        return rand.nextInt(50)+5;
    }

    public Physics(){

    }
    /*
    public static Vector getVectorBetween(Body a, Body b){

        float force = (float)((G*a.mass*b.mass) / Math.pow(getDistanceBetween(a,b),2));
        double angle = Math.atan2(Math.abs(a.y - b.y),Math.abs(a.x - b.x));

        Vector vector = new Vector(force,angle);
        return vector;

    }
    */

    public static double getInitVy(long d, Body a){
        return Math.sqrt((G*a.mass) / d);
    }

    public static float getFx(Body a, Body b){

        float force = getForceBetween(a,b);
        double angle = getAngleBetween(a,b);
        float fx = (float)(force*Math.cos(angle)); 

        if(a.xPos > b.xPos){
            return -fx;
        }else{
            return fx;
        }

    }

    public static float getFy(Body a, Body b){
        float force = getForceBetween(a,b);
        double angle = getAngleBetween(a,b);
        float fy = (float)(force*Math.sin(angle)); 

        if(a.yPos > b.yPos){
            return -fy;
        }else{
            return fy;
        }
    }

    public static float getForceBetween(Body a, Body b){
        float force = (float)((G*a.mass*b.mass) / Math.pow(getDistanceBetween(a,b),2));
        return force;
    }

    public static double getDistanceBetween(Body a, Body b){ 
        double xKatet = Math.abs(a.getXPos()*Main.distanceScale - b.getXPos()*Main.distanceScale);
        double yKatet = Math.abs(a.getYPos()*Main.distanceScale - b.getYPos()*Main.distanceScale);

        double distance = Math.hypot(xKatet, yKatet);
        return distance;
    }

    public static double getAngleBetween(Body a, Body b){
        long deltaX = Math.abs(a.xPos-b.xPos);
        long deltaY = Math.abs(a.yPos-b.yPos);

        double angle = Math.atan2(deltaY, deltaX);

        return angle;
    }


}

2 个答案:

答案 0 :(得分:1)

(鉴于信息和代码墙很少,我可能会走得太远,但有疑问,我可以删除答案)

您正在使用不同的线程访问相同的列表。碰撞处理方法

public void collision(Body a, Body b) {
    ...
    bodies.remove(a);
    bodies.remove(b);
    bodies.add(newBody);
}

由在Main#start()方法中启动的主物理线程执行。该线程正在修改列表。这可能发生 Swing Event Dispatch Thread(也是绘画的负责人)正在迭代paintComponent方法中的实体:

public void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    ...
    for(int i=0;i<bodies.size();i++){
        bodies.get(i).displayPlanet(g2d);

    }
}

添加其他检查无法解决此问题。你需要某种形式的同步。蛮力锤只是在bodies列表上同步:

public void collision(Body a, Body b) {
    ...
    synchronized (bodies) 
    {
        bodies.remove(a);
        bodies.remove(b);
        bodies.add(newBody);
    }
}

public void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    ...

    synchronized (bodies)
    {
        for(int i=0;i<bodies.size();i++){
            bodies.get(i).displayPlanet(g2d);
        }
    }
}

(基本上,这样的事情必须在一个线程可能读取列表的所有地方完成,而另一个线程到列表中)。

但同样:这是务实的。您可以考虑java.util.concurrent包中的一个线程安全数据结构,或者一些手动的,更细粒度的锁定解决方案,可能还有一些ReadWriteLock

在这里给出一个更集中的答案是太多的代码(但至少,我想指出(几乎可以肯定)你的问题的原因

答案 1 :(得分:1)

您的方法Main.update()中存在问题,其基本要素是:

public void update(float deltaTime){
    for(int i=0; i<bodies.size();i++){

        // ...

        //sets the forces for all bodies
        for(int n=0; n<bodies.size();n++){
            // ...
                    //collision detection
                    if(Physics.getDistanceBetween(bodies.get(i), bodies.get(n)) < (bodies.get(i).radius + bodies.get(n).radius)*distanceScale){
                        collision(bodies.get(i),bodies.get(n)); 
                    }
            }
    }
}

如果ibodies.size() - 1并且与另一个身体发生碰撞,则两个碰撞体将被替换为单个身体,从而将身体总数减少1.此时ibodies.size(),不是bodies的有效索引。然而,您继续内循环,在此期间再次执行bodies.get(i),生成IndexOutOfBoundsException