Getting rotateTransition and pathtransition to work on a resizable window - animation

I had a bit of a problem developing a test Javafx program that deals with passing rectangle objects across the screen from different directions. In order to make this work, I created the rectangle objects and then three line objects. I then created a PathTransition object that would have a rectangle travel along each line object. From there, I created a Pane object and added both the three rectangles and three lines on top of it.
To simulate that the rectangles were coming from different directions, I used a RotateTransition object and used the Pane object as its node. Basically, the program is just a rotating pane that carries three lines that each have a rectangle moving down the path of said line. However, when I want to resize the window, I don't know how to exactly cause the size of the pane to change with it without messing up the animations. Any help would be greatly appreciated. Thanks! Here is the code:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.animation.PathTransition;
import javafx.animation.RotateTransition;
import javafx.animation.Timeline;
public class Test extends Application {
#Override
public void start(Stage primaryStage) {
// Pane object
Pane pane = new Pane();
// Rectangle objects
Rectangle yellowRectangle = new Rectangle(10,10,10,10);
yellowRectangle.setFill(Color.YELLOW);
Rectangle greenRectangle = new Rectangle(10,10,10,10);
greenRectangle.setFill(Color.GREEN);
Rectangle blueRectangle = new Rectangle(10,10,10,10);
blueRectangle.setFill(Color.BLUE);
Line yellowLine = new Line(0,0,500,500);
yellowLine.setStroke(Color.YELLOW);
Line greenLine = new Line(500,0,0,500);
greenLine.setStroke(Color.GREEN);
Line blueLine = new Line(0,250,500,250);
blueLine.setStroke(Color.BLUE);
pane.getChildren().addAll(yellowRectangle, greenRectangle, blueRectangle, yellowLine, greenLine, blueLine);
// Pathtransitions
PathTransition yellowPath = new PathTransition();
yellowPath.setDuration(Duration.millis(4000));
yellowPath.setPath(yellowLine);
yellowPath.setNode(yellowRectangle);
yellowPath.setCycleCount(Timeline.INDEFINITE);
yellowPath.setAutoReverse(true);
yellowPath.play();
PathTransition greenPath = new PathTransition();
greenPath.setDuration(Duration.millis(4000));
greenPath.setPath(greenLine);
greenPath.setNode(greenRectangle);
greenPath.setCycleCount(Timeline.INDEFINITE);
greenPath.setAutoReverse(true);
greenPath.play();
PathTransition bluePath = new PathTransition();
bluePath.setDuration(Duration.millis(4000));
bluePath.setPath(blueLine);
bluePath.setNode(blueRectangle);
bluePath.setCycleCount(Timeline.INDEFINITE);
bluePath.setAutoReverse(true);
bluePath.play();
// Rotate Transition
RotateTransition yellowRotate = new RotateTransition();
yellowRotate.setAxis(Rotate.Z_AXIS);
yellowRotate.setByAngle(360);
yellowRotate.setCycleCount(500);
yellowRotate.setDuration(Duration.millis(10000));
yellowRotate.setAutoReverse(true);
yellowRotate.setNode(pane);
yellowRotate.play();
// Borderpane
BorderPane borderPane = new BorderPane();
borderPane.setCenter(pane);
Scene scene = new Scene(borderPane, 1000, 1000);
// Bind the BorderPane to the scene
borderPane.prefHeightProperty().bind(scene.heightProperty());
borderPane.prefWidthProperty().bind(scene.widthProperty());
primaryStage.setTitle("Test");
primaryStage.setScene(scene);
primaryStage.setResizable(true);
primaryStage.show();
}
}

Related

Printing pixelates when clipped

