En me baladant sur les réalisations de l'éminent Docteur Goulu, je suis tombé sur cette superbe montre de Pierre Kunz, nommée « Infinity Looping » (depuis renommée Insanity avec un changement de design qui ne lui réussit pas).
Après avoir admiré le mécanisme extrêmement simple pour une montre de ce genre, j'avoue avoir été tenté par l'achat – mais bon, $20 000, voilà qui n'entre pas dans mon budget ! Tous mes remords de pauvre ont d'ailleurs disparu puisque la montre a été tirée en édition limitée, et n'est plus disponible. Le jour où je serai riche et célèbre, j'écumerai (enfin, je ferai écumer par une armée de secrétaires) le web à la recherche d'un ancien magnat du pétrole devenu pauvre qui revendra sa montre, que je rachèterai alors à quatre fois sa valeur pour le simple plaisir d'acquérir enfin ce joyau mécanique. Mais je m'égare.
Revenons à nos moutons : le jour de ma découverte (dimanche), je me suis lancé le défi de reprogrammer cette montre en deux heures. Pour des raisons indépendantes de ma volonté (ah, j'ai horreur quand le boulot vient s'immiscer dans le weekend… surtout s'il me coupe en plein élan), j'ai été coupé après une heure trente et n'ai pu reprendre que le lendemain soir. Au final, je pense y avoir passé plus de temps, parce que mes capacités graphiques font passer Picasso pour du réalisme et que j'ai eu du mal à maîtriser Inkscape (superbe logiciel, que je conseille fortement).
Sans plus vous faire languir, voilà le résultat brut ; l'analyse suit juste après.
Pour une raison étrange, 12, 1 et 2 sont mal placés. Je corrigerais ça ce soir.
Comme toujours, avant de foncer, une petit étape de réflexion s'est imposée.
D'abord, je ne voulais pas écrire des centaines de ligne de code : j'ai donc décidé de garder le code pour l'animation, et de faire à part tous les graphismes.
Graphismes
Comme dit plus haut, tous ces fichiers sont créés avec Inkscape et gardés en vectoriel ; je ne les exporte en png que pour un affichage sans problème sur ce blog.
Les engrenages
C'est la seule partie mécanique de la montre, celle qui fait fonctionner l'ensemble.
Pour pouvoir les représenter, il a fallu réfléchir un peu : quel rapport leur donner ? Combien de dents ?
En fait, les deux problèmes sont reliés. Il faut se rendre compte que pour faire un tour du cadran, le petit engrenage doit faire six tours : on obtient ainsi le rapport de 0,16 qui nous resservira tout le temps.
Arbitrairement, j'ai donc fixé le nombre de dents du gros engrenage à 150, et son rayon à 90 (enfin, arbitrairement… j'ai tout de même veillé à ce qu'il s'agisse de multiples de 6 ! ). On en déduit alors la topologie du petit engrenage : 150/6 = 25 dents et 90/6 = 15 de rayon. Ne restait plus qu'à les représenter ! Inkscape a justement un module très pratique pour créer des engrenages. Pour obtenir des formes compatibles, il faut veiller à conserver le même module (circular pitch) et à ne changer que le nombre de dents ; puis une simple homothétie suffit à transformer l'engrenage pour lui donner la taille voulue.
J'obtins ainsi les deux formes suivantes :
Pour que les deux engrenages puissent s'emboîter sans souci, j'ai dû appliquer une rotation de 6 degrés au petit avant de lui fusionner ses barres porteuses ; ainsi à angle 0 les deux formes s'emboîtent :
La flèche
Mes talents graphiques se sont exprimés à plein régime ! La seule difficulté étant de placer correctement le centre : en effet, décaler les engrenages pour obtenir le centre de rotation est facile, mais trouver le centre de cette forme (l'intersection des croisillons) est plus facile à faire en SVG qu'en code : j'ai donc placé l'origine du dessin au bon endroit pour m'éviter de futurs problèmes.
Le bras porteur
C'est la forme qui m'a pris le plus de temps à concevoir, parce qu'elle doit être à la fois ouverte pour permettre la lecture et assez grosse pour supporter le roulement. Sans compter qu'il faut la centrer correctement sur un élément plein, et faire en sorte que la distance (centre du cercle)-(centre de l'ouverture) corresponde au profil des engrenages !
Le ruban
Paradoxalement, la forme n'aura pas été si dure à obtenir. On peut créer facilement des formes-oïdes avec Inkscape grâce à un outil « spirographe » qui prend en paramètre la taille de l'engrenage porteur (90 dans mon cas), la taille de l'engrenage porté (15) et la distance entre le « stylo » et le centre de l'engrenage porté (la hauteur de la flèche). Et voilà comment on obtient ce magnifique ruban :
Pour ajouter les heures, j'ai utilisé la fonction dédiée pour placer un texte le long d'un chemin. Je n'ai pas trouvé de solutions plus élégantes que de placer des espaces pour avoir les lettres aux bons emplacements… attention, l'algorithme de calcul du chemin n'est pas facile, l'édition du fichier svg est assez lourde !
Le code
Et voilà, il ne restait plus qu'à placer correctement tous ces objets sur la scène.
J'ai choisi d'utiliser ActionScript3, le langage de Flash. Ce choix est uniquement motivé par ma volonté de produire une animation visible sur le web en sortie ; cependant le code est adaptable à n'importe quel langage qui permet de manipuler des sprites.
Il est à noter que j'aurais pu coder l'animation directement en SVG, mais je n'en ai aucune expérience et cela aurait explosé mon crédit d'heures. Pour les curieux, voici un exemple de montre animée en pur svg (attention, cela nécessite un navigateur, et pas le logiciel-qui-se-fait-passer-pour-un-navigateur Internet Explorer).
En soi, le code est extrêmement simple, la seule difficulté étant dans les rotations. En effet, une fois le svg importé dans flash (j'utilise Flex SDK 3, la version libre de Flash), les effets de rotation s'appliquent sur le coin en haut à gauche, et pas par rapport au centre de l'image : un résultat assez étrange quand on s'attend à voir tourner l'engrenage ! J'ai donc été obligé de placer manuellement certains des graphismes dans un conteneur abstrait (qui n'a aucune représentation physique) : une fois placé dans ce conteneur de façon convenable, on peut appliquer une rotation au conteneur pour obtenir l'effet voulu.
Ensuite, monter la montre dans le bon sens ; de haut en bas : d'abord placer le ruban, puis le gros engrenage, puis le petit, puis le bras porteur, puis la fléchette. Et voilà !
Enfin, animer la montre. Je me suis basée sur l'excellent libraire de tweening TweenMax ce qui me permet de réaliser cette partie en deux lignes de code.
Code complet :
package { import com.greensock.easing.Linear; import com.greensock.TweenMax; import flash.display.Sprite; /** * Une montre fonctionnant selon le principe de l'épitrochoïde. * Le bras principal indique l'heure à la façon standard (son angle sert de repère) tandis que la petite fléchette donne l'heure et les minutes. * Même si le temps c'est de l'argent, quand on a assez de monnaie pour acheter cette petite merveille, on n'en est plus à compter les secondes. * Inspiration : http://horlogerie.wordpress.com/2009/02/02/infinity-looping/ * @author Neamar */ public class Main extends Sprite { /** * Liste des constantes permettant de faire fonctionner l'application. * NOTE : Seules TAILLE et UNITE_TEMPS peuvent être modifiées. LEs autres valeurs ont été choisies à la création de l'application, et sont dépendantes des graphiques ; ne les modifiez pas ! */ //Taille de l'application private static const TAILLE:int = 370; //Durée que met l'aiguille à faire un tour (en secondes). Pour une horloge, placer cette valeur sur 60*60 = 3600 (moche) //La durée que met le petit engrenage pour faire un tour complet est extrapolé de cette valeur en la multipliant par 6. private const UNITE_TEMPS:int = 6; //Le rayon extérieur de l'engrenage principal sur lequel roule le petit engrenage private static const RAYON_GROS_ENGRENAGE:int = 90; //Le rayon extérieur du petit engrenage private static const RAYON_PETIT_ENGRENAGE:int = 15; //La taille des dents des engrenages, ce qui permet de faire s'emboiter deux engrenages dont on connait les rayons extérieurs respectifs. private static const DENTURE:int = 1; /** * Pour garder le code le plus léger possible, tous les graphiques sont dans des fichiers vectoriels SVG. * Pour les éditer, vous pouvez utiliser Inkscape (par exemple). */ [Embed(source='Fleche.svg')] private var CFleche:Class; [Embed(source='Fond.svg')] private var CRuban:Class; [Embed(source='Porteur.svg')] private var CPorteur:Class; [Embed(source='Gros_Engrenage.svg')] private var CGros_Engrenage:Class;//150 dents [Embed(source='Petit_Engrenage.svg')] private var CPetit_Engrenage:Class;//150/6 = 25 dents /** * Définir la liste des objets constituants la montre. * NOTE : certains des objets définis ici sont abstraits et n'existent pas réellement dans le mécanisme ; ils sont simplement là pour réaliser facilement des "changements de repère" afin d'obtenir des rotations cohérentes. */ //Bâti global de l'application, la montre en réalité. private var Bati:Sprite = new Sprite; //Le ruban en fond (un dessin d'épitrochoïde) private var Ruban:Sprite = new CRuban(); //Le gros engrenage private var Gros_Engrenage:Sprite = new CGros_Engrenage(); //Le bras. Représentation abstraite du levier qui fait tourner la roulette. private var Bras:Sprite = new Sprite; //Le bras. Représentation concrète du levier qui fait tourner la roulette, centré dans la représentation abstraite pour obtenir une ortation par rapport au point voulu, et pas par rapport au point en haut à gauche. private var Porteur:Sprite = new CPorteur(); //Le petit engrenage. Représentation abstraite. private var Roulette:Sprite = new Sprite(); //Représentation concrète de l'engrenage. private var Petit_Engrenage:Sprite = new CPetit_Engrenage(); //Fléchette qui désigne les minutes private var Flechette:Sprite = new CFleche(); public function Main():void { //Placer le centre de rotation au milieu Bati.x = Bati.y = TAILLE / 2; //Première étape : centrer les engrenages vectoriels à l'intérieur des formes qui les contiendront. Ruban.x = Ruban.y = -TAILLE / 2; Gros_Engrenage.x = Gros_Engrenage.y = -RAYON_GROS_ENGRENAGE; Petit_Engrenage.x = Petit_Engrenage.y = -RAYON_PETIT_ENGRENAGE; //Position de départ de la roulette et de la fléchette (penser à utiliser la denture des engrenages) Roulette.y = - RAYON_GROS_ENGRENAGE - RAYON_PETIT_ENGRENAGE + DENTURE; Flechette.y = - RAYON_GROS_ENGRENAGE - RAYON_PETIT_ENGRENAGE + DENTURE; //Faire tourner les objets pendant UNITE_TEMPS, avec une accélération nulle (vitesse constante), et répéter à l'infini. TweenMax.to(Roulette, UNITE_TEMPS, { ease:Linear.easeNone, repeat: -1, rotation:360 } ); TweenMax.to(Flechette, UNITE_TEMPS, { ease:Linear.easeNone, repeat: -1, rotation:360 } ); //Le bras fait un tour quand la roulette en fait 6 : TweenMax.to(Bras, 6*UNITE_TEMPS, {ease:Linear.easeNone, repeat:-1, rotation:360 } ); /** * Procéder au montage des pièces dans le bon ordre pour avoir un affichage cohérent (et pas le ruban au dessus de toutes les pièces !) */ //Partie statiques : Ruban et engrenage Bati.addChild(Ruban); Bati.addChild(Gros_Engrenage); //Monter l'engrenage externe et l'encastrer avec le bras : Roulette.addChild(Petit_Engrenage); Bras.addChild(Roulette); Bras.addChild(Porteur); Bras.addChild(Flechette); Bati.addChild(Bras); //Et enfin afficher le bati addChild(Bati); } } }
Pour conclure, j'aimerais souligner à nouveau l'apparente complexité des formules mathématiques qui régissent une épitrochoïde et la facilité mécanique des choses (placer un engrenage sur un autre et le faire tourner) ; encore un bel exemple de modélisation, qui montre que selon le point de vue adopté, on obtient plus ou moins rapidement le même résultat.