代码之家  ›  专栏  ›  技术社区  ›  ubiquibacon

如何用三角学而不是向量来处理球对球的碰撞?

  •  4
  • ubiquibacon  · 技术社区  · 15 年前

    我正在制作一个球物理应用程序的简化版本,可在 this SO question . 通过阅读这个问题提供的代码和链接,我收获了很多,但在我的程序中,我不使用向量,我只是在球碰到墙壁时用三角函数更新球的坐标(每个球都有随机的角度和速度)。

    关于如何处理球与球的碰撞,各地都有相关信息 没有 三角学,但我没有找到一个能解释它是怎么做到的。 具有 三角学

    --编辑9/13/2010--

    成功…有点。。。我做了我想做的,但我不能按我想做的去做。如果有一种方法不用矢量来计算球与球之间的碰撞,我就无法理解。即使如此,向量似乎是处理所有类型碰撞的一种更简单的方法…我只是希望我能知道当我开始我的程序时…我可以节省两三天的工作时间:)我的所有代码(完整吗?)程序如下。我添加了一些简洁的特性,比如阴影和球半径的减小,这让你看到了当一个大球打到一个小球时两个球的质量差异。总共有五个类文件, AddLogic.java , Ball.java , BallBuilder.java , MouseEventHandler.java Vector2D.java .

    AdLogLogic

    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);
        }
    }
    

    爪哇岛

    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.
    
        }
    
    }
    

    鼠标事件处理程序.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;
            }
    
        }
    
    }
    

    二维向量

    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 回复  |  直到 12 年前
        1
  •  5
  •   Dr. belisarius    15 年前

    当球弹起时,速度角会改变,每次弹起后你都应该改变速度角。

    另外,正如您稍后将看到的,当两个球碰撞时,速度值(称为“模量”)也会发生变化。

    <编辑::< /P>

    我觉得你在加速球

    <

    int x=(int)(ball.getBallTempx()。+
    (ball.getballpathlength()*math.cos(ball.getballangle());
    < /代码> 
    
    

    似乎符合

    int x=(int)(ball.getBallTempx()。+
    (ball.getballspeed()*math.cos(ball.getballangle());
    < /代码> 
    
    

    “Y”也一样

    我是对的吗?

    编辑2::。

    实际上,您不需要tempx、tempy、previoucenterx和previoucentery。

    试试这些方法

    private int xcoordinateupdate(球){
    
    int x=(int)(ball.getBallCenterx()+(ball.speed())
    *math.cos(ball.getBallangle());
    
    返回X;
    < /代码> 
    
    

    (Y也一样)

    编辑3::。

    再来一个…:

    您需要编写的是这个公式(当您试图碰撞球时,任何其他公式都将失败):。

    伪代码>。

    BallCenterX=BallCenterX+Ballspeed*Math.cos(角度)

    Ballcentery=Ballcentery+Ballspeed*数学.sin(角度)

    忘记任何“temp”和“old”值。它们会损害您的球碰撞功能。

    每次弹跳后都要换。

    alt text

    另外,正如您稍后将看到的,当两个球碰撞时,速度值(称为“模量”)也会发生变化。

    编辑::

    我觉得你在加速球

    这个

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

    似乎符合

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

    “Y”也一样

    我说的对吗?

    编辑2:

    实际上,你也不需要tempx,tempy,previoucenterx和previoucentery。

    试试这些方法

    private int xCoordinateUpdate(Ball ball) {
    
       int x = (int) (ball.getBallCenterX()+ (ball.Speed() 
                                          * Math.cos(ball.getBallAngle())));
    
    return x;
    

    (Y也一样)

    编辑3:

    再一个。。。:)

    您需要编写的是这个公式(当您试图碰撞球时,任何其他公式都将失败):

    伪代码& GT;

    BallCenterX=BallCenterX+Ballspeed*Math.cos(角度)

    Ballcentery=Ballcentery+Ballspeed*数学.sin(角度)

    忘记任何“temp”和“old”值。它们会损害你的球碰撞特征。

        2
  •  2
  •   Dr. belisarius    15 年前

    我会一步一步地开始编辑这个答案,直到完成为止。我将尝试引导您使用程序的“半矢量化”版本,尽量减少工作量。
    请继续更新代码,并对我的建议进行评论。

    首先,以下几点:

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

    你的工作是弧度的,但是 Y坐标更新 Y坐标更新 似乎你在用相同的角度来进行梯度测试(因为你将90分进行比较)。

    对所有涉及的数学来说,使用弧度更容易。

    此外,由于速度角会考虑到这一点,因此无需使用变容goingup等。

    您可以设置间隔中的初始随机角度(0…2π。

    你应该改变(事实上反映)每次边界碰撞后的速度角(然后,当我们完成后,在每次球间碰撞后)。

    对于这种反思,法律是:

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

    我们将保留顶点上的反射,稍后…

        3
  •  2
  •   Dr. belisarius    15 年前

    推荐文章