On macOS Big Sur, running JDK 17 and JavaFX 18, when you have an irregular clipping region, the clipped image ends up badly pixelated when printed.
The pixelation happens both on actual paper (on my Epson printer), as well as when saved to a PDF (using macOSs innate "Save to PDF" feature).
If you have a clip region with a Rectangle, there is no pixelation, everything looks smooth and crisp.
Note, that it must be a Rectangle, a rectangular Polygon has problems as well.
This is a screen shot of the actual screen rendering:
Next is a screen shot of viewing the resulting PDF.
You can see both the text and lines are pixelated within the circle.
Seems to me that its rendering the node in to the "PDF resolution", which is 72 DPI (not actual printer resolution), and then masking the region using a bitmap operation rather than actually clipping the content to the irregular border. But it special cases the Rectangle and clips to that.
Is there anything that can be done about this?
Below is a sample demonstrating this.
package pkg;
import javafx.application.Application;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class App extends Application {
Pane printPane;
#Override
public void start(Stage stage) {
var scene = new Scene(getView(), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private Pane getView() {
var flowPane = new FlowPane(getCirclePane(), getRectPane());
var b = new Button("Print");
printPane = flowPane;
b.setOnAction((t) -> {
var job = PrinterJob.createPrinterJob();
if (job != null && job.showPrintDialog(null)) {
if (job.printPage(printPane)) {
job.endJob();
}
}
});
var bp = new BorderPane();
bp.setCenter(flowPane);
bp.setBottom(b);
return bp;
}
private Pane getCirclePane() {
var pane = new Pane();
var circle = new Circle(100, 100, 45);
pane.setClip(circle);
var text = new Text(75, 100, "Circle");
pane.getChildren().add(text);
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
return pane;
}
private Pane getRectPane() {
var pane = new Pane();
var rect = new Rectangle(50, 50, 200, 125);
pane.setClip(rect);
var text = new Text(125, 100, "Rectangle");
pane.getChildren().add(text);
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
return pane;
}
}
Update:
I came up with this as a workaround.
Simply, that which I can't clip, I can paint over.
I create a new rect the size of the overall scene, then use Shape.subtract(...) to punch a hole in it in the shape of my clip region. I fill this new shape with my background color. Then I put this in a new pane, and use a Stack pane to layer it on top of my master. It's imperfect, and a little bit fiddly, and it chafes having to do this. But it works and is easy to explain.
So, using the circle as an example:
private Pane getCirclePane() {
var pane = new Pane();
var circle = new Circle(100, 100, 45);
var text = new Text(75, 100, "Circle");
pane.getChildren().add(text);
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
Rectangle rect = new Rectangle(0, 0, 250, 200);
Shape overlay = Shape.subtract(rect, circle);
overlay.setFill(Color.WHITE);
Pane overlayPane = new Pane();
overlayPane.getChildren().add(overlay);
pane = new StackPane(pane, overlayPane);
return pane;
}
As an interim workaround, you may be able to adapt the approach seen here; it uses Shape.subtract() to create a suitable matte that functions like the corresponding clip.
var c = new Circle(100, 100, 80);
var r = new Rectangle(250, 200);
Shape matte = Shape.subtract(r, c);
matte.setFill(Color.WHITE);
pane.getChildren().add(matte);
As printed:
As tested:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) {
var scene = new Scene(getView());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private Pane getView() {
final var printBox = new HBox(8, getCirclePane(), getRectPane());
var b = new Button("Print");
b.setOnAction((t) -> {
var job = PrinterJob.createPrinterJob();
if (job != null && job.showPrintDialog(null)) {
if (job.printPage(printBox)) {
job.endJob();
}
}
});
var bp = new BorderPane();
bp.setCenter(printBox);
bp.setBottom(b);
BorderPane.setAlignment(b, Pos.BOTTOM_CENTER);
return bp;
}
private Pane getCirclePane() {
var pane = new Pane();
var text = new Text(80, 100, "Circle");
pane.getChildren().add(text);
addPattern(pane);
var c = new Circle(100, 100, 80);
var r = new Rectangle(250, 200);
Shape matte = Shape.subtract(r, c);
matte.setFill(Color.WHITE);
pane.getChildren().add(matte);
return pane;
}
private Pane getRectPane() {
var pane = new Pane();
var rect = new Rectangle(50, 50, 200, 125);
pane.setClip(rect);
var text = new Text(125, 100, "Rectangle");
pane.getChildren().add(text);
addPattern(pane);
return pane;
}
private void addPattern(Pane pane) {
for (int i = 0; i < 10; i++) {
double ox = i * 25;
pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
}
}
}

JavaFX - Borders of ImageView and Pane do not scale / zoom properly inside ScrollPane

in order to achieve something like a minimap, I used a StackPane to layer a Pane on top of an ImageView. I bound the the scaling x- and y-properties of the StackPane to a Slider in order to achieve a zooming effect. I added a few Circle objects to the Pane. These circles should of course also be zoomable (which they are), but in this case more importantly, they should stay at their relative position when scaling happens. The following picture shows what the GUI looks like so far.
As of yet, I have distinguished two problematic cases.
Problem A - Correct positions but 'out of bounds'
In this more simple case, everything works fine, except for the fact that zooming in on the image seemingly makes it exceed the bounds of the StackPane. As a result, you cannot scroll to the image's border anymore. Check the next screenshot to see what I mean.
Problem B - 'In bounds' but out of position
To make the StackPane grow appropriately, I bound its size properties to the zoom factor multiplied with the image's size. However, I am pretty sure that this approach somehow messes with the underlying coordinate structure, because now the circle 'leaves' its position at a scale factor > 1. Said behaviour is depicted in the last screenshot.
I have added my code below. Any help would be greatly appreciated :)
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class ScalingProblem extends Application {
private static final String IMAGE_URL = "https://st2.depositphotos.com/3034795/10774/i/950/depositphotos_107745620-stock-photo-labyrinth-or-maze.jpg";
#Override
public void start(Stage primaryStage) throws Exception {
// Prepare the main part of the GUI.
Image backgroundImage = new Image(IMAGE_URL);
ImageView imageView = new ImageView(backgroundImage);
Circle circle = new Circle(40, Color.ORANGE);
circle.setStroke(Color.BLUE);
circle.setStrokeWidth(5);
Pane layer = new Pane(circle);
StackPane stackPane = new StackPane(imageView, layer);
ScrollPane scrollPane = new ScrollPane(stackPane);
scrollPane.setPannable(true);
// Prepare the controls on the left side.
Slider xSlider = new Slider();
Slider ySlider = new Slider();
Slider radiusSlider = new Slider();
Slider zoomSlider = new Slider();
VBox vBox = new VBox(xSlider, ySlider, radiusSlider, zoomSlider);
vBox.setSpacing(10);
vBox.setPrefWidth(200);
// Adjust the sliders.
adjustSlider(xSlider, 0, 1000, 50);
adjustSlider(ySlider, 0, 1000, 50);
adjustSlider(radiusSlider, 0, 100, 15);
adjustSlider(zoomSlider, 0, 3, 1);
zoomSlider.setMajorTickUnit(1);
zoomSlider.setMinorTickCount(1);
zoomSlider.setShowTickMarks(true);
zoomSlider.setShowTickLabels(true);
// Do all necessary binding.
circle.centerXProperty().bind(xSlider.valueProperty());
circle.centerYProperty().bind(ySlider.valueProperty());
circle.radiusProperty().bind(radiusSlider.valueProperty());
stackPane.scaleXProperty().bind(zoomSlider.valueProperty());
stackPane.scaleYProperty().bind(zoomSlider.valueProperty());
/*
* So far, I always needed to make the StackPane's size grow / shrink depending on the zoom level. If the
* following two lines are commented out, zooming in will make the ImageView exceed the bounds of the StackPane.
* As a result, the StackPane does not 'grow' appropriately so that you cannot pan the view to see the edges of
* the background.
*/
stackPane.prefWidthProperty().bind(zoomSlider.valueProperty().multiply(backgroundImage.getWidth()));
stackPane.prefHeightProperty().bind(zoomSlider.valueProperty().multiply(backgroundImage.getHeight()));
// Piece the GUI together.
BorderPane root = new BorderPane();
root.setCenter(scrollPane);
root.setLeft(vBox);
root.setPrefHeight(300);
root.setPrefWidth(500);
// Show the stage.
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private void adjustSlider(Slider slider, double min, double max, double start) {
slider.setMin(min);
slider.setMax(max);
slider.setValue(start);
}
public static void main(String[] args) {
launch();
}
}
ScrollPane uses the unmodified layout bounds to determine the content size. This does not include the scaling transformation. To prevent the content from growing beyond the scrollable area, wrap the scaled node in a Group.
Modify the ScrollPane creation like this:
ScrollPane scrollPane = new ScrollPane(new Group(stackPane));
and remove the binding to the prefWidth/prefHeight, i.e. delete the following lines:
stackPane.prefWidthProperty().bind(zoomSlider.valueProperty().multiply(backgroundImage.getWidth()));
stackPane.prefHeightProperty().bind(zoomSlider.valueProperty().multiply(backgroundImage.getHeight()));

JavaFX - Multiple lights on a dark background

I'm attempting to create something like a minimap with JavaFX, i.e. a (darkened) background image with some circles layered on top of it. Some of these circles need to be able to shed light on the background, revealing a small part of it. The following figure shows what I am aiming at.
I have worked my way forward using this SO solution, but at the moment, I am stuck because it seems that you can only set one instance of javafx.scene.effect.Lighting on the underlying StackPane.
I would really like to keep this as simple as possible and ideally only employ JavaFX. Any help is greatly appreciated :)
I recommend adding together Circles filled with a RadialGradient on a Pane with black background using BlendMode.LIGHTEN and combining this with a ImageView containing the "map" using BlendMode.MULTIPLY:
private Circle circle;
// gradient with yellow in the center and black on the border
private final static RadialGradient GRADIENT = new RadialGradient(0, 0, 0.5, 0.5, 0.5, true, CycleMethod.NO_CYCLE, new Stop(0, Color.YELLOW), new Stop(1, Color.BLACK));
private void newCircle(Pane container) {
circle = new Circle(50, GRADIENT);
circle.setBlendMode(BlendMode.LIGHTEN);
circle.setManaged(false);
container.getChildren().add(circle);
}
private void setCirclePosition(MouseEvent event) {
circle.setCenterX(event.getX());
circle.setCenterY(event.getY());
}
#Override
public void start(Stage primaryStage) {
Image image = new Image(imageURL);
ImageView imageView = new ImageView(image);
Pane mask = new Pane();
mask.setBlendMode(BlendMode.MULTIPLY);
mask.setStyle("-fx-background-color: black;");
mask.setOnMouseMoved(this::setCirclePosition); // move cricle with mouse
newCircle(mask);
// create new circle on mouse click
mask.setOnMouseClicked(evt -> {
newCircle(mask);
setCirclePosition(evt);
});
StackPane root = new StackPane(imageView, mask);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

How to show a javafx 3D object?

I wrote a javafx object "Ball" to create a Sphere. I'm now trying to make the object appear in my main class.
Ideally, I would use a key listener to create/destroy balls. But I can't even get the balls to appear on the screen, or even make my 1500x900 screen appear at all.
Here my code for the ball:
// ball object
package bouncingballs;
import javafx.animation.Interpolator;
import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.scene.layout.Pane;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Sphere;
import javafx.util.Duration;
import static javafx.util.Duration.seconds;
public class Ball extends Pane {
//Create 3D ball
private Sphere ball;
private Double radius;
private PhongMaterial color;
private Polygon poly;
private PathTransition path;
private Integer speed;
//Create path and animate ball in constructor
public Ball(Double radius, PhongMaterial color, Polygon poly) {
this.radius = radius;
this.color = color;
ball.setRadius(radius);
ball.setMaterial(color);
this.poly = poly;
speed = 10;
path.setPath(poly);
path.setNode(ball);
path.setInterpolator(Interpolator.LINEAR);
path.setDuration(Duration.seconds(speed));
path.setCycleCount(Timeline.INDEFINITE);
path.play();
}
//some test accessors/mutators
public void setRadius(Double radius) {
this.radius = radius;
}
public Double getRadius() {
return radius;
}
}
Here is my code for my main class, it should create Ball objects and display them animated. The animation should follow the Polygon object poly to simulate a bouncing ball.
//main object to show Balls to screen
package bouncingballs;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
public class BouncingBalls extends Application {
#Override
public void start(Stage primaryStage) {
//create path to simulate bouncing ball
Polygon poly = new Polygon(750, 850, 50, 675, 500, 50, 750, 850, 1000, 50, 1450, 675);//creates path to simulate bouncing ball on 1500x900 screen
Double radius = 50.0;
PhongMaterial color = new PhongMaterial();
color.setDiffuseColor(Color.OLIVE);
Ball ball = new Ball(radius, color, poly);
StackPane root = new StackPane();
root.getChildren().add(ball);
Scene scene = new Scene(root, 1500, 900);
primaryStage.setTitle("Bouncing Balls");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args)
{launch(args);
}
}
You have a bunch of errors or otherwise weird things:
Your Ball class creates a Sphere, but you never add that Sphere to the scene graph (so it could never be seen).
Your Ball class extends Pane, which is weird because a Ball isn't really a Pane. If you are to extend anything, probably Sphere would be best.
You use a StackPane for your root. It is probably not best to do that for 3D graphics, as the Pane subclasses are really designed for laying out 2D systems. For 3D, you probably just want to stick to basic Groups as containers.
When you have a 3D scene, you probably want to use the constructor which switches depth buffering on.
For 3D work, you want to set a PerspectiveCamera on the scene.
You probably want some lighting on your scene. JavaFX will add some default lighting, but it may not match what you need.
You should check the Scene3D ConditionalFeature to see if 3D is supported on your platform.
You probably want to set the Z co-ordinates of your Sphere appropriately and ensure that it is something which will lie within your perspective camera's field of view.
You can find some sample code which displays a Sphere (the earth), here:
JavaFX material's bump and spec maps
The sample demonstrates some of the points made above.

JavaFX 8 2d Scroller

Hey I am trying to make a simple 2d scroller but I simply cannot seem to get the picture to scroll. I am using an ImagveView for the picture, and I have tried both a translatetransition and a pathtransition, but I can't get any good results? Here is my code:
public void aboutSceneAnimation() {
Rectangle2D psb = Screen.getPrimary().getVisualBounds();
Path path = new Path();
MoveTo moveTo = new MoveTo();
moveTo.setX(0.0);
path.getElements().add(moveTo);
PathTransition pt = new PathTransition();
pt.setDuration(Duration.INDEFINITE);
pt.setNode(map);
pt.setPath(path);
pt.setCycleCount(4);
pt.setAutoReverse(true);
pt.play();
}
public Scene aboutScene() {
Rectangle2D psb = Screen.getPrimary().getVisualBounds();
//Create the map
BorderPane border = new BorderPane();
border.getChildren().addAll(map2);
//Size the map
map2.setFitWidth(psb.getWidth());
map2.setFitHeight(psb.getHeight());
Scene scene = new Scene(border, 500, 500);
aboutSceneAnimation();
return scene;
}
The way it works is that when the program starts there is a start scene loaded into my stage, and then I just show the stage. When a button is pressed I simply change to the aboutScene which also calls the aboutSceneAnimation, which is where I want the map to scroll. Any ideas would be greatly appreciated!
-Cheers
You need to use a Pane instead of a BorderPane, because in a BorderPane the center node will be resized to fill the available space in the middle.
In your PathTransition you only add from where to start, i. e. the MoveTo, but you don't add where it should go, e. g. a LineTo.
Additional note:
Since your example in the question contains only bits and pieces that don't work together, please read about How to create a Minimal, Complete, and Verifiable example.

Resources