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.
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.
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é.
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.