문제

O'k so I'm trying to write a simple collision detection between 2 moving ovals(circles) and update their position and speed using this guide I found. However the results I get are not quite polished, the objects ARE colliding and updating but not always correctly, they get tangled somethimes and I'm frustrated since I cant figure out why this is happening.
NOTE: please ignore the collisions of the objects with the borders, they get stuck sometimes(I need to change that and its NOT the issue I'm having trouble with)

Here is my code of the 2 classes I use:

package letifer.com;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class MaxwellsDemon extends JPanel{
    public static final long serialVersionUID = 1L;

    private final Dimension DIMS = new Dimension(800, 500);
    private final int UPDATE_INTERVAL = 20, BALLS_COUNT = 20;
    private final JFrame FRAME = new JFrame("MaxwellsDemon");
    private int leftCountR, leftCountB, rightCountR, rightCountB;
    private Ball balls[];
    private Random random;
    private Timer timer;

    public MaxwellsDemon(){
        super();
        initThis();
        initFrame();
        initBalls();
        registerTimer();
        timer.start();
    }

    private void initThis(){
        setSize(DIMS);
        setBackground(Color.white);
        random = new Random();
    }

    private void initFrame(){
        FRAME.setContentPane(new Container(){
            public void paint(Graphics g){
                super.paint(g);
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
                for(int i=0; i<2; i++)
                    g2d.drawLine(0, DIMS.height+i, DIMS.width-1, DIMS.height+i);
                g2d.setColor(Color.blue);
                g2d.fillOval(25, DIMS.height+20, Ball.radius*2, Ball.radius*2);
                g2d.fillOval(DIMS.width-90, DIMS.height+20, Ball.radius*2, Ball.radius*2);
                g2d.setColor(Color.red);
                g2d.fillOval(25, DIMS.height+60, Ball.radius*2, Ball.radius*2);
                g2d.fillOval(DIMS.width-90, DIMS.height+60, Ball.radius*2, Ball.radius*2);
                g2d.setFont(new Font("Serif", Font.BOLD, 18));
                g2d.setColor(Color.black);
                g2d.drawString(String.format("= %d", leftCountB), 45, DIMS.height+32);
                g2d.drawString(String.format("= %d", leftCountR), 45, DIMS.height+72);
                g2d.drawString(String.format("= %d", rightCountB), DIMS.width-70, DIMS.height+32);
                g2d.drawString(String.format("= %d", rightCountR), DIMS.width-70, DIMS.height+72);
            }
        });
        FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        FRAME.getContentPane().setPreferredSize(new Dimension(DIMS.width, DIMS.height+100));
        FRAME.getContentPane().add(this);
        FRAME.setResizable(false);
        FRAME.pack();
        FRAME.setLocationRelativeTo(null);
        FRAME.setVisible(true);
    }

    private void initBalls(){
        balls = new Ball[BALLS_COUNT];
        leftCountR = 0; leftCountB = 0; rightCountR = 0; rightCountB = 0;
        for(int i=0, f=1; i<BALLS_COUNT; i++, f=-f){
            balls[i] = new Ball(new Point2D.Double(random.nextInt(DIMS.width-Ball.radius*2-1)+Ball.radius+1,
                                random.nextInt(DIMS.height-Ball.radius*2-1)+Ball.radius+1),
                                0.15,
                                random.nextDouble()*(Math.PI*2),
                                (f>0?Color.blue:Color.red));
            if(balls[i].c == Color.blue){
                if(balls[i].locOnScreen.x < DIMS.width/2){
                    leftCountB++;
                }else{
                    rightCountB++;
                }
            }else{
                if(balls[i].locOnScreen.x < DIMS.width/2){
                    leftCountR++;
                }else{
                    rightCountR++;
                }
            }
        }
    }

    private void update(){
        checkForCollisions();
        updateBalls();      
    }

    private void checkForCollisions(){
        for(int i=0; i<BALLS_COUNT-1; i++){
            for(int j=i+1; j<BALLS_COUNT; j++){
                double dx = balls[i].getX()-balls[j].getX();
                double dy = balls[i].getY()-balls[j].getY();
                Point2D.Double difference = new Point2D.Double(dx, dy);
                double distanceAtFrameEnd = Math.sqrt(dx*dx+dy*dy);
                double collisionDistance = Ball.radius*2;
                if(distanceAtFrameEnd < collisionDistance){

                    //go back in time :)
                    double millisecondsAfterCollision = moveBackToCollisionPoint(balls[i], balls[j], distanceAtFrameEnd, collisionDistance);

                    //calculate the new distance vector.
                    dx = balls[i].getX()-balls[j].getX();
                    dy = balls[i].getY()-balls[j].getY();
                    difference.setLocation(dx, dy);
                    Point2D.Double normalPlane = new Point2D.Double(difference.x, difference.y);
                    //normalize it
                    double distance = Math.sqrt(dx*dx+dy*dy);
                    normalPlane.x /= distance;
                    normalPlane.y /= distance;

                    //calculate the collision vector(rotate by 90deg PI/2).
                    Point2D.Double collisionPlane = new Point2D.Double(-normalPlane.y, normalPlane.x);

                    //calculate prior velocities relative the the collision plane and normal.
                    double velIx = balls[i].getVX(), velIy = balls[i].getVY();
                    double velJx = balls[j].getVX(), velJy = balls[j].getVY();
                    double n_vel1 = (velIx * normalPlane.x) + (velIy * normalPlane.y);
                    double c_vel1 = (velIx * collisionPlane.x) + (velIy * collisionPlane.y);
                    double n_vel2 = (velJx * normalPlane.x) + (velJy * normalPlane.y);
                    double c_vel2 = (velJx * collisionPlane.x) + (velJy * collisionPlane.y);

                    //calculate the scaler velocities of each object after the collision.
                    double n_vel1_after = ((n_vel1 * (1/*ballI mass*/ - 1/*ballJ mass*/)) + (2 * 1/*ballJ mass*/ * n_vel2)) / (1/*ballJ mass*/ + 1/*ballJ mass*/);
                    double n_vel2_after = ((n_vel2 * (1/*ballJ mass*/ - 1/*ballI mass*/)) + (2 * 1/*ballJ mass*/ * n_vel1)) / (1/*ballJ mass*/ + 1/*ballJ mass*/);
                    //double velObject2Tangent_After = c_vel2;
                    //double velObject1Tangent_After = c_vel1;

                    //convert the scalers to vectors by multiplying by the normalized plane vectors.
                    Point2D.Double vec_n_vel2_after = new Point2D.Double(n_vel2_after * normalPlane.x, n_vel2_after * normalPlane.y);
                    Point2D.Double vec_c_vel2 = new Point2D.Double(c_vel2 * collisionPlane.x, c_vel2 * collisionPlane.y);
                    Point2D.Double vec_n_vel1_after = new Point2D.Double(n_vel1_after * normalPlane.x, n_vel1_after * normalPlane.y);
                    Point2D.Double vec_c_vel1 = new Point2D.Double(c_vel1 * collisionPlane.x, c_vel1 * collisionPlane.y);

                    //combine the vectors back into a single vector in world space.
                    Point2D.Double vel1_after = new Point2D.Double(vec_n_vel1_after.x + vec_c_vel1.x, vec_n_vel1_after.y + vec_c_vel1.y);
                    Point2D.Double vel2_after = new Point2D.Double(vec_n_vel2_after.x + vec_c_vel2.x, vec_n_vel2_after.y + vec_c_vel2.y);

                    //reapply the move-back from before the collision (using the post collision velocity)
                    Point2D.Double object1AdjustedPositionAfterCollision = new Point2D.Double(balls[i].getX() + vel1_after.x * millisecondsAfterCollision, balls[i].getY() + vel1_after.y * millisecondsAfterCollision);
                    Point2D.Double object2AdjustedPositionAfterCollision = new Point2D.Double(balls[j].getX() + vel2_after.x * millisecondsAfterCollision, balls[j].getY() + vel2_after.y * millisecondsAfterCollision);

                    //set the objects new positions and velocities.
                    balls[i].setX(object1AdjustedPositionAfterCollision.x);
                    balls[i].setY(object1AdjustedPositionAfterCollision.y);
                    balls[j].setX(object2AdjustedPositionAfterCollision.x);
                    balls[j].setY(object2AdjustedPositionAfterCollision.y);

                    balls[i].setVX(vel1_after.x);
                    balls[i].setVY(vel1_after.y);
                    balls[j].setVX(vel2_after.x);
                    balls[j].setVY(vel2_after.y);                   
                }
            }
        }
    }

    private double moveBackToCollisionPoint(Ball object1, Ball object2, double distanceAtFrameEnd, double collisionDistance){
        //calc the position at the start of the frame.
        double object1PosAtFrameStart_X = (object1.getX() - object1.getVX() * (double)UPDATE_INTERVAL);
        double object1PosAtFrameStart_Y = (double)(object1.getY() - object1.getVY() * (double)UPDATE_INTERVAL);
        Point2D.Double object1PosAtFrameStart = new Point2D.Double(object1PosAtFrameStart_X, object1PosAtFrameStart_Y);

        double object2PosAtFrameStart_X = (object2.getX() - object2.getVX() * (double)UPDATE_INTERVAL);
        double object2PosAtFrameStart_Y = (object2.getY() - object2.getVY() * (double)UPDATE_INTERVAL);
        Point2D.Double object2PosAtFrameStart = new Point2D.Double(object2PosAtFrameStart_X, object2PosAtFrameStart_Y);

        //calc the distance between the objects at the start of the frame.
        Point2D.Double differenceAtFrameStart = new Point2D.Double(object2PosAtFrameStart.x - object1PosAtFrameStart.x, object2PosAtFrameStart.y - object1PosAtFrameStart.y);
        double distanceAtFrameStart = Math.sqrt(differenceAtFrameStart.x*differenceAtFrameStart.x + differenceAtFrameStart.y*differenceAtFrameStart.y);

        //calculate the total distance change during the frame and the required change to reach the collision.
        double distanceTotalDelta = distanceAtFrameEnd - distanceAtFrameStart;
        double distanceDeltaToCollision = collisionDistance - distanceAtFrameStart;

        // Calculate the percentage change to the collision and after the collision.
        double percentageDeltaToCollision = distanceDeltaToCollision / distanceTotalDelta;
        double percentageDeltaAfterCollision = 1 - percentageDeltaToCollision;

        // Calculate the time before and after the collision in the frame.
        double millisecondsToCollision = (double)UPDATE_INTERVAL * percentageDeltaToCollision;
        double millisecondsAfterCollision = (double)UPDATE_INTERVAL * percentageDeltaAfterCollision;

        // Calculate and move the objects to their positions at the point of collision.
        double object1PosAtCollision_X = (object1PosAtFrameStart_X + object1.getVX() * millisecondsToCollision);
        double object1PosAtCollision_Y = (object1PosAtFrameStart_Y + object1.getVY() * millisecondsToCollision);
        Point2D.Double object1PosAtCollision = new Point2D.Double(object1PosAtCollision_X, object1PosAtCollision_Y);
        object1.setX(object1PosAtCollision.x);
        object1.setY(object1PosAtCollision.y);

        double object2PosAtCollision_X = (object2PosAtFrameStart_X + object2.getVX() * millisecondsToCollision);
        double object2PosAtCollision_Y = (object2PosAtFrameStart_Y + object2.getVY() * millisecondsToCollision);
        Point2D.Double object2PosAtCollision = new Point2D.Double(object2PosAtCollision_X, object2PosAtCollision_Y);
        object2.setX(object2PosAtCollision.x);
        object2.setY(object2PosAtCollision.y);

        return millisecondsAfterCollision;
    }

    private void updateBalls(){
        leftCountR = 0; leftCountB = 0; rightCountR = 0; rightCountB = 0;
        for(int i=0; i<BALLS_COUNT; i++){
            balls[i].update(UPDATE_INTERVAL, DIMS);
            if(balls[i].c == Color.blue){
                if(balls[i].locOnScreen.x < DIMS.width/2){
                    leftCountB++;
                }else{
                    rightCountB++;
                }
            }else{
                if(balls[i].locOnScreen.x < DIMS.width/2){
                    leftCountR++;
                }else{
                    rightCountR++;
                }
            }
        }
    }

    private void registerTimer(){
        timer = new Timer(UPDATE_INTERVAL, new ActionListener() {           
            @Override
            public void actionPerformed(ActionEvent arg0) {
                update();
                FRAME.repaint();
            }
        });
        timer.setRepeats(true);
        timer.setDelay(UPDATE_INTERVAL);
    }

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));

        for(int i=0; i<BALLS_COUNT; i++){
            g2d.setColor(balls[i].c);
            g2d.fillOval(balls[i].locOnScreen.x-Ball.radius, balls[i].locOnScreen.y-Ball.radius, Ball.radius*2, Ball.radius*2);
        }
    }

    public static void main(String[] args){
        new MaxwellsDemon();
    }

}

