Как обработать столкновение шара с шаром с помощью тригонометрии вместо векторов? - PullRequest
4 голосов
/ 10 сентября 2010

Я делаю упрощенную версию приложения по физике шаров, которое можно найти по адресу в этом вопросе .Я многому научился, читая код и ссылки, предоставленные по этому вопросу, но в моей программе я не использую векторы, я просто обновляю координаты моих шаров (каждый из которых имеет случайный угол и скорость) функциями тригонометрии, когда онипоразить стены.

Везде есть информация о том, как справляться со столкновениями шариков без тригонометрии, но я не нашел ни одной, которая объясняла бы, как это можно сделать с тригонометрия.

- РЕДАКТИРОВАТЬ 13.09.2010 -

Успех ... вроде ... Я сделал то, что хотел сделать, но я не смог сделать это так, как хотел.Если есть способ вычислить столкновения шара с шаром без использования векторов, это ускользнуло от меня.Несмотря на это, векторы, кажется, являются более простым способом обработки коллизий всех типов ... Я просто хотел бы знать, что, когда я запустил свою программу ... спас бы меня два или три дня работы :) ВсеКод для моей (полной?) программы приведен ниже.Я добавил несколько полезных функций, таких как тени и уменьшающийся радиус шара, который действительно позволяет увидеть разницу в массе двух шариков, когда большой шарик ударяет по маленькому шарику.Всего существует пять файлов классов: AddLogic.java, Ball.java, BallBuilder.java, MouseEventHandler.java и Vector2D.java.

AddLogic.java

import java.awt.*;
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;
import java.util.ArrayList;

public class AddLogic implements Runnable {//Make AddLogic a runnable task.

    private BallBuilder ballBuilder;
    private BufferStrategy strategy;
    private static ArrayList objectsToDraw = new ArrayList();
    private int floorHeight = 33;

    public AddLogic(BallBuilder ballBuilder, BufferStrategy strategy) {
        this.ballBuilder = ballBuilder;
        this.strategy = strategy;
    }

    private void logic(BallBuilder ballBuilder, BufferStrategy strategy) {
        this.ballBuilder = ballBuilder;
        this.strategy = strategy;

        while (true) {//Main loop. Draws all objects on screen and calls update methods.
            Graphics2D g = (Graphics2D) strategy.getDrawGraphics();//Creates the Graphics2D object g and uses it with the double buffer.
            g.setColor(Color.gray);
            g.fillRect(0, 0, ballBuilder.getWidth(), ballBuilder.getHeight());//Draw the wall.
            g.setColor(Color.lightGray);
            g.fillRect(0, ballBuilder.getHeight() - floorHeight, ballBuilder.getWidth(), floorHeight);//Draw the floor.
            g.setColor(Color.black);
            g.drawLine(0, ballBuilder.getHeight() - floorHeight, ballBuilder.getWidth(), ballBuilder.getHeight() - floorHeight);//Draw the line between the wall and floor.

            if (objectsToDrawIsEmpty() == true) {//If no balls have been made display message telling users how to make new ball.
                g.setColor(Color.red);
                g.drawString("Click Mouse For New Ball", (ballBuilder.getWidth() / 2) - 70, ballBuilder.getHeight() / 2);
            }

            for (int i = 0; i < objectsToDraw.size(); i++) {//Draw shadows for all balls.
                Ball ball = (Ball) objectsToDraw.get(i);
                g.setColor(Color.darkGray);
                g.fillOval(
                        (int) ball.ballPosition.getX() - (int) ((ball.ballPosition.getY() / (350 / ball.getBallRadius())) / 2),
                        ballBuilder.getHeight() - (floorHeight / 2) - (int) ((ball.ballPosition.getY() / (1250 / ball.getBallRadius())) / 2),
                        (int) ball.ballPosition.getY() / (350 / ball.getBallRadius()),
                        (int) ball.ballPosition.getY() / (1250 / ball.getBallRadius()));
            }

            for (int i = 0; i < objectsToDraw.size(); i++) {//Draw all balls by looping through them and checking for any vector or collision updates that need to be made.
                Ball ball = (Ball) objectsToDraw.get(i);
                g.setColor(ball.getBallColor());
                g.fillOval(
                        (int) ball.ballPosition.getX() - ball.getBallRadius(),
                        (int) ball.ballPosition.getY() - ball.getBallRadius(),
                        ball.getBallRadius() * 2,
                        ball.getBallRadius() * 2);

                vectorUpdate(ball);//Update ball vector coordinates.

                collisionCheck(ball);//Check ball to ball and ball to wall collisions.

            }

            if (MouseEventHandler.mouseEventCheck() == true) {// Creates a new ball when mouse is clicked.
                Ball ball = new Ball(ballBuilder);
                objectsToDraw.add(ball); //Adds the new ball to the array list.
                MouseEventHandler.mouseEventUpdate(); //Resets the mouse click event to false.

            }

            g.dispose();//To aid Java in garbage collection.
            strategy.show();//Show all graphics drawn on the buffer.

            try {//Try to make thread sleep for 5ms.  Results in a frame rate of 200FPS.
                Thread.sleep(5);
            }

            catch (Exception e) {//Catch any exceptions if try fails.

            }
        }
    }

