Javafx 3D: 3DShape material lost translucency and white colouring surrounding sphere - javafx

I have the following piece of code that displays a sphere inside a box.
package com.example.animation3d;
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class Camera3D extends Application {
public static final int WIDTH = 1280;
public static final int HEIGHT = 720;
public static final int ROTATE_SENS = 20;
public static final int ZOOM_SENS = 20;
public Rotate yRotate;
public double cursorX;
#Override
public void start(Stage stage) throws Exception {
Box box = new Box(5, 10, 5);
PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.web("rgba(34, 139, 34, 0.5)"));
box.setMaterial(material);
Sphere sphere = new Sphere(0.5);
sphere.setMaterial(new PhongMaterial(Color.RED));
Group group = new Group();
group.getChildren().addAll(box, sphere);
Translate pivot = new Translate();
yRotate = new Rotate(0, Rotate.Y_AXIS);
// Create and position camera
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.getTransforms().addAll (
pivot,
yRotate,
new Rotate(-20, Rotate.X_AXIS),
new Translate(0, 0, -50)
);
Scene scene = new Scene(group, WIDTH, HEIGHT);
scene.setFill(Color.ALICEBLUE);
scene.setCamera(camera);
stage.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
switch (event.getCode()) {
case W:
camera.translateZProperty().set(camera.getTranslateZ() + 10);
break;
case S:
camera.translateZProperty().set(camera.getTranslateZ() - 10);
break;
case A:
yRotate.angleProperty().set(yRotate.getAngle() + 10);
break;
case D:
yRotate.angleProperty().set(yRotate.getAngle() - 10);
break;
case UP:
sphere.setTranslateY(sphere.getTranslateY() - 1);
break;
case DOWN:
sphere.setTranslateY(sphere.getTranslateY() + 1);
break;
case LEFT:
sphere.setTranslateX(sphere.getTranslateX() - 1);
break;
case RIGHT:
sphere.setTranslateX(sphere.getTranslateX() + 1);
break;
}
});
stage.setTitle("Camera");
stage.setScene(scene);
stage.show();
}
}
However, when I run the program,
Translucent effect gradually lost as I move the sphere. I have added a material to the box to make it look translucent. However, when I move the sphere with arrow keys, the box seems to become opaque again, which I do not want.
Whitish background surrounding the sphere as I move it. When the sphere is moved with arrow keys, a whitish colouring surrounds the sphere and trails behind it, which I do not want.
How do these problems arise, and how can they be fixed? Thanks in advance!

I was able to reproduce your finding, although it appeared to be a trail of altered transparency following the sphere as it moved; the trail was comprised of rectangles, each approximating the sphere's boundary.
With reference to this answer, I changed the order in which the objects were added to the group:
group.getChildren().addAll(sphere, box);
Empirically, like you, I noticed that the trails disappeared if I rotated the box slightly between each move of the sphere:
yRotate.angleProperty().set(yRotate.getAngle() + 0.1);
yRotate.angleProperty().set(yRotate.getAngle() - 0.1);
A variation of your example to work around the problem is shown below. I'm guessing that it forces a depth recalculation, as suggested here, but I'd welcome any addition insight.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class Camera3D extends Application {
public static final int WIDTH = 500;
public static final int HEIGHT = 500;
public Rotate yRotate;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
var box = new Box(5, 10, 5);
var material = new PhongMaterial(Color.web("#20902080"));
box.setMaterial(material);
var sphere = new Sphere(3);
sphere.setMaterial(new PhongMaterial(Color.RED));
Group group = new Group();
group.getChildren().addAll(sphere, box);
Translate pivot = new Translate();
yRotate = new Rotate(0, Rotate.Y_AXIS);
// Create and position camera
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.getTransforms().addAll(
pivot,
yRotate,
new Rotate(-20, Rotate.X_AXIS),
new Translate(0, 0, -30)
);
Scene scene = new Scene(group, WIDTH, HEIGHT);
scene.setFill(Color.ALICEBLUE);
scene.setCamera(camera);
stage.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
switch (event.getCode()) {
case W ->
camera.translateZProperty().set(camera.getTranslateZ() + 10);
case S ->
camera.translateZProperty().set(camera.getTranslateZ() - 10);
case A ->
yRotate.angleProperty().set(yRotate.getAngle() + 10);
case D ->
yRotate.angleProperty().set(yRotate.getAngle() - 10);
case UP -> {
sphere.setTranslateY(sphere.getTranslateY() - 1);
adjust();
}
case DOWN -> {
sphere.setTranslateY(sphere.getTranslateY() + 1);
adjust();
}
case LEFT -> {
sphere.setTranslateX(sphere.getTranslateX() - 1);
adjust();
}
case RIGHT -> {
sphere.setTranslateX(sphere.getTranslateX() + 1);
adjust();
}
}
});
stage.setTitle("Camera");
stage.setScene(scene);
stage.show();
}
private void adjust() {
yRotate.angleProperty().set(yRotate.getAngle() + 0.1);
yRotate.angleProperty().set(yRotate.getAngle() - 0.1);
}
}

Related

Get Viewport of translated and scaled node

The ask: How do I get the viewing rectangle in the coordinates of a transformed and scaled node?
The code is attached below, it is based upon the code from this answer: JavaFX 8 Dynamic Node scaling
The details:
I have a simple pane, BigGridPane that contains a collection of squares, all 50x50.
I have it within this PanAndZoomPane construct that was lifted from the answer referenced above. I can not honestly say I fully understand the PanAndZoomPane implementation. For example, it's not clear to me why it needs a ScrollPane at all, but I have not delved in to trying without it.
The PanAndZoomPane lets me pan and zoom my BigGridPane. This works just dandy.
There are 4 Panes involved in this total construct, in this heirarchy: ScrollPane contains PanAndZoomPane which contains Group which contains BigGridPane.
ScrollPane
PanAndZoomPane
Group
BigGridPane
I have put listeners on the boundsInLocalProperty and boundsInParentProperty of all of these, and the only one of these that changes while panning and zooming, is the boundsInParentProperty of the PanAndZoomPane. (For some reason I've seen it trigger on the scroll pane, but all of the values are the same, so I don't include that here).
Along with the boundsInParentProperty changes, the translateX, translateY, and myScale properties of the PanAndZoomPane change as things move around. This is expected, of course. myScale is bound to the scaleX and scaleY properties of the PanAndZoomPane.
This is what it looks like at startup.
If I pan the grid as shown, putting 2-2 in the upper left:
We can see the properties of the PanAndZoomPane.
panAndZoom in parent: BoundingBox [minX:-99.5, minY:-99.5, minZ:0.0,
width:501.5, height:501.5, depth:0.0,
maxX:402.0, maxY:402.0, maxZ:0.0]
paz scale = 1.0 - tx: -99.0 - ty: -99.0
Scale is 1 (no zoom), and we've translated ~100x100. That is, the origin of the BigGridPane is at -100,-100. This all makes complete sense. Similarly, the bounding box shows the same thing. The origin is at -100,-100.
In this scenario, I would like to derive a rectangle that shows me what I'm seeing in the window, in the coordinates of the BigGridPane. That would mean a rectangle of
x:100 y:100 width:250 height:250
Normally, I think, this would be the viewport of the ScrollPane, but since this code isn't actually using the ScrollPane for scrolling (again, I'm not quite exactly what it's role is here), the ScrollPane viewport never changes.
I should note that there are shenanigans happening right now because of the retina display on my mac. If you look at the rectangles, showing 5x5, they're 50x50 rectangles, so we should be seeing 10x10, but because of the retina display on my iMac, everything is doubled. What we're seeing in BigGridPane coordinates is a 250x250 block of 5 squares, offset by 100x100. The fact that this is being showing in a window of 500x500 is a detail (but unlikely one we can ignore).
But to reiterate what my question is, that's what I'm trying to get: that 250x250 square at 100x100.
It's odd that it's offset by 100x100 even though the frame is twice as big (500 vs 250). If I pan to where 1-1 is the upper left, the offset is -50,-50, like it should be.
Now, let's add zooming, and pan again to 2-2.
1 click of the scroll wheel and the scale jumps to 1.5.
panAndZoom in parent: BoundingBox [minX:-149.375, minY:-150.375, minZ:0.0,
width:752.25, height:752.25, depth:0.0,
maxX:602.875, maxY:601.875, maxZ:0.0]
paz scale = 1.5 - tx: -23.375 - ty: -24.375
What I want, again, in this case, is a rectangle in BigGridPane coordinates. Roughly:
x:100 y:100 w:150 h:150
We see we're offset by 2x2 boxes (100x100) and we see 3+ boxes (150x150).
So. Back to the bounding box. MinX and minY = -150,-150. This is good. 100 x 1.5 = 150. Similarly the width and height are 750. 500 x 1.5 = 750. So, that is good.
The translates are where we go off the rails. -23.375, -24.375. I have no idea where these numbers come from. I can't seem to correlate them to anything in regards to 100, 150, 1.5 zoom, etc.
Worse, if we pan (while still at 1.5 scale) to "0,0", before, at scale=1, tx and ty were both 0. That's good.
panAndZoom in parent: BoundingBox [minX:0.625, minY:0.625, minZ:0.0,
width:752.25, height:752.25, depth:0.0,
maxX:752.875, maxY:752.875, maxZ:0.0]
paz scale = 1.5 - tx: 126.625 - ty: 126.625
Now, they're 126.625 (probably should be rounded to 125). I have no idea where those numbers come from.
I've tried all sorts of runs on the numbers to see where these numbers come from.
JavaFX knows what the numbers are! (even if the whole retina thing is kind of messing with my head, I'm going to ignore it for the moment).
And I don't see anything in the transforms of any of the panes.
So, my coordinate systems are all over the map, and I'd like to know what part of my BigGridPane is being shown in my panned and scaled view.
Code:
package pkg;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PanZoomTest extends Application {
private ScrollPane scrollPane = new ScrollPane();
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0d);
private final DoubleProperty deltaY = new SimpleDoubleProperty(0.0d);
private final Group group = new Group();
PanAndZoomPane panAndZoomPane = null;
BigGridPane1 bigGridPane = new BigGridPane1(10, 10, 50);
#Override
public void start(Stage primaryStage) throws Exception {
scrollPane.setPannable(true);
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
group.getChildren().add(bigGridPane);
panAndZoomPane = new PanAndZoomPane();
zoomProperty.bind(panAndZoomPane.myScale);
deltaY.bind(panAndZoomPane.deltaY);
panAndZoomPane.getChildren().add(group);
SceneGestures sceneGestures = new SceneGestures(panAndZoomPane);
scrollPane.setContent(panAndZoomPane);
panAndZoomPane.toBack();
addListeners("panAndZoom", panAndZoomPane);
scrollPane.addEventFilter(MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scrollPane.addEventFilter(MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scrollPane.addEventFilter(ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
AnchorPane anchorPane = new AnchorPane();
anchorPane.getChildren().add(scrollPane);
anchorPane.setTopAnchor(scrollPane, 1.0d);
anchorPane.setRightAnchor(scrollPane, 1.0d);
anchorPane.setBottomAnchor(scrollPane, 1.0d);
anchorPane.setLeftAnchor(scrollPane, 1.0d);
BorderPane root = new BorderPane(anchorPane);
Label label = new Label("Pan and Zoom Test");
root.setTop(label);
Scene scene = new Scene(root, 250, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void addListeners(String label, Node node) {
node.boundsInLocalProperty().addListener((o) -> {
System.out.println(label + " in local: " + node.getBoundsInLocal());
});
node.boundsInParentProperty().addListener((o) -> {
System.out.println(label + " in parent: " + node.getBoundsInParent());
System.out.println("paz scale = " + panAndZoomPane.getScale() + " - "
+ panAndZoomPane.getTranslateX() + " - "
+ panAndZoomPane.getTranslateY());
System.out.println(group.getTransforms());
});
}
class BigGridPane extends Region {
int rows;
int cols;
int size;
Font numFont = Font.font("sans-serif", 8);
FontMetrics numMetrics = new FontMetrics(numFont);
public BigGridPane(int cols, int rows, int size) {
this.rows = rows;
this.cols = cols;
this.size = size;
int sizeX = cols * size;
int sizeY = rows * size;
setMinSize(sizeX, sizeY);
setMaxSize(sizeX, sizeY);
setPrefSize(sizeX, sizeY);
populate();
}
#Override
protected void layoutChildren() {
System.out.println("grid layout");
super.layoutChildren();
}
private void populate() {
ObservableList<Node> children = getChildren();
children.clear();
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
Rectangle r = new Rectangle(i * size, j * size, size, size);
r.setFill(null);
r.setStroke(Color.BLACK);
String label = i + "-" + j;
Point2D p = new Point2D(r.getBoundsInLocal().getCenterX(), r.getBoundsInLocal().getCenterY());
Text t = new Text(label);
t.setX(p.getX() - numMetrics.computeStringWidth(label) / 2);
t.setY(p.getY() + numMetrics.getLineHeight() / 2);
t.setFont(numFont);
children.add(r);
children.add(t);
}
}
}
}
class PanAndZoomPane extends Pane {
public static final double DEFAULT_DELTA = 1.5d; //1.3d
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public DoubleProperty deltaY = new SimpleDoubleProperty(0.0);
private Timeline timeline;
public PanAndZoomPane() {
this.timeline = new Timeline(30);//60
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
}
public double getScale() {
return myScale.get();
}
public void setScale(double scale) {
myScale.set(scale);
}
public void setPivot(double x, double y, double scale) {
// note: pivot value must be untransformed, i. e. without scaling
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(200), new KeyValue(translateXProperty(), getTranslateX() - x)), //200
new KeyFrame(Duration.millis(200), new KeyValue(translateYProperty(), getTranslateY() - y)), //200
new KeyFrame(Duration.millis(200), new KeyValue(myScale, scale)) //200
);
timeline.play();
}
public double getDeltaY() {
return deltaY.get();
}
public void setDeltaY(double dY) {
deltaY.set(dY);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
public class SceneGestures {
private DragContext sceneDragContext = new DragContext();
PanAndZoomPane panAndZoomPane;
public SceneGestures(PanAndZoomPane canvas) {
this.panAndZoomPane = canvas;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
public EventHandler<ScrollEvent> getOnScrollEventHandler() {
return onScrollEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
sceneDragContext.mouseAnchorX = event.getX();
sceneDragContext.mouseAnchorY = event.getY();
sceneDragContext.translateAnchorX = panAndZoomPane.getTranslateX();
sceneDragContext.translateAnchorY = panAndZoomPane.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
panAndZoomPane.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX);
panAndZoomPane.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY);
event.consume();
}
};
/**
* Mouse wheel handler: zoom to pivot point
*/
private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = PanAndZoomPane.DEFAULT_DELTA;
double scale = panAndZoomPane.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
panAndZoomPane.setDeltaY(event.getDeltaY());
if (panAndZoomPane.deltaY.get() < 0) {
scale /= delta;
} else {
scale *= delta;
}
double f = (scale / oldScale) - 1;
double dx = (event.getX() - (panAndZoomPane.getBoundsInParent().getWidth() / 2 + panAndZoomPane.getBoundsInParent().getMinX()));
double dy = (event.getY() - (panAndZoomPane.getBoundsInParent().getHeight() / 2 + panAndZoomPane.getBoundsInParent().getMinY()));
panAndZoomPane.setPivot(f * dx, f * dy, scale);
event.consume();
}
};
}
class FontMetrics {
final private Text internal;
public float lineHeight;
public FontMetrics(Font fnt) {
internal = new Text();
internal.setFont(fnt);
Bounds b = internal.getLayoutBounds();
lineHeight = (float) b.getHeight();
}
public float computeStringWidth(String txt) {
internal.setText(txt);
return (float) internal.getLayoutBounds().getWidth();
}
public float getLineHeight() {
return lineHeight;
}
}
}
Generally, you can get the bounds of node1 in the coordinate system of node2 if both are in the same scene using
node2.sceneToLocal(node1.localToScene(node1.getBoundsInLocal()));
I don't understand all the code you posted; I don't really know why you are using a scroll pane when you seem to be implementing all the panning and zooming yourself. Here is a simpler version of a PanZoomPane and then a test which shows how to use the idea above to get the bounds of the viewport in the coordinate system of the panning/zooming content. The "viewport" is just the bounds of the panning/zooming pane in the coordinate system of the content.
If you need the additional functionality in your version of panning and zooming, you should be able to adapt this idea to that; but it would take me too long to understand everything you are doing there.
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
public class PanZoomPane extends Region {
private final Node content ;
private final Rectangle clip ;
private Affine transform ;
private Point2D mouseDown ;
private static final double SCALE = 1.01 ; // zoom factor per pixel scrolled
public PanZoomPane(Node content) {
this.content = content ;
getChildren().add(content);
clip = new Rectangle();
setClip(clip);
transform = Affine.affine(1, 0, 0, 1, 0, 0);
content.getTransforms().setAll(transform);
content.setOnMousePressed(event -> mouseDown = new Point2D(event.getX(), event.getY()));
content.setOnMouseDragged(event -> {
double deltaX = event.getX() - mouseDown.getX();
double deltaY = event.getY() - mouseDown.getY();
translate(deltaX, deltaY);
});
content.setOnScroll(event -> {
double pivotX = event.getX();
double pivotY = event.getY();
double scale = Math.pow(SCALE, event.getDeltaY());
scale(pivotX, pivotY, scale);
});
}
public Node getContent() {
return content ;
}
#Override
protected void layoutChildren() {
clip.setWidth(getWidth());
clip.setHeight(getHeight());
}
public void scale(double pivotX, double pivotY, double scale) {
transform.append(Transform.scale(scale, scale, pivotX, pivotY));
}
public void translate(double x, double y) {
transform.append(Transform.translate(x, y));
}
public void reset() {
transform.setToIdentity();
}
}
import javafx.application.Application;
import javafx.beans.binding.Binding;
import javafx.beans.binding.ObjectBinding;
import javafx.geometry.Bounds;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class PanZoomTest extends Application {
private Binding<Bounds> viewport ;
#Override
public void start(Stage stage) {
Node content = createContent(50, 50, 50) ;
PanZoomPane pane = new PanZoomPane(content);
viewport = new ObjectBinding<>() {
{
bind(
pane.localToSceneTransformProperty(),
pane.boundsInLocalProperty(),
content.localToSceneTransformProperty()
);
}
#Override
protected Bounds computeValue() {
return content.sceneToLocal(pane.localToScene(pane.getBoundsInLocal()));
}
};
viewport.addListener((obs, oldViewport, newViewport) -> System.out.println(newViewport));
BorderPane root = new BorderPane(pane);
Button reset = new Button("Reset");
reset.setOnAction(event -> pane.reset());
HBox buttons = new HBox(reset);
buttons.setAlignment(Pos.CENTER);
buttons.setPadding(new Insets(10));
root.setTop(buttons);
Scene scene = new Scene(root, 800, 800);
stage.setScene(scene);
stage.show();
}
private Node createContent(int columns, int rows, double cellSize) {
GridPane grid = new GridPane() ;
ColumnConstraints cc = new ColumnConstraints();
cc.setMinWidth(cellSize);
cc.setPrefWidth(cellSize);
cc.setMaxWidth(cellSize);
cc.setFillWidth(true);
cc.setHalignment(HPos.CENTER);
for (int column = 0 ; column < columns ; column++) {
grid.getColumnConstraints().add(cc);
}
RowConstraints rc = new RowConstraints();
rc.setMinHeight(cellSize);
rc.setPrefHeight(cellSize);
rc.setMaxHeight(cellSize);
rc.setFillHeight(true);
rc.setValignment(VPos.CENTER);
for (int row = 0 ; row < rows ; row++) {
grid.getRowConstraints().add(rc);
}
for (int x = 0 ; x < columns ; x++) {
for (int y = 0 ; y < rows ; y++) {
Label label = new Label(String.format("[%d, %d]", x, y));
label.setBackground(new Background(
new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY),
new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, new Insets(1,1,0,0))
));
label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
grid.add(label, x, y);
}
}
return grid ;
}
public static void main(String[] args) {
launch();
}
}

Issues with JavaFX Translate

I have this sample:
package bit.fxtest2;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TransformTest2 extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("TransformTest2");
var bp = new BorderPane();
bp.setCenter(new DragPane());
var scene = new Scene(bp, 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private static class DragPane extends Pane {
ObjectProperty<Transform> xform = new SimpleObjectProperty<>(new Translate(0, 0));
public DragPane() {
ObjectProperty<Point2D> mouseDown = new SimpleObjectProperty<>();
setOnMousePressed(e -> {
var mousePress = new Point2D(e.getX(), e.getY());
mouseDown.set(mousePress);
});
setOnMouseDragged(e -> {
var dragPoint = new Point2D(e.getX(), e.getY());
var delta = dragPoint.subtract(mouseDown.get());
var t = new Translate(delta.getX(), delta.getY());
xform.set(xform.get().createConcatenation(t));
mouseDown.set(dragPoint);
System.out.println("mp = " + mouseDown);
updateTransform();
});
populate();
updateTransform();
}
private void populate() {
ObservableList<Node> children = getChildren();
children.clear();
children.add(new Line(0, 0, 200, 0));
children.add(new Line(200, 0, 200, 200));
children.add(new Line(200, 200, 0, 200));
children.add(new Line(0, 200, 0, 0));
}
private void updateTransform() {
ObservableList<Transform> transforms = getTransforms();
transforms.clear();
transforms.add(xform.get());
}
}
}
If you run the code, two things happen.
First, as you start dragging, the box drags, but it starts getting very jerky, and bounces back and forth. If you print out the mouse motions they move back and forth.
Second, after you've dragged the box, say, down and to the right, you'll notice that you can no longer drag it in the upper left area of the window.
This is because the Translate is affecting the Pane itself, not necessarily the contents of the Pane. Since the OnMouse handlers are on the Pane itself, and the Pane is no longer in the upper left area, no handlers are called.
So, two questions.
First, why the jerky behavior?
Second, how can I apply Transforms (not just translate) to the children of a pane, and not the pane itself?
The answer to the first question (the jerkiness) is that it's because your calculations for the transform are incorrect.
When the dragging is processed, the pane is translated by the amount that was dragged. This leaves the coordinates of the mouse relative to the pane as being the same as they were when the mouse was first pressed.
For example, suppose you click on the pane at (100,100), so mouseDown contains the value (100,100). You then drag it, so suppose when the drag event is processed the mouse has moved to (102,101) in the pane's coordinate system. Then delta will be (2,1), so the pane will be translated by (an additional) (2,1), after which the mouse will again be over the point (100,100) in the pane's coordinate system.
Therefore, the correct thing to do here is not to change the value of mouseDown.
Simply removing the line
mouseDown.set(dragPoint);
fixes that issue.
For the second issue: As long as the user starts the drag inside the actual pane, then it all works fine; this seems to be the natural thing to do.
But if you really want to be able to drag from anywhere in the window, you can place the nodes to be dragged in a Group and apply the translation to the group. Note that this time, because the Pane is not moving, you do need to update the mouseDown value:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TransformTest2 extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("TransformTest2");
var bp = new BorderPane();
bp.setCenter(new DragPane());
var scene = new Scene(bp, 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private static class DragPane extends Pane {
ObjectProperty<Transform> xform = new SimpleObjectProperty<>(new Translate(0, 0));
Group group ;
public DragPane() {
ObjectProperty<Point2D> mouseDown = new SimpleObjectProperty<>();
group = new Group();
getChildren().add(group);
setOnMousePressed(e -> {
var mousePress = new Point2D(e.getX(), e.getY());
mouseDown.set(mousePress);
});
setOnMouseDragged(e -> {
var dragPoint = new Point2D(e.getX(), e.getY());
var delta = dragPoint.subtract(mouseDown.get());
var t = new Translate(delta.getX(), delta.getY());
xform.set(xform.get().createConcatenation(t));
mouseDown.set(dragPoint);
System.out.println("mp = " + mouseDown);
updateTransform();
});
populate();
updateTransform();
}
private void populate() {
ObservableList<Node> children = group.getChildren();
children.clear();
children.add(new Line(0, 0, 200, 0));
children.add(new Line(200, 0, 200, 200));
children.add(new Line(200, 200, 0, 200));
children.add(new Line(0, 200, 0, 0));
}
private void updateTransform() {
ObservableList<Transform> transforms = group.getTransforms();
transforms.clear();
transforms.add(xform.get());
}
}
}
If you don't want the additional node, you can achieve the same effect by handling the mouse events on the scene, and update the transforms for the pane.

How do I add a Tooltip to a rectangular region of a JavaFX Canvas

In my JavaFX app I have a TableView with multiple columns, one of which displays data in a graphical form. To do this I have created a CanvasCell object that creates and manages its own Canvas to deal with the drawing. The drawing part works just fine.
I'd now like to put Tooltips over some regions within the Canvas/Cell. There may be multiple Tooltips per Cell (which prevents me from adding the Tooltip at the Cell level) and they should only trigger in specific regions of the graph. However, I'm not managing to get it functioning at all. I don't seem to understand the interactions of Display Node hierarchy well enough (read "at all") to be able to place the Tooltip anywhere where it will actually work.
Documentation for JavaFX is sparse and Google + SO has come up blank for all searches that I've tried. Is there anyone who knows how to do this sort of thing or should I just write it off as "not an option" for now.
For info, the CanvasCell calls a draw() function inside an extended Canvas object on updateItem(). The code in which I've tried to create a Tooltip sits inside that draw() function and looks like:
Rectangle rect = new Rectangle(leftVal, topVal, width, height);
gc.fillRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
Tooltip tooltip = new Tooltip("Tooltip Text");
Tooltip.install(rect, tooltip);
but that code was written more in hope than anything else and doesn't generate anything useful in the interface.
If someone can point me in the right direction, I will be very grateful.
If you don't need the timing control illustrated here, you can simply install the Tooltip on the enclosing Canvas and leverage Shape::contains to condition the text as shown below.
node.setOnMouseMoved(e -> {
tooltips.forEach((color, bounds) -> {
if (bounds.contains(e.getX(), e.getY())) {
tooltip.setText(color.toString());
}
});
});
As suggested here, Java 9 and later provide control over Tooltip timing via the properties showDelay and showDuration.
A similar approach is illustrated here for Swing.
import javafx.application.Application;
import javafx.scene.shape.Rectangle;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
/**
* #see https://stackoverflow.com/a/53785468/230513
* #see https://stackoverflow.com/a/53753537/230513
*/
public class CanvasTooltipDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
Scene sc = new Scene(root, 400, 400);
stage.setScene(sc);
Canvas canvas = new Canvas(200, 200);
root.getChildren().add(canvas);
Map<Color, Rectangle> tooltips = new HashMap<>();
tooltips.put(Color.RED, new Rectangle(0, 0, 100, 100));
tooltips.put(Color.BLUE, new Rectangle(100, 0, 100, 100));
tooltips.put(Color.YELLOW, new Rectangle(0, 100, 100, 100));
tooltips.put(Color.GREEN, new Rectangle(100, 100, 100, 100));
GraphicsContext gc = canvas.getGraphicsContext2D();
tooltips.forEach((color, bounds) -> {
gc.setFill(color);
gc.fillRect(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
});
setToolTips(canvas, tooltips);
stage.show();
}
private void setToolTips(Node node, Map<Color, Rectangle> tooltips) {
Tooltip tooltip = new Tooltip();
Tooltip.install(node, tooltip);
node.setOnMouseMoved(e -> {
tooltips.forEach((color, bounds) -> {
if (bounds.contains(e.getX(), e.getY())) {
tooltip.setText(color.toString());
}
});
});
node.setOnMouseExited(e -> {
tooltip.hide();
});
}
public static void main(String[] args) {
Application.launch(args);
}
}
I have the same solution as per #Slaw suggested. My idea is to make it more centralized so that you can pass your node and its regions you want to show the tooltips.
In the below demo, you can use the setToolTips() as static utitlity method for multiple nodes.
Note: some part of the logic is referred from Tooltip core implementation ;)
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.HashMap;
import java.util.Map;
public class MultiTooltipDemo extends Application {
private double lastMouseX;
private double lastMouseY;
private static int TOOLTIP_XOFFSET = 10;
private static int TOOLTIP_YOFFSET = 7;
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
Scene sc = new Scene(root, 600, 600);
stage.setScene(sc);
stage.show();
StackPane box1 = new StackPane();
box1.setMaxSize(200, 200);
box1.setStyle("-fx-background-color:red, blue, yellow, green; -fx-background-insets: 0 100 100 0, 0 0 100 100, 100 100 0 0, 100 0 0 100;");
root.getChildren().add(box1);
Map<String, Rectangle2D> tooltips = new HashMap<>();
tooltips.put("I am red", new Rectangle2D(0, 0, 100, 100));
tooltips.put("I am blue", new Rectangle2D(100, 0, 100, 100));
tooltips.put("I am yellow", new Rectangle2D(0, 100, 100, 100));
tooltips.put("I am green", new Rectangle2D(100, 100, 100, 100));
setToolTips(box1, tooltips);
}
private void setToolTips(Node node, Map<String, Rectangle2D> tooltips) {
Duration openDelay = Duration.millis(1000);
Duration hideDelay = Duration.millis(5000);
Tooltip toolTip = new Tooltip();
Timeline hideTimer = new Timeline();
hideTimer.getKeyFrames().add(new KeyFrame(hideDelay));
hideTimer.setOnFinished(event -> {
toolTip.hide();
});
Timeline activationTimer = new Timeline();
activationTimer.getKeyFrames().add(new KeyFrame(openDelay));
activationTimer.setOnFinished(event -> {
Bounds nodeScreenBounds = node.localToScreen(node.getBoundsInLocal());
double nMx = nodeScreenBounds.getMinX();
double nMy = nodeScreenBounds.getMinY();
toolTip.setText("");
tooltips.forEach((str, bounds) -> {
double mnX = nMx + bounds.getMinX();
double mnY = nMy + bounds.getMinY();
double mxX = mnX + bounds.getWidth();
double mxY = mnY + bounds.getHeight();
if (lastMouseX >= mnX && lastMouseX <= mxX && lastMouseY >= mnY && lastMouseY <= mxY) {
toolTip.setText(str);
}
});
if (!toolTip.getText().isEmpty()) {
toolTip.show(node.getScene().getWindow(), lastMouseX + TOOLTIP_XOFFSET, lastMouseY + TOOLTIP_YOFFSET);
hideTimer.playFromStart();
}
});
node.setOnMouseMoved(e -> {
double buffPx = 2;
double eX = e.getScreenX();
double eY = e.getScreenY();
// Not hiding for slight mouse movements while tooltip is showing
if (hideTimer.getStatus() == Animation.Status.RUNNING) {
if (lastMouseX - buffPx <= eX && lastMouseX + buffPx >= eX && lastMouseY - buffPx <= eY && lastMouseY + buffPx >= eY) {
return;
}
}
lastMouseX = e.getScreenX();
lastMouseY = e.getScreenY();
toolTip.hide();
hideTimer.stop();
activationTimer.playFromStart();
});
node.setOnMouseExited(e -> {
toolTip.hide();
activationTimer.stop();
hideTimer.stop();
});
}
public static void main(String[] args) {
Application.launch(args);
}
}

Draw Arrows over Nodes

I want to draw arrows in a group over my grid view. The example works fine with 3x3 grid. But if I change this size to e.g. 4x4 these arrows are on the wrong place.
I colorized the source field (green) and the destination field (red) to make sure I target the right cells. The program clears the arrowGroup and draws two arrows every 3s.
import eu.lestard.grid.GridModel;
import eu.lestard.grid.GridView;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import static javafx.scene.paint.Color.RED;
public class App extends Application {
private GridView<States> gridView;
private StackPane stackPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane borderPane = new BorderPane();
stackPane = new StackPane();
borderPane.setCenter(stackPane);
Group arrowGroup = new Group();
GridModel<States> gridModel = new GridModel<>();
gridModel.setDefaultState(States.EMPTY);
gridModel.setNumberOfColumns(3);
gridModel.setNumberOfRows(3);
gridView = new GridView<>();
gridView.setGridModel(gridModel);
stackPane.getChildren().add(gridView);
stackPane.getChildren().add(arrowGroup);
final Scene scene = new Scene(borderPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
new Thread(() -> {
for (int i = 1; i <= 1000000; i++) {
Platform.runLater( () -> {
arrowGroup.getChildren().clear();
drawArrow(arrowGroup, new Point2D(0,0), new Point2D(2,1));
drawArrow(arrowGroup, new Point2D(1,1), new Point2D(0,2));
});
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.interrupted();
}
}
}).start();
}
// getRelativeBounds, getCenter based on https://stackoverflow.com/a/43119383/772883
private void drawArrow(Group group, Point2D from, Point2D to) {
final Line line = new Line();
System.out.println(String.format("Draw arrow from cell %s to %s", from, to));
System.out.println(String.format("group coord %s %s", group.getLayoutX(), group.getLayoutY()));
// Note: (X,Y) -> (Column, Row) => access via (Y,X)
final Pane cellPane = gridView.getCellPane(gridView.getGridModel().getCell(((int) from.getY()), (int) from.getX() ));
final Pane cellPane2 = gridView.getCellPane(gridView.getGridModel().getCell((int) to.getY() , (int) to.getX()));
cellPane.setBackground(new Background(new BackgroundFill(Color.DARKGREEN, CornerRadii.EMPTY, Insets.EMPTY)));
cellPane2.setBackground(new Background(new BackgroundFill(RED, CornerRadii.EMPTY, Insets.EMPTY)));
Bounds n1InCommonAncestor = getRelativeBounds(cellPane, gridView);
Bounds n2InCommonAncestor = getRelativeBounds(cellPane2, gridView);
Point2D n1Center = getCenter(n1InCommonAncestor);
Point2D n2Center = getCenter(n2InCommonAncestor);
System.out.println(String.format("Draw arrow from coord %s to %s", n1Center, n2Center));
System.out.println(n1Center);
System.out.println(n2Center);
line.setStartX(n1Center.getX());
line.setStartY(n1Center.getY());
line.setEndX(n2Center.getX());
line.setEndY(n2Center.getY());
group.getChildren().add(line);
}
private Bounds getRelativeBounds(Node node, Node relativeTo) {
Bounds nodeBoundsInScene = node.localToScene(node.getBoundsInLocal());
return relativeTo.sceneToLocal(nodeBoundsInScene);
}
private Point2D getCenter(Bounds b) {
return new Point2D(b.getMinX() + b.getWidth() / 2, b.getMinY() + b.getHeight() / 2);
}
public static enum States {
EMPTY,
X,
O
}
}
(If have replaced the arrows with lines to reduce the code.)
There is a gist withe the code and a gradle buildfile:
https://gist.github.com/anonymous/c54b12ee04b7e45f2e9f58e9de1d1df0
It would be great if somebody could explain why does only work with 3x3. Is there any better option than a group?

Circle movement upon rectangle Collision

I'm working on understanding collision detection and movement and I am having issues getting the movement down correctly. my goal is to allow a player to move around rectangles but not through them. my problem is that once the player reaches the bounds of the rectangle they are unable to backup. any help would be greatly appreciated.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import static javafx.scene.input.KeyCode.DOWN;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Circle;
public class CircleMovement extends Application {
//create the panes to handle the game
BorderPane mainPane = new BorderPane();
GridPane infoPane = new GridPane();
Pane gameField = new Pane();
Scene scene;
//create circle/movement properties
double increment = 5.0;
double radius = 10;
double x = radius, y = radius;
//create Rectangle properties
double Rwidth = 80;
double Rheight = 20;
//create player and objects
Circle player = new Circle(x, y, radius);
Rectangle r1 = new Rectangle(0, 100, Rwidth, Rheight);//Rectangle(int x, int y, int width, int height)
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
setPanes();
gameField.getChildren().addAll(r1, player);
moveCircleOnKeyPress(scene, player);//pass to player movement method
//set the stage
stage.setScene(scene);
stage.show();
}
public void moveCircleOnKeyPress(Scene scene, Circle player) {//player movement
scene.setOnKeyPressed((KeyEvent event) -> {
switch (event.getCode()) {
case UP:
player.setCenterY(player.getCenterY() - increment);//move player
if (player.getBoundsInLocal().intersects(r1.getBoundsInLocal())) {
player.centerYProperty().setValue(r1.getY());
}
break;
case RIGHT:
player.setCenterX(player.getCenterX() + increment);
if (player.getBoundsInLocal().intersects(r1.getBoundsInLocal())) {
player.centerYProperty().setValue(r1.getY());
}
break;
case DOWN:
player.setCenterY(player.getCenterY() + increment);
if (player.getBoundsInLocal().intersects(r1.getBoundsInLocal())) {
player.centerYProperty().setValue(r1.getY());
}
break;
case LEFT:
player.setCenterX(player.getCenterX() - increment);
if (player.getBoundsInLocal().intersects(r1.getBoundsInLocal())) {
player.centerYProperty().setValue(r1.getY());
}
break;
}
});
}
public void setPanes() {
infoPane.setMaxSize(200, 200);
mainPane.setMaxSize(800, 800);
mainPane.setCenter(gameField);//place the main game inside the center
mainPane.setRight(infoPane);
mainPane.setPadding(new Insets(10, 10, 10, 10));
mainPane.setStyle("-fx-border-color: red");
gameField.setStyle("-fx-background-color: white");
gameField.setStyle("-fx-border-color: black");
gameField.setMaxSize(600, 600);
scene = new Scene(mainPane, 800, 800);
player.setFill(Color.RED);
}
}
Since you are using Shape, it's a good idea to use Shape.intersect(player, r1).getBoundsInLocal().getWidth() != -1 to detect intersection. Also, instead of using player.centerYProperty().setValue(...);, just use player.setCenterY(...). You had a couple more problems, but the most important one is the idea of what to do once an intersection is detected. Once an intersection is detected you should go back to the distance just before the detection(only if you are moving in small enough steps).
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import static javafx.scene.input.KeyCode.DOWN;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
public class CircleMovement extends Application
{
//create the panes to handle the game
BorderPane mainPane = new BorderPane();
GridPane infoPane = new GridPane();
Pane gameField = new Pane();
Scene scene;
//create circle/movement properties
double increment = 5.0;
double radius = 10;
double x = radius, y = radius;
//create Rectangle properties
double Rwidth = 80;
double Rheight = 20;
//create player and objects
Circle player = new Circle(x, y, radius);
Rectangle r1 = new Rectangle(0, 100, Rwidth, Rheight);//Rectangle(int x, int y, int width, int height)
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage) throws Exception
{
setPanes();
gameField.getChildren().addAll(r1, player);
moveCircleOnKeyPress(scene, player);//pass to player movement method
//set the stage
stage.setScene(scene);
stage.show();
}
public void moveCircleOnKeyPress(Scene scene, Circle player)
{//player movement
scene.setOnKeyPressed((KeyEvent event) ->
{
switch (event.getCode())
{
case UP:
player.setCenterY(player.getCenterY() - increment);//move player
//if (player.getBoundsInLocal().intersects(r1.getBoundsInLocal()))
if (Shape.intersect(player, r1).getBoundsInLocal().getWidth() != -1)
{
player.setCenterY(player.getCenterY() + increment);
}
break;
case RIGHT:
player.setCenterX(player.getCenterX() + increment);
if (Shape.intersect(player, r1).getBoundsInLocal().getWidth() != -1)
{
player.setCenterX(player.getCenterX() - increment);
}
break;
case DOWN:
player.setCenterY(player.getCenterY() + increment);
if (Shape.intersect(player, r1).getBoundsInLocal().getWidth() != -1)
{
player.setCenterY(player.getCenterY() - increment);
}
break;
case LEFT:
player.setCenterX(player.getCenterX() - increment);
if (Shape.intersect(player, r1).getBoundsInLocal().getWidth() != -1)
{
player.setCenterX(player.getCenterX() + increment);
}
break;
}
});
}
public void setPanes()
{
infoPane.setMaxSize(200, 200);
mainPane.setMaxSize(800, 800);
mainPane.setCenter(gameField);//place the main game inside the center
mainPane.setRight(infoPane);
mainPane.setPadding(new Insets(10, 10, 10, 10));
mainPane.setStyle("-fx-border-color: red");
gameField.setStyle("-fx-background-color: white");
gameField.setStyle("-fx-border-color: black");
gameField.setMaxSize(600, 600);
scene = new Scene(mainPane, 800, 800);
player.setFill(Color.RED);
}
}

Resources