0
votes

I have to detect when two "balls" collide in a javaFX program. Each time a button is clicked, a new ball is added to the pane. I know that getChildren() returns an observable list that contains the node for each ball, and when I print the list with two circles it will print, for example, Circle[centerX=30.0, centerY=30.0, radius=20.0, fill=0x9ac26780], Circle[centerX=224.0, centerY=92.0, radius=20.0, fill=0x9ac26780]

My idea was to use nested loops to compare the (x,y) coordinates of each ball to every other ball. How do I access centerX and centerY from each Circle in order to compare them? I tried getChildren().sublist(0,0); thinking that I would get the centerX value for the first element, but that does not work. I also tried getCenterX, because Ball extends Circle, but that also failed. Thanks for your time.

public class Exercise20_05 extends Application {
  @Override // Override the start method in the Application class
   public void start(Stage primaryStage) {

MultipleBallPane ballPane = new MultipleBallPane();
ballPane.setStyle("-fx-border-color: yellow");

Button btAdd = new Button("+");
Button btSubtract = new Button("-");
HBox hBox = new HBox(10);
hBox.getChildren().addAll(btAdd, btSubtract);
hBox.setAlignment(Pos.CENTER);

// Add or remove a ball
btAdd.setOnAction(e -> ballPane.add());
btSubtract.setOnAction(e -> ballPane.subtract());

// Pause and resume animation
ballPane.setOnMousePressed(e -> ballPane.pause());
ballPane.setOnMouseReleased(e -> ballPane.play());

// Use a scroll bar to control animation speed
ScrollBar sbSpeed = new ScrollBar();
sbSpeed.setMax(20);
sbSpeed.setValue(10);
ballPane.rateProperty().bind(sbSpeed.valueProperty());

BorderPane pane = new BorderPane();
pane.setCenter(ballPane);
pane.setTop(sbSpeed);
pane.setBottom(hBox);

// Create a scene and place the pane in the stage
Scene scene = new Scene(pane, 250, 150);
primaryStage.setTitle("MultipleBounceBall"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}

private class MultipleBallPane extends Pane {
private Timeline animation;

public MultipleBallPane() {
  // Create an animation for moving the ball
  animation = new Timeline(
    new KeyFrame(Duration.millis(50), e -> moveBall()));
  animation.setCycleCount(Timeline.INDEFINITE); //animation will play indefinitely
  animation.play(); // Start animation
}

public void add() {
  Color color = new Color(Math.random(), 
    Math.random(), Math.random(), 0.5);
  //creates a new Ball at (30, 30) with a radius of 20
  getChildren().add(new Ball(30, 30, 20, color)); 
  ballCollision();
}

public void subtract() {
  if (getChildren().size() > 0) {
    getChildren().remove(getChildren().size() - 1); 
  }
}

public void play() {
  animation.play();
}

public void pause() {
  animation.pause();
}

public void increaseSpeed() {
  animation.setRate(animation.getRate() + 0.1);
}

public void decreaseSpeed() {
  animation.setRate(
    animation.getRate() > 0 ? animation.getRate() - 0.1 : 0);
}

public DoubleProperty rateProperty() {
    return animation.rateProperty();
}

protected void moveBall() {
  for (Node node: this.getChildren()) {
    Ball ball = (Ball)node;
    // Check boundaries
    if (ball.getCenterX() < ball.getRadius() || 
        ball.getCenterX() > getWidth() - ball.getRadius()) {
      ball.dx *= -1; // Change ball move direction
    }
    if (ball.getCenterY() < ball.getRadius() || 
        ball.getCenterY() > getHeight() - ball.getRadius()) {
      ball.dy *= -1; // Change ball move direction
    }

    // Adjust ball position
    ball.setCenterX(ball.dx + ball.getCenterX());
    ball.setCenterY(ball.dy + ball.getCenterY());

    ballCollision();
  }
}

//check for ball collisions
protected void ballCollision() {
    /*System.out.println(getChildren().size());
    getChildren returns an observableList; this observableList is what
    keeps track of the balls (specifically, the nodes added to ballPane)
    added each time the + button is clicked
    */
    ObservableList ballList = getChildren();
    System.out.println(ballList.get(0));
    //if there are 2 or more balls, check for collision
        if (ballList.size() > 1) {
           //compare each (x,y) coordinate value to every other (x,y) value
            for (int i = 0; i < ballList.size(); i++) {
                for (int k = 0; k < ballList.size(); k++) {
//                    if (ballList.sublist(i,i) < 1) {
//                        
//                    }
                }
            }
        }
    }
}

class Ball extends Circle {
 private double dx = 1, dy = 1;

Ball(double x, double y, double radius, Color color) {
  super(x, y, radius);
  setFill(color); // Set ball color
  }
 }

 /**
  * The main method is only needed for the IDE with limited
  * JavaFX support. Not needed for running from the command line.
  */
 public static void main(String[] args) {
   launch(args);
 }
}

Edit: Thanks to a couple of people, I was able to get the collision check to work. One ball will get removed, but I get the ConcurrentModificationException. Here is the updated method:

protected void ballCollision() {
    ObservableList ballList = getChildren();
    //if there are 2 or more balls, check for collision
        if (ballList.size() > 1) {
           //compare each (x,y) coordinate value to every other (x,y) value
            for (int i = 0; i < ballList.size(); i++) {
                for (int k = i + 1; k < ballList.size(); k++) {
                    Circle c1 = (Circle) ballList.get(i);
                    Circle c2 = (Circle) ballList.get(k);

                    if ((c1.getCenterX() <= c2.getCenterX() * 1.10 &&
                        (c1.getCenterX() >= c2.getCenterX()*.90)) &&
                       ((c1.getCenterY() <= c2.getCenterY() * 1.10) && 
                         c1.getCenterY() >= c2.getCenterY() * .90)){

                            ballList.remove(c2);

                    }
                }
            }
        }
    }

Final Edit: Thanks to David Wallace for taking his time to help me. The issue was that I was calling ballCollision inside the for-each loop of the moveBall method. Once I moved it outside the loop, it worked perfectly.

1
If you know all child nodes are Circles, just do ballList.get(i), cast to a Circle and call getCenterX(), etc.James_D
@James_D thanks, that works. Now I have to remove a ball when a collision happens. When I try that, I get the ConcurrentModificationException. I think this is because I am trying to remove an element from the list while I am iterating over the list.so8857

1 Answers

1
votes

You can treat the ObservableList just like any other List. You will probably want to cast the elements to the right class, as shown here. Use the hypot method of the Math class to calculate the distance between the centres.

for (int first = 0; first < ballList.size(); first++) {
    Ball firstBall = (Ball) ballList.get(first);
    for (int second = first + 1; second < ballList.size(); second++) {
        Ball secondBall = (Ball) ballList.get(second);

        double distanceBetweenCentres = Math.hypot(
            firstBall.getCenterX() - secondBall.getCenterX(), 
            firstBall.getCenterY() - secondBall.getCenterY());

        if (distanceBetweenCentres <= firstBall.getRadius() + secondBall.getRadius()) {
            System.out.println("Collision between ball " + first + " and ball " + second);
        }

    }
}