    private void vectorUpdate(Ball ball) {//Update the ball vector based upon the ball's current position and its velocity.

        ball.ballPosition.setX(ball.ballPosition.getX() + ball.ballVelocity.getX());
        ball.ballPosition.setY(ball.ballPosition.getY() + ball.ballVelocity.getY());

    }

    private void collisionCheck(Ball ball) {//Check for ball to wall collisions. Call check for ball to ball collisions at end of method.

        if (ball.ballPosition.getX() - ball.getBallRadius() < 0) {//Check for ball to left wall collision.
            ball.ballPosition.setX(ball.getBallRadius());
            ball.ballVelocity.setX(-(ball.ballVelocity.getX()));
            ball.decreaseBallRadius(ball);//Decrease ball radius by one pixel. Called on left, top, and right walls, but not bottom because it looks weird watching shadow get smaller during bottom bounce.

        }

        else if (ball.ballPosition.getX() + ball.getBallRadius() > ballBuilder.getWidth()) {//Check for ball to right wall collision.
            ball.ballPosition.setX(ballBuilder.getWidth() - ball.getBallRadius());
            ball.ballVelocity.setX(-(ball.ballVelocity.getX()));
            ball.decreaseBallRadius(ball);//Decrease ball radius by one pixel. Called on left, top, and right walls, but not bottom because it looks weird watching shadow get smaller during bottom bounce.
        }

        if (ball.ballPosition.getY() - ball.getBallRadius() < 0) {//Check for ball to top wall collision.
            ball.ballPosition.setY(ball.getBallRadius());
            ball.ballVelocity.setY(-(ball.ballVelocity.getY()));
            ball.decreaseBallRadius(ball);//Decrease ball radius by one pixel. Called on left, top, and right walls, but not bottom because it looks weird watching shadow get smaller during bottom bounce.
        }

        else if (ball.ballPosition.getY() + ball.getBallRadius() + (floorHeight / 2) > ballBuilder.getHeight()) {//Check for ball to bottom wall collision.  Floor height is accounted for to give the appearance that ball is bouncing in the center of the floor strip.
            ball.ballPosition.setY(ballBuilder.getHeight() - ball.getBallRadius() - (floorHeight / 2));
            ball.ballVelocity.setY(-(ball.ballVelocity.getY()));
        }

        for (int i = 0; i < objectsToDraw.size(); i++) {//Check to see if a ball is touching any other balls by looping through all balls and checking their positions.
            Ball otherBall = (Ball) objectsToDraw.get(i);

            if (ball != otherBall && Math.sqrt(Math.pow(ball.ballPosition.getX() - otherBall.ballPosition.getX(), 2.0) + Math.pow(ball.ballPosition.getY() - otherBall.ballPosition.getY(), 2.0)) < ball.getBallRadius() + otherBall.getBallRadius()) {
                resolveBallToBallCollision(ball, otherBall);//If the ball is hitting another ball calculate the new vectors based on the variables of the balls involved.
            }

        }

    }

