Comment faire pousser une forêt sur Actionscript3 / Flash en quelques * lignes de code

Dans ce commentaire, je me vantais d'avoir écrit un programme qui a créé une forêt «d'apparence décente» dans deux cents lignes de code. Malheureusement, la réalité s'est avérée légèrement plus grande - les sources extraites contiennent environ 2100 lignes de code, dont environ 700 sont des commentaires, des pensées à voix haute, de l'ancien code rejeté et des tentatives de documentation des méthodes. Cependant, la taille de l'exécutable SWF s'est avérée être de 13 112 octets.

Tout a commencé avec le fait que sur le forum Kongregate.com, où je traînais activement à ce moment-là, l'un des participants a suggéré de participer à la génération procédurale de quelque chose, le premier sujet était "Forest" .



Naturellement, chacun avait sa propre idée de ce que devrait être la forêt à cultiver. A cette époque, je lisais des livres sur toutes sortes de magie, par conséquent, je voulais faire pousser une forêt. La forêt est constituée d'arbres - nous écrivons la classe Tree {...}. Un arbre est composé de branches et de feuilles - nous écrivons la classe Branch {...} et nous pensons, avons-nous vraiment besoin de prendre en compte chaque feuille de l'arbre? En conséquence, la «branche» a acquis le paramètre «avec des feuilles» et l'arbre a acquis une paire de textures, une pour les branches et un tronc, une pour les feuilles. La texture «sous l'arbre» ​​était relativement simple à faire - il y a du bruit perlin, vous pouvez l'étirer, l'envelopper, la peindre, la considérer comme prête, mais vous avez dû bricoler avec les feuilles.

Cependant, je n'étais pas satisfait du bruit de la haussière sur la texture d'un arbre, au lieu de cela, j'ai proposé une bumpmapping - c'est-à-dire Il a créé une carte de hauteur, l'a peaufinée sous le demi-cercle de la branche visible de côté, puis a rempli la texture principale de brun et a superposé une carte de hauteur avec l'éclairage ajusté au grincement latéral. Le code résultant est le suivant:

private function generateBranch():void { branchBitmap = new BitmapData(512, 512, true, 0xff8080ff); //branchBitmap.perlinNoise(32, 256, 2, 100 + Math.round(Math.random() * 900), true, true, 7, true); var hm:BitmapData = new BitmapData(512, 512, false, 0); var seed:int = 1000 + Math.random() * 2000; hm.perlinNoise(24,192, 2, seed, true, true, BitmapDataChannel.BLUE, false); // blue only. Is a heightmap var i:int; var j:int; for (i = 0; i < 512; i++) { if (Math.abs(i - 256) > 100) r = 0; else r = 200 * Math.sqrt(1 - (i - 256) * (i - 256) / 10000);// square curve //r = 200 * Math.sin(Math.PI * (i - 128) / 256); // sine curve for (j = 0; j < 512; j++) hm.setPixel(i, j, Math.round(r*(500.0 + (hm.getPixel(i, j)-128))*0.002)); // now, r means position on the "log", and initial perlin noise is log's texture. // perlinNoise median 128, highest offset taking as 100, the result offset needs to be ~0.2 in multiplier } var v:Vector.<int> = new Vector.<int>(); var vv:Vector.<Number> = new Vector.<Number>(3); for (i = 1; i < 511; i++) { v.length = 0; v.push(hm.getPixel(0, i-1), hm.getPixel(1, i-1), hm.getPixel(2, i-1), hm.getPixel(0, i), hm.getPixel(1, i), hm.getPixel(2, i), hm.getPixel(0, i+i), hm.getPixel(1, i+1), hm.getPixel(2, i+1)); for (j = 1; j < 510; j++) { var g:int = -1 * v[0] - 2 * v[1] - 1 * v[2] + 1 * v[8] + 2 * v[7] + 1 * v[6]; // gradient by Y var r:int = -1 * v[0] - 2 * v[3] - 1 * v[6] + 1 * v[2] + 2 * v[5] + 1 * v[8]; // gradient by X //if ((i > 50) && (i < 55) && (j > 50) && (j < 55)) trace(g, r); var b:int = v[5]; r += 128; g += 128; var p:uint = r *0x10000 + g*0x100 + b; branchBitmap.setPixel(j, i, p); v.shift(); v.push(hm.getPixel(j + 2, i + 1)); v[2] = hm.getPixel(j + 2, i - 1); v[5] = hm.getPixel(j + 2, i); } } var bf:BlurFilter = new BlurFilter(2,8); // ___ // bevelFilter is not what I need, it bevels a rectangle and just that [___] // dropShadowFilter requires empty alpha I believe // convolution filter works best on self, while it can do what I need branchBitmap.applyFilter(branchBitmap, branchBitmap.rect, P0, bf); hm.copyPixels(branchBitmap, branchBitmap.rect, P0); //branchBitmap.perlinNoise(32, 256, 0, seed, true, true, 7, true); // naked grayscale // 0 octaves means 50% gray filling branchBitmap.fillRect(branchBitmap.rect, 0xff808080); // it looks like I'll have enough details just by perlin-noising the heightmap var inc:Number = Math.PI / 3; var azi:Number = -Math.PI * 1 / 4; var cx:Number = Math.cos(inc) * Math.cos(azi); var cy:Number = Math.cos(inc) * Math.sin(azi); var cz:Number = Math.sin(inc); azi = 1 - 2 * cz; inc = 1 / cz; // cos(lighting) to be normalized into (0..1) via cz for (i = 0; i < 512; i++) for (j = 0; j < 512; j++) { p = branchBitmap.getPixel(j, i); var h:uint = hm.getPixel(j, i); // give a vector here somewhere vv[0]= (h >> 16)-128; vv[1] = ((h >> 8) & 255)-128; vv[2] = 26; // balance constant, a normal is always pointing upwards, Basis.Normalize(vv); var m:Number = inc*(cx * vv[0] + cy * vv[1] + cz * vv[2]); // cos(lightangle) r = (p >> 16) & 255; g = (p >> 8) & 255; b = p & 255; r = Math.max(0,Math.min(255, r * m)); g = Math.max(0,Math.min(255, g * m)); b = Math.max(0,Math.min(255, b * m)); branchBitmap.setPixel(j, i, 0x10000 * r + 0x100 * g + b); } branchBitmap.applyFilter(branchBitmap, branchBitmap.rect, P0,bf); // should be here, without blurring it's liney hm = new BitmapData(192, 512, false); hm.copyPixels(branchBitmap, new Rectangle(160, 0, 192, 512), P0); branchBitmap = hm; } 

"Basis" est une classe d'aide pour les vecteurs à la Vector3D, mais comme le code a été écrit sous Flash 10.1, il n'y avait pas encore de tels vecteurs, ou j'ai préféré créer mon propre vélo. La texture sous la branche avec des feuilles a été dessinée comme suit: d'abord une feuille a été faite, puis il a été déterminé si les branches avaient une feuille centrale, cela a déterminé la longueur du morceau de la branche à laquelle les feuilles étaient attachées, puis elles ont été attachées (calculées sur la texture) à un angle par rapport à la branche par la largeur calculée de la feuille . La forme de la feuille a été définie comme un cercle déformé avec plusieurs points de référence décalés du cercle d'un rayon d'une demi-feuille, et la longueur de la tige a été définie séparément, tout cela a été dessiné sur la texture de la feuille en noir et blanc et conservé pour l'avenir. (Plus précisément, il y avait deux textures de «branche avec des feuilles», une pour les extrémités, c'est-à-dire des branches pour lesquelles rien ne pousse de la «fin», mais avec des feuilles, une feuille était dessinée dessus à la fin de la branche, la seconde pour «mi "Sans feuille de fin.)

Ensuite, la partie la plus difficile est à quoi ressemblera l'arbre? Ici, j'ai pensé et expérimenté pendant longtemps. J'ai décidé de faire grandir l'arbre - les branches s'étirent en longueur (poussent en fait à partir de la fin), engendrent parfois des branches sur le côté, les branches s'étirent jusqu'au soleil (en haut) et quelques autres conditions. Il s'est avéré un hachage terrible, la meilleure option que nous avons réussi à partager ressemblait à ceci:

image
(Curieusement, diary.ru est un excellent service d'hébergement de photos, jusqu'à présent rien n'a mal tourné!)

J'en suis venu à la conclusion que nous devons en quelque sorte réduire la densité des branches. Initialement, l'idée était de les limiter gravitationnellement - c'est-à-dire des branches trop "lourdes" se détachent et tombent. J'ai commencé à compter le moment de force en flexion, en le comparant à la force de l'arbre (j'ai traîné les valeurs de quelque part, je les ai marquées comme constantes et j'ai commencé à tester) - cela s'est avéré mal, parfois le tronc s'est cassé, même si cela n'aurait pas dû arriver, et l'arbre a été plié en toute sécurité , parfois une grosse branche s'est cassée au début, le résultat a conduit à un tronc déséquilibré et elle s'est cassée à nouveau, cette fois à cause d'une perte d'équilibre vertical, et parfois une branche qui était de structure tout à fait normale, grossissant en épaisseur, initialement pliée sous son poids, puis cassé, ventes si rien dans ce n'est plus grandi. Il a marqué parce que le défi était une date limite.

La deuxième tentative a été de limiter à la fois la croissance de nouvelles branches et la survie des anciennes / précédentes en utilisant l'éclairage. De la troisième tentative d'implémentation (les deux premières sont restées sous la forme de fonctions commentées), cela s'est avéré comme ceci: j'ai construit un réseau de voxel tridimensionnel avec un côté de 0,5 mètre (oui, toutes les valeurs étaient en mètres et en kilogrammes - je voulais vraiment de la physique réelle pour une vraie forêt alors), qui a été remplie d'abord des zéros, puis en faisant le tour de l'arbre, chaque branche a contribué au remplissage du réseau sous la forme de son volume divisé par un ou deux voxels. Le fait est que toutes les branches (dans tous les cas, presque toutes) en tant que pièces distinctes du cadre calculé étaient plus courtes que 0,5 m, ce qui nous a permis d'utiliser une approximation approximative. En plus du remplissage, chaque branche "jette une ombre" sur les voxels sous-jacents sous la forme d'un remplissage supplémentaire des voxels sous et légèrement autour du voxel avec une branche (la forme finale est une pyramide carrée, mais jouer avec un cercle se brisait, et donc elle ne s'allumait pas de toute façon). Ce treillis a été utilisé comme limiteur, si l'une des branches commence à pousser au milieu de l'arbre - il y aura moins de lumière, il sera plus court et peut ne pas grandir du tout ou mourir par manque d'éclairage. Des branches mortes sont alors tombées.

Cette option a permis d'obtenir des arbres relativement transparents et relativement compacts en termes de portée, la première version de travail ressemblait à ceci:



Dans cette version, j'ai toujours débogué le mécanisme de croissance de l'arbre lui-même, et l'arbre pouvait être vu de tous les côtés. L'arbre a été dessiné une branche à la fois, le tableau de branches a d'abord été trié par distance de l'observateur, comme dans le bon vieux cours VMX sur les graphiques en trois dimensions de 1996, j'ai sélectionné des couleurs à des fins cosmétiques de la gamme HSB pour chaque appel "dessine-moi un arbre" de sorte que la forêt n'est pas monotone, le squelette de l'arbre est également tourné de manière aléatoire pour dessiner. Au total, il y avait six à huit modèles d'arbres à dessiner, chacun poussant sous sa propre influence RNG, le paysage de la terre a émis un autre bruit d'aubière, et l'endroit où faire pousser l'arbre a été choisi au hasard en utilisant un ensemble de plages de points autorisés pour la croissance en se déplaçant sur le côté observateur à distance. Si l'arbre planté au point A, et le rayon de l'arbre R sélectionné pour «grandir», alors les valeurs (AR, A + R) sont devenues interdites pour la croissance à la distance actuelle, lors du passage au suivant (-0,05), cet intervalle a diminué de 0,1, et a été supprimée lorsqu'elle a été réduite à zéro.

La dernière nuance (et en fait la première et immédiatement prise en compte) de tout l'algorithme est qu'il est TRÈS LONG. Pour faire le tour de l'arbre "adulte", il faut quelques secondes pour dessiner, quelques autres pour dessiner les textures d'un arbre, cela prend de une demi-seconde à deux, et Adobe Flash n'est pas conçu pour des intervalles de calcul aussi longs sans mettre à jour l'écran (plus précisément, sans retourner le contrôle au moteur) . Par conséquent, nous avions besoin d'un algorithme qui puisse enregistrer l'état entre les appels, continuer à travailler à partir de l'endroit où il a été interrompu et contrôler son temps d'exécution, et en même temps ne pas se paniquer et ne pas paniquer le moteur flash. La sauvegarde de l'état a été implémentée comme une paire de propriétés de la classe principale, divisée en étapes - en sélectionnant les fonctions «faire pousser un arbre une fois», «dessiner un arbre fini» et «dessiner un morceau de terre» et mesurer le temps passé, respectivement, dès la prochaine «fois» il a fallu plus de quelques secondes pour un arbre, l'arbre a été considéré comme "prêt" et mis de côté. Il s'est avéré trois grandes phases: créer des textures, «faire pousser» des arbres, placer des arbres finis sur l'écran.

Le résultat ressemble à ceci:



Vous pouvez jouer ici . Optimisé (plus précisément, écrit) pour Flash 10.1, prendre en compte un tas de mises à jour flash en termes de sécurité peut être terriblement lent - dans ce cas, je vous conseille de télécharger la version de débogage d'Adobe Flash Player 11.5 et de l'ouvrir hors ligne. L'ensemble du dessin prend 5-6 minutes, après les deux premiers à l'écran, un mouvement commence à se produire, ce qui peut être intéressant à observer. Après le dessin, vous pouvez appuyer sur Ctrl + clic pour enregistrer le résultat sous la forme d'un fichier PNG quadruple par rapport à la taille de la fenêtre.

Source: https://habr.com/ru/post/fr429256/


All Articles