🌸 Arbre fractal

Un arbre dessiné par récursion: chaque branche engendre deux (parfois trois) branches plus courtes, plus fines et anglées différemment, jusqu'à ce que de petites grappes de fleurs roses apparaissent aux extrémités.

1.00×

Ce que fait cette page

Cette page dessine un arbre par récursion. Le tronc part du bas du cadre et monte à la verticale; à chaque niveau, il se sépare en deux branches plus courtes, plus fines et inclinées de part et d'autre de la direction précédente. Quand une branche devient trop courte, la récursion s'arrête et je dessine à sa place une petite grappe de cercles roses qui tient lieu de floraison.

Le curseur « Multiplicateur d'angle » change l'écartement des branches en temps réel, de 0,30 à 1,80. À gauche, l'arbre se referme sur lui-même comme un peuplier; à droite, il s'étale jusqu'à retomber vers le sol. Le bouton « Nouvel arbre » tire une nouvelle silhouette au hasard.

Comment c'est construit

Tout tient dans une seule fonction, drawBranch(), qui reçoit un point de départ, une longueur, un angle, une épaisseur de trait et une profondeur. Elle trace le segment, calcule son extrémité avec Math.cos() et Math.sin(), puis se rappelle deux fois depuis cette extrémité: une fois à l'angle courant moins l'écartement, une fois à l'angle plus l'écartement. L'écartement de base est de 24 degrés, converti en radians et multiplié par la valeur du curseur. La longueur suivante vaut entre 0,68 et 0,80 fois la longueur courante, et l'épaisseur 0,70 fois. La profondeur maximale, MAX_DEPTH, est de 10.

Le cas de base est double: profondeur épuisée, ou longueur sous 6 pixels. Les deux mènent à drawLeafCluster(), qui empile de quatre à sept disques dessinés avec arc(), décalés au hasard de quelques pixels et remplis d'un rose semi-transparent.

Deux détails séparent le schéma de quelque chose d'organique. D'abord un bruit léger: à chaque branche j'ajoute un écart aléatoire d'au plus 0,09 radian, et environ une fois sur cinq, une troisième branche part vers l'avant quand la profondeur dépasse 2. Ensuite la couleur: branchColor() interpole du brun foncé du tronc vers un brun-vert plus pâle à mesure que la profondeur restante diminue.

Le hasard vient d'un générateur pseudoaléatoire déterministe, mulberry32(), réamorcé avec la même graine à chaque redessin. C'est ce qui permet de bouger le curseur sans que l'arbre se réorganise: seul l'angle change, la structure reste identique. Le bouton change la graine, donc l'arbre. Le canvas est enfin redimensionné selon devicePixelRatio plafonné à 2, avec setTransform(), pour que les traits fins restent nets sur un écran haute densité.

Ce que j'en ai retenu

La récursion se comprend beaucoup mieux quand le résultat est visible. Le cas de base cesse d'être une formalité: mal placé, la pile déborde et la page fige. Ici il est double, et les deux conditions servent.

J'ai surtout retenu la valeur d'un générateur aléatoire réamorçable. Tant que je m'appuyais sur Math.random(), chaque déplacement du curseur produisait un arbre différent et il devenait impossible de juger l'effet de l'angle. En rendant le hasard reproductible, j'ai séparé ce que je veux contrôler de ce que je laisse au hasard. C'est le même réflexe que sur un banc d'essai: un test qui n'est pas reproductible ne mesure rien.

Reste le coût. À profondeur 10, avec deux branches par niveau et une troisième occasionnelle, un arbre représente quelques milliers de segments. C'est encore assez léger pour tout redessiner à chaque événement du curseur. Un niveau de plus doublerait la facture: un bon rappel de ce que la croissance exponentielle veut dire concrètement.