在重力作用下弹跳的球不会停在界外

The bouncing ball under gravity does not stop at bounds

我正在申请,需要一个在重力作用下弹跳的球。球弹得很好,但它永远不会停止。

我尝试打印停止点的坐标以及速度。这是其中一个案例的输出:

Found it... line 24. (0.00)i + (-0.01)j {14.14(0.79)}    (-1.79, 651.57)

因此球停在高度 651.67 而边界是 600。这是另一种情况:

Found it... line 24. (0.00)i + (-0.01)j {14.14(0.79)}    (-1.79, 1624.58)

代码如下:

GUI.java

import java.util.Timer;
import java.util.TimerTask;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.stage.Stage;

public class GUI extends Application {

    @Override
    public void start(Stage theStage) throws Exception {
        theStage.setTitle("Bouncy Ball");

        Group root = new Group();
        Scene theScene = new Scene(root);
        theStage.setScene(theScene);

        Canvas canvas = new Canvas(750, 600);
        root.getChildren().add(canvas);

        CircleSprite sprite = new CircleSprite(30, new Point(50, 50));
        root.getChildren().add(sprite.image);
        sprite.setVelocity(new Vector(new Point(10, 10)));

        theStage.show();

        theStage.setOnCloseRequest(e -> {
            System.exit(0);
        });

        AnimationTimer gameLoop = new AnimationTimer() {

            @Override
            public void handle(long now) {
                if (sprite.update(new Bounds(700, 600))) {
                    this.stop();
                }
            }
        };

        gameLoop.start();

        new Timer().scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                Vector velocity = sprite.getVelocity();
                velocity.setYComponent(velocity.getYComponent() + 1);
                if (Math.abs(sprite.getVelocity().getYComponent()) <= 0.01
                        && sprite.getCentre().getY() + 2 * sprite.getRadius() >= 500) {
                            System.out.println("Found it... line 55");
                    cancel();
                }
            }
        }, 0, 100);
    }

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

}

CircleSprite.java

public class CircleSprite extends Circle {
    public javafx.scene.shape.Circle image;

    public CircleSprite(long radius, Point centre) {
        super(radius, centre);
        image = new javafx.scene.shape.Circle(centre.getX(), centre.getY(), radius);
    }

    public boolean update(Bounds bounds) {
        Point pos = this.getCentre();
        Vector velocity = this.getVelocity();
        Point finalPos = new Point(pos.getX() + velocity.getXComponent(), pos.getY() + velocity.getYComponent());
        image.setLayoutX(finalPos.getX());
        image.setLayoutY(finalPos.getY());
        setCentre(finalPos);
        if (finalPos.getX() <= 0 || finalPos.getX() + 2 * getRadius() >= bounds.maxX) {
            velocity.setXComponent(velocity.getXComponent() * (-1) * 0.75);
        }
        if (finalPos.getY() <= 0 || finalPos.getY() + 2 * getRadius() >= bounds.maxY) {
            velocity.setYComponent(velocity.getYComponent() * (-1) * 0.75);
        }
        if (Math.abs(velocity.getYComponent()) <= 0.01 && getCentre().getY() + 2 * getRadius() >= bounds.maxY) {
            System.out.println("Found it... line 24" + velocity + "    " + getCentre());
            return true;
        }
        return false;
    }
}

Bounds.java

public class Bounds {
    public double maxX;
    public double maxY;

    public Bounds(double maxX, double maxY) {
        this.maxX = maxX;
        this.maxY = maxY;
    }
}

Circle.java

public class Circle extends Shape {
    private long radius;
    private Point centre;

    public Circle(long radius, Point centre) {
        this.radius = radius;
        this.centre = centre;
        this.setVelocity(new Vector(new Point(0.0d, 0.0d), new Point(0, 0)));
        this.setMass(0);
        this.setRestitution(1);
        this.setAcceleration(new Vector(new Point(0, 0), new Point(0, 0)));
    }

    /*
     * Testing for whether or not two circles intersect is very simple: take the
     * radii of the two circles and add them together, then check to see if this sum
     * is greater than the distance between the two circles.
     */
    public boolean isColliding(Circle a, Circle b) {
        long dist = a.getRadius() + b.getRadius();

        // In general multiplication is a much cheaper operation than taking the square
        // root of a value.

        return ((dist * dist) < (a.getCentre().getX() - b.getCentre().getX())
                * (a.getCentre().getX() - b.getCentre().getX())
                + (a.getCentre().getY() - b.getCentre().getY()) * (a.getCentre().getY() - b.getCentre().getY()));
    }