    private void resolveBallToBallCollision(Ball ball, Ball otherBall) {//Calculate the new vectors after ball to ball collisions.
        Vector2D delta = (ball.ballPosition.subtract(otherBall.ballPosition));//Difference between the position of the two balls involved in the collision.
        float deltaLength = delta.getLength();//The (x, y) of the delta squared.

        Vector2D minimumTranslationDistance = delta.multiply(((ball.getBallRadius() + otherBall.getBallRadius()) - deltaLength) / deltaLength);//The minimum distance the balls should move apart once they.

        float ballInverseMass = 1 / ball.getBallMass();//half the ball mass.
        float otherBallInverseMass = 1 / otherBall.getBallMass();//half the other ball mass.

        ball.ballPosition = ball.ballPosition.add(minimumTranslationDistance.multiply(ballInverseMass / (ballInverseMass + otherBallInverseMass)));//Calculate the new position of the ball.
        otherBall.ballPosition = otherBall.ballPosition.subtract(minimumTranslationDistance.multiply(otherBallInverseMass / (ballInverseMass + otherBallInverseMass)));//Calculate the new position of the other ball.

        Vector2D impactVelocity = (ball.ballVelocity.subtract(otherBall.ballVelocity));//Find the veloicity of the impact based upon the velocities of the two balls involved.
        float normalizedImpactVelocity = impactVelocity.dot(minimumTranslationDistance.normalize());//

        if (normalizedImpactVelocity > 0.0f) {//Returns control to calling object if ball and other ball are intersecting, but moving away from each other.
            return;
        }

        float restitution = 2.0f;//The constraint representing friction. A value of 2.0 is 0 friction, a value smaller than 2.0 is more friction, and a value over 2.0 is negative friction.

        float i = (-(restitution) * normalizedImpactVelocity) / (ballInverseMass + otherBallInverseMass);
        Vector2D impulse = minimumTranslationDistance.multiply(i);

        ball.ballVelocity = ball.ballVelocity.add(impulse.multiply(ballInverseMass));//Change the velocity of the ball based upon its mass.
        otherBall.ballVelocity = otherBall.ballVelocity.subtract(impulse.multiply(otherBallInverseMass));//Change the velocity of the other ball based upon its mass.
    }

    public static boolean objectsToDrawIsEmpty() {//Checks to see if there are any balls to draw.
        boolean empty = false;
        if (objectsToDraw.isEmpty()) {
            empty = true;
        }

        return empty;

    }

    public void run() {//Runs the AddLogic instance logic in a new thread.
        logic(ballBuilder, strategy);
    }
}

Ball.java

import java.awt.*;

public class Ball {

    private int ballRadius;
    private float ballMass;
    public Vector2D ballPosition = new Vector2D();
    public Vector2D ballVelocity = new Vector2D();
    private Color ballColor;

    public Ball(BallBuilder ballBuilder) {//Construct a new ball.
        this.ballRadius = 75;//When ball is created make its radius 75 pixels.
        this.ballMass = ((float)(4 / 3 * Math.PI * Math.pow(ballRadius, 3.0)));//When ball is created make its mass that the volume of a sphere the same size.
        this.ballPosition.set(ballRadius, ballBuilder.getHeight() - ballRadius);//When ball is created make its starting coordinates the bottom left hand corner of the screen.
        this.ballVelocity.set(randomVelocity(), randomVelocity());//When ball is created make its (x, y) velocity a random value between 0 and 2.

        if (AddLogic.objectsToDrawIsEmpty() == true) {//If the ball being created is the first ball, make it blue, otherwise pick a random color.
            this.ballColor = Color.blue;
        } else {
            this.ballColor = randomColor();
        }

    }