and the Ball class:

package letifer.com;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;

public class Ball{

    public static int radius = 7;

    private Point2D.Double loc;
    private double speed;
    private double dir;
    Point locOnScreen;  
    Color c;

    public Ball(Point2D.Double loc, double speed, double dir, Color c){
        this.loc = loc;
        this.speed = speed;
        this.dir = dir;
        this.c = c;
        this.locOnScreen = new Point((int)(loc.x+0.5f), (int)(loc.y+0.5f));
    }

    public void update(int timePassed, Dimension dims){     
        loc.x = loc.x + speed * timePassed * Math.cos(dir);
        loc.y = loc.y + speed * timePassed * Math.sin(dir);
        if(loc.x <= 0 + radius){
            dir = (-dir+Math.PI+2*Math.PI)%(2*Math.PI);
        }
        if(loc.x >= dims.width - radius - 1){
            dir = (-dir+Math.PI+2*Math.PI)%(2*Math.PI);
        }
        if(loc.y <= 0 + radius){
            dir = (-dir+2*Math.PI)%(2*Math.PI);
        }
        if(loc.y >= dims.height - radius - 1){
            dir = (-dir+2*Math.PI)%(2*Math.PI);
        }   
        locOnScreen.setLocation((int)(loc.x+0.5f), (int)(loc.y+0.5f));
    }