    public long getRadius() {
        return this.radius;
    }

    public void setRadius(long radius) {
        this.radius = radius;
    }

    public Point getCentre() {
        return this.centre;
    }

    public void setCentre(Point centre) {
        this.centre = centre;
    }
}

Shape.java

public class Shape {
    private Vector velocity;
    private Vector acceleration;
    private long mass;
    private double invMass;
    private float restitution;

    public Vector getAcceleration() {
        return this.acceleration;
    }

    public void setAcceleration(Vector acceleration) {
        this.acceleration = acceleration;
    }

    public long getMass() {
        return this.mass;
    }

    public void setMass(long mass) {
        this.mass = mass;
        this.setInvMass(mass);
    }

    public Vector getVelocity() {
        return this.velocity;
    }

    public void setVelocity(Vector velocity) {
        this.velocity = velocity;
    }

    public double getInvMass() {
        return this.invMass;
    }

    private void setInvMass(long mass) {
        if (mass == 0) {
            invMass = Long.MAX_VALUE;
            return;
        }
        this.invMass = 1.0d / (double) mass;
    }

    public float getRestitution() {
        return this.restitution;
    }

    public void setRestitution(float restitution) {
        this.restitution = restitution;
    }

}

Vector.java

public class Vector {
    private Point p1;
    private Point p2;
    private double xComponent;
    private double yComponent;
    private double angle;
    private double magnitude;

    /*
     * The constructor makes a vector crossing through two points p1 and p2.
     * 
     * @param p1 The source point(x1, x2)
     */
    public Vector(Point p1, Point p2) {
        this.p1 = p1;
        this.p2 = p2;
        this.xComponent = this.p2.getX() - this.p1.getX();
        this.yComponent = this.p2.getY() - this.p1.getY();
        this.angle = Math.atan2(this.yComponent, this.xComponent);
        this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
    }

    public Vector(Point p2) {
        Point p1 = new Point(0, 0);
        this.p1 = p1;
        this.p2 = p2;
        this.xComponent = this.p2.getX() - this.p1.getX();
        this.yComponent = this.p2.getY() - this.p1.getY();
        this.angle = Math.atan2(this.yComponent, this.xComponent);
        this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
    }

    public Vector(double magnitude, Vector unitVector) {
        scaledProduct(magnitude, unitVector);
    }

    private void scaledProduct(double magnitude, Vector unitVector) {
        Point point = new Point(magnitude * unitVector.getXComponent(), magnitude * unitVector.getYComponent());
        new Vector(point);
    }

    public static Vector scalarProduct(double magnitude, Vector unitVector) {
        Point point = new Point(magnitude * unitVector.getXComponent(), magnitude * unitVector.getYComponent());
        return new Vector(point);
    }

    public static double dotProduct(Vector v1, Vector v2) {
        return (v1.xComponent * v2.xComponent + v1.yComponent * v2.yComponent);
    }

    public static Vector sum(Vector v1, Vector v2) {
        return new Vector(new Point(v1.getXComponent() + v2.getXComponent(), v1.getYComponent() + v2.getYComponent()));
    }

    public static Vector difference(Vector from, Vector vector) {
        return new Vector(new Point(from.getXComponent() - vector.getXComponent(),
                from.getYComponent() - vector.getYComponent()));
    }

    public static double angleBetween(Vector v1, Vector v2) {
        return Math.acos(Vector.dotProduct(v1, v2) / (v1.getMagnitude() * v2.getMagnitude()));
    }

    public Point getP1() {
        return this.p1;
    }

    public void setP1(Point p1) {
        this.p1 = p1;
    }

    public Point getP2() {
        return this.p2;
    }

    public void setP2(Point p2) {
        this.p2 = p2;
    }

    public double getXComponent() {
        return this.xComponent;
    }

    public void setXComponent(double d) {
        this.xComponent = d;
    }

    public double getYComponent() {
        return this.yComponent;
    }

    public void setYComponent(double d) {
        this.yComponent = d;
    }

    public double getAngle() {
        return this.angle;
    }

    public void setAngle(double angle) {
        this.angle = angle;
    }

    public double getMagnitude() {
        return this.magnitude;
    }

    public void setMagnitude(double length) {
        this.magnitude = length;
    }

    @Override
    public boolean equals(Object v) {
        Vector vector = (Vector) v;
        return ((this.xComponent == vector.xComponent) && (this.yComponent == vector.yComponent));
    }

