Javafx RotateTransition rendering smooth - javafx

I’m making some trials with JavaFX RotateTransition, applying a simple model found on a jfx itself documentation file:
rotateTransition = RotateTransitionBuilder.create()
.node(elements)
.duration(Duration.seconds(4))
.fromAngle(0)
.toAngle(720)
.cycleCount(3)
.autoReverse(true)
.build();
Above, elements is a Group of bare Arc primitives.
When this group has a limited number of nodes, say 20, the animation goes smooth but when I increase the number of nodes to 500 (nested actually, Group of Group) the animation still works but does not result any more fluid.
The question is: does this nodes limit can be considered too much for this task? How to speed up the rendering?
I have found the thread below that in a similar context asserts that could be a matter of using the right Animation class, but I’m not sure that the proposed AnimationTimer does apply well to a rotation.
http://mail.openjdk.java.net/pipermail/openjfx-dev/2013-June/008104.html
I have also tried to use setCache(true) to every node with no visible improvements.
Thank you!
Edit: Arc generation. No strange things but a binder and a EventHandler.
Arc arc = new Arc();
arc.centerXProperty().bind(plotRadiusBinding);
arc.centerYProperty().bind(plotRadiusBinding);
arc.radiusXProperty().bind(plotRadiusBinding);
arc.radiusYProperty().bind(plotRadiusBinding);
arc.setStartAngle(startAngle * 180 / PI);
arc.setLength(radiansLength * 180 / PI);
arc.setType(ArcType.ROUND);
arc.setStroke(defaultArcColor);
arc.setStrokeType(StrokeType.INSIDE);
arc.setFill(null);
arc.setOnMouseClicked(arcEventHandler);

I had similar issues when moving images around when I used KeyFrames (KeyFrames)
I could improve it by using
I had success with implementing the stuff by myself:
Animation animation = new Transition() {
{
setCycleDuration(Duration.millis(1000));
}
#Override
protected void interpolate(double frac) {
// your rotation code here
}
}
If you could provide your arc generation source code it might be helpful.

for me this works flawlessly for 500 arcs:
package application;
import java.util.Random;
import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Arc;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Pane root = new Pane();
Group group = new Group();
Random rand = new Random();
for(int i=0; i<1000; i++){
Arc c = new Arc(200 + rand.nextInt(400), 200 + rand.nextInt(400), 10, 10, 0, 360);
group.getChildren().add(c);
}
root.getChildren().add(group);
RotateTransition rotateTransition = new RotateTransition(Duration.millis(5000), group);
rotateTransition.setFromAngle(0);
rotateTransition.setToAngle(720);
rotateTransition.setCycleCount(3);
rotateTransition.setAutoReverse(true);
rotateTransition.play();
Scene scene = new Scene(root,800,800);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
if this is still to laggy and you only want to draw arcs you could directly invoke the graphics-context of a canvas.

Related

JavaFX SequentialTransition IllegalStateException: Cannot stop when embedded in another animation

The test program below reproduces the problem. I understand why the exception is thrown but I would like to know how can I work around it or use a different construct in JavaFX to get what I want.
The full application is a robot simulator with multiple robots that move autonomously, independently, and simultaneously around a field. Each robot has its own SequentialTransition for its particular set of movements. The program adds the SequentialTransitions to a ParallelTransition, which it then plays. Everything was fine until I put in a listener that notices if a robot runs into an obstacle. I've simplified the collision detection in the test program to apply to only one robot and one wall. The point of the error is marked with //** BROKEN!! IllegalStateException on next line.
I really do want to stop the SequentialTransition for a robot that runs into an obstacle but let the other robot(s) continue. How can I do this?
The error comes up in Java 8 but also in Java 11 and JavaFX 15.
package sample;
import javafx.animation.ParallelTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private static final double FIELD_WIDTH = 600;
private static final double FIELD_HEIGHT = 600;
private Pane field = new Pane();
ParallelTransition parallel = new ParallelTransition();
SequentialTransition sequentialRobot1 = new SequentialTransition();
SequentialTransition sequentialRobot2 = new SequentialTransition();
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
// Place one field boundary for testing.
Line northBoundary = new Line(0, 0, FIELD_WIDTH, 0);
northBoundary.setStrokeWidth(5.0);
field.getChildren().add(northBoundary);
// Place the robots on the field.
// The first robot.
Rectangle robotBody1 = new Rectangle(100, 300, 60, 60);
robotBody1.setArcHeight(15);
robotBody1.setArcWidth(15);
robotBody1.setStroke(Color.BLACK);
robotBody1.setFill(Color.CRIMSON);
field.getChildren().add(robotBody1);
robotBody1.boundsInParentProperty().addListener((observable, oldValue, newValue) -> {
if (northBoundary.getBoundsInParent().intersects(robotBody1.getBoundsInParent())) {
//** BROKEN!! IllegalStateException on next line
sequentialRobot1.stop();
System.out.println("Collision detected");
parallel.play();
}
});
TranslateTransition translateTransition1 = new TranslateTransition();
translateTransition1.setNode(robotBody1);
translateTransition1.setByX(0);
translateTransition1.setByY(-300);
translateTransition1.setDuration(Duration.seconds(1));
translateTransition1.setOnFinished(event -> {
robotBody1.setLayoutX(robotBody1.getLayoutX() + robotBody1.getTranslateX());
robotBody1.setLayoutY(robotBody1.getLayoutY() + robotBody1.getTranslateY());
robotBody1.setTranslateX(0);
robotBody1.setTranslateY(0);
});
sequentialRobot1.getChildren().add(translateTransition1);
// The second robot.
Rectangle robotBody2 = new Rectangle(300, 300, 60, 60);
robotBody2.setArcHeight(15);
robotBody2.setArcWidth(15);
robotBody2.setStroke(Color.BLACK);
robotBody2.setFill(Color.CYAN);
field.getChildren().add(robotBody2);
TranslateTransition translateTransition2 = new TranslateTransition();
translateTransition2.setNode(robotBody2);
translateTransition2.setByX(0);
translateTransition2.setByY(-100);
translateTransition2.setDuration(Duration.seconds(1));
translateTransition2.setOnFinished(event -> {
robotBody2.setLayoutX(robotBody2.getLayoutX() + robotBody2.getTranslateX());
robotBody2.setLayoutY(robotBody2.getLayoutY() + robotBody2.getTranslateY());
robotBody2.setTranslateX(0);
robotBody2.setTranslateY(0);
});
sequentialRobot2.getChildren().add(translateTransition2);
parallel.getChildren().addAll(sequentialRobot1, sequentialRobot2);
parallel.play();
primaryStage.setTitle("Field");
primaryStage.setScene(new Scene(field, FIELD_WIDTH, FIELD_HEIGHT, Color.GRAY));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
#Slaw's idea of using an AnimationTimer is probably the best direction to go, but the obvious answer is to not use the ParallelTransition at all. Since the robot animations are going to be independent, just use the SequentialTransitions and start them all at the same time by calling play() on each.

Javafx darken background

I have FXML application with 10 circles in AnchorPane. I want to hover mouse on one circle and make other 9 and background to darken.
The best I could do was some basic FadeTransition, which only made them disappear, not darken, plus I cant figure out how to select all children of node except one that I have mouse on. Selecting all children except one manually seems not really efficient for more objects.
I tried to google it up, but I just cant find anything.
Please, post a link to thread related to similar problem or sample code. Any help would be really appreciated.
You can use the following sample. Please note that there are some assumptions made, such as every node in the scene graph is a Shape object and that every shape has a Color object associated with the fill. The sample code is sufficient to derive other solutions related specifically to your use case.
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
public class SelectionApp extends Application {
private Pane root = new Pane();
private Parent createContent() {
root.setPrefSize(800, 600);
root.getChildren().add(new Rectangle(800, 600, Color.AQUA));
for (int i = 0; i < 10; i++) {
Circle circle = new Circle(25, 25, 25, Color.GREEN);
// just place them randomly
circle.setTranslateX(Math.random() * 700);
circle.setTranslateY(Math.random() * 500);
circle.setOnMouseEntered(e -> select(circle));
circle.setOnMouseExited(e -> deselect(circle));
root.getChildren().add(circle);
}
return root;
}
private void select(Shape node) {
root.getChildren()
.stream()
.filter(n -> n != node)
.map(n -> (Shape) n)
.forEach(n -> n.setFill(darker(n.getFill())));
}
private void deselect(Shape node) {
root.getChildren()
.stream()
.filter(n -> n != node)
.map(n -> (Shape) n)
.forEach(n -> n.setFill(brighter(n.getFill())));
}
private Color darker(Paint c) {
return ((Color) c).darker().darker();
}
private Color brighter(Paint c) {
return ((Color) c).brighter().brighter();
}
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(createContent());
primaryStage.setTitle("Darken");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Make JavaFX wait and continue with code

Basically I am trying to make a short effect using JavaFX. I have the shape of a heart (added together from two circles and a polygon) that I can vary in size using the double value p. "Standart Size" would be p = 1.0;.
I am trying to add a pumping effect to the heart. I have the method pumpOnce():
public void pumpOnce(){
p = p + 1;
initHeart();
//Here goes what ever it takes to make stuff working!!
p = p - 1;
initHeart();
}
initHeart() draws the heart based on p.
I have found out that Thread.sleep(); or similar methods will not work due to the thread philosophy in JavaFX.
But what can I use instead?
The JavaFX animations are probably the way to go, but the "thread philosophy" in JavaFX isn't hard to work with if you want to roll your own, or do other, more complicated things in background threads.
The following code will pause and change the value in a label (full disclosure, I'm reusing code I wrote for another question):
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.xml.datatype.Duration;
public class DelayWithTask extends Application {
private static Label label;
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
label = new Label();
label.setText("Waiting...");
StackPane root = new StackPane();
root.getChildren().add(label);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
delay(5000, () -> label.setText("Hello World"));
}
public static void delay(long millis, Runnable continuation) {
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
try { Thread.sleep(millis); }
catch (InterruptedException e) { }
return null;
}
};
sleeper.setOnSucceeded(event -> continuation.run());
new Thread(sleeper).start();
}
}
The basic JavaFX background tool is the Task, any JavaFX application that actually does anything will probably be littered with these all over. Learn how to use them.
Dave's solution is great for general purpose off thread based work in JavaFX.
If you wish to use the animation facilities of JavaFX, the solutions below demonstrate this using a Timeline or a ScaleTransition. The timeline implements a discrete scale of the UI element, so every quarter of a second the UI element is scaled larger or back to it's original size. The scale transition implements a smooth scale of the UI element, so the UI element gradually gets larger then smaller using an interpolated scale factor with the default easing interpolator.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BeatingHeart extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage stage) {
ImageView heart = new ImageView(HEART_IMAGE_LOC);
animateUsingTimeline(heart);
// animateUsingScaleTransition(heart);
StackPane layout = new StackPane(heart);
layout.setPrefWidth(heart.getImage().getWidth() * 2);
layout.setPrefHeight(heart.getImage().getHeight() * 2);
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private void animateUsingTimeline(ImageView heart) {
DoubleProperty scale = new SimpleDoubleProperty(1);
heart.scaleXProperty().bind(scale);
heart.scaleYProperty().bind(scale);
Timeline beat = new Timeline(
new KeyFrame(Duration.ZERO, event -> scale.setValue(1)),
new KeyFrame(Duration.seconds(0.5), event -> scale.setValue(1.1))
);
beat.setAutoReverse(true);
beat.setCycleCount(Timeline.INDEFINITE);
beat.play();
}
private void animateUsingScaleTransition(ImageView heart) {
ScaleTransition scaleTransition = new ScaleTransition(
Duration.seconds(1), heart
);
scaleTransition.setFromX(1);
scaleTransition.setFromY(1);
scaleTransition.setFromZ(1);
scaleTransition.setToX(1.1);
scaleTransition.setToY(1.1);
scaleTransition.setToZ(1.1);
scaleTransition.setAutoReverse(true);
scaleTransition.setCycleCount(Animation.INDEFINITE);
scaleTransition.play();
}
private static final String HEART_IMAGE_LOC =
"http://icons.iconarchive.com/icons/mirella-gabriele/valentine/128/Heart-red-icon.png";
// icon obtained from: http://www.iconarchive.com/show/valentine-icons-by-mirella-gabriele/Heart-red-icon.html
// icon license: Free for non-commercial use, commercial use not allowed.
}

How to avoid a 'machine gun' effect to the bullets in my game?

Some days ago i asked this:
How to have multiple instances on the screen of the same sprite at the same time with javafx2
and partially solved the question elaborating the suggestion of jewelsea.
I have this obstacle now: when a key is pressed to 'fire' bullets, weapon shoot bullets as fast as a machine gun..
I would like to limit the amount of bullets that the weapon of the hero of my game can shoot..for example to decide to shoot a bullet every 0.5 secs or just when a key is pressed and not to have always a machine gun effect...
In my game the part of program that controls the 'fire' effect is like this:
scene.setOnKeyTyped(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event2) {
if (event2.getCode()==KeyCode.F); { .........
Before i've tried also using setOnKeyPressed and setOnKeyReleased with the same results..
So what could i try to shoot just a bullet also keeping press the 'F' key or to limit the bullets in number?
Thank you in advance and good bye!
I've done this by using a Timeline as a timer and starting it and stopping it on key pressed and key released:
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class KeyEventTest extends Application {
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 400, 400);
Duration firingInterval = Duration.millis(500);
Timeline firing = new Timeline(
new KeyFrame(Duration.ZERO, event -> fire()),
new KeyFrame(firingInterval));
firing.setCycleCount(Animation.INDEFINITE);
scene.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.F && firing.getStatus() != Animation.Status.RUNNING) {
firing.playFromStart();
}
});
scene.setOnKeyReleased(event -> {
if (event.getCode() == KeyCode.F) {
firing.stop();
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
private void fire() {
// dummy implementation:
System.out.println("Fire!");
}
public static void main(String[] args) {
launch(args);
}
}
It's fairly easy to adapt this to additionally limit the number of bullets on the screen at any time, etc.

JavaFX + AWT Canvas

I read that running AWT With JavaFX is a bad idea.
But we have an old application that runs on Swing and uses the AWT canvas(Cannot change due to an external library that uses the canvas)
Is it really such a horrible idea?
Is there a workaround for this?
Update
Although the code in this answer used to work on Windows with an earlier version of JavaFX, I retested the same same code on OS X 10.9.5 + JavaFX 8u72 and the code no longer works.
The line swingNode.setContent(awtInitializerTask.get()); which instructs the JavaFX thread to wait on the awt thread to initialize the awt canvas never returns, blocking execution and startup of the app.
Just put your AWT canvas in a SwingNode and watch your thread management and you'll be fine.
import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javax.swing.*;
import java.awt.*;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class AwtCanvasWrapper extends Application {
private static final int W = 200;
private static final int H = 100;
#Override public void start(final Stage stage) throws Exception {
final AwtInitializerTask awtInitializerTask = new AwtInitializerTask(() -> {
JPanel jPanel = new JPanel();
jPanel.add(new CustomAwtCanvas(W, H));
return jPanel;
});
SwingUtilities.invokeLater(awtInitializerTask);
SwingNode swingNode = new SwingNode();
swingNode.setContent(awtInitializerTask.get());
stage.setScene(new Scene(new Group(swingNode), W, H));
stage.setResizable(false);
stage.show();
}
private class AwtInitializerTask extends FutureTask<JPanel> {
public AwtInitializerTask(Callable<JPanel> callable) {
super(callable);
}
}
private class CustomAwtCanvas extends Canvas {
public CustomAwtCanvas(int width, int height) {
setSize(width, height);
}
public void paint(Graphics g) {
Graphics2D g2;
g2 = (Graphics2D) g;
g2.setColor(Color.GRAY);
g2.fillRect(
0, 0,
(int) getSize().getWidth(), (int) getSize().getHeight()
);
g2.setColor(Color.BLACK);
g2.drawString("It is a custom canvas area", 25, 50);
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
Here is the output:
Related Question
Interoperability between Graphics2D and GraphicsContext
Answering some additional questions
But that one is for swing components.
Yes, but awt components can be wrapped in Swing components.
furthermore It says in the docs that it should not be used of heavyweight components
Regardless, it seems to work for me, your mileage may vary.
performance is crucial for my app
Then try the approach with your app and check:
The painting is reliable.
The performance is acceptable.
If either of the above checks fail then you may need to use a different approach (though I do not know what that approach would be, maybe just spawn Frame as a new window in which to include the the AWT canvas content rather than embedding the canvas inside the JavaFX scene).

Resources