JavaFX animation/View 3d with panes - java

I've been working in JavaFX for the first time to try to make an app which I can use to demonstrate a simple animation with button controls. To do this I've used a BoarderPane for the primary stage, with both the left,right, and bottom using Gridpanes.
However, for the center I need to be able to draw a sphere with a line within it which I can rotate for different views while simultaneously being able to animated, or at the very least snap-move, the line within.
I've tried using a Pane for the center which doesn't work. I've tried making it it's own scene and sub scene which doesn't work. And I can't use a canvas as that is only for 2D animation.
Is there a way I can animate the line or rotate the camera while maintaining the BoarderPane layout I've created?
I've tried looking at the following before to understand what I could do but most just seem incompatible with the BoarderPane:
JavaFX Rotating Camera Around a Pivot
JavaFX Canvas rotate image with fixed center (and without bouncing)

Whenever you want to mix 2D and 3D (and camera) you have to use a SubScene container for the 3D content:
SubScene provides separation of different parts of a scene, each of which can be rendered with a different camera, depth buffer, or scene anti-aliasing. A SubScene is embedded into the main scene or another sub-scene.
If you have a BorderPane container, you can perfectly add the subScene to its center.
For a similar use case, you can check the Qubit3D class from here, which is mainly a group that holds a sub scene with an Sphere and a cylinder (both from the FXyz 3D library).
You can add this group easily to your 2D scene:
private final Rotate rotate = new Rotate(0, 0, 0, 0, javafx.geometry.Point3D.ZERO.add(1, 1, 1));
#Override
public void start(Stage primaryStage) throws Exception {
final Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(10),
new KeyValue(rotate.angleProperty(), 360)));
final Qubit3D qubit = new Qubit3D();
final BorderPane root = new BorderPane(qubit);
final Button buttonAnimate = new Button("Animate");
buttonAnimate.setOnAction(e -> {
rotate.setAngle(0);
timeline.playFromStart();
});
root.setLeft(new StackPane(buttonAnimate));
final Button buttonStop = new Button("Stop");
buttonStop.setOnAction(e -> timeline.stop());
root.setRight(new StackPane(buttonStop));
Scene scene = new Scene(root, 600, 400, true, SceneAntialiasing.BALANCED);
scene.setFill(Color.BISQUE);
primaryStage.setScene(scene);
primaryStage.setTitle("Qubit3D Sample");
primaryStage.show();
qubit.rotateRod(rotate);
}
The only modification I've added to Qubit3D is:
public void rotateRod(Rotate rotate) {
rodSphere.getTransforms().setAll(rotate);
}
If you run it:
Note that you can interact with the sphere (via mouse dragged events), while you can also start/stop a full rotation of sphere and rod.

Related

JavaFX Canvas.setScaleX/Y(2) not scaling to twice the size

