Issue
I have a collection of shapes that I am trying to check for collision between each other, but I all I have achieved so far is detecting collisions between three nodes closest to each other.
The method addToRectCollection adds stack panes to the collection, onDragged males each shape draggable.
What should I change/add to detect collisions between all of the elements of the collection?
Below is my code:
public class CollisionDection extends Application {
final ObjectProperty<Point2D> mousePosition = new SimpleObjectProperty<>();
Group gp = new Group();
*I try to detect collisions between elements of this list*
ArrayList<StackPane> stackPanes = new ArrayList<>();
@Override
public void start(Stage stage) throws Exception {
Scene sc = new Scene(gp, 700, 300);
Button btn = new Button();
btn.setText("CREATE REct");
btn.setOnAction(event -> {
Rectangle r = createRect(0);
r.setFill(Color.RED);
Text text = new Text(".");
StackPane stack = new StackPane();
StackPane.setMargin(r, new Insets(8, 8, 8, 8));
stack.getChildren().addAll(r, text);
ChoiceBox<String> dropdown = new ChoiceBox<>();
stack.getChildren().add(dropdown);
makeDraggable(stack);
addToRectCollection(stack);
gp.getChildren().add(stack);
});
gp.getChildren().add(btn);
stage.setScene(sc);
stage.show();
}
private void addToRectCollection(StackPane r) {
stackPanes.add(r);
stackPanes.forEach(rect -> {
stackPanes.forEach(rect2 -> {
if (!rect.equals(rect2)) {
setOnDragegd(rect2, rect);
setOnDragegd(rect, rect2);
}
});
});
}
private void setOnDragegd(StackPane pane, StackPane paneToSetDragged) {
paneToSetDragged.setOnMouseDragged((MouseEvent event) -> {
double deltaX = event.getSceneX() - mousePosition.get().getX();
double deltaY = event.getSceneY() - mousePosition.get().getY();
paneToSetDragged.setLayoutX(paneToSetDragged.getLayoutX() + deltaX);
paneToSetDragged.setLayoutY(paneToSetDragged.getLayoutY() + deltaY);
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
var intersect = paneToSetDragged.getBoundsInParent().intersects(pane.getBoundsInParent());
if (intersect) {
pane.setStyle("-fx-background-color: #6a5246");
paneToSetDragged.setStyle("-fx-background-color: #6a5246");
Group g = new Group();
g.getChildren().add(paneToSetDragged);
g.getChildren().add(pane);
gp.getChildren().add(g);
paneToSetDragged.setLayoutX(paneToSetDragged.getLayoutX() - deltaX);
paneToSetDragged.setLayoutY(paneToSetDragged.getLayoutY() - deltaY);
} else {
pane.setStyle("-fx-background-color: #ffffff");
paneToSetDragged.setStyle("-fx-background-color: #ffffff");
}
});
}
private void makeDraggable(StackPane ractangleToSetDragged) {
ractangleToSetDragged.setOnMousePressed((MouseEvent event) -> mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY())));
ractangleToSetDragged.setOnMouseDragged((MouseEvent event) -> {
double deltaX = event.getSceneX() - mousePosition.get().getX();
double deltaY = event.getSceneY() - mousePosition.get().getY();
ractangleToSetDragged.setLayoutX(ractangleToSetDragged.getLayoutX() + deltaX);
ractangleToSetDragged.setLayoutY(ractangleToSetDragged.getLayoutY() + deltaY);
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
});
}
public Rectangle createRect(int x) {
Rectangle r = new Rectangle();
r.setX(x);
r.setY(60);
r.setWidth(300);
r.setHeight(150);
return r;
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Solution
You need to test for collision with all other panes in the handler for StackPane.onMouseDragged
, for each pane you add to the collection.
What you're doing now is incorrect because you re-set the handler for each pane any time you add a new StackPane. That may work using StackPane.addEventHandler(MouseEvent.MOUSE_DRAGGED, ...)
instead of StackPane.setOnMouseDragged(...)
, but it would be heavy, slow, and memory consuming, as it would potentially chain a large number of event handlers for each new StackPane.
As you've only got one moving pane at once (when it's being dragged), you'd rather set the onMouseDragged
handler once for each new pane, and then inside this handler, loop through your stackPanes
ArrayList to test for intersection with all other panes, which would change your code to something like this:
private void addToRectCollection(StackPane r) {
stackPanes.add(r);
r.setOnMouseDragged((MouseEvent event) -> {
double deltaX = event.getSceneX() - mousePosition.get().getX();
double deltaY = event.getSceneY() - mousePosition.get().getY();
r.setLayoutX(r.getLayoutX() + deltaX);
r.setLayoutY(r.getLayoutY() + deltaY);
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
//Test collision for this pane with all other ones
testCollision(r);
});
}
private void testCollision(StackPane r) {
//Loop through all stackPanes
for (StackPane r2 : stackPanes) {
if (r2 == r) //except r
continue;
//test intersection with r
var intersect = r.getBoundsInParent().intersects(r2.getBoundsInParent());
if (intersect) {
//Style intersecting pane
r2.setStyle("-fx-background-color: #6a5246");
r.setStyle("-fx-background-color: #6a5246");
Group g = new Group();
g.getChildren().add(r);
g.getChildren().add(r2);
gp.getChildren().add(g);
r.setLayoutX(r.getLayoutX() - deltaX);
r.setLayoutY(r.getLayoutY() - deltaY);
} else {
//Unstyle
r2.setStyle("-fx-background-color: #ffffff");
r.setStyle("-fx-background-color: #ffffff");
}
}
}
(I've got rid of the setOnDragegd
method, which would only be setting the onMouseDragged
handler and defined a testCollision
method specialized in assessing collisions with the pane being dragged)
You will still have a problem with the styling though, as you're re-doing it any time you detect one collision, whereas any pane could very well be intersecting with several other ones at any time... That could be solved in 2 obvious ways :
- Memorizing the known intersections, and updating them as a pane is being dragged, then assessing which panes should be colored
- Re-testing every possible collision on every event by assessing each pane against every other one preceding it the
stackPanes
arrayList instead of the simple loop I offered here. (viable only for small numbers of panes)
But that could be for another question...
Answered By - julien.giband
Answer Checked By - David Goodson (JavaFixing Volunteer)