IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

JavaFX : simuler la rotation d'une carte sur elle même en utilisant une transformation de perspective
Un billet de bouye

Le , par bouye

0PARTAGES

Enfin de retour ! L’été bat son plein ici et j'avais décidé de prendre 15 jours de congés après le break des fêtes de fin d’année pour me détendre. Mais bon toutes les bonnes choses ont une fin et ce n'est pas plus mal car il pleut désormais à verse depuis hier (une dépression tropicale est proche).

Dans l’entrée précédente, nous avons vu comment simuler la rotation d'une carte sur elle même en utilisant une transformation de mise à l’échelle. Bien que n’étant pas graphiquement très réaliste, cette manière de faire à l'avantage d’être en 2D et de ne pas demander beaucoup de puissance.

Aujourd'hui, nous allons rester en 2D mais nous allons être amené à utiliser une transformation (ou plutôt, dans ce cas, un effet) plus réaliste : une transformation de perspective en utilisant la classe PerspectiveTransform. Cet effet créant une transformation qui n'est pas affine, il est plus gourmand en puissance graphique et de calcul et peut demander également plus de mémoire.

Il s'agit d'un effet graphique qui prend les 4 coins d'un nœud et les déplace aux coordonnées désignées, déformant ainsi le contenu du nœud (dans notre cas une instance d'ImageView).


L'image s’inscrit dans une boite englobante rectangulaire qui contient 4 sommets :
  • UL - le coin supérieur gauche (Upper Left).
  • UR - le coin supérieur droit (Upper Right).
  • LL - le coin inférieur gauche (Lower Left).
  • LR - le coin inférieur droit (Lower Right).


Nous allons leur appliquer une modification d'enveloppe : c'est à dire que nous allons déplacer les coordonnées de ces 4 points en spécifiant une quadrilatère défini par les coordonnées destination des 4 sommets. Quand la transformation est appliquée, l'image s'en trouve modifiée et a une apparence déformée. C'est là le principe de l'effet de perspective.

Note : ici nous effectuons la rotation sur des instances d'ImageView. Mais on pourrait tout aussi bien le faire sur des contrôles interactif (ce qui était le but initial du poseur de la question sur OTN). Or, la transformation correcte des coordonnées des événements souris n'est pas garantie lors que l'effet PerspectiveTransformest utilisé. Mieux vaudra donc désactiver toute saisie utilisateur durant l'animation du passage d'un contrôle à un autre pour ensuite les réactiver lorsque l'animation est terminée.



Une fausse animation en utilisant un effet de perspective.

Ici, il ne s'agit pas non-plus d'une vraie rotation de la carte sur elle-même. Nous utilisons simplement un effet graphique qui donne l'impression que la rotation a lieu.

Lors du passage de 0° à 90°, l'enveloppe de la transformation de la face avant est modifiée de manière à ce que UL, UR, LL et LR soient alignés sur une seule et même ligne verticale.


Les déplacement des 4 sommets à 0°, 45° et 90° (face avant).

Lors du passage de 90° à 180°, l'enveloppe de la transformation de la face arrière est modifiée de manière à ce que UL, UR, LL et LR, qui initialement étaient alignés sur une seule et même ligne verticale, reprennent leur positions d'origine pour afficher afficher la carte de face.


Les déplacement des 4 sommets à 90°, 135° et 180° (face arrière).

Le déplacement de chacun des 4 points s'effectue de manière linéaire ; il n'y a pas besoin de s’embêter à essayer de calculer un déplacement en suivant une courbe pour tenter de simuler la perspective : l'animation étant rapide, elle est déjà suffisante en soit pour tromper l’œil.

Tout d'abord, nous allons définir une variable offset qui va contenir le décalage verticale maximal de la perspective et, ici, lui donner une valeur de 10 pixels.

Code Java : Sélectionner tout
private final double offset = 10;

Initialisons ensuite nos instances d'ImageView en leur donnant à chacune un effet graphique de type PerspectiveTransform :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
final ImageView frontCard = new ImageView(sourceImage); 
frontCard.setViewport(new Rectangle2D(0, 286, 98, 143)); 
final PerspectiveTransform frontPerspective = new PerspectiveTransform(0, 0, 98, 0, 98, 143, 0, 143); 
frontCard.setEffect(frontPerspective); 
// 
final ImageView backCard = new ImageView(sourceImage); 
backCard.setViewport(new Rectangle2D(197, 572, 98, 143)); 
final PerspectiveTransform backPerspective = new PerspectiveTransform(98 / 2d, -offset, 98 / 2d, offset, 98 / 2d, 143 - offset, 98 / 2d, 143 + offset); 
backCard.setEffect(backPerspective);

Ici, l'enveloppe de la face avant de la carte n'est pas modifiée (on voit donc l'as de face) tandis que tous les points du dos sont alignés sur une même ligne verticale (on ne voit donc pas cette face).

Une fois de plus, pour rendre la chose plus aisément compréhensible, nous allons découper l'animation en plusieurs sous-parties. Ce n'est pas forcement la manière la plus optimisée de faire, mais cela rend le code moins complexe à saisir.

Code Java : Sélectionner tout
1
2
3
4
final SequentialTransition animation = new SequentialTransition( 
        flip(98, 143, frontCard, frontPerspective, backCard, backPerspective), 
        flip(98, 143, backCard, backPerspective, frontCard, frontPerspective)); 
animation.setCycleCount(SequentialTransition.INDEFINITE);

Nous implémentons maintenant la méthode flip() pour animer séparément chaque face. Ici, nous allons créer une Timeline qui permettra d'animer chaque point de l'enveloppe. À chaque KeyFrame (étape clé de l’animation) nous avons 4 points avec 2 coordonnées X et Y par point = 8 valeurs à spécifier.

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private Transition flip(double width, double height, Node front, PerspectiveTransform frontPerspective, Node back, PerspectiveTransform backPerspective) { 
    final Timeline perspectiveOutFront = new Timeline( 
            new KeyFrame(Duration.ZERO, 
                    new KeyValue(frontPerspective.ulxProperty(), 0), 
                    new KeyValue(frontPerspective.ulyProperty(), 0), 
                    new KeyValue(frontPerspective.urxProperty(), width), 
                    new KeyValue(frontPerspective.uryProperty(), 0), 
                    new KeyValue(frontPerspective.lrxProperty(), width), 
                    new KeyValue(frontPerspective.lryProperty(), height), 
                    new KeyValue(frontPerspective.llxProperty(), 0), 
                    new KeyValue(frontPerspective.llyProperty(), height) 
            ), 
            new KeyFrame(halfFlipDuration, 
                    new KeyValue(frontPerspective.ulxProperty(), width / 2d), 
                    new KeyValue(frontPerspective.ulyProperty(), offset), 
                    new KeyValue(frontPerspective.urxProperty(), width / 2d), 
                    new KeyValue(frontPerspective.uryProperty(), -offset), 
                    new KeyValue(frontPerspective.lrxProperty(), width / 2d), 
                    new KeyValue(frontPerspective.lryProperty(), height + offset), 
                    new KeyValue(frontPerspective.llxProperty(), width / 2d), 
                    new KeyValue(frontPerspective.llyProperty(), height - offset) 
            )); 
    // 
    final Timeline perspectiveInBack = new Timeline( 
            new KeyFrame(Duration.ZERO, 
                    new KeyValue(backPerspective.ulxProperty(), width / 2d), 
                    new KeyValue(backPerspective.ulyProperty(), -offset), 
                    new KeyValue(backPerspective.urxProperty(), width / 2d), 
                    new KeyValue(backPerspective.uryProperty(), offset), 
                    new KeyValue(backPerspective.lrxProperty(), width / 2d), 
                    new KeyValue(backPerspective.lryProperty(), height - offset), 
                    new KeyValue(backPerspective.llxProperty(), width / 2d), 
                    new KeyValue(backPerspective.llyProperty(), height + offset) 
            ), 
            new KeyFrame(halfFlipDuration, 
                    new KeyValue(backPerspective.ulxProperty(), 0), 
                    new KeyValue(backPerspective.ulyProperty(), 0), 
                    new KeyValue(backPerspective.urxProperty(), width), 
                    new KeyValue(backPerspective.uryProperty(), 0), 
                    new KeyValue(backPerspective.lrxProperty(), width), 
                    new KeyValue(backPerspective.lryProperty(), height), 
                    new KeyValue(backPerspective.llxProperty(), 0), 
                    new KeyValue(backPerspective.llyProperty(), height) 
            )); 
  
    // 
    return new SequentialTransition(perspectiveOutFront, perspectiveInBack); 
}

Et nous répétons ces mêmes animations en inversant les faces des cartes pour l'animation de 180° a 360°.

Voici le code complet du test :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package test; 
  
import javafx.animation.KeyFrame; 
import javafx.animation.KeyValue; 
import javafx.animation.SequentialTransition; 
import javafx.animation.Timeline; 
import javafx.animation.Transition; 
import javafx.application.Application; 
import javafx.beans.binding.DoubleBinding; 
import javafx.geometry.Pos; 
import javafx.geometry.Rectangle2D; 
import javafx.scene.Node; 
import javafx.scene.Scene; 
import javafx.scene.control.Slider; 
import javafx.scene.control.ToggleButton; 
import javafx.scene.control.ToolBar; 
import javafx.scene.effect.PerspectiveTransform; 
import javafx.scene.image.Image; 
import javafx.scene.image.ImageView; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.StackPane; 
import javafx.stage.Stage; 
import javafx.util.Duration; 
  
public class Test_D2Perspective1 extends Application { 
  
    private final Duration halfFlipDuration = Duration.seconds(1); 
  
    private final double offset = 10; 
  
    @Override 
    public void start(Stage primaryStage) { 
        final Image sourceImage = new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/a/a1/Svg-cards-2.0.svg/1280px-Svg-cards-2.0.svg.png"); 
        // 
        final ImageView frontCard = new ImageView(sourceImage); 
        frontCard.setViewport(new Rectangle2D(0, 286, 98, 143)); 
        final PerspectiveTransform frontPerspective = new PerspectiveTransform(0, 0, 98, 0, 98, 143, 0, 143); 
        frontCard.setEffect(frontPerspective); 
        // 
        final ImageView backCard = new ImageView(sourceImage); 
        backCard.setViewport(new Rectangle2D(197, 572, 98, 143)); 
        final PerspectiveTransform backPerspective = new PerspectiveTransform(98 / 2d, -offset, 98 / 2d, offset, 98 / 2d, 143 - offset, 98 / 2d, 143 + offset); 
        backCard.setEffect(backPerspective); 
        // 
        final StackPane stackPane = new StackPane(); 
        stackPane.getChildren().addAll(frontCard, backCard); 
        final ToggleButton playButton = new ToggleButton("Play"); 
        StackPane.setAlignment(playButton, Pos.TOP_LEFT); 
        final Slider timeSlider = new Slider(0, 4 * halfFlipDuration.toMillis(), 0); 
        timeSlider.setDisable(true); 
        final ToolBar toolBar = new ToolBar(); 
        toolBar.getItems().addAll(playButton, timeSlider); 
        final BorderPane root = new BorderPane(); 
        root.setTop(toolBar); 
        root.setCenter(stackPane); 
        final Scene scene = new Scene(root, 300, 250); 
        primaryStage.setTitle("2D: perspective"); 
        primaryStage.setScene(scene); 
        primaryStage.show(); 
        // 
        final SequentialTransition animation = new SequentialTransition( 
                flip(98, 143, frontCard, frontPerspective, backCard, backPerspective), 
                flip(98, 143, backCard, backPerspective, frontCard, frontPerspective)); 
        animation.setCycleCount(SequentialTransition.INDEFINITE); 
        playButton.selectedProperty().addListener((observableValue, oldValue, newValue) -> { 
            if (newValue) { 
                animation.play(); 
            } else { 
                animation.pause(); 
            } 
        }); 
        timeSlider.valueProperty().bind(new DoubleBinding() { 
            { 
                bind(animation.currentTimeProperty()); 
            } 
  
            @Override 
            public void dispose() { 
                super.dispose(); 
                unbind(animation.currentTimeProperty()); 
            } 
  
            @Override 
            protected double computeValue() { 
                return animation.getCurrentTime().toMillis(); 
            } 
        }); 
    } 
  
    private Transition flip(double width, double height, Node front, PerspectiveTransform frontPerspective, Node back, PerspectiveTransform backPerspective) { 
        final Timeline perspectiveOutFront = new Timeline( 
                new KeyFrame(Duration.ZERO, 
                        new KeyValue(frontPerspective.ulxProperty(), 0), 
                        new KeyValue(frontPerspective.ulyProperty(), 0), 
                        new KeyValue(frontPerspective.urxProperty(), width), 
                        new KeyValue(frontPerspective.uryProperty(), 0), 
                        new KeyValue(frontPerspective.lrxProperty(), width), 
                        new KeyValue(frontPerspective.lryProperty(), height), 
                        new KeyValue(frontPerspective.llxProperty(), 0), 
                        new KeyValue(frontPerspective.llyProperty(), height) 
                ), 
                new KeyFrame(halfFlipDuration, 
                        new KeyValue(frontPerspective.ulxProperty(), width / 2d), 
                        new KeyValue(frontPerspective.ulyProperty(), offset), 
                        new KeyValue(frontPerspective.urxProperty(), width / 2d), 
                        new KeyValue(frontPerspective.uryProperty(), -offset), 
                        new KeyValue(frontPerspective.lrxProperty(), width / 2d), 
                        new KeyValue(frontPerspective.lryProperty(), height + offset), 
                        new KeyValue(frontPerspective.llxProperty(), width / 2d), 
                        new KeyValue(frontPerspective.llyProperty(), height - offset) 
                )); 
        // 
        final Timeline perspectiveInBack = new Timeline( 
                new KeyFrame(Duration.ZERO, 
                        new KeyValue(backPerspective.ulxProperty(), width / 2d), 
                        new KeyValue(backPerspective.ulyProperty(), -offset), 
                        new KeyValue(backPerspective.urxProperty(), width / 2d), 
                        new KeyValue(backPerspective.uryProperty(), offset), 
                        new KeyValue(backPerspective.lrxProperty(), width / 2d), 
                        new KeyValue(backPerspective.lryProperty(), height - offset), 
                        new KeyValue(backPerspective.llxProperty(), width / 2d), 
                        new KeyValue(backPerspective.llyProperty(), height + offset) 
                ), 
                new KeyFrame(halfFlipDuration, 
                        new KeyValue(backPerspective.ulxProperty(), 0), 
                        new KeyValue(backPerspective.ulyProperty(), 0), 
                        new KeyValue(backPerspective.urxProperty(), width), 
                        new KeyValue(backPerspective.uryProperty(), 0), 
                        new KeyValue(backPerspective.lrxProperty(), width), 
                        new KeyValue(backPerspective.lryProperty(), height), 
                        new KeyValue(backPerspective.llxProperty(), 0), 
                        new KeyValue(backPerspective.llyProperty(), height) 
                )); 
  
        // 
        return new SequentialTransition(perspectiveOutFront, perspectiveInBack); 
    } 
  
    public static void main(String[] args) { 
        launch(args); 
    } 
}

Une fois de plus, nous pouvons également utiliser un effet de changement de couleur pour simuler l'assombrissement des faces de manière à rendre la fausse rotation plus réaliste :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package test; 
  
import javafx.animation.KeyFrame; 
import javafx.animation.KeyValue; 
import javafx.animation.ParallelTransition; 
import javafx.animation.SequentialTransition; 
import javafx.animation.Timeline; 
import javafx.animation.Transition; 
import javafx.application.Application; 
import javafx.beans.binding.DoubleBinding; 
import javafx.geometry.Pos; 
import javafx.geometry.Rectangle2D; 
import javafx.scene.Node; 
import javafx.scene.Scene; 
import javafx.scene.control.Slider; 
import javafx.scene.control.ToggleButton; 
import javafx.scene.control.ToolBar; 
import javafx.scene.effect.ColorAdjust; 
import javafx.scene.effect.PerspectiveTransform; 
import javafx.scene.image.Image; 
import javafx.scene.image.ImageView; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.StackPane; 
import javafx.stage.Stage; 
import javafx.util.Duration; 
  
public class Test_D2Perspective2 extends Application { 
  
    private final Duration halfFlipDuration = Duration.seconds(1); 
  
    private final double offset = 10; 
  
    @Override 
    public void start(Stage primaryStage) { 
        final Image sourceImage = new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/a/a1/Svg-cards-2.0.svg/1280px-Svg-cards-2.0.svg.png"); 
        // 
        final ImageView frontCard = new ImageView(sourceImage); 
        frontCard.setViewport(new Rectangle2D(0, 286, 98, 143)); 
        final PerspectiveTransform frontPerspective = new PerspectiveTransform(0, 0, 98, 0, 98, 143, 0, 143); 
        frontCard.setEffect(frontPerspective); 
        final ColorAdjust frontColorAdjust = new ColorAdjust(); 
        frontPerspective.setInput(frontColorAdjust); 
        // 
        final ImageView backCard = new ImageView(sourceImage); 
        backCard.setViewport(new Rectangle2D(197, 572, 98, 143)); 
        final PerspectiveTransform backPerspective = new PerspectiveTransform(98 / 2d, -offset, 98 / 2d, offset, 98 / 2d, 143 - offset, 98 / 2d, 143 + offset); 
        backCard.setEffect(backPerspective); 
        final ColorAdjust backColorAdjust = new ColorAdjust(); 
        backPerspective.setInput(backColorAdjust); 
        // 
        final StackPane stackPane = new StackPane(); 
        stackPane.getChildren().addAll(frontCard, backCard); 
        final ToggleButton playButton = new ToggleButton("Play"); 
        StackPane.setAlignment(playButton, Pos.TOP_LEFT); 
        final Slider timeSlider = new Slider(0, 4 * halfFlipDuration.toMillis(), 0); 
        timeSlider.setDisable(true); 
        final ToolBar toolBar = new ToolBar(); 
        toolBar.getItems().addAll(playButton, timeSlider); 
        final BorderPane root = new BorderPane(); 
        root.setTop(toolBar); 
        root.setCenter(stackPane); 
        final Scene scene = new Scene(root, 300, 250); 
        primaryStage.setTitle("2D: perspective + shadow"); 
        primaryStage.setScene(scene); 
        primaryStage.show(); 
        // 
        final SequentialTransition animation = new SequentialTransition( 
                flip(98, 143, frontCard, frontPerspective, frontColorAdjust, backCard, backPerspective, backColorAdjust), 
                flip(98, 143, backCard, backPerspective, backColorAdjust, frontCard, frontPerspective, frontColorAdjust)); 
        animation.setCycleCount(SequentialTransition.INDEFINITE); 
        playButton.selectedProperty().addListener((observableValue, oldValue, newValue) -> { 
            if (newValue) { 
                animation.play(); 
            } else { 
                animation.pause(); 
            } 
        }); 
        timeSlider.valueProperty().bind(new DoubleBinding() { 
            { 
                bind(animation.currentTimeProperty()); 
            } 
  
            @Override 
            public void dispose() { 
                super.dispose(); 
                unbind(animation.currentTimeProperty()); 
            } 
  
            @Override 
            protected double computeValue() { 
                return animation.getCurrentTime().toMillis(); 
            } 
        }); 
    } 
  
    private Transition flip(double width, double height, Node front, PerspectiveTransform frontPerspective, ColorAdjust frontColorAdjust, Node back, PerspectiveTransform backPerspective, ColorAdjust backColorAdjust) { 
        final Timeline perspectiveOutFront = new Timeline( 
                new KeyFrame(Duration.ZERO, 
                        new KeyValue(frontPerspective.ulxProperty(), 0), 
                        new KeyValue(frontPerspective.ulyProperty(), 0), 
                        new KeyValue(frontPerspective.urxProperty(), width), 
                        new KeyValue(frontPerspective.uryProperty(), 0), 
                        new KeyValue(frontPerspective.lrxProperty(), width), 
                        new KeyValue(frontPerspective.lryProperty(), height), 
                        new KeyValue(frontPerspective.llxProperty(), 0), 
                        new KeyValue(frontPerspective.llyProperty(), height) 
                ), 
                new KeyFrame(halfFlipDuration, 
                        new KeyValue(frontPerspective.ulxProperty(), width / 2d), 
                        new KeyValue(frontPerspective.ulyProperty(), offset), 
                        new KeyValue(frontPerspective.urxProperty(), width / 2d), 
                        new KeyValue(frontPerspective.uryProperty(), -offset), 
                        new KeyValue(frontPerspective.lrxProperty(), width / 2d), 
                        new KeyValue(frontPerspective.lryProperty(), height + offset), 
                        new KeyValue(frontPerspective.llxProperty(), width / 2d), 
                        new KeyValue(frontPerspective.llyProperty(), height - offset) 
                )); 
        final Timeline changeBrightnessFront = new Timeline( 
                new KeyFrame(Duration.ZERO, new KeyValue(frontColorAdjust.brightnessProperty(), 0)), 
                new KeyFrame(halfFlipDuration, new KeyValue(frontColorAdjust.brightnessProperty(), -1))); 
        final ParallelTransition flipOutFront = new ParallelTransition(perspectiveOutFront, changeBrightnessFront); 
        // 
        final Timeline perspectiveInBack = new Timeline( 
                new KeyFrame(Duration.ZERO, 
                        new KeyValue(backPerspective.ulxProperty(), width / 2d), 
                        new KeyValue(backPerspective.ulyProperty(), -offset), 
                        new KeyValue(backPerspective.urxProperty(), width / 2d), 
                        new KeyValue(backPerspective.uryProperty(), offset), 
                        new KeyValue(backPerspective.lrxProperty(), width / 2d), 
                        new KeyValue(backPerspective.lryProperty(), height - offset), 
                        new KeyValue(backPerspective.llxProperty(), width / 2d), 
                        new KeyValue(backPerspective.llyProperty(), height + offset) 
                ), 
                new KeyFrame(halfFlipDuration, 
                        new KeyValue(backPerspective.ulxProperty(), 0), 
                        new KeyValue(backPerspective.ulyProperty(), 0), 
                        new KeyValue(backPerspective.urxProperty(), width), 
                        new KeyValue(backPerspective.uryProperty(), 0), 
                        new KeyValue(backPerspective.lrxProperty(), width), 
                        new KeyValue(backPerspective.lryProperty(), height), 
                        new KeyValue(backPerspective.llxProperty(), 0), 
                        new KeyValue(backPerspective.llyProperty(), height) 
                )); 
        final Timeline changeBrightnessBack = new Timeline( 
                new KeyFrame(Duration.ZERO, new KeyValue(backColorAdjust.brightnessProperty(), -1)), 
                new KeyFrame(halfFlipDuration, new KeyValue(backColorAdjust.brightnessProperty(), 0))); 
        final ParallelTransition flipInBack = new ParallelTransition(perspectiveInBack, changeBrightnessBack); 
        // 
        return new SequentialTransition(flipOutFront, flipInBack); 
    } 
  
    public static void main(String[] args) { 
        launch(args); 
    } 
}

Ici, j'ai créé des instances de Timeline séparées pour gérer la perspective et le changement de couleur mais, bien sûr, il est tout a fait possible de rajouter les instances de KeyFrame dans une seule et unique ligne temporelle.

Voila, c'est tout pour cette fois, lors du prochain billet, nous laisserons de coté la 2D et nous attaquerons enfin la 3D !

Une erreur dans cette actualité ? Signalez-le nous !