    @Override
    public String toString() {
        return String.format("(%.2f)i + (%.2f)j {%.2f(%.2f)}", this.xComponent, this.yComponent, this.magnitude,
                this.angle);
    }
}

Point.java

public class Point {
    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public static double distance(Point p1, Point p2) {
        return Math.sqrt(
                (p1.getX() - p2.getX()) * (p1.getX() - p2.getX()) + (p1.getY() - p2.getY()) * (p1.getY() - p2.getY()));
    }

    public double getX() {
        return x;
    }

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

    public double getY() {
        return y;
    }

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

    @Override
    public String toString() {
        return String.format("(%.2f, %.2f)", this.x, this.y);
    }
}

代码有什么问题?我知道 GUI.javaCircleSprite.java 中的退出条件有问题,但不知道是什么。有两件事困扰着我:

1. Why is the height out of bound when it stops?

2. Why does the x goes negative?

这里发生的情况如下(仅考虑 x 维度;同样适用于 y):

球以v的速度过界。您反转速度并降低其幅度。这可能会导致新速度不够高,无法在下一个更新步骤中回到边界,并且您再次反转速度以进一步降低速度。这导致球在边界外以越来越小的步幅来回移动,有效地给人以停止的印象。

值示例:

Frame 1
 vx = -16
 x  = 1

Frame 2
 vx = 12
  x = -15

Frame 3
 vx = -9
  x = -3

Frame 4
 vx = -6.75
  x = -12

...

有两种方法可以解决这个问题:

  1. 仅更新​​速度,如果球正朝着超出边界的方向移动。

     public boolean update(Bounds bounds) {
         Point pos = this.getCentre();
         Vector velocity = this.getVelocity();
         Point finalPos = new Point(pos.getX() + velocity.getXComponent(), pos.getY() + velocity.getYComponent());
         image.setLayoutX(finalPos.getX());
         image.setLayoutY(finalPos.getY());
         setCentre(finalPos);
    
         if ((finalPos.getX() <= 0 && velocity.getXComponent() < 0) || (finalPos.getX() + 2 * getRadius() >= bounds.maxX && velocity.getXComponent() > 0)) {
             velocity.setXComponent(velocity.getXComponent() * (-1) * 0.75);
         }
         if ((finalPos.getY() <= 0 && velocity.getYComponent() < 0) || (finalPos.getY() + 2 * getRadius() >= bounds.maxY && velocity.getYComponent() > 0)) {
             velocity.setYComponent(velocity.getYComponent() * (-1) * 0.75);
         }
         if (Math.abs(velocity.getYComponent()) <= 0.01 && getCentre().getY() + 2 * getRadius() >= bounds.maxY) {
             System.out.println("Found it... line 24" + velocity + "    " + getCentre());
             return true;
         }
         return false;
     }
    
  2. 首先防止球落在界外

     public boolean update(Bounds bounds) {
         Point pos = this.getCentre();
         Vector velocity = this.getVelocity();
         Point finalPos = new Point(pos.getX() + velocity.getXComponent(), pos.getY() + velocity.getYComponent());
    
         boolean invertX = true;
         if (finalPos.getX() <= 0) {
             // mirror on left
             finalPos.setX(-finalPos.getX());
         } else if (finalPos.getX() + 2 * getRadius() >= bounds.maxX) {
             // mirror on right
             finalPos.setX(2 * (bounds.maxX - 2 * getRadius()) - finalPos.getX());
         } else {
             invertX = false;
         }
    
         if (invertX) {
             velocity.setXComponent(velocity.getXComponent() * (-1) * 0.75);
         }
    
         boolean invertY = true;
         if (finalPos.getY() <= 0) {
             // mirror on top
             finalPos.setY(-finalPos.getY());
         } else if (finalPos.getY() + 2 * getRadius() >= bounds.maxY) {
             // mirror on bottom
             finalPos.setY(2 * (bounds.maxY - 2 * getRadius()) - finalPos.getY());
         } else {
             invertY = false;
         }
    
         if (invertY) {
             velocity.setYComponent(velocity.getYComponent() * (-1) * 0.75);
         }
    
         setCentre(finalPos);
         image.setLayoutX(finalPos.getX());
         image.setLayoutY(finalPos.getY());
    
         if (Math.abs(velocity.getYComponent()) <= 0.01 && getCentre().getY() + 2 * getRadius() >= bounds.maxY) {
             System.out.println("Found it... line 24" + velocity + "    " + getCentre());
             return true;
         }
         return false;
     }