Maybe I'm misunderstanding something basic, but I'm experimenting with JavaFX and am baffled why scaling a Canvas (using .setScaleX/Y) with value of 2 doesn't result in canvas with two times bigger width/height.
The relevant code is this: (I'm not using any .fxml at this point)
public class Main extends Application {
#Override
public void start(Stage primaryStage){
AnchorPane pane = new AnchorPane();
Canvas canvas = new Canvas();
canvas.setWidth(100);
canvas.setHeight(100);
canvas.getGraphicsContext2D().setFill(Color.BLUE);
canvas.getGraphicsContext2D().fillRect(0,0,100,100);
canvas.setScaleX(2);
canvas.setScaleY(2);
pane.getChildren().add(canvas);
primaryStage.setScene(new Scene(pane, 200, 200));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This results in the canvas taking about 2/3 (in terms of width/heighth, not area) of the window. Using scale of 3 makes it fill the whole window (bar one pixel gap at the edge) and I don't understand why. I looked at the documentation but it seems like scaling factor of 2 should result in two times the dimensions.
EDIT:
So per suggestion below, translating the canvas by half it's width/height fixes the issue. But so does centering it in a StackPane, so I don't understand why the AnchorPane won't anchor it by it's top/left edge if it really is 200x200 after scaling, it's as if it was anchoring it by it's 100x100 edges. But if the Canvas is 100x100 then how is it drawing outside of those bounds if simply centered in StackPane?
EDIT2:
With some experimenting I think I confirmed that the AnchorPane is positioning the Canvas "as if" it was still 100x100. So by setting the top and left anchors to 50 I can get the same results as translating by 50 or as if centering it in StackPane.
But that means that effectively the Canvas is "drawing" outside of it's "edges". Surely this can't be right?
The information added in your edits in the original post give most of the answer to the question.
First note that scaleX and scaleY scale a node around its center. From the documentation: "Defines the factor by which coordinates are scaled about the center of the object along the X axis of this Node."
Second, note that transformations applied to a node are not taken into account in the layout calculations for that node. From the layout documentation (See the section "Visual Bounds vs Layout Bounds"):
So for example ... if a ScaleTransition is used to pulse the size of a
button, that pulse animation will not disturb layout around that
button. If an application wishes to have the effect, clip, or
transform factored into the layout of a node, it should wrap that node
in a Group.
The last sentence in that quote from the documentation refers to the fact that the layout bounds of a Group includes any transformations applied to the group's children (but not to the group itself).
To visualize what is happening, start with a 100x100 canvas. The layout ignores the scaling, so you can visualize this as anchoring the canvas to the top left corner of the anchor pane before applying the scaling. Then the canvas is scaled about its center, pushing all four corners 50 pixels away from the center in both dimensions. Consequently the bounds of the canvas in the anchor pane's coordinate system are from (-50, -50) to (150, 150). You can verify this by adding
System.out.println(canvas.getBoundsInParent());
after the stage is shown.
There are two solutions. One is to wrap the canvas in a Group. The layout bounds of the Group will account for the scaling on the canvas:
public class Main extends Application {
#Override
public void start(Stage primaryStage){
AnchorPane pane = new AnchorPane();
Canvas canvas = new Canvas();
canvas.setWidth(100);
canvas.setHeight(100);
canvas.getGraphicsContext2D().setFill(Color.BLUE);
canvas.getGraphicsContext2D().fillRect(0,0,100,100);
canvas.setScaleX(2);
canvas.setScaleY(2);
Group group = new Group(canvas);
AnchorPane.setTopAnchor(group, 0.0);
AnchorPane.setLeftAnchor(group, 0.0);
pane.getChildren().add(group);
primaryStage.setScene(new Scene(pane, 200, 200));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The other solution is to apply a transformation which scales the canvas about its top-left corner:
public class Main extends Application {
#Override
public void start(Stage primaryStage){
AnchorPane pane = new AnchorPane();
Canvas canvas = new Canvas();
canvas.setWidth(100);
canvas.setHeight(100);
canvas.getGraphicsContext2D().setFill(Color.BLUE);
canvas.getGraphicsContext2D().fillRect(0,0,100,100);
Scale scale = new Scale(2,2);
scale.setPivotX(0);
scale.setPivotY(0);
canvas.getTransforms().add(scale);
pane.getChildren().add(canvas);
primaryStage.setScene(new Scene(pane, 200, 200));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You've already added canvas to the pane, try to apply .setScaleX/Y before pane.getChildren().add(canvas).

How can i add movable objects inside a jpg pic?

I am new in java.
I am trying to build a three bead game.
I am having problems to place 6 movable objects inside a jpg file.
I have tried using JPanel but the objects doesn't show.
Not sure if I understood you correctly, but I suppose you want to display background (a JPG image) and then programatically move three beads on it. If that's the case, there's Simple tutorial on how to work with images in JavaFX. To just display image, you need to do the following:
public void start(Stage stage) throws FileNotFoundException {
//Creating an image
Image image = new Image(new FileInputStream("file path"));
//Setting the image view 1
ImageView imageView1 = new ImageView(image);
//Creating a Group object
Group root = new Group(imageView1);
//Creating a scene object
Scene scene = new Scene(root, 600, 400);
//Adding scene to the stage
stage.setScene(scene);
//Displaying the contents of the stage
stage.show();
}
To move beads, you need to keep their state in your application and re-draw it periodically. The tutorial I've mentioned describes basics of pixel manipulation so you may want to check that too.

JavaFX: Trouble binding properties to panes

I am a beginner who just recently started learning JavaFX, and I seem to be making the same reoccurring mistake within my programs. For example, in the following code I am trying to draw the X-Axis and Y-Axis and have it binded to half the width and height of the pane. When executed, the axes are very small and located at the topleft corner, but as you resize the window of the application, the axes slowly increase in size until the window not being resized anymore.
public class Debug extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
GraphFunc test = new GraphFunc();
pane.getChildren().add(test);
Scene scene = new Scene(pane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class GraphFunc extends Pane {
private double xAxisSpan, yAxisSpan;
public GraphFunc() {
xAxisSpan = 100;
yAxisSpan = 100;
drawAxes();
}
private void drawAxes() {
Line xAxis = new Line(0, 50, 100, 50);
Line yAxis = new Line(50, 0, 50, 100);
xAxis.setStartX(0);
xAxis.startYProperty().bind(heightProperty().divide(2));
xAxis.endXProperty().bind(widthProperty());
xAxis.endYProperty().bind(heightProperty().divide(2));
yAxis.startXProperty().bind(widthProperty().divide(2));
yAxis.setStartY(0);
yAxis.endXProperty().bind(widthProperty().divide(2));
yAxis.endYProperty().bind(heightProperty());
getChildren().addAll(xAxis, yAxis);
}
}
I am confused because when pane is change to a StackPane, this does not happen. Also if I moved the code in drawAxes() to start() and added the lines to pane it would also not do this. Please explain, I cannot seem to understand what is happening after researching and playing around with it.
This is happening because of Pane. Pane is meant to be used when absolute positioning of children is required.
This is from Oracle documentation:
This class may be used directly in cases where absolute positioning of children is required since it does not perform layout beyond resizing resizable children to their preferred sizes. It is the application's responsibility to position the children since the pane leaves the positions alone during layout.
Pane pane = new Pane();
GraphFunc test = new GraphFunc();
pane.getChildren().add(test);
Scene scene = new Scene(pane, 500, 500);
If you remove the width and height from the scene and instead set the width and height directly on to test like this:
public void start(Stage primaryStage) {
GridPane pane = new GridPane();
GraphFunc test = new GraphFunc();
test.setPrefSize(500, 500);
pane.getChildren().add(test);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
You will get this:
drawn image
However you will not be able to span.
From Oracle documentation:
Note: if an application needs children to be kept aligned within a
parent (centered, positioned at top-left, etc), it should use a
StackPane instead.
Also from Oracle documentation:
Pane does not clip its content by default, so it is possible that
childrens' bounds may extend outside its own bounds, either if
children are positioned at negative coordinates or the pane is resized
smaller than its preferred size.

Why image disappears from titled pane after mouse event?

I have a titled pane, and I'm displaying an image in this pane, but the image is very big so I resize it, to fit the pane. Since the image becomes smaller, the user might want to view it in original size, so I implemented a method on mouse click opening the original image in new bigger window.
imgViewPicture.setFitWidth(titledPanePicture.getPrefWidth());
imgViewPicture.setPreserveRatio(true);
imgViewPicture.setSmooth(true);
imgViewPicture.setCache(true);
titledPanePicture.setContent(imgViewPicture);
The problem is, when I click on titledPanePicture the small image disappears, its content is cleared. Why? For the solution I added a reloading of the content on mouseclick event. But I dont like it, I think the content should stay the same for titledPanePicture.
Here is my mouseclick event:
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
Group root = new Group();
Scene scene = new Scene(root);
scene.setFill(Color.BLACK);
HBox box = new HBox();
imgViewPicture.setFitWidth(0);
box.getChildren().add(imgViewPicture);
root.getChildren().add(box); // before tPane
stage.setTitle("Picture Original Size");
stage.setScene(scene);
stage.sizeToScene();
stage.show();
reloadContentofTitledPane(); // This line is the fix but I find it unnecessary
Tips would be appreciated.
You seem to use the same ImageView in both scenes. However a Node can only be placed at a single place in a single scene graph.
Create a new node for your new Scene.
ImageView newImageView = new ImageView(imgViewPicture.getImage());
...
box.getChildren().add(newImageView);

How to scale and set coordinates of MediaView?

I would like to embedd a video in my JavaFx 2.x scene and also resize it and relocate it to my needs. The problem I'm having is following. If I build a MediaView component and then translate X or Y coordinate, then the whole view is being properly moved like so:
MediaView mv = (...)
mv.setTranslateX(200);
mv.setTranslateY(200);
I could do a similar transformation with scaling property:
MediaView mv = (...)
mv.setScaleX(2);
mv.setScaleY(2);
which will properly scale mv instance two times in dimension.
However, the problem is when I combine those two translations. The mv instance is being scaled but it always ends up in screen coordinates (0,0). This is of course incorrect behavior from my perspective.
I have also tried to wrap my MediaView component within some wrapper node, like Group and perform translations on this element. The behavior is the same.
How can I properly move and scale MediaView component at the same time?
Edit:
Here is my code, although I'm using here ImageView. This is irrelevant, however. After running this code, image will be placed at (0,0) instead of (100,100).
#Override
public void start(Stage stage) throws Exception {
Group root = new Group();
// Img's dimension is 200x200
javafx.scene.image.Image img = new javafx.scene.image.Image("/home/bachman/projects/cs-player/src/main/resources/content.png");
ImageView iv = new ImageView(img);
root.getChildren().add(iv);
// Move Image View
iv.setTranslateX(100);
iv.setTranslateY(100);
// Scale Image View
iv.setScaleX(2.0);
iv.setScaleY(2.0);
Scene scene = new Scene(root);
stage.setWidth(600);
stage.setHeight(600);
stage.setScene(scene);
stage.show();
}
When you use setScaleX, setScaleY, scaling occurs from the center of the node.
If you want to translate by an amount in addition to scaling, you need to take the scaling expansion into account when you set the required translation values. For example, if the node doubles in size (and you want to translate the node to a position relative to the upper left corner of the unscaled node), you need to translate by an additional half the node width and height.
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.media.*;
import javafx.scene.transform.*;
import javafx.stage.Stage;
public class TransformedVideo extends Application {
public static void main(String[] args) throws Exception { launch(args); }
#Override public void start(final Stage stage) throws Exception {
final MediaPlayer oracleVid = new MediaPlayer(new Media("http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"));
final MediaView mediaView = new MediaView(oracleVid);
mediaView.setFitWidth(320); mediaView.setFitHeight(240); mediaView.setPreserveRatio(false);
mediaView.setTranslateX(mediaView.getFitWidth() / 2 + 200);
mediaView.setTranslateY(mediaView.getFitHeight() / 2 + 200);
mediaView.setScaleX(2); mediaView.setScaleY(2);
// alternative method of scaling and translating.
// mediaView.getTransforms().addAll(
// new Translate(200, 200),
// new Scale(2, 2)
// );
Group group = new Group(mediaView);
stage.setScene(new Scene(group, 1250, 800));
stage.show();
oracleVid.play();
System.out.println(group.getBoundsInParent());
}
}
When performing multiple transforms on a node, rather than using the setScale/setTransform/setRotate methods, it is often easier to just supply a list of transforms to the getTransforms() method.

Categories