How to make the javafx animation using path transition smooth? - java

My goal for now is to make the animation of a little circle, going along the path of a big circle. In the future, I would need to dynamically change the speed of revolution using a slider, but that is not a problem now. I can't figure out how to make this animation smooth? By this I mean that I want my little circle to not slow down when it reaches the end of animation. Is this possible using path transition? Or should I use some other method to implement animation in order for it to be smooth?
Here is what I have for now:
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SpinningCircles extends Application {
BorderPane root = new BorderPane();
int BigCircRad = 200;
int BigCircX = 750;
int BigCircY = 400;
double duration = 1;
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage){
Circle cir1 = new Circle(BigCircRad);
Circle cir2 = new Circle(BigCircRad/10);
cir1.setFill(Color.WHITE);
cir2.setFill(Color.WHITE);
cir1.setStroke(Color.BLUE);
cir2.setStroke(Color.BLUE);
cir1.setStrokeWidth(4);
cir2.setStrokeWidth(4);
cir1.setCenterX(BigCircX);
cir2.setCenterX(BigCircX);
cir1.setCenterY(BigCircY);
cir2.setCenterY(BigCircY-BigCircRad);
PathTransition pt = new PathTransition();
pt.setNode(cir2);
pt.setDuration(Duration.seconds(duration));
pt.setPath(cir1);
pt.setDelay(Duration.seconds(1));
pt.setCycleCount(Animation.INDEFINITE);
pt.play();
root.getChildren().addAll(cir1, cir2);
Scene scene = new Scene(root, 1000, 500);
stage.setScene(scene);
stage.setTitle("Spinning circles");
stage.setMaximized(true);
stage.setFullScreen(true);
stage.show();
}
}

You need to set the interpolator for the animation.
From the docs, the interpolator
Controls the timing for acceleration and deceleration at each
Transition cycle.
Default interpolator is set to Interpolator.EASE_BOTH.
The default, Interpolator.EASE_BOTH
will make an animation start slow, then accelerate and slow down again towards the end, all in a smooth manner.
Instead, use a LINEAR interpolator:
pt.setInterpolator(Interpolator.LINEAR);
Note the interpolator must be set before the animation is started:
PathTransition pt = new PathTransition();
pt.setNode(cir2);
pt.setDuration(Duration.seconds(duration));
pt.setPath(cir1);
pt.setDelay(Duration.seconds(1));
pt.setCycleCount(Animation.INDEFINITE);
pt.setInterpolator(Interpolator.LINEAR);
pt.play();

Related

JavaFx Equivalent to Graphics context.rotate() for nodes

If I am drawing in a canvas, I can rotate the coordinate system of the graphicscontext without rotating anything that is already drawn in the corresponding canvas, same with translations.
Is there anything similar possible in a group? If I just rotate and translate it's children I don't get the right effect because translations orient themselves on the unrotated system of the group.
If not, is there anything like a 3d canvas with that functionality?
As Slaw pointed out, what i was looking for were the classes in the package javafx.scene.transform.
Here is an example: Let's say I want a Line starting from (200, 200) with a length of 200 in direction 60 degrees from the x-axis. This would be not so hard to do without the transform package aswell but it shall only serve as an easy example.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.shape.Line;
import javafx.scene.transform.Affine;
import javafx.stage.Stage;
public class Example extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage stage) throws Exception {
Group group = new Group();
Scene scene = new Scene(group, 400, 400);
stage.setScene(scene);
// Does not work as intended
Line line1 = new Line(0,0, 200, 0);
line1.setTranslateX(200);
line1.setTranslateY(200);
line1.setRotate(60);
// Does work as intended.
Line line2 = new Line(0, 0, 200, 0);
Affine affine = new Affine();
affine.appendTranslation(200, 200);
affine.appendRotation(60);
line2.getTransforms().add(affine);
group.getChildren().addAll(line1, line2);
stage.show();
}
}

JavaFX: Drawing a infinite symbol and moving along

I need to create a JavaFX application that generates a path in the form of an infinite symbol, and then create a rectangle that will move across that path.
So far I know to create a circle and square and with transitionPath to move that rectangle , but how to create an infinity shape? I'm very fresh in JavaFx (and in development as well) so please don't be harsh :)
Here is my code with Circle shape:
import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PathTransitionDemo extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Rectangle rectangle = new Rectangle (0, 0, 25, 50);
rectangle.setFill(Color.ORANGE);
Circle circle = new Circle(125, 100, 50);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
pane.getChildren().add(circle);
pane.getChildren().add(rectangle);
PathTransition pt = new PathTransition();
pt.setDuration(Duration.millis(4000));
pt.setPath(circle);
pt.setNode(rectangle);
pt.setOrientation(
PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pt.setCycleCount(Timeline.INDEFINITE);
pt.setAutoReverse(true);
pt.play();
circle.setOnMousePressed(e -> pt.pause());
circle.setOnMouseReleased(e -> pt.play());
Scene scene = new Scene(pane, 250, 200);
primaryStage.setTitle("PathTransitionDemo"); // Unos nayiva pozornice e
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I looked everywhere for some hint, but without luck :(
I found on the web an SVG path to draw an "infinity" shape, so replace your circle with:
SVGPath svg = new SVGPath();
svg.setFill(Color.TRANSPARENT);
svg.setStrokeWidth(1.0);
svg.setStroke(Color.BLACK);
svg.setContent("M 787.49,150 C 787.49,203.36 755.56,247.27 712.27,269.5 S 622.17,290.34 582.67,279.16 508.78,246.56 480,223.91 424.93,174.93 400,150 348.85,98.79 320,76.09 256.91,32.03 217.33,20.84 130.62,8.48 87.73,30.5 12.51,96.64 12.51,150 44.44,247.27 87.73,269.5 177.83,290.34 217.33,279.16 291.22,246.56 320,223.91 375.07,174.93 400,150 451.15,98.79 480,76.09 543.09,32.03 582.67,20.84 669.38,8.48 712.27,30.5 787.49,96.64 787.49,150 z");
and use it for drawing, transition and event catching.
You may need to adapt it to your need.
If you are looking for better "infinity" shapes, then search for "lemniscate".

Rotate scrollpane over the X-axis in javafx - star wars like effect

I have a problem with a rotation of a usercontrol in javafx. My setup is as follows:
I have a scene with in the center a 400 by 600 scrollpanel called scrollpane, later populated dynamically with a vbox that contains a list of labels with text.
What I want to do is add a rotation on this panel to make it look like the starwars introduction text. I've managed to get the animation that scrolls through the text working, but when trying to rotate the panel over the X_AXIS it won't do as I want.
Goal: Panel that is rotated as if it was this text
Currently My best attempt after spending hours transforming:
scrollpane.getTransforms().add(new Rotate(50, 300, 200, 20, Rotate.X_AXIS));
As you can see the text is aimed at the proper angle, but the control itself is not actually 3d rotated over the X-axis.
What do I need to add in order to go from what I currently have to the desired effect?
(That the top of the panel in absolute pixels is less wide compared to the bottom).
You've rotated it backwards; probably you're not seeing the rotation because you have something else wrong in your code.
This works for me:
import javafx.application.Application;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.text.Font;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class StarWarsScrollPane extends Application {
private final String text = "It is a period of civil war. Rebel spaceships, "
+ "striking from a hidden base, have won their first victory against the evil Galactic Empire."
+ " During the battle, Rebel spies managed to steal secret plans to the Empire's ultimate weapon,"
+ " the DEATH STAR, an armored space station with enough power to destroy an entire planet."
+ " Pursued by the Empire's sinister agents, Princess Leia races home aboard her starship,"
+ " custodian of the stolen plans that can save her people and restore freedom to the galaxy....";
#Override
public void start(Stage primaryStage) {
Label label = new Label(text);
label.setWrapText(true);
label.setFont(Font.font(18));
ScrollPane crawler = new ScrollPane(label);
crawler.setVbarPolicy(ScrollBarPolicy.NEVER);
crawler.setFitToWidth(true);
crawler.getTransforms().add(new Rotate(-50, 300, 200, 20, Rotate.X_AXIS));
Scene scene = new Scene(crawler, 400, 400);
scene.setCamera(new PerspectiveCamera());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Note that if you really want a scrolling "crawl" of text, you don't really need a scroll pane, but you can just use a text node and translate it in an animation. If you do this, be sure to add the translation after you add the rotation: transforms are applied in reverse order (as though you are right-multiplying the affine transformation matrices).
Here's an example of this ;)
import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.DepthTest;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.util.Duration;
public class StarWarsCrawler extends Application {
private final String text = "It is a period of civil war. Rebel spaceships, "
+ "striking from a hidden base, have won their first victory against the evil Galactic Empire.\n\n"
+ "During the battle, Rebel spies managed to steal secret plans to the Empire's ultimate weapon,"
+ " the DEATH STAR, an armored space station with enough power to destroy an entire planet.\n\n"
+ "Pursued by the Empire's sinister agents, Princess Leia races home aboard her starship,"
+ " custodian of the stolen plans that can save her people and restore freedom to the galaxy....";
#Override
public void start(Stage primaryStage) {
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
int width = (int) primaryScreenBounds.getWidth() ;
int height = (int) primaryScreenBounds.getHeight() ;
Text textNode = createText(width);
Translate translate = new Translate();
textNode.getTransforms().add(new Rotate(-60, 300, height/2, height/30, Rotate.X_AXIS));
textNode.getTransforms().add(translate);
Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(45), new KeyValue(translate.yProperty(), -10*height))
);
textNode.setTranslateY(2*height);
StackPane root = new StackPane();
generateStarField(width, height, root);
root.getChildren().add(textNode);
Scene scene = createScene(root);
primaryStage.setFullScreenExitHint("");
primaryStage.setFullScreen(true);
primaryStage.setScene(scene);
primaryStage.show();
animation.play();
animation.setOnFinished(e -> Platform.exit());
}
private Scene createScene(StackPane root) {
Scene scene = new Scene(root, Color.BLACK);
PerspectiveCamera camera = new PerspectiveCamera();
camera.setDepthTest(DepthTest.ENABLE);
scene.setCamera(camera);
scene.setCursor(Cursor.NONE);
scene.setOnMouseClicked(e -> {
if (e.getClickCount() ==2) {
Platform.exit();
}
});
return scene;
}
private Text createText(int width) {
Text textNode = new Text(text);
textNode.setWrappingWidth(width*1.25);
textNode.setFont(Font.font("Franklin Gothic", width/12));
textNode.setFill(Color.rgb(229, 177, 58));
return textNode;
}
private void generateStarField(int width, int height, StackPane root) {
int numStars = width * height / 900 ;
Random rng = new Random();
for (int i = 1 ; i <= numStars ; i++) {
double hue = rng.nextDouble() * 360 ;
double saturation = rng.nextDouble() * 0.1 ;
Color color = Color.hsb(hue, saturation, 1.0);
Circle circle = new Circle(rng.nextInt(width), rng.nextInt(height), 2*rng.nextDouble(), color);
circle.setManaged(false);
circle.setTranslateZ(rng.nextDouble() * height * 1.25);
root.getChildren().add(circle);
}
}
public static void main(String[] args) {
launch(args);
}
}

Java FX Canvas doesn't show until complete

I want to create a java FX application that draws lines on a Canvas one step at a time, with a noticable time between line segments. In the below application I have what I imagined would draw a diagonal line, stall a second and then draw the next diagonal line. Instead, the FX window pops up blank, waits 2 seconds, and then shows the two diagonal lines at the same time. How do I achieve the effect I am looking for? Is javafx.scene.canvas.Canvas not the right object to be using?
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class FrameCanvas extends Application{
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage primaryStage)throws Exception{
////////////////////Basic FX stuff
Canvas theCanvas = new Canvas(900,900);
StackPane theLayout = new StackPane();
theLayout.getChildren().add(theCanvas);
Scene theScene = new Scene(theLayout,900,900);
primaryStage.setScene(theScene);
primaryStage.show();
///////////////////////
/////Drawing an X
///////////////////////
GraphicsContext gc = theCanvas.getGraphicsContext2D();
Thread.sleep(1000);
gc.strokeLine(0,0,200,200);
Thread.sleep(1000);
gc.strokeLine(200,0,0,200);
/////////////////////////////
}
}
Don't block (e.g. Thread.sleep(...)) the FX Application Thread. That thread is responsible for rendering the scene, so you will prevent any updates from being rendered.
Instead, use an animation for functionality like this (after all, an animation is really what you're creating here):
public void start(Stage primaryStage)throws Exception{
////////////////////Basic FX stuff
Canvas theCanvas = new Canvas(900,900);
StackPane theLayout = new StackPane();
theLayout.getChildren().add(theCanvas);
Scene theScene = new Scene(theLayout,900,900);
primaryStage.setScene(theScene);
primaryStage.show();
///////////////////////
/////Drawing an X
///////////////////////
GraphicsContext gc = theCanvas.getGraphicsContext2D();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(1), e -> gc.strokeLine(0,0,200,200)),
new KeyFrame(Duration.seconds(2), e -> gc.strokeLine(200,0,0,200))
);
timeline.play();
/////////////////////////////
}

Making More Than One Circle in Java

I have a project in class where I need to display a traffic light with simply three cirlces. I started with the yellow one, and then attempted to add a red one in some random other place just to see if I could do it, however the yellow one is the only one showing. I can't tell if the red one is somehow underneath the yellow one, but in any case it doesn't make much sense to me as to why the red circle isn't showing.
package tryingGraphicsStuff;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.control.*;
public class TryingGraphicsStuff extends Application{
#Override
public void start(Stage stage) throws Exception {
// create circle
Circle circle = new Circle();
circle.setCenterX(150);
circle.setCenterY(150);
circle.setRadius(50);
circle.setFill(Color.RED);
// place on pane
StackPane p = new StackPane();
p.getChildren().add(circle);
// ensure it stays centered if window resized
//circle.centerXProperty().bind(p.widthProperty().divide(2));
//circle.centerYProperty().bind(p.heightProperty().divide(2));
Circle circleTwo = new Circle();
circleTwo.setCenterX(400);
circleTwo.setCenterY(400);
circleTwo.setRadius(50);
circleTwo.setFill(Color.YELLOW);
// place on pane
p.getChildren().add(circleTwo);
// create scene from pane
Scene scene = new Scene(p, 300, 1000);
// place scene on stage
stage.setTitle("Circle");
stage.setScene(scene);
stage.show();
}
public static void main (String [] args)
{
Application.launch(args);
}
}
A StackPane "lays out its children in a back-to-front stack". (The stack here is in z-coordinates). It is a "layout pane" which actually manages the placement of the child nodes for you. Consequently, the centerX and centerY properties of the circles are ignored, and they appear one on top of the other in the order they are added (so the red one is underneath the yellow one, and the only one you see is the yellow one). By default, the stack pane centers them.
All "layout panes" position the nodes for you. For example, a VBox will position nodes in a vertical stack, with the first one at the top, the second below, and so on. So if you used a VBox instead of a StackPane, the circles would appear one below the other (in the y-direction), but note they would still not respect the centerX and centerY properties.
The Pane class itself does not manage the layout of its child nodes; so if you want to use the coordinates for shape objects, Pane is probably your best option. Group behaves similarly, but takes on the bounds of the union of its child bounds, so it acts like Pane but its local coordinate system is different.
The following demo shows all these options. Again, Pane will be the one that behaves in an intuitive way.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
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 CircleLayoutExample extends Application {
#Override
public void start(Stage primaryStage) {
TabPane tabs = new TabPane();
tabs.getTabs().add(createTab(new StackPane()));
tabs.getTabs().add(createTab(new VBox()));
tabs.getTabs().add(createTab(new Pane()));
tabs.getTabs().add(createTab(new Group()));
Scene scene = new Scene(tabs, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private Tab createTab(Pane pane) {
Circle c1 = new Circle(150, 150, 50, Color.RED);
Circle c2 = new Circle(400, 400, 50, Color.YELLOW);
pane.getChildren().addAll(c1, c2);
Tab tab = new Tab(pane.getClass().getSimpleName());
tab.setContent(pane);
return tab ;
}
// annoyingly, Pane and Group do not have a common superclass with a getChildren()
// method, so just reproduce the code...
private Tab createTab(Group pane) {
Circle c1 = new Circle(150, 150, 50, Color.RED);
Circle c2 = new Circle(400, 400, 50, Color.YELLOW);
pane.getChildren().addAll(c1, c2);
Tab tab = new Tab(pane.getClass().getSimpleName());
tab.setContent(pane);
return tab ;
}
public static void main(String[] args) {
launch(args);
}
}
Yeah your both the circles are overlapping.
You can simply use a VBox instead of StackPane. It will solve your issue.
VBox p = new VBox();
As other answers have suggested, using a VBox would help you out the most here, since it will automatically put its children into a vertical row. Here is a brief snippet using an array (so you can make as many circles as you want)
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.scene.paint.*;
public class TryingGraphicsStuff extends Application{
#Override
public void start(Stage stage) throws Exception {
Circle[] circle = new Circle[3]; // create 3 circles
VBox vBox = new VBox(); // vbox will put circles in vertical row
vBox.setAlignment(Pos.CENTER); // center circles
for(int i = 0; i < circle.length; i++){
circle[i] = new Circle(50); // initialize circles with radius of 50
vBox.getChildren().add(circle[i]);
}
circle[0].setFill(Color.RED);
circle[1].setFill(Color.YELLOW);
circle[2].setFill(Color.GREEN);
// add vbox to scene
Scene scene = new Scene(vBox, 300, 800);
stage.setTitle("Circle");
stage.setScene(scene);
stage.show();
}
public static void main (String [] args){
Application.launch(args);
}
}
As always, please understand the code and don't just mindlessly copy and paste. Cheers!
I'm actually a bit confused by the code above. According to your numbers the red one should be the one showing and not the yellow one. Your scene is only 300px wide and you center the yellow circle at 400 which will put it out of view (having a radius of only 50).
Either increase your scene size or move your circle inside your view.

Categories

Resources