Nesse
comentário, eu me gabava de ter escrito um programa que criava uma floresta de aparência decente em duzentas linhas de código. Infelizmente, a realidade acabou sendo um pouco maior - as fontes escavadas contêm cerca de 2100 linhas de código, das quais cerca de 700 são comentários, pensamentos em voz alta, código descartado antigo e tentativas de documentar métodos. O tamanho do executável SWF, no entanto, acabou sendo 13112 bytes.
Tudo começou com o fato de que no fórum Kongregate.com, onde eu estava participando ativamente da época, um dos participantes sugeriu competir na geração processual de algo, o primeiro tópico foi
"Floresta" .

Naturalmente, todos tinham sua própria idéia do que deveria ser a floresta que eles cultivariam. Naquela época, eu li livros sobre todo tipo de magia, como resultado, eu queria cultivar uma floresta. A floresta consiste em árvores - escrevemos a classe Árvore {...}. Uma árvore consiste em galhos e folhas - escrevemos na classe Branch {...} e pensamos: realmente precisamos levar em consideração cada folha da árvore? Como resultado, o “ramo” adquiriu o parâmetro “com folhas” e a árvore adquiriu um par de texturas, uma para galhos e um tronco, outra para folhas. A textura “sob a árvore” era relativamente simples de fazer - há um ruído constante, você pode esticar, embrulhar, pintar, considerar pronto, mas você teve que mexer nas folhas.
No entanto, eu não estava satisfeito apenas com o ruído da textura de uma árvore, em vez disso, criei o bumpmapping - ou seja, Ele criou um mapa de altura, ajustou-o sob o semicírculo do galho visível de lado, depois encheu a textura principal de marrom e sobrepôs um mapa de altura com a iluminação ajustada ao guincho lateral. O código resultante é o seguinte:
private function generateBranch():void { branchBitmap = new BitmapData(512, 512, true, 0xff8080ff);
"Base" é uma classe auxiliar para vetores no Vector3D, mas como o código foi escrito no Flash 10.1, ainda não existia esse tipo de vetor, ou eu preferia fazer minha própria bicicleta. A textura sob o galho com folhas foi desenhada da seguinte forma: primeiro uma folha foi feita, depois foi determinado se os galhos tinham uma folha central, determinando o comprimento da parte do galho ao qual as folhas foram anexadas e, em seguida, elas foram anexadas (calculadas na textura) em um ângulo ao galho pela largura calculada da folha . A forma da folha foi definida como um círculo distorcido com vários pontos de referência deslocados do círculo por um raio de meia folha e o comprimento das estacas foi definido separadamente, tudo isso foi desenhado na textura da folha em preto e branco e salvo para o futuro. (Mais precisamente, havia duas texturas de “galho com folhas”, uma para as extremidades, ou seja, galhos para os quais nada cresce a partir do “final”, mas com folhas, uma folha foi desenhada nela no final do galho, a segunda para “meio "Sem folha final.)
Então a parte mais difícil é a aparência da árvore? Aqui eu pensei e experimentei por um longo tempo. Decidi fazer a árvore crescer de verdade - os galhos se estendem em comprimento (na verdade, crescem do final), às vezes geram galhos para o lado, os galhos se estendem para o sol (para cima) e mais algumas condições. O resultado foi um hash terrível, a melhor opção que conseguimos compartilhar era a seguinte:
(Curiosamente, o diary.ru é um excelente serviço de hospedagem de fotos, até agora nada deu errado!)Cheguei à conclusão de que precisamos de alguma forma reduzir a densidade dos galhos. Inicialmente, a ideia era limitá-los gravitacionalmente - ou seja, galhos "pesados" demais simplesmente quebram e caem. Comecei a contar o momento da força na flexão, comparando-o com a força da árvore (arrastei os valores de algum lugar, marquei-o como constantes e comecei a testar) - resultou mal, às vezes o tronco quebrava, mesmo que não devesse ter acontecido, e a árvore estava dobrada com segurança , às vezes um galho grande quebrou no início, o resultado levou a um tronco desequilibrado e quebrou novamente, desta vez por causa de uma perda de equilíbrio vertical, e às vezes um galho com estrutura bastante normal, crescendo em espessura, inicialmente dobrado sob seu peso e quebrou, vendas se nada nele não é mais cresceu. Ele marcou porque o desafio era um prazo.
A segunda tentativa foi limitar o crescimento de novos galhos e a sobrevivência de galhos antigos / anteriores usando a iluminação. Da terceira tentativa de implementação (as duas primeiras permaneceram na forma de funções comentadas), ficou assim: eu construí uma treliça de voxel tridimensional com um lado de 0,5 metros (sim, todos os valores que existem em metros e quilogramas - eu realmente queria física real para uma floresta real), que estava preenchida primeiro zeros, depois, ao contornar a árvore, cada ramo contribuía para o preenchimento da treliça na forma de seu volume dividido por um ou dois voxels. O fato é que todos os ramos (em todo caso, quase todos) como partes separadas do quadro calculado eram menores que 0,5 m, o que nos permitiu usar uma aproximação aproximada. Além do preenchimento, cada ramo "lança uma sombra" nos voxels subjacentes na forma de preenchimento adicional dos voxels abaixo e ligeiramente ao redor do voxel com um ramo (a forma final é uma pirâmide quadrada, mas brincar com um círculo estava quebrando e, portanto, não estava iluminando). Essa treliça foi usada como limitador, se um dos galhos começar a crescer no meio da árvore - haverá menos luz lá, será mais curto e poderá não crescer mais ou morrer por falta de iluminação. Galhos mortos caíram.
Essa opção tornou possível obter árvores que eram relativamente transparentes quando vistas e relativamente compactas em termos de escopo, a primeira versão de trabalho era assim:

Nesta versão, eu ainda depurei o mecanismo de crescimento da árvore, e a árvore podia ser vista de todos os lados. A árvore foi desenhada um ramo de cada vez, o conjunto de ramos foi classificado pela distância do observador, como no bom e velho curso VMX sobre gráficos tridimensionais de 1996, selecionei cores para fins cosméticos da linha HSB para cada chamada "desenhe-me uma árvore" para que a floresta não seja monótona, o esqueleto da árvore também é girado aleatoriamente para desenhar. No total, havia de seis a oito modelos de árvores para desenho, cada um crescendo sob sua própria influência RNG, a paisagem da terra provocou outro ruído, e o local onde cultivar a árvore foi selecionado aleatoriamente usando um conjunto de faixas de pontos permitidos para o crescimento ao se mover para o lado observador a distância. Se a árvore plantada no ponto A e o raio da árvore R selecionado para "crescer", os valores (AR, A + R) ficam proibidos de crescer na distância atual, ao passar para a próxima (-0,05), esse intervalo diminui em 0,1 e foi removido quando foi reduzido a zero.
A última (e de fato a primeira e imediatamente levada em consideração) nuance de todo esse algoritmo é que é MUITO LONGO. Para percorrer a árvore "adulta", são necessários alguns segundos para desenhar, mais alguns para desenhar as texturas de uma árvore, de meio segundo a dois, e o Adobe Flash não foi projetado para intervalos de computação tão longos sem atualizar a tela (mais precisamente, sem retornar o controle ao mecanismo) . Portanto, precisávamos de um algoritmo que soubesse manter o estado entre as chamadas, continuar trabalhando do local em que foi interrompido e controlar seu tempo de execução e, ao mesmo tempo, não entre em pânico e não entre em pânico no mecanismo flash. Salvar o estado foi implementado como um par de propriedades da classe Principal, dividindo-se em estágios - selecionando as funções "cultivar uma árvore uma vez", "desenhar uma árvore concluída" e "desenhar um pedaço de terra" e medir o tempo gasto, respectivamente, assim que a próxima "uma vez" levou mais de alguns segundos para uma árvore, a árvore foi considerada "pronta" e deixada de lado. Foram três grandes fases: a criação de texturas, o "cultivo" de árvores, a colocação de árvores prontas na tela.
O resultado fica assim:

Você pode jogar
aqui . Otimizado (mais precisamente, escrito) para o Flash 10.1, levando em consideração várias atualizações do flash em termos de segurança pode ser muito lento - nesse caso, aconselho que você baixe a versão de depuração do Adobe Flash Player 11.5 e abra-a offline. O desenho inteiro leva de 5 a 6 minutos, após os dois primeiros na tela algum movimento começa a ocorrer, o que pode ser interessante de observar. Após o desenho, você pode pressionar Ctrl + clique para salvar o resultado como um arquivo PNG quádruplo em comparação com o tamanho da janela.