How to select a 2D node in a 3D scene? - javafx

Here is my code. You can copy-paste and follow what I write bellow to see the problem yourself.
public class MyApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(new MyView(), 100, 150);
stage.setScene(scene);
stage.show();
}
private class MyView extends BorderPane {
MyView() {
GridPane board = new GridPane();
int size = 3;
for (int i = 0; i < size*size; i++) {
BorderPane pane = new BorderPane();
pane.setMinSize(30, 30);
pane.setBackground(new Background(new BackgroundFill(Color.RED, null, null)));
pane.setBorder(new Border(new BorderStroke(null, BorderStrokeStyle.SOLID,
null, null, null)));
pane.setOnMousePressed(e -> {
PickResult pick = e.getPickResult();
Pane selectedNode = (Pane) pick.getIntersectedNode();
selectedNode.setBackground(new Background(new BackgroundFill(Color.GREEN, null, null)));
});
board.add(pane, i / size, i % size);
}
Box box = new Box(20d, 20d, 20d);
BorderPane boardPane = new BorderPane(box, null, null, board, null);
Group root = new Group(boardPane);
SubScene scene = new SubScene(root, USE_PREF_SIZE, USE_PREF_SIZE, true, SceneAntialiasing.BALANCED);
scene.widthProperty().bind(widthProperty());
scene.heightProperty().bind(heightProperty());
setCenter(scene);
}
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
I create a subscene with a grid of squares. When i press on a square I want its background to change color. This works in 2 situations:
if I don't add the Box to the boardPane
if I don't set the scene with a depth buffer
or both. But if i both add the box and set a depth buffer, the squares don't receive the event. instead the boardPane receives it. i guess it's something to do with 2D nodes in a 3D scene.
I tried setting combinations of these methods :setPickOnBounds, setDepthTest, setMouseTransparent but nothing worked.
What's the solution?

Once you have a 3D scene, there aren't really such things as 2D nodes, everything in the scene graph has an x, y and z co-ordinate; even things that were previously treated as 2D nodes when the depth buffer was set to false.
When you place a box in your root border pane, that root border pane assumes the 3D co-ordinates of the box. For picking purposes, the box you have defined is represented in 3D space from z co-ordinates -10 to 10, so the root border pane is defined as -10 for picking purposes. The border panes that you place inside the root border pane have no z co-ordinate defined for them, so they end up at a z co-ordinate of 0, which, from the viewers perspective is behind the root border pane.
So, the root border pane is receiving clicks, but because it is now on a different z plane than the rest of its contents, the other 2D square contents you have defined do not receive clicks. One could argue that the root border pane is not rendered at all as it has no color, so it should be treated as transparent and the clicks just go through to the child nodes, but it seems that is just not how the 3D picking algorithm for JavaFX works.
For your example, to get everything in the same Z plane and picking working correctly, add the following line inside your for loop: pane.setTranslateZ(-10);.
Note: I debugged this by adding the following line to your source code (which reported to me the target of the mouse clicks and the x,y,z pick result co-ordinate for each click):
root.setOnMouseClicked(System.out::println);
My advice is to avoid using layout panes that are designed for 2D purposes (e.g. border panes) to attempt laying out elements in 3D space. The JavaFX layout panes are really only meant for 2D layout. To handle positioning in 3D space you should manage co-ordinates yourself, by just using Groups rather than anything that derives from Pane. At the very least don't try adding 3D elements into 2D layout panes as the results can be confusing (as you have discovered).
You can further separate 2D and 3D items by placing them in different sub scenes (that is really what the intention of the sub scene notion in JavaFX is I think). An example of an application with multiple sub scenes is shown in the following answer: How to create custom 3d model in JavaFX 8?

Related

JavaFX 3D boxes overlapping each other

I am having a problem with JavaFX 3D, the problem is as follows:
When I turn my perspective camera around, the last added box (blue box) overlaps the first added box (red box), here is a screenshot:
can anyone tell me why is this happening? And is there a way to fix it? (the boxes are literally 2 box classes with a width, height, depth, position and color)
Minimal reproducible example since somebody asked for it:
Box box1 = new Box();
Box box2 = new Box();
box1.setWidth(300);
box2.setWidth(300);
box1.setHeight(300);
box2.setHeight(300);
box1.setDepth(300);
box2.setDepth(300);
box1.setTranslateX(300);
box2.setTranslateX(300);
box1.setTranslateY(300);
box2.setTranslateX(300);
Group root = new Group();
root.getChildren().addAll(box, box2);
PerspectiveCamera cam = new PerspectiveCamera();
Scene scene = new Scene(root);
scene.setCamera(camera);
stage.setScene(scene);
stage.show();
where stage is the stage inside public void start(Stage stage), JavaFX's default run method (any class that extends Application should implement it)
You probably have to add a subscene with the depth buffer enabled. See: https://openjfx.io/javadoc/15/javafx.graphics/javafx/scene/SubScene.html#%3Cinit%3E(javafx.scene.Parent,double,double,boolean,javafx.scene.SceneAntialiasing)
Solution:
I used the following constructor:
new Scene(root, WIDTH, HEIGHT, true, SceneAntialiasing.BALANCED);
instead of:
new Scene(root);

JavaFX 3D Having two scenes with two cameras viewing the same objects

Is there any way to have two scenes that are having two different cameras, but looking at the same objects at the same time with out duplicating instances of every object?
I am developing a 3D game that has main scene with camera that is following the player and I would want to have another scene in the corner that is showing the bird's eye view of the same environment, like a mini map.
Any ideas how to develop this?
For starters, you can't have two scenes at the same stage. You could have two scenes and two stages, but obviously that would mean having duplicated objects, that need to be synchronized between scenes and stages.
However, there is possible way to get, in the same stage and same scene, a small mini-map (2D) node on top of the overall 3D node, each of them having their own camera.
This is based on this answer, and the existing CameraView class in the FXyz library.
As you can see, CameraView is basically an ImageView node that goes on the main scene to the right bottom corner (or anywhere else), while the 3D part goes to a SubScene in the center of the scene.
Both the subScene and the imageView can have mouse/keyboard event handling, and both have a camera, so in a way you have two different 3D views with their own control of the same 3D object.
To get a "live" ImageView that reflects the content of the subScene, looking like a real subScene, but without duplicating objects, the CameraView mainly uses:
Node::snapshot: taking snapshots of the subScene with will get an updated Image for the ImageView.
SnapshotParameters::setCamera (see javadoc. This not so well-known feature allows taking the snapshot with a given perspective based on a given camera.
AnimationTimer to do this process all over again on every frame/pulse.
The following is a simple use case of the CameraView, that can be added to your project including the org.fxyz3d:fxyz3d:0.5.2 dependency.
#Override
public void start(Stage stage) {
// 1. SubScene
// 3D node
SpringMesh spring = new SpringMesh(10, 2, 2, 8 * 2 * Math.PI, 200, 100, 0, 0);
spring.setCullFace(CullFace.NONE);
spring.setTextureModeVertices3D(1530, p -> p.f);
// root
Group worldRoot = new Group(spring);
// Camera
PerspectiveCamera camera = new PerspectiveCamera(true);
CameraTransformer cameraTransform = new CameraTransformer();
cameraTransform.setTranslate(0, 0, 0);
cameraTransform.getChildren().add(camera);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setTranslateZ(-100);
camera.setFieldOfView(20);
cameraTransform.ry.setAngle(-30.0);
cameraTransform.rx.setAngle(-15.0);
worldRoot.getChildren().add(cameraTransform);
// SubScene
SubScene subScene = new SubScene(worldRoot, 800,600, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.DARKSLATEGRAY);
subScene.setCamera(camera);
// mouse, key events on subScene:
// subScene.setOnKeyPressed(event -> {...});
// subScene.setOnMousePressed(event -> {...});
// subScene.setOnMouseDragged(event -> {...});
// 2. CameraView
CameraView cameraView = new CameraView(subScene);
cameraView.setFirstPersonNavigationEabled(true);
cameraView.setFitWidth(350);
cameraView.setFitHeight(225);
cameraView.getRx().setAngle(-45);
cameraView.getT().setZ(-100);
cameraView.getT().setY(-500);
cameraView.getCamera().setTranslateZ(-100);
// Right-bottom corner
StackPane.setAlignment(cameraView, Pos.BOTTOM_RIGHT);
StackPane.setMargin(cameraView, new Insets(5));
// 3. Scene
StackPane root = new StackPane(subScene, cameraView);
root.setStyle("-fx-background-color: DEEPSKYBLUE;");
subScene.widthProperty().bind(root.widthProperty());
subScene.heightProperty().bind(root.heightProperty());
Scene scene = new Scene(root, 810,610, true, SceneAntialiasing.BALANCED);
stage.setTitle("MiniMapTest");
stage.setScene(scene);
stage.show();
// start timer
cameraView.startViewing();
}
Running the application, you will get:
and you can move the camera on the mini-map view to get a different view of the 3D object:
Now it's up to you to play around with both 3D subScene and 2D cameraView to get the effect you want. By setting cameraView.setFirstPersonNavigationEabled(false) the mini-map won't allow user interaction, and you could control its camera (i.e, keeping a given zoom level of the subScene...).

JavaFX 3D PerspectiveCamera affects drag position of a node

I have been having issues trying to move nodes in JavaFX within a 3D Scene. The issue is that I want the mouse to stay at the position within the node I've clicked, i.e., center. With a PerspectiveCamera it will alter the position. I've tried a couple of different methods and haven't had any luck. 2D Rectangles, and 3D Boxes(without a camera) work perfectly, but once a PerspectiveCamera is added, regardless of true/false parameter, I have issues.
I am wondering if this a bug that should be reported, or if there is some way to get another the perspective affecting the moving of nodes
public class Move extends Application {
double x0,xDiff;
double y0,yDiff;
#Override
public void start(Stage primaryStage) {
Box b = new Box(100,100,1);
b.setLayoutX(0);
b.setLayoutY(0);
// b.setTranslateZ(20000);
Pane root = new Pane();
root.getChildren().add(b);
PhongMaterial p = new PhongMaterial();
p.setDiffuseColor(Color.RED);
b.setMaterial(p);
Scene scene = new Scene(root, 2000, 1250,true);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setTranslateZ(-1000);
camera.setFarClip(2000);
scene.setCamera(camera);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
b.setOnMousePressed(event
->{
x0 = event.getSceneX();
y0 = event.getSceneY();
event.consume();
});
b.setOnMouseDragged(event
->{
xDiff = event.getSceneX() - x0;
yDiff = event.getSceneY() - y0;
b.setLayoutX(b.getLayoutX() + xDiff);
b.setLayoutY(b.getLayoutY() + yDiff);
x0 = event.getSceneX();
y0 = event.getSceneY();
});
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I'm using Java 8 update 91 or 181 (it seems netbeans puts the default at 91, but I have 181 as well)
JavaFX Moving 3D objects with mouse on a virtual plane
I also found this post, and had tried the answer and it seemed like it also had some issues, but seemed much better(except it it was hard to test with the additional code of spinning the node when dragging, so if you drag off the sphere it rotates instead).
Thank you very much
EDIT: After trying to go back to how I originally dragged, I was able to get to a point where I could get the mouse cursor to stay in the middle, but I am trying to figure out how to get the exact position.
b.setLayoutX(b.getLayoutX() + (event.getX()));
b.setLayoutY(b.getLayoutY() + (event.getY()));
will give me the center of the node.
I originally used similar code to this for 2D, but was having issues with 3D, which I am assuming was due to the differences of 0,0 top-left corner, vs 0,0,0 in the center.
The code for 2D was something along the lines of
b.setLayoutX(b.getLayoutX() + (event.getX()-b.getMinX()));
b.setLayoutY(b.getLayoutY() + (event.getY()-b.getMinY()));
Essentially, from what I see when I set the original layout + the event position it just moves the center/top-left to the coordinates of the mouse event, so I would try to get the difference between the origin of the node and the mouseEvent, which is what event.getX() does, and try to figure out the difference to move, which is what the event.getSceneX() - x0 is for. I tried doing it without the Scene X/Y but it doesn't seem to work properly, but I'm not sure if using the SceneX/Y is what I should be doing.

JavaFX - Get Coordinates of Node Relative to its Parent

I am making a simple graphical interface for saving previously generated images. All images come to me square but I want to allow for some cropping functionality (more precisely cutting off equal parts from the bottom and top of the image). I want to do this by allowing the user to drag a shaded region over the image which will tell the user that this region will be cropped out. See the below image for details. To enable this drag functionality I have added small triangles that I want the user to drag which in turn will move the shaded regions about. However the coordinates for the triangles are all weird and seem nonsensical. Therefor I was wondering what the best way is to get the coordinates of the triangles in relation to the ImageView (or their first common parent node) in terms of ImageView-side-lengths. So if the triangle is in the center its coordinates are [0.5, 0.5] for instance.
The Image view will be moving around inside the scene and will also be changing size so it is vital that I can get the coordinates relative to not only the ImageView but also to the size of the ImageView.
Here is also the surrounding hierarchy of nodes if that helps. The Polygons are the triangles and the regions are the rectangles.
Thanks for all forms of help!
Node.getBoundsInParent returns the bounds of a node in it's parent coordinates. E.g. polygon.getBoundsInParent() would return the bounds in the VBox.
If you need to "go up" one additional step, you can use parent.localToParent to do this. vBox.localToParent(boundsInVbox) returns the bounds in the coordinate system of the AnchorPane.
To get values relative to the size of the image, you simply need to divide by it's size.
The following example only allows you to move the cover regions to in one direction and does not check, if the regions intersect, but it should be sufficient to demonstrate the approach.
The interesting part is the event handler of the button. It restricts the viewport of the second image to the part of the first image that isn't covered.
private static void setSideAnchors(Node node) {
AnchorPane.setLeftAnchor(node, 0d);
AnchorPane.setRightAnchor(node, 0d);
}
#Override
public void start(Stage primaryStage) {
// create covering area
Region topRegion = new Region();
topRegion.setStyle("-fx-background-color: white;");
Polygon topArrow = new Polygon(0, 0, 20, 0, 10, 20);
topArrow.setFill(Color.WHITE);
VBox top = new VBox(topRegion, topArrow);
top.setAlignment(Pos.TOP_CENTER);
topArrow.setOnMouseClicked(evt -> {
topRegion.setPrefHeight(topRegion.getPrefHeight() + 10);
});
// create bottom covering area
Region bottomRegion = new Region();
bottomRegion.setStyle("-fx-background-color: white;");
Polygon bottomArrow = new Polygon(0, 20, 20, 20, 10, 0);
bottomArrow.setFill(Color.WHITE);
VBox bottom = new VBox(bottomArrow, bottomRegion);
bottom.setAlignment(Pos.BOTTOM_CENTER);
bottomArrow.setOnMouseClicked(evt -> {
bottomRegion.setPrefHeight(bottomRegion.getPrefHeight() + 10);
});
Image image = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/402px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg");
ImageView imageView = new ImageView(image);
setSideAnchors(top);
setSideAnchors(bottom);
setSideAnchors(imageView);
AnchorPane.setTopAnchor(top, 0d);
AnchorPane.setBottomAnchor(bottom, 0d);
AnchorPane.setTopAnchor(imageView, 0d);
AnchorPane.setBottomAnchor(imageView, 0d);
AnchorPane container = new AnchorPane(imageView, top, bottom);
ImageView imageViewRestricted = new ImageView(image);
Button button = new Button("restrict");
button.setOnAction(evt -> {
// determine bouns of Regions in AnchorPane
Bounds topBounds = top.localToParent(topRegion.getBoundsInParent());
Bounds bottomBounds = bottom.localToParent(bottomRegion.getBoundsInParent());
// set viewport accordingly
imageViewRestricted.setViewport(new Rectangle2D(
0,
topBounds.getMaxY(),
image.getWidth(),
bottomBounds.getMinY() - topBounds.getMaxY()));
});
HBox root = new HBox(container, button, imageViewRestricted);
root.setFillHeight(false);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

Strange behaviour when having a group in vbox

Can someone please explain me why this strange behaviour exists? When I have a group in a vbox every item in a child appears to modify the siblings to.
Following strange behaviour happens:
everything normal here
here too everything normal
whoops, why did the searchbar move???
First of the structure of the application I have:
root (VBox) //vbox so the menubar has its own space
├───menubar (MenuBar)
└───contentroot (Group)
├───searchbar (TextField) //searchbar should always be on the top left
└───nodeRoot (Group)
├───circle1 (Nodes)
└───circle2 (Nodes)
The root is a vbox so the menubar has its own unchallenged space. The searchbar should always be top left, directly under the menubar.
The nodeRoot should contain every other node. Kinda like a drawing board which I should be able to drag around.
Code:
public void start(Stage primaryStage) throws Exception{
VBox root = new VBox();
MenuBar menuBar = new MenuBar(new Menu("File"));
Group contentRoot = new Group();
contentRoot.getChildren().add(new TextField("SearchBar"));
Group nodeRoot = new Group();
contentRoot.getChildren().add(nodeRoot);
root.getChildren().addAll(menuBar, contentRoot);
Circle circle = new Circle(30, Color.RED);
nodeRoot.getChildren().add(circle);
Scene scene = new Scene(root, 300, 275);
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnMousePressed(event -> {
circle.setTranslateX(event.getSceneX() - 15);
circle.setTranslateY(event.getSceneY() - 15);
});
}
My guess why this happens:
The problem started to appear after I added the menubar and put everything into a VBox. This is when the siblings of the nodeRoot get changed too. My guess is that because VBox is a Region the behaviour is different than a normal group which expands. But then I dont understand why it only happens if the item moves to the left or top.
Can somebody please explain why this happens and how I can fix it?
From the javadocs for Group:
A Group will take on the collective bounds of its children and is not
directly resizable.
When you click near the top or left of the scene, the circle's bounds include negative values. Since the group takes on those bounds, it also takes on negative values. The TextField never has any layout bounds set, so the Group positions it at (0,0). Hence the text field can end up below or to the right of the circle. The vbox positions the group in order to try and contain it entirely, so it shifts it right if it contains negative x-value bounds and down if it contains negative y-value bounds.
If you use a Pane to contain the circle, instead of a Group:
Pane contentRoot = new Pane();
it behaves more intuitively: the Pane does not take on the union of the bounds of its child nodes, so if the circle has negative bounds, it just moves left and/or above the pane's visible area.

Resources