Correction de trajectoire
Par Guillaume Savaton le dimanche 28 novembre 2010, 12:38 - Lien permanent
Depuis que j'utilise régulièrement Sozi pour mes présentations, je remarque que la trajectoire de la "caméra" entre deux vues n'est pas rectiligne. Il est parfois difficile de deviner quel chemin sera suivi, et donc quels éléments du document seront visibles pendant une transition. Dans ce billet je donne le compte-rendu d'une petite expérience permettant d'identifier le problème et de proposer une solution.
L'algorithme d'interpolation des vues dans Sozi 10.9
L'animation du déplacement de la caméra se fait par interpolation entre un rectangle de départ et un rectangle d'arrivée. Nous proposons ci-dessous un exemple de document dans lequel le rectangle vert correspond à la vue de départ et le rectangle rouge à la vue d'arrivée.
Cliquez sur l'image pour observer le déplacement de la caméra.
Voici un extrait du code source de ce document :
<svg xmlns="http://www.w3.org/2000/svg"> <g id="conteneur"> <rect id="depart" x="10" y="10" width="50" height="30" stroke="green" fill="#ada" fill-opacity="0.5" transform="translate(70, 100) scale(4) rotate(30)" /> <rect id="arrivee" x="10" y="10" width="50" height="30" stroke="red" fill="#daa" fill-opacity="0.5" transform="translate(400, 500) scale(6) rotate(-30)" /> </g> </svg>
La méthode d'affichage d'une vue est détaillée dans le billet Ajuster l'affichage d'un document SVG sur une région délimitée par un rectangle. Ici, nous l'avons un peu simplifiée. Pour chaque rectangle, nous avons besoin de connaître :
- ses dimensions effectives, qui, en fonction de la taille de la zone d'affichage, serviront à calculer le facteur d'échelle à appliquer,
- la translation et la rotation à appliquer au document.
L'accès aux propriétés géométriques des rectangles se fait de la manière suivante :
function calculerGeometrie (id) { // Lire les coordonnées et dimensions du rectangle var rect = document.getElementById(id); var x = parseFloat(rect.getAttribute("x")); var y = parseFloat(rect.getAttribute("y")); var width = parseFloat(rect.getAttribute("width")); var height = parseFloat(rect.getAttribute("height")); // Calculer l'inverse de la matrice de transformation du rectangle // et le facteur d'échelle à appliquer au document var matrix = rect.getCTM().inverse(); var scale = Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b); return { width: width / scale, height: height / scale, translateX: (matrix.e - x) / scale, translateY: (matrix.f - y) / scale, rotate: Math.atan2(matrix.b, matrix.a) * 180 / Math.PI }; } var depart = calculerGeometrie("depart"); var arrivee = calculerGeometrie("arrivee");
La technique d'animation d'une transformation a déjà été exposée dans le billet Animer une transformation SVG.
Ici, nous ne donnerons que le contenu de la fonction avancer :
function avancer () { var dateMs = Date.now(); if(dateMs >= dateArriveeMs) { window.clearInterval(horloge); dateMs = dateArriveeMs; } var progression = (dateMs - dateDepartMs) / (dateArriveeMs - dateDepartMs); var restant = 1 - progression; // Interpoler les rectangles source et destination var inter = {}; for (var attr in depart) { inter[attr] = depart[attr] * restant + arrivee[attr] * progression; } // Calculer les coordonnées et les dimensions de la zone d'affichage var scale = Math.min(window.innerWidth / inter.width, window.innerHeight / inter.height); var displayWidth = inter.width * scale; var displayHeight = inter.height * scale; var displayX = (window.innerWidth - displayWidth) / 2; var displayY = (window.innerHeight - displayHeight) / 2; // Calculer la translation à appliquer au document var translateX = inter.translateX * scale + displayX; var translateY = inter.translateY * scale + displayY; // Appliquer la transformation conteneur.setAttribute("transform", "translate(" + translateX + "," + translateY + ") " + "scale(" + scale + ") " + "rotate(" + inter.rotate + ")" ); }
Ici, chaque vue intermédiaire est obtenue par interpolation linéaire entre les propriétés géométriques des rectangles de départ et d'arrivée.
Pour chaque propriété représentée par l'attribut attr, nous calculons :
inter[attr] = depart[attr] * restant + arrivee[attr] * progression;
Lorsqu'on affiche la trace du déplacement (cliquez dans l'image au début de cette section), on observe qu'elle n'est pas rectiligne.
Voici une tentative d'explication de ce phénomène : jusqu'à présent, nous avons toujours cru que la trajectoire était entièrement déterminée par les coordonnées de la translation (translateX et translateY), comme si la rotation et l'homothétie se faisaient "sur place" (c'est-à-dire autour du centre du rectangle courant).
En réalité, le centre de la rotation est situé à l'origine du repère, c'est-à-dire au coin supérieur gauche de l'image. On peut donc considérer que notre rotation est elle-même la composition d'une rotation "sur place" et d'une translation, cette dernière venant s'ajouter à la translation que nous avons calculée.
Pour mieux maîtriser la trajectoire de la caméra, nous allons donc revoir la manière dont nous effectuons les transformations, en prenant à chaque fois comme point de référence le centre du rectangle.
Une nouvelle méthode de calcul
Nous ne montrerons ici que les fragments de Javascript qui ont changé.
Tout d'abord, dans la fonction calculerGeometrie, nous renonçons à calculer la matrice de transformation inverse, et nous nous intéressons uniquement à la transformation subie par le rectangle. Cela nous permet de calculer les coordonnées effectives du centre du rectangle.
function calculerGeometrie (id) { // Lire les coordonnées et dimensions du rectangle ... // Calculer la matrice de transformation du rectangle // et le facteur d'échelle qui lui a été appliqué var matrix = rect.getCTM(); var scale = Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b); // Calculer les coordonnées du centre du rectangle var center = document.documentElement.createSVGPoint(); center.x = x + width / 2; center.y = y + height / 2; center = center.matrixTransform(matrix); return { cx: center.x, cy: center.y, width: width * scale, height: height * scale, rotate: Math.atan2(matrix.b, matrix.a) * 180 / Math.PI }; }
Dans la fonction avancer, nous modifions le calcul de la translation.
Ses coordonnées sont calculées de manière à placer le rectangle interpolé au centre de la zone d'affichage.
Nous modifions également l'attribut transform pour que la rotation soit centrée sur le centre du rectangle et pour effectuer la translation avant l'homothétie :
function avancer () { ... // Calculer la translation à appliquer au document var translateX = - inter.cx + inter.width / 2 + displayX / scale; var translateY = - inter.cy + inter.height / 2 + displayY / scale; // Appliquer la transformation conteneur.setAttribute("transform", "scale(" + scale + ") " + "translate(" + translateX + "," + translateY + ") " + "rotate(" + (- inter.rotate) + "," + inter.cx + "," + inter.cy + ")" );
Cliquez dans l'image ci-dessous pour observer le résultat :
Téléchargement
Évaluer ce billet
5/5
- Note : 5
- Votes : 2
- Plus haute : 5
- Plus basse : 5

Commentaires
Génial !
Maintenant pour que ce soit parfait, il faudrait pouvoir effectuer d'abord la translation, puis la rotation, histoire de donner un effet plus dynamique (et le zoom au début ou à la fin selon qu'on "entre" dans l'image ou qu'on "sorte")
Merci, en tout cas !
Évaluer ce commentaire
0/5
Bonsoir,
c'est en écrivant un billet concernant le mode d'exportation SVG de Freemind et Freeplane que j'ai découvert Sozi au travers du commentaire d'un visiteur. Sur le principe, je suis absolument conquis. Un fil de discussion sur le forum de Freeplane fait part de l'éventuelle création d'un module "Prezi-like" qui permettrait de faire du Prezi avec une carte Freeplane exportée en SVG...et voilà que j'apprends que Sozi fait exactement la même chose !
J'ai donc installé tous les modules, comme indiqué sur le site de sozi.baierouge.fr, mais j'ai malheureusement un message d'erreur sous inkscape (un prublème Python visiblement).
Je sais que la partie commentaire d'un blog n'est pas fait pour résoudre des problèmes techniques, mais où puis-je faire remonter cette anomalie ?
Merci et félicitations pour le travail dont j'ai pu admirer le résultat grâce au fichier d'exemple.
Franck
Évaluer ce commentaire
0/5
Bonjour,
désolé pour mon message d'hier : en fait je m'étais trompé de fichier (trop pressé, sûrement !)
Maintenant que ça marche, je teste tout ça et j'en ferai un billet prochainement.
Merci
Franck.
Évaluer ce commentaire
0/5