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.
Related
I'm trying to create a equilateral triangle with JavaFX. It is said that use the Polygon and setLayoutX() and setLayoutY(). So how to do that? This is the code I tried:
#Override public void start(Stage stage) throws Exception {
stage.setTitle("Board");
StackPane root = new StackPane();
Scene scene = new Scene(root, 600, 519);
stage.setScene(scene);
Polygon triangle = new Polygon();
stage.show();
}
Two problems:
You did not add the polygon to the container
You did not define the points of the polygon
Your code should be something like this:
#Override
public void start (Stage stage) throws Exception {
stage.setTitle("Board");
Polygon triangle = new Polygon();
//triangle.setLayoutX(100);
//triangle.setLayoutY(400);
triangle.getPoints()
.addAll(new Double[] {300.0, 50.0, 250.0, 100.0, 350.0, 100.0,});
Group root = new Group(triangle); // You can replace with StackPane for center alignment
Scene scene = new Scene(root, 600, 519);
stage.setScene(scene);
stage.show();
}
Once you do that, you should see something like the image below. You need to figure out the (x,y) coordinates for your triangle.
When I uncommented the setLayoutX() and setLayoutY() lines, the result was like the image below.
If you need a drawing in Java FX tutorial, check out this site.
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);
});
}
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);
I want to make half radial gauge. I've tried to put an arc of 180° and I put a line on it, and I tried to move this line to use this as an indicator of position.
I tried to move the line to different position in Scene Builder and it appears that it could works but when I tried to move with code it doesn't.
private Scanner sc;
public void deplacement_aiguille(){
sc = new Scanner(System.in);
double angle = sc.nextDouble();
String valangle = Double.toString(angle);
valeur_gisement.setText(valangle);
float xfin = (float) ((Math.cos(angle))*200);
float yfin = (float) ((Math.sin(angle)*200));
if(angle<90){
gisement.relocate(5,10);
gisement.setEndX(xfin);
gisement.setEndY(-yfin);
}
if (angle>90){
gisement.relocate(6,10);
gisement.setEndX(xfin);
gisement.setEndY(yfin);
}
if(angle==90){
gisement.setEndX(0);
gisement.setEndY(200);
}
if(angle==180){
gisement.setEndX(200);
gisement.setEndY(0);
}
if(angle==0){
gisement.relocate(5,10);
gisement.setEndX(-200);
gisement.setEndY(0);
}
}
With this code I want to move the line in my gridpane to the column number 5 and row number 10 when my angle is smaller than 90 and to the column number 6 and row 10 when angle is bigger than 90
I'm not sure if it's the good way to do it. So if you have any better idea I'll take it. But if it's the good way to do it would you help me to make it works.
Thanks
I'd put the Arc and the Line in a Pane (for absolute positioning) and use a Rotate transform to move the line.
IMO More important is to bind the Rotate to the input value. In my example I use a Slider for it:
public class Gauge extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Arc arc = new Arc();
arc.setCenterX(100);
arc.setCenterY(0);
arc.setRadiusX(100);
arc.setRadiusY(100);
arc.setStartAngle(0);
arc.setLength(180);
arc.relocate(0, 0);
double lineCenterX = 100d;
double lineCenterY = 100d;
Line line = new Line(lineCenterX, lineCenterY, 0, 100);
line.setStroke(Color.YELLOW);
Rotate rotate = new Rotate();
rotate.setPivotX(lineCenterX);
rotate.setPivotY(lineCenterY);
line.getTransforms().add(rotate);
Pane pane = new Pane();
pane.getChildren().addAll(arc, line);
Slider slider = new Slider();
rotate.angleProperty().bind(slider.valueProperty().multiply(1.8));
VBox vbox = new VBox(10);
vbox.getChildren().addAll(pane, slider);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
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.