    public double getX(){
        return loc.x;
    }

    public double getY(){
        return loc.y;
    }

    public void setX(double x){
        loc.x = x;
    }

    public void setY(double y){
        loc.y = y;
    }

    public double getVX(){
        return speed * Math.cos(dir);
    }

    public double getVY(){
        return speed * Math.sin(dir);
    }

    public void setVX(double vx){
        double vy = getVY();
        speed = Math.sqrt(vy*vy+vx*vx);
        dir = Math.atan2(vx, vy);
    }

    public void setVY(double vy){
        double vx = getVX();
        speed = Math.sqrt(vy*vy+vx*vx);
        dir = Math.atan2(vx, vy);
    }
}

PS:I'm not much of a java coder so any notes on good/bad coding practice will be appreciated.

도움이 되었습니까?

해결책

Let ui and vi be the positions of circle i before and after the current timestep, assuming no collisions, respectively, for i = 1, 2. Let ri be the radius of circle i, for i = 1, 2. Let R = r1 + r2.

Assume that the circles move at a constant speed over the duration of the timestep. That is, the path of ball i is described by pi(t) = (1 - t)ui + tvi, t in [0,1], assuming no collision.

We can detect a collision by solving the equation ||p2(t) - p1(t)|| = r1 + r2 = R for t. Let's simplify this equation.

||p2(t) - p1(t)|| = R

||(1-t)(u2 - u1) + t(v2 - v1)||2 = R2

||(1 - t)U + tV||2 = R2 where U = u2 - u1 and V = v2 - v1

<(1 - t)U + tV, (1 - t)U + tV> = R2

(1 - t)<U, (1 - t)U + tV> + t<V, (1 - t)U + tV> = R2

(1 - t)2<U, U> + (1 - t)t<U, V> + t(1 - t)<V, U> + t2<V, V> = R2

(1 - t)2<U, U> + 2(1 - t)t<U, V> + t2<V, V> = R2

At this point it should be clear: this is just a quadratic equation in t! Solve for the possible values of t. If there is no real solution in the interval [0,1], then there is no collision. If there are any real solutions in the interval [0,1], the earliest (minimum) solution in the interval [0,1] describes the time of collision.

Let T be the described earliest real solution in the interval [0,1]. Then p1(T) and p2(T) are the positions at the time of collision. The vector between these positions is the normal of the collision.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top