    public void decreaseBallRadius(Ball ball){//Decrease the ball radius.
        if(ball.getBallRadius() <= 15){//If the ball radius is less than or equal to 15 return control to calling object, else continue.
            return;
        }

        ball.setBallRadius(ball.getBallRadius() - 1);//Decrease the ball radius by 1 pixel.
        ball.setBallMass((float)(4 / 3 * Math.PI * Math.pow(ballRadius, 3.0)));//Recalcualte the mass based on the new radius.

    }

    public int getBallRadius() {
        return ballRadius;
    }

    public float getBallMass(){
        return ballMass;
    }

    public Color getBallColor() {
        return ballColor;
    }

    private void setBallRadius(int newBallRadius) {
        this.ballRadius = newBallRadius;
    }

    private void setBallMass(float newBallMass){
        this.ballMass = newBallMass;
    }

    private float randomVelocity() {//Generate a random number between 0 and 2 for the (x, y) velocity of a ball.
        float speed = (float)(Math.random() * 2);
        return speed;
    }

    private Color randomColor() {//Generate a random color for a new ball based on the generation of a random red, green, and blue value.
        int red = (int) (Math.random() * 255);
        int green = (int) (Math.random() * 255);
        int blue = (int) (Math.random() * 255);
        ballColor = new Color(red, green, blue);
        return ballColor;
    }

}

BallBuilder.java

import java.awt.*;
import java.awt.image.*;
import java.util.concurrent.*;
import javax.swing.*;

public class BallBuilder extends Canvas{
    private int frameHeight = 600;
    private int frameWidth = 800;

    private static BufferStrategy strategy;//Create a buffer strategy named strategy.

    public BallBuilder(){
        setIgnoreRepaint(true);//Tell OS that we will handle any repainting manually.
        setBounds(0,0,frameWidth,frameHeight);

        JFrame frame = new JFrame("Bouncing Balls");
        JPanel panel = new JPanel();

        panel.setPreferredSize(new Dimension(frameWidth, frameHeight));
        panel.add(this);

        frame.setContentPane(panel);
        frame.pack();
        frame.setResizable(false);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        addMouseListener(new MouseEventHandler());

        createBufferStrategy(2);//Create a double buffer for smooth graphics.
    strategy = getBufferStrategy();//Apply the double buffer to the buffer strategy named strategy.

    }

    public static void main(String[] args) {
        BallBuilder ballBuilder = new BallBuilder(); // Creates a new ball builder.
        ExecutorService executor = Executors.newSingleThreadExecutor();//Creates a thread executor that uses a single thread.
        executor.execute(new AddLogic(ballBuilder, strategy));//Executes the runnable task AddLogic on the previously created thread.

    }

}

MouseEventHandler.java

import java.awt.event.*;

public class MouseEventHandler extends MouseAdapter{

    private static boolean mouseClicked = false;

    public void mousePressed(MouseEvent e){//If either of the mouse buttons is pressed the mouse clicked variable is set to true.
        mouseClicked = true;
    }

    public static void mouseEventUpdate(){//When called, sets the mouse clicked variable back to false.
        mouseClicked = false;
    }

    public static boolean mouseEventCheck(){//Returns the state of the mouse clicked variable.
        if(mouseClicked){
            return true;
        }

        else{
            return false;
        }

    }

}

Vector2D

public class Vector2D {//A class that takes care of ball position and speed vectors.

    private float x;
    private float y;

    public Vector2D() {
        this.setX(0);
        this.setY(0);
    }

    public Vector2D(float x, float y) {
        this.setX(x);
        this.setY(y);
    }

    public void set(float x, float y) {
        this.setX(x);
        this.setY(y);
    }

    public void setX(float x) {
        this.x = x;
    }

    public void setY(float y) {
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }

    public float dot(Vector2D v2) {//Speciality method used during calculations of ball to ball collisions.
        float result = 0.0f;
        result = this.getX() * v2.getX() + this.getY() * v2.getY();
        return result;
    }

    public float getLength() {
        return (float) Math.sqrt(getX() * getX() + getY() * getY());
    }

    public Vector2D add(Vector2D v2) {
        Vector2D result = new Vector2D();
        result.setX(getX() + v2.getX());
        result.setY(getY() + v2.getY());
        return result;
    }

    public Vector2D subtract(Vector2D v2) {
        Vector2D result = new Vector2D();
        result.setX(this.getX() - v2.getX());
        result.setY(this.getY() - v2.getY());
        return result;
    }

    public Vector2D multiply(float scaleFactor) {
        Vector2D result = new Vector2D();
        result.setX(this.getX() * scaleFactor);
        result.setY(this.getY() * scaleFactor);
        return result;
    }

    public Vector2D normalize() {//Speciality method used during calculations of ball to ball collisions.
        float length = getLength();
        if (length != 0.0f) {
            this.setX(this.getX() / length);
            this.setY(this.getY() / length);
        } else {
            this.setX(0.0f);
            this.setY(0.0f);
        }

        return this;
    }

}

Ответы [ 3 ]

5 голосов
/ 12 сентября 2010

Угол скорости меняется, когда мяч отскакивает, его следует менять после каждого отскока.

alt text

Также, как вы увидите позже,ЗНАЧЕНИЕ скорости (называемое «модулем») также изменяется, когда сталкиваются два шара.

РЕДАКТИРОВАТЬ :::

Мне кажется, что вы ускоряете шары

The

int x = (int) (ball.getBallTempX() + 
              (ball.getBallPathLength() * Math.cos(ball.getBallAngle())));  

Кажется, соответствует

int x = (int) (ball.getBallTempX() + 
              (ball.getBallSpeed() * Math.cos(ball.getBallAngle())));  

И то же самое для "у"

Я прав?

Редактировать 2::

На самом деле вам также не нужны TempX, TempY, PreviousCenterX и PreviousCenterY.

Попробуйте эти методы

private int xCoordinateUpdate(Ball ball) {

   int x = (int) (ball.getBallCenterX()+ (ball.Speed() 
                                      * Math.cos(ball.getBallAngle())));

return x;

(то же самое дляY)

Редактировать 3 ::

Еще один ...:)

Вам нужно кодировать эту формулу (любой другойошибка при попытке столкновения шаров):

Псевдокод>

BallCenterX = BallCenterX + BallSpeed ​​* Math.cos (угол)

BallCenterY = BallCenterY + BallSpeed ​​* Math.sin(угол)

Забудьте любой "темп"и "старые" значения.Они ухудшат вашу способность сталкиваться с мячом.

2 голосов
/ 12 сентября 2010

alt text

2 голосов
/ 12 сентября 2010

Я начну этот ответ и продолжу редактировать его шаг за шагом, пока мы не закончим. Я постараюсь привести вас к «полу-векторизованной» версии программы, стараясь свести к минимуму усилия.
Пожалуйста, продолжайте обновлять код по мере продвижения и комментируйте мои предложения.

Сначала несколько вещей:

В

      private double randomBallAngle(){  
         ballAngle = Math.toRadians((Math.random()*90));  
         return ballAngle;  
      }

Вы работаете в радианах, но в yCoordinateUpdate и yCoordinateUpdate кажется, что вы используете тот же угол в градусах (потому что вы сравниваете с 90).

Использовать радианы проще для всей математики.

Кроме того, переменные GoingUp и т. Д. Не нужны, так как об этом позаботится угол скорости.

Вы можете установить начальный случайный угол в интервале (0 ... 2 Pi).

Вам следует изменять (фактически отражать) угол скорости после каждого столкновения границы (и после этого, когда мы закончим, после каждого столкновения между шарами тоже).

Для этого отражения, законы:

Upper or lower wall:    New Angle =  - OldAngle 

For Right or left wall: New Angle = Pi - OldAngle  

Мы сохраним отражения на вершине на потом ...

...