Informatique TP3 : Interface graphiques et tracer de fractales CPP 1A Romain Casati, Wafa Johal, Frederic Devernay, Matthieu Moy Avril - juin 2014 Ce TP est dédié à la construction de courbes fractales. La notion de récursivité peut être illustrée géométriquement par des fractales. Ce TP débute par une présentation de fonctions graphiques de Python. Il vous est ensuite demandé de construire un certain nombre de courbes fractales. 1 Les fonctions graphiques de Python Nous allons utiliser l outil turtle afin de réaliser ce TP. La fenêtre graphique Turtle de Python est assimilable à un ensemble de points d un plan. Chaque point est désigné par deux coordonnées entières sur le plan. L origine (coordonnées (0, 0)) est située au centre de la fenêtre. La taille de la fenêtre par défaut est de (400, 300). 1.1 Les fonctions graphiques de base Le TP nécessite l utilisation des trois fonctions suivantes : pendown() : met le crayon en position basse permet de tracer. penup() : permet de lever le crayon en vue de déplacement sans tracer. goto(x, y) ou goto([x, y]) : sert à déplacer le curseur vers un point dont on précise les coordonnées. Par exemple, goto(1, 15) place le curseur au point de coordonnées (1, 15), si le crayon est en positon basse lors de l appel de cette fonction, alors une ligne sera tracée à partir du point courant jusqu à (1, 15). Si le crayon était en position haute (up) alors la ligne n est pas tracée mais le curseur est déplacé. Exercice 1 Dans le shell, faites appel à la libraire turtle : from turtle import * puis testez les commandes suivantes : penup() goto(100,50) pendown() goto([100,100]) forward(50) right(90) backward(100) left(120) goto([-100,-100]) Observez le comportement de la tortue dans la fenêtre graphique et vous pourrez en déduire les fonctionnalités de ces commandes. 1.2 Tracer un carré Exercice 2 Écrivez une première fonction permettant de tracer un carré partant du milieu de la fenêtre (il est conseillé de l écrire dans un fichier, mais ce fichier ne doit pas s appeler turtle.py). La longueur du côté est un paramètre de la fonction : 1 # trace_ carre ( x) trace le carre de cote x dont le coin 2 # inferieur gauche est au milieu de la fenetre graphique 3 def trace_carre (x): 4 # à vous de compl é ter 1
Figure 1 Fonctionnement de trace_polygone Remarque : Dans le reste du TP, on pourra représenter un point par une liste de deux coordonnées p = [x, y]. Ainsi, un segment pourra se représenter comme une liste de deux points [p1, p2] (Soit [[x1, y1], [x2, y2]]). 1.3 Tracer d un Polygone La fonction suivante permet de tracer un polygone régulier. Elle admet en paramètres le centre du polygone, son rayon, le nombre de côtés et l angle d un des sommets par rapport à la droite horizontale passant par son centre. Un tracé de polygone nécessite de manipuler des angles et des projections. En effet, le tracé d un polygone de n côtés est une séquence de n tracés de lignes, avec une rotation de 2π/n entre chaque tracé. La figure 1 montre les étapes du tracer d un triangle, d un carré et d un hexagone. 1 from math import * 2 # rotation autour de [ c_x, c_y ] avec un angle donne, du point [x, y] 3 # rotation : 4 # Parametres : 5 # - point_ centre - [ nombre, nombre ] : le centre de rotation 6 # - angle - nombre : l angle de rotation 7 # - point - [ nombre, nombre ] : le point x, y) qui subit la rotation 8 # Retourne : 9 # [ nombre, nombre ] : une liste des coordonn é es x et y du point apr ès rotation 10 def rotation ( point_centre, angle, point ): 11 x = point [0] 12 y = point [1] 13 c_x = point_ centre [0] 14 c_y = point_ centre [1] 15 return [(x - c_x ) * cos ( angle ) - (y - c_y ) * sin ( angle ) + c_x, 16 (x - c_x ) * sin ( angle ) + (y - c_y ) * cos ( angle ) + c_y ] La fonction rotation est une fonction auxiliaire utilisée pour le tracer du polygone. 1 # trace_ polygone : 2 # Parametres : 3 # - centre - [ nombre, nombre ] : le centre du polygone 4 # - rayon - nombre : son rayon 5 # - faces - nombre : le nombres de faces du polygone 6 # - angle - nombre : l angle du premier sommet en radian 7 # Effet : 8 # dessine le polygone demande 9 def iter (i, prec, theta, centre ): 10 if i!= 0: 11 goto ( prec ) 12 suivant = rotation ( centre, theta, prec ) 13 pendown () 14 goto ( suivant ) 15 iter ( i - 1, suivant, theta, centre ) 16 17 def trace_ polygone ( centre, rayon, faces, angle ): 18 theta = 2 * pi / faces 19 x = centre [0] 20 y = centre [1] 2
21 depart = rotation ( centre, angle, [x, y]) 22 iter ( faces, depart, theta, centre ) Exercice 3 Une erreur s est glissée dans chacune des fonctions iter et trace_polygone(mauvais copié-collé). Observez le code de trace_polygone et itere et corrigez ces deux erreurs. Testez ces fonctions avec un jeu de test en manipulant les valeurs des paramètres. Observez les résultats sur la fenêtre graphique. Tracez des polygones en changeant le nombre de faces, l angle et le rayon. Remarque : Pour des informations complémentaires sur le graphisme en Python (il existe d autres fonctions), vous pouvez consulter le manuel (voir la page https://docs.python.org/3.4/library/turtle.html) 2 Une première courbe fractale : le flocon de von Koch 2.1 Les fractales Une fractale est une sorte de courbe mathématique un peu complexe extrêmement riche en détails, et qui possède une propriété intéressante visuellement : lorsque l on regarde des détails de petite taille, on retrouve des formes correspondant aux détails de plus grande taille (auto-similarité). 2.2 Le flocon de von Koch La première courbe à tracer a été imaginée par le mathématicien suédois Niels Fabian Helge von Koch, afin de montrer que l on pouvait tracer des courbes continues en tout point, mais dérivables en aucun. Le principe est simple : on divise un segment initial en trois morceaux, et on construit un triangle équilatéral sans base au-dessus du morceau central. On réitère le processus n fois, n étant appelé l ordre. Dans la figure suivante on voit les ordres 0, 1 et 2 de cette fractale. Nous allons écrire le programme qui réalise le flocon de von Koch en plusieurs sous-fonctions : une fonction tracer_segment qui prend comme argument les coordonnées d un segment et qui trace ce segment dans la fenêtre ; une fonction d itération (ex. itere_koch) qui décrit la transformation à faire subir à ce segment ; elle prend en argument les coordonnées d un segment et retourne une liste qui contient 4 segments (voir figure précédente) ; une fonction récursive fract qui prend quatre paramètres : un element (pour le moment cet élément sera un segment, mais par la suite il pourra s agir d une surface ou autre), une fonction d affichage tracer_element (il s agit de tracer_segment pour le flocon de von Koch), une transformation itere et un ordre. Si l ordre est nul, il suffit de dessiner l élément en appelant tracer_element(element), sinon il faut utiliser itere(element) qui retourne une liste, puis appeler récursivement la fonction f ract sur chaque élément de cette liste ; une fonction koch, qui prend en paramètre un segment et un ordre et dessine une courbe de von Koch entre les deux extrémités du segment. Exercice 4 Écrivez successivement les fonctions : tracer_segment itere_koch fract koch N oubliez pas de tester chaque fonction avant de passer à la suivante. Essayez donc à présent de tracer la courbe à l ordre 1, 3, 5. Les extrémités ne doivent pas changer, quelque soit l ordre demandé. Seule la quantité de détails doit varier. Cette courbe est souvent appelée flocon, parce que l on a constaté à quel point elle ressemble à un flocon de neige si l on part, non pas d un segment, mais d un triangle, comme le montre la figure 2. 3
Exercice 5 Écrivez une fonction flocon qui dessine un flocon pour un ordre donné en paramètre en appelant trois fois la fonction koch (Cf figure 2). Figure 2 Flocon de von Koch 3 Généralisation 3.1 Le moteur de fractales Il est intéressant de systématiser cette méthode afin de pouvoir l appliquer à d autres courbes fractales (il existe de nombreux types de fractales, mais nous allons nous restreindre aux courbes similaires à celle de von Koch dans le TP). En fait on a déjà fait une grosse partie du travail en écrivant les sous-fonctions pour von Koch ; la fonction fract est très générique, et on peut la réutiliser pour générer d autres types de fractales. Elle constitue un véritable moteur de fractales. Il suffit de changer la fonction itere (et éventuellement la fonction d affichage) afin de changer le motif généré. 3.2 Un arbre Utilisez le moteur de fractales pour construire un arbre. Voici une construction possible à partir d un segment : Exercice 6 Écrivez une fonction itere_arbre, qui à partir d un segment, renvoie les coordonnées des segments de la branches. Vous pourrez écrire la fonction arbre en faisant appelle à la fonction fract. 3.3 Fractale à base de parallélogramme Cette fractale est plus complexe mais donne un résultat très intéressant. Elle consiste à transformer un segment en une ligne brisée de la façon suivante : 4
π p Le segment central (incliné sur la figure) a la même longueur que celui de départ et est incliné d un angle de la forme π p par rapport à l horizontale. En faisant varier p, vous pouvez obtenir des courbes différentes. Exercice 7 Écrivez une fonction itere_parallelogramme, qui à partir d un segment renvoie les coordonnées des trois sous-segment comme illustré sur la figure ci-dessus. Ensuite vous pouvez utiliser la fonction f ract dans une fonction parallelo qui affichera la fractale à base de parallélogramme à partir d un segment et d un ordre donnés. 4 Bonus 4.1 Bonus 1 : Fractale à base de triangles pleins Maintenant nous allons tracer une fractale dont l élément de base est la surface d un triangle. Nous représenterons un triangle par les coordonnées de ses trois sommets. Écrivez une fonction tracer_triangle, qui trace un triangle plein de la couleur de votre choix. Puis écrire une fonction itere_triangle, qui découpe un triangle en trois sous-triangles comme illustré sur la figure 4.1. Vous pourrez utiliser les fonctions annexes suivantes : color(c) : fixe la couleur du crayon et de remplissage. (Les couleurs sont obtenues par la fonction color(r, g, b) mais on dispose des couleurs prédéfinies : "black", "white", "red", "green", "blue", "yellow", "cyan" et "magenta"). color(r, g, b) : construit une couleur à partir de ses trois composantes r, g et b (r : rouge, g : vert, b : blue) données comme entiers entre 0 et 1. begin_f ill() : doit être appelée afin de commencer le remplissage d une forme à tracer. end_f ill() : appelée afin de stopper le remplissage. ordre 1 ordre 6 Enfin écrire une fonction fractale_triangle, qui utilise le moteur de fractales pour obtenir un rendu similaire à la figure (à droite). 4.2 Bonus 2 : Fractale à base de carré plein La même chose que pour les triangles... Vous pouvez écrire une fonction traçant un carré plein : carre_plein(x, y, l, h), qui remplit, avec la couleur courante du crayon, le rectangle dont le coin inférieur gauche se trouve en (x, y), de largeur l et de hauteur h. 4.3 Bonus 3 : Soyez créatifs! Vous pouvez utiliser la même méthodologie que pour les précédentes fonctions pour générer vos propres fractales à partir du motif de votre choix. Libre à vous de combiner formes, couleurs, rotations, etc. 5
5 Solution des exercices Exercice 2 : tracer un carré : 1 from turtle import * 2 from trace_ polygone_ ferme import * 3 4 # trace_ carre ( x) trace le carre de cote x dont le coin 5 # inferieur gauche est au milieu de la fenetre graphique 6 def trace_carre (x): 7 pendown () 8 goto (0,0) 9 goto (0,x) 10 goto (x,x) 11 goto (x,0) 12 13 trace_ carre (100) 14 done () Exercice 3 : Les 2 erreurs dans les fonctions iter et trace_polygone. 1 from math import * 2 3 def iter (i, prec, theta, centre ): 4 if i!= 0 : 5 penup () # Manquait dans la premi è re version 6 goto ( prec ) 7 suivant = rotation ( centre, theta, prec ) 8 pendown () 9 goto ( suivant ) 10 iter ( i - 1, suivant, theta, centre ) 11 12 def trace_ polygone ( centre, rayon, faces, angle ) : 13 theta = 2 * pi / faces 14 x = centre [0] 15 y = centre [1] 16 # Il manquait "+ rayon " dans l original 17 depart = rotation ( centre, angle, [ x + rayon, y]) 18 iter ( faces, depart, theta, centre ) Exercice 4 : Trace un segment 1 def trace_ segment ( segment ): 2 point_ depart = segment [0] 3 point_ arrive = segment [1] 4 penup () 5 goto ( point_depart ) 6 pendown () 7 goto ( point_arrive ) Itere koch 1 def itere_ koch ( segment ): 2 pa = segment [0] 3 pb = segment [1] 4 xa = pa [0] 5 ya = pa [1] 6 xb = pb [0] 7 yb = pb [1] 8 xbb = ( xa + 2 * xb) / 3 9 ybb = ( ya + 2 * yb) / 3 10 xaa = ( xa * 2 + xb) / 3 11 yaa = ( ya * 2 + yb) / 3 12 ab = rotation ([ xaa, yaa ], pi /3, [xbb, ybb ]) 13 return [[[xa,ya], [xaa,yaa]], [[xaa,yaa],ab], [ab,[xbb,ybb]], [[xbb,ybb],[xb,yb]]] 6
Fract 1 def fract ( element, trace_element, itere, ordre ): 2 if ordre == 0: 3 trace_ element ( element ) 4 else : 5 for e in itere ( element ): 6 fract (e, trace_element, itere, ordre - 1) Koch 1 def koch ( segment, ordre ): 2 fract ( segment, trace_segment, itere_koch, ordre ) Exercice 6 : les arbres 1 def itere_ arbre ( segment ): 2 a = segment [0] 3 xa = a [0] 4 ya = a [1] 5 b = segment [1] 6 xb = b [0] 7 yb = b [1] 8 xab = (xa + xb )/2 9 yab = (ya + yb )/2 10 d = rotation ([ xab, yab ], -1* pi /3, [xb,yb ]) 11 xd = d [0] 12 yd = d [1] 13 g = rotation ([ xab, yab ], pi /3, [xb,yb ]) 14 xg = g [0] 15 yg = g [1] 16 return [[[xa,ya], [xab,yab]], [[xab,yab], [xd,yd]], [[xab,yab], [xg,yg]], [[xab,yab], [xb,yb]]] 1 def arbre ( segment, ordre ): 2 fract ( segment, trace_segment, itere_arbre, ordre ) 7