如何使用三角函数而不是向量来处理球与球之间的碰撞?

4
我正在制作一个简化版的球体物理应用程序,该应用程序可在此SO问题中找到。我通过阅读该问题提供的代码和链接获得了很多收获,但在我的程序中,我不使用向量,而是使用三角函数更新我的球的坐标(每个球都有随机角度和速度),当它们撞到墙壁时。
有关如何处理球与球之间的碰撞的信息随处可见,但我发现没有任何解释如何使用三角函数进行处理的内容。
--编辑9/13/2010--
成功...有点...我完成了我想做的事情,但我无法按照我想要的方式完成。如果有一种方法可以在不使用向量的情况下计算球与球之间的碰撞,那么我还未掌握。即便如此,向量似乎是处理所有类型碰撞的更容易的方法...我只希望我在开始编写程序时就知道这一点...这会为我节省两三天的工作 :) 我的(完整?)程序的所有代码如下。我添加了一些很棒的功能,比如阴影和逐渐减小的球半径,这真的让你看到大球撞击小球时两个球质量的差异。总共有五个类文件,AddLogic.javaBall.javaBallBuilder.javaMouseEventHandler.javaVector2D.javaAddLogic.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;
    }

}

1
向量是三角学中的野兽,它是一种缩短方程式的方式,可以一次性为所有维度编写一个方程式。 - Dr. belisarius
我现在明白了,问题是我的程序快要完成了,而且我所有的更新都是用基本三角函数完成的,如果我引入最近学到的向量知识,那么我迄今为止编写的所有碰撞检测都将毫无意义。目前看来,我打算尝试将两者重叠,使用我已经有的碰撞检测,但仅在球与球之间的碰撞中使用向量。这似乎是一种浪费(如果当时我知道现在所知道的东西,我会将向量用于所有内容),但除非有人知道如何排除向量,否则我就束手无策了。 - ubiquibacon
@typoknig 感谢您发布代码。我认为有很多可以改进的地方,例如,UpdateX应该只有一两行代码(基本上是newX = oldX + speed * Math.Cos(angle))。但是在类中有一些变量您没有包括,这对我来说不太清楚,所以我不想冒险给出错误的答案,这会浪费我们双方很多时间。请尽量发布完整的代码。谢谢!...(并在评论中发布文本“@belisarius”,这样我就会收到SO消息提醒) - Dr. belisarius
@belisarius,问题已更新。还有感谢您让我知道 "@" 符号的作用。我通常加上它只是因为看到其他人这样做,但我没有意识到它会提醒用户。是否有其他类似于 SO 使用惯例的页面? - ubiquibacon
@belisarius 我更新了我的问题中的代码到新的矢量化版本。看看吧...添加了一些不错的功能。 - ubiquibacon
显示剩余4条评论
3个回答

6
当球弹跳时,速度角度会改变,您应该在每次弹跳后进行更改。

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、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(angle)

BallCenterY = BallCenterY + BallSpeed * Math.sin(angle)

忘记任何“temp”和“old”值。它们会影响你的球碰撞功能。


@belisarius 不,我没有加速球。ballPathLength只是斜边长度。每一帧ballPathLength增加ballSpeed,这就使得球移动。由于我知道ballAngle和斜边(ballPathLength),所以我可以使用cossin来更新球的坐标。现在球始终保持初始速度,没有加速。 - ubiquibacon
@typoknig ballPathLength 没有任何物理意义。只需将其删除并在上述公式中用速度替换它。空间= 速度*时间。不需要斜边。 - Dr. belisarius
@belisarius 如果斜边(ballPathLength)保持不变,那么使用此公式移动我的球是不可能的。 斜边(ballPathLength)的增加是我移动球的方式。即使大幅提高球速并将其用于三角测量公式中来代替斜边(ballPathLength),也永远无法移动球,因为 ballSpeed 的值永远不会改变。 - ubiquibacon
@belisarius 我需要 Temp 的值,以便在球撞到墙壁时知道我的斜边起点。我原本想在发布代码之前删除 Previous 值……它们是矢量化尝试的一部分。你回答中的 Edit 2 并不起作用。我认为我使用的公式妨碍了你帮助我的努力。在 ballPathLength 中放置的任何值都必须满足以下要求:1.) 这是一个每帧增加的值 2.) 它代表了一条线的长度 该线的起点 在窗口的其中一条边缘上。 - ubiquibacon
@belisarius,现在我已经全部搞定了,非常感谢您的帮助!我需要在代码中进行一些清理,但是一旦完成,我会发布它(可能是明天)。再次感谢! - ubiquibacon
显示剩余7条评论

2

alt text


2
我将开始回答并逐步编辑直到完成。我会尝试引导您制作一个“半矢量化”版本的程序,尽可能减少工作量。
请在进展中持续更新代码,并对我的建议进行评论。
首先,有几件事情需要注意:
在...
      private double randomBallAngle(){  
         ballAngle = Math.toRadians((Math.random()*90));  
         return ballAngle;  
      }

您正在使用弧度单位,但在yCoordinateUpdateyCoordinateUpdate中似乎使用的是以度为单位的相同角度(因为您正在与90进行比较)。
对于所有涉及到的数学问题,使用弧度更容易。
另外,变量GoingUp等不需要,因为速度角度会处理它们。
您可以在区间(0 … 2 Pi)中设置初始随机角度。
应该在每次边界碰撞后(之后,在完成后,每次球间碰撞后)改变(实际上反射)速度角度。
对于这种反射,定律如下:
Upper or lower wall:    New Angle =  - OldAngle 

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

我们将稍后讨论有关顶点的反射问题...


@typoknig 我在下面发布一张图纸 - Dr. belisarius
@belisarius 看起来当球的角度小于或等于15度时,它们会粘在屏幕顶部。 - ubiquibacon
@typoknig 也就是说...setPathLength 是用来干什么的? - Dr. belisarius
@typoknig 它们也会固定在底部吗? - Dr. belisarius
@belisarius 每次球弹开墙壁时,我将ballPathLenght(斜边长度)设置为1,以便正确计算我的新坐标。每帧我都会将 ballSpeed(假设为5px) 添加到ballPathLenght(假设为1)中。因此,每帧ballPathLength都会增加ballSpeed,直到球撞到墙壁,然后将ballPathLenght重新设置为1,这样cossin就会为我计算出正确的坐标。 - ubiquibacon
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接