Lorsque nous nous sommes quittés la dernière fois, nous avions positionné des contrôles 2D dans un environnement 3D. Nous avions alors appliqué des rotations sur chacune des faces de notre carte pour la faire se tourner sur elle-même. Cependant, il semble un peu idiot, maintenant que nous avons accès à un environnement 3D, de devoir encore manipuler chacune des faces séparément alors que nous pourrions effecteur une seule rotation de 360° sur un objet représentant la carte entière au lieu d'effectuer plusieurs petites rotations sur chacune des faces.
Commençons par charger nos images sur chacune de nos faces avec le même code que d'habitude :
| Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 | 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 ImageView backCard = new ImageView(sourceImage); backCard.setViewport(new Rectangle2D(197, 572, 98, 143)); |
| Code Java : | Sélectionner tout |
final Group card = new Group(frontCard, backCard);
| Code Java : | Sélectionner tout |
1 2 | backCard.setVisible(false); //backCard.toBack(); |
| Code Java : | Sélectionner tout |
1 2 3 4 5 6 | final RotateTransition rotateCard = new RotateTransition(halfFlipDuration.multiply(4), card); rotateCard.setFromAngle(0); rotateCard.setToAngle(360); rotateCard.setInterpolator(Interpolator.LINEAR); final Timeline animation = rotateCard; animation.setCycleCount(RotateTransition.INDEFINITE); |
Erreur...
Et... et quelque chose cloche... Nous avons en effet notre carte qui tourne bien sur elle-même de 360° ; mais elle nous présente toujours la même face : l'as...
Donc, nous allons devoir modifier la rotation pour qu'à certaines étapes clés, la face montrant le dos devienne visible tandis que celle montrant l'as devienne invisible et inversement lors de la seconde partie de la rotation.
On peut aussi résoudre le problème en faisant que la face montrant le dos passe par dessus celle montrant l'as et inversement lors de la seconde partie (idem cela demande plus de travail lors de l'affichage car alors les deux faces sont visibles).
Commençons par réécrire notre rotation en utilisant cette fois-ci une instance de la classe Timeline plutôt que de la classe RotateTransition :
| Code Java : | Sélectionner tout |
1 2 3 4 5 | final Timeline rotateCard = new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(card.rotateProperty(), 0)), new KeyFrame(halfFlipDuration.multiply(4), new KeyValue(card.rotateProperty(), 360))); final Timeline animation = rotateCard; animation.setCycleCount(SequentialTransition.INDEFINITE); |
- À zero secondes, la carte subit une rotation de 0°.
- À la fin de l'animation, la carte subit une rotation de 360°.
Les valeurs intermédiaires seront interpolées entre ces deux étapes clés et pour le moment, à l'affichage, le résultat est identique à celui précédemment obtenu.
Nous allons maintenant insérer deux nouvelles étapes clés au ¼ et ¾ de la rotation pour modifier la visibilité de chacune des faces :
| Code Java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | final Timeline rotateCard = new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(card.rotateProperty(), 0)), new KeyFrame(halfFlipDuration, actionEvent -> { frontCard.setVisible(false); backCard.setVisible(true); // backCard.toFront(); }), new KeyFrame(halfFlipDuration.multiply(3), actionEvent -> { frontCard.setVisible(true); backCard.setVisible(false); // frontCard.toFront(); }), new KeyFrame(halfFlipDuration.multiply(4), new KeyValue(card.rotateProperty(), 360))); |
Lors de l’exécution, tout semble être correct désormais : notre face as disparait pour afficher la face dos puis l'inverse se produit et notre carte semble tourner correctement sur elle-même...
Enfin un résultat correct ?
Mais est-ce vraiment le cas ? Notre l'image qui représente le dos de la carte est symétrique suivant l'axe vertical donc nous ne pouvons pas vraiment nous rendre compte d'un défaut... cependant si nous utilisons une image qui n'offre pas une telle symétrie ou qui contient du texte, un nouveau problème apparait rapidement :
Non, ce n'est toujours pas bon...
Eh oui... lorsqu'il est visible, le dos de la carte subit un effet miroir et donc apparait inversé suivant l'axe Y. Et cela se voit particulièrement lorsqu'on y affiche du texte. Ce problème était d'ailleurs déjà apparent lorsque nous ne voyions que la face as de la carte durant l'animation ; vous vous en rendrez compte si vous remontez quelques images plus haut.
En y réfléchissant, ce problème est tout à fait normal. N'oubliez pas : à l'origine le dos de notre carte est orienté vers nous, tout comme l'as. Ainsi, lorsque le groupe qui définit la carte tourne de 180°, la face visible (que ce soit l'as ou le dos) a également tourné de 180° et donc nous voyons son miroir. Nous pouvons facilement corriger ceci en rajoutant lors de la création de la face dos :
| Code Java : | Sélectionner tout |
1 2 | backCard.setRotationAxis(Rotate.Y_AXIS); backCard.setRotate(180); |
Bingo !
Voici donc le code final de cette transformation :
| 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 | package test; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.SequentialTransition; import javafx.animation.Timeline; import javafx.application.Application; import javafx.beans.binding.DoubleBinding; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; import javafx.scene.Group; import javafx.scene.PerspectiveCamera; import javafx.scene.Scene; import javafx.scene.SceneAntialiasing; import javafx.scene.SubScene; import javafx.scene.control.Slider; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToolBar; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.transform.Rotate; import javafx.stage.Stage; import javafx.util.Duration; public class Test_D3Goup1 extends Application { private final Duration halfFlipDuration = Duration.seconds(1); @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 ImageView backCard = new ImageView(sourceImage); backCard.setViewport(new Rectangle2D(197, 572, 98, 143)); backCard.setRotationAxis(Rotate.Y_AXIS); backCard.setRotate(180); // final Group card = new Group(frontCard, backCard); backCard.setVisible(false); // backCard.toBack(); card.setRotationAxis(Rotate.Y_AXIS); // final StackPane stackPane = new StackPane(); stackPane.getChildren().addAll(card); final SubScene subScene = new SubScene(stackPane, 300, 250, false, SceneAntialiasing.BALANCED); subScene.setCamera(new PerspectiveCamera()); 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(subScene); final Scene scene = new Scene(root, 300, 250); primaryStage.setTitle("3D: Group"); primaryStage.setScene(scene); primaryStage.show(); // final Timeline rotateCard = new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(card.rotateProperty(), 0)), new KeyFrame(halfFlipDuration, actionEvent -> { frontCard.setVisible(false); backCard.setVisible(true); // backCard.toFront(); }), new KeyFrame(halfFlipDuration.multiply(3), actionEvent -> { frontCard.setVisible(true); backCard.setVisible(false); // frontCard.toFront(); }), new KeyFrame(halfFlipDuration.multiply(4), new KeyValue(card.rotateProperty(), 360))); final Timeline animation = rotateCard; 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(); } }); } public static void main(String[] args) { launch(args); } } |
| 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 | package test; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.ParallelTransition; import javafx.animation.SequentialTransition; import javafx.animation.Timeline; import javafx.application.Application; import javafx.beans.binding.DoubleBinding; import javafx.geometry.Pos; import javafx.geometry.Rectangle2D; import javafx.scene.Group; import javafx.scene.PerspectiveCamera; import javafx.scene.Scene; import javafx.scene.SceneAntialiasing; import javafx.scene.SubScene; import javafx.scene.control.Slider; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToolBar; import javafx.scene.effect.ColorAdjust; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.transform.Rotate; import javafx.stage.Stage; import javafx.util.Duration; public class Test_D3Goup2 extends Application { private final Duration halfFlipDuration = Duration.seconds(1); @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 ImageView backCard = new ImageView(sourceImage); backCard.setViewport(new Rectangle2D(197, 572, 98, 143)); backCard.setRotationAxis(Rotate.Y_AXIS); backCard.setRotate(-180); // final Group card = new Group(frontCard, backCard); backCard.setVisible(false); // backCard.toBack(); card.setRotationAxis(Rotate.Y_AXIS); final ColorAdjust cardColorAdjust = new ColorAdjust(); card.setEffect(cardColorAdjust); // final StackPane stackPane = new StackPane(); stackPane.getChildren().addAll(card); final SubScene subScene = new SubScene(stackPane, 300, 250, false, SceneAntialiasing.BALANCED); subScene.setCamera(new PerspectiveCamera()); 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(subScene); final Scene scene = new Scene(root, 300, 250); primaryStage.setTitle("3D: Group + shadow"); primaryStage.setScene(scene); primaryStage.show(); // final Timeline rotateCard = new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(card.rotateProperty(), 0)), new KeyFrame(halfFlipDuration, actionEvent -> { frontCard.setVisible(false); backCard.setVisible(true); // backCard.toFront(); }), new KeyFrame(halfFlipDuration.multiply(3), actionEvent -> { frontCard.setVisible(true); backCard.setVisible(false); // frontCard.toFront(); }), new KeyFrame(halfFlipDuration.multiply(4), new KeyValue(card.rotateProperty(), 360))); final Timeline changeBrightness = new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(cardColorAdjust.brightnessProperty(), 0)), new KeyFrame(halfFlipDuration, new KeyValue(cardColorAdjust.brightnessProperty(), -1)), new KeyFrame(halfFlipDuration.multiply(2), new KeyValue(cardColorAdjust.brightnessProperty(), 0)), new KeyFrame(halfFlipDuration.multiply(3), new KeyValue(cardColorAdjust.brightnessProperty(), -1)), new KeyFrame(halfFlipDuration.multiply(4), new KeyValue(cardColorAdjust.brightnessProperty(), 0)) ); final ParallelTransition animation = new ParallelTransition(rotateCard, changeBrightness); 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(); } }); } public static void main(String[] args) { launch(args); } } |
Vous avez lu gratuitement 3 745 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.