JavaFx 2D part in 3D application - javafx

I have a small problem with an Application I write.
I want to have to have a 3D field and on the right side a toolbar that contains 2D-Components like buttons.
I tried to simply add these Components to my root-Group, but then it is impossible to read the text and they move with all the others.
So, how can I seperate these two areas? Possibly with two scenes?
Thank you for every hint you can give :)

The best approach is using a SubScene for the 3D nodes. You can keep all the 2D content on top of it without rendering problems.
You can read about the SubScene API here.
This is a small sample of what you can do with this node:
private double mousePosX, mousePosY;
private double mouseOldX, mouseOldY;
private final Rotate rotateX = new Rotate(-20, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(-20, Rotate.Y_AXIS);
#Override
public void start(Stage primaryStage) throws Exception {
// 3D
Box box = new Box(5, 5, 5);
box.setMaterial(new PhongMaterial(Color.GREENYELLOW));
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.getTransforms().addAll (rotateX, rotateY, new Translate(0, 0, -20));
Group root3D = new Group(camera,box);
SubScene subScene = new SubScene(root3D, 300, 300, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.AQUAMARINE);
subScene.setCamera(camera);
// 2D
BorderPane pane = new BorderPane();
pane.setCenter(subScene);
Button button = new Button("Reset");
button.setOnAction(e->{
rotateX.setAngle(-20);
rotateY.setAngle(-20);
});
CheckBox checkBox = new CheckBox("Line");
checkBox.setOnAction(e->{
box.setDrawMode(checkBox.isSelected()?DrawMode.LINE:DrawMode.FILL);
});
ToolBar toolBar = new ToolBar(button, checkBox);
toolBar.setOrientation(Orientation.VERTICAL);
pane.setRight(toolBar);
pane.setPrefSize(300,300);
Scene scene = new Scene(pane);
scene.setOnMousePressed((MouseEvent me) -> {
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
rotateX.setAngle(rotateX.getAngle()-(mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle()+(mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
});
primaryStage.setScene(scene);
primaryStage.setTitle("3D SubScene");
primaryStage.show();
}
For more complex scenarios, have a look at the 3DViewer application under the OpenJFX project.

Related

JavaFx bindBidirectional Circle with textField

I try bind circle w textfield. When I try bind circle start jumping when I try move it. Why circle jump? When circle is unbind doesn't jump and I can move it unproblematically. Please help me. Where is the problem? I add text because stack overflow block my post
public class BindTest extends Application {
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setPadding(new Insets(5));
root.setHgap(10);
root.setVgap(10);
Scene scene = new Scene(root, 500, 250);
primaryStage.setTitle("Laczenie");
primaryStage.setScene(scene);
primaryStage.show();
TextField textFieldX = new TextField();
TextField textFieldY = new TextField();
Circle circle = new Circle(12, 22, 22, Color.YELLOW);
circle.setOnMousePressed(circleOnMousePressedHandler);
circle.setOnMouseDragged(circleOnMouseDraggedHandler);
root.add(textFieldX, 1, 0);
root.add(textFieldY, 2, 0);
root.add(circle, 2, 2);
circle.translateYProperty().addListener((observable, oldValue, newValue) -> {
textFieldX.setText((String.valueOf(circle.getCenterX() + newValue.doubleValue())));
});
circle.translateYProperty().addListener((observable, oldValue, newValue) -> {
textFieldY.setText((String.valueOf(circle.getCenterX() + newValue.doubleValue())));
});
StringConverter<Number> stringConverter = new NumberStringConverter();
textFieldX.textProperty().bindBidirectional(circle.translateXProperty(), stringConverter);
textFieldY.textProperty().bindBidirectional(circle.translateXProperty(), stringConverter);
}
public static void main(String[] args) {
launch(args);
}
}
private double coordinateX, coordinateY, orgTranslateX, orgTranslateY;
EventHandler<MouseEvent> circleOnMousePressedHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
coordinateX = event.getSceneX();
coordinateY = event.getSceneY();
orgTranslateX = ((Circle) (event.getSource())).getTranslateX();
orgTranslateY = ((Circle) (event.getSource())).getTranslateY();
}
};
EventHandler<MouseEvent> circleOnMouseDraggedHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double offsetX = event.getSceneX() - coordinateX;
double offsetY = event.getSceneY() - coordinateY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
((Circle) (event.getSource())).setTranslateX(newTranslateX);
((Circle) (event.getSource())).setTranslateY(newTranslateY);
}
};
You have two problems. One is a typo, you have textFieldY bound to circle.translateXProperty(), which probably causes all manner of weirdness.
More significantly, since you've bound the TextField text properties to the circle translate X & Y properties bidirectionally, you don't need the listeners. So what's happening is that you drag the circle which triggers the listeners which triggers the bindings which move the circle which triggers the listeners which triggers the bindings...
Just delete the listeners, fix the typo and everything works.

How to get 2D coordinates on window for 3D object in javafx

In javafx if we have 2D HUD (made of Pane and then out of it we create SubScene object for 2D Hud) and 3D SubScene and inside 3D scene we have some object with coordinates (x,y,z) - how can we get 2D coordinates in our HUD of the object if it is in visual field of our perspective camera?
I tried to get first Scene coordinates of the object and then convert it (sceneToScreen) coordinates and the same for point (0,0) of Pane and then to subtract first point from second point but i don't get right result. Sorry because of my bad English.Can Someone help with this?
There is a way to convert the 3D coordinates of an object in a subScene to a 2D scene coordinates, but unfortunately it uses private API, so it is advised not to rely on it.
The idea is based on how the camera projection works, and it is based on the com.sun.javafx.scene.input.InputEventUtils.recomputeCoordinates() method that is used typically for input events from a PickResult.
Let's say you have a node in a sub scene. For a given point of that node, you can obtain its coordinates like:
Point3D coordinates = node.localToScene(Point3D.ZERO);
and you can find out about the sub scene of the node:
SubScene subScene = NodeHelper.getSubScene(node);
Now you can use the SceneUtils::subSceneToScene method that
Translates point from inner subScene coordinates to scene coordinates.
to get a new set of coordinates, referenced to the scene:
coordinates = SceneUtils.subSceneToScene(subScene, coordinates);
But these are still 3D coordinates.
The final step to convert those to 2D is with the use of CameraHelper::project:
final Camera effectiveCamera = SceneHelper.getEffectiveCamera(node.getScene());
Point2D p2 = CameraHelper.project(effectiveCamera, coordinates);
The following sample places 2D labels in the scene, at the exact same position of the 8 vertices of a 3D box in a subScene.
private final Rotate rotateX = new Rotate(0, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(0, Rotate.Y_AXIS);
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private Group root;
#Override
public void start(Stage primaryStage) {
Box box = new Box(150, 100, 50);
box.setDrawMode(DrawMode.LINE);
box.setCullFace(CullFace.NONE);
Group group = new Group(box);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setFieldOfView(20);
camera.getTransforms().addAll (rotateX, rotateY, new Translate(0, 0, -500));
SubScene subScene = new SubScene(group, 500, 400, true, SceneAntialiasing.BALANCED);
subScene.setCamera(camera);
root = new Group(subScene);
Scene scene = new Scene(root, 500, 400);
primaryStage.setTitle("HUD: 2D Labels over 3D SubScene");
primaryStage.setScene(scene);
primaryStage.show();
updateLabels(box);
scene.setOnMousePressed(event -> {
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
});
scene.setOnMouseDragged(event -> {
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
updateLabels(box);
});
}
private List<Point3D> generateDots(Node box) {
List<Point3D> vertices = new ArrayList<>();
Bounds bounds = box.getBoundsInLocal();
vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ())));
return vertices;
}
private void updateLabels(Node box) {
root.getChildren().removeIf(Label.class::isInstance);
SubScene oldSubScene = NodeHelper.getSubScene(box);
AtomicInteger counter = new AtomicInteger(1);
generateDots(box).stream()
.forEach(dot -> {
Point3D coordinates = SceneUtils.subSceneToScene(oldSubScene, dot);
Point2D p2 = CameraHelper.project(SceneHelper.getEffectiveCamera(box.getScene()), coordinates);
Label label = new Label("" + counter.getAndIncrement() + String.format(" (%.1f,%.1f)", p2.getX(), p2.getY()));
label.setStyle("-fx-font-size:1.3em; -fx-text-fill: blue;");
label.getTransforms().setAll(new Translate(p2.getX(), p2.getY()));
root.getChildren().add(label);
});
}
The FXyz3D library has another similar sample.
EDIT
A late edit of this answer, but it is worthwhile mentioning that there is no need for private API. There is public API in the Node::localToScene methods that allows traversing the subScene.
So this just works (note the true argument):
Point3D p2 = box.localToScene(dot, true);
According to the JavaDoc for Node::localToScene:
Transforms a point from the local coordinate space of this Node into the coordinate space of its scene. If the Node does not have any SubScene or rootScene is set to true, the result point is in Scene coordinates of the Node returned by getScene(). Otherwise, the subscene coordinates are used, which is equivalent to calling localToScene(Point3D).
Without true the conversion is within the subScene, but with it, the conversion goes from the current subScene to the scene. In this case, this methods calls SceneUtils::subSceneToScene, so we don't need to do it anymore.
With this, updateLabels gets simplified to:
private void updateLabels(Node box) {
root.getChildren().removeIf(Label.class::isInstance);
AtomicInteger counter = new AtomicInteger(1);
generateDots(box).stream()
.forEach(dot -> {
Point3D p2 = box.localToScene(dot, true);
Label label = new Label("" + counter.getAndIncrement() + String.format(" (%.1f,%.1f)", p2.getX(), p2.getY()));
label.setStyle("-fx-font-size:1.3em; -fx-text-fill: blue;");
label.getTransforms().setAll(new Translate(p2.getX(), p2.getY()));
root.getChildren().add(label);
});
}

Box 3D JavaFX Rotate

I have two Boxes (Group), and when I rotate, the image displays like this:
Display Boxes
Rotate Boxes
When rotating, the Box (JANELA_MEIO_BOX) is distorted:
public class Demo1 extends Application {
private PhongMaterial texturedMaterial = new PhongMaterial();
private Image texture = new Image("/T3D/mapfooter.JPG");
private final PhongMaterial redMaterial = new PhongMaterial();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(final Stage stage) {
redMaterial.setSpecularColor(Color.ORANGE);
redMaterial.setDiffuseColor(Color.RED);
texturedMaterial.setDiffuseMap(texture);
javafx.scene.shape.Box JANELA_MEIO_BOX = new javafx.scene.shape.Box();
/* rotate */
JANELA_MEIO_BOX.setWidth(600.0);
JANELA_MEIO_BOX.setHeight(340.0);
JANELA_MEIO_BOX.setDepth(100.0);
JANELA_MEIO_BOX.setMaterial(texturedMaterial);
Group JANELA_001 = new Group();
stage.setTitle("Cube");
final CameraView cameraView = new CameraView();
final Scene scene = new Scene(cameraView, 1000, 800, true);
scene.setFill(new RadialGradient(225, 0.85, 300, 300, 500, false,
CycleMethod.NO_CYCLE, new Stop[]{new Stop(0f, Color.BLUE),
new Stop(1f, Color.LIGHTBLUE)}));
PerspectiveCamera camera = new PerspectiveCamera();
scene.setCamera(camera);
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
javafx.scene.shape.Box JAN_MAIN = new javafx.scene.shape.Box();
JAN_MAIN.setMaterial(redMaterial);
JAN_MAIN.setWidth(1000.0);
JAN_MAIN.setHeight(600.0);
JAN_MAIN.setDepth(100.0);
JAN_MAIN.getTransforms().add(new Translate(1, 1, 1));
JANELA_MEIO_BOX.getTransforms().add(new Translate(1, 1, 1));
JANELA_001.getChildren().addAll(JAN_MAIN, JANELA_MEIO_BOX);
cameraView.add(JANELA_001);
/* mouse events */
cameraView.frameCam(stage, scene);
MouseHandler mouseHandler = new MouseHandler(scene, cameraView);
KeyHandler keyHandler = new KeyHandler(stage, scene, cameraView);
/* scene */
stage.setScene(scene);
stage.show();
}
When rotating, the Box (JANELA_MEIO_BOX) is distorted
You have two boxes: a 1000x600x100 cube, and a 600x340x100 cube.
When you put both of them in a group, they are placed in the center: the bigger one goes from -500 to 500 in X, -300 to 300 in Y, -50 to 50 in Z, and the same goes for the smaller one, also in Z from -50 to 50.
When you render two shapes with their faces in the same exact Z coordinate you will always get these artifacts.
One quick solution, if you want to see both shapes, is just making the smaller one a little bit deeper:
JANELA_MEIO_BOX.setDepth(100.1);
And it is also convenient that you set Scene Antialiasing to Balanced:
final Scene scene = new Scene(cameraView, 1000, 800, true, SceneAntialiasing.BALANCED);

JavaFX Transition: Invert button color

I want to change the color of a button to it's invert color and back as a transition.
I was able to use Timeline with ColorAdjust to do this with the brightness, but I didn't found a effect class to transition the color of a button with Timeline.
Hope someone out there is able to help me?
Thanks!
A inversion effect can be achieved by using a Blend effect with BlendMode.DIFFERENCE and a topInput that is black for no inversion and white for completely inverted colors. Example:
#Override
public void start(Stage primaryStage) {
Blend blendEffect = new Blend(BlendMode.DIFFERENCE);
ColorInput input = new ColorInput();
blendEffect.setTopInput(input);
Button btn = new Button("Inversion Animation");
input.widthProperty().bind(btn.widthProperty());
input.heightProperty().bind(btn.heightProperty());
btn.setEffect(blendEffect);
btn.setStyle("-fx-body-color: orange;");
DoubleProperty brightness = new SimpleDoubleProperty(0);
input.paintProperty().bind(Bindings.createObjectBinding(() -> Color.BLACK.interpolate(Color.WHITE, brightness.get()), brightness));
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(brightness, 0d)),
new KeyFrame(Duration.seconds(1), new KeyValue(brightness, 1d))
);
timeline.setOnFinished(evt -> timeline.setRate(-timeline.getRate()));
btn.setOnAction((ActionEvent event) -> {
timeline.play();
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}

Connecting Circles with a Polyline JavaFX

I have created circles on a graph in JavaFX, and I want to connect those circles with a polyline. Does anyone know the syntax for doing this? Thanks!
A Polyline may work, but you can do this easier, if you use the Path class, since this allows you to access to the individual elements of the path (PathElements). You can use bindings to bind the position of the line points to the positions of the circles. This way the lines will stay at the appropriate positions, even if you move the circles later.
Example
private static void bindLinePosTo(Circle circle, LineTo lineTo) {
lineTo.xProperty().bind(circle.centerXProperty());
lineTo.yProperty().bind(circle.centerYProperty());
}
private static void animate(Circle circle, Duration duration, double dy) {
Timeline animation = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(circle.centerYProperty(), circle.getCenterY())),
new KeyFrame(duration, new KeyValue(circle.centerYProperty(), circle.getCenterY()+dy)));
animation.setAutoReverse(true);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
}
#Override
public void start(Stage primaryStage) {
MoveTo start = new MoveTo();
LineTo line1 = new LineTo();
LineTo line2 = new LineTo();
Circle c1 = new Circle(10, 100, 5);
Circle c2 = new Circle(50, 100, 5);
Circle c3 = new Circle(100, 100, 5);
c1.setFill(Color.RED);
c2.setFill(Color.RED);
c3.setFill(Color.RED);
start.xProperty().bind(c1.centerXProperty());
start.yProperty().bind(c1.centerYProperty());
bindLinePosTo(c2, line1);
bindLinePosTo(c3, line2);
Path path = new Path(start, line1, line2);
Pane root = new Pane(path, c1, c2, c3);
animate(c1, Duration.seconds(1), 100);
animate(c2, Duration.seconds(2), 50);
animate(c3, Duration.seconds(0.5), 150);
Scene scene = new Scene(root, 110, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
Use the Binding API, e. g. like this.

Resources