Computação Gráfica

Prof. Márcio Sarroglia Pinho

Exercício - Geração de Terrenos

O objetivo desta aula é exercitar a criação de terrenos.

O código disponível neste link faz a geração de uma malha de triângulos que define um terreno.
Para utilizar o exemplo, abra o projeto OpenGL.cbp, no Windows. Caso haja algum problema com o projeto, na máquina que você está utilizando, baixe o arquivo da aula sobre Texturas em OpenGL e substitua o programa principal pelo fonte Terreno2.cpp.

A classe responsável pela criação é a Terreno, que possui a seguinte definição:


class Terreno{
    int nVerticeX, nVerticeZ;
    float Largura, Profundidade, AlturaMaxima;
   
    TPoint PontosDoTerreno[MAX][MAX];
    TTriangle TriangulosDoTerreno[MAX+1][(MAX-1)*2];

public:
    Terreno(int nVerticeX, int nVerticeZ, float Largura, float Profundidade, float AlturaMaxima);
    TTriangle getFaceDoTerreno(int x, int z);
    void GeraVerticesDoTerreno(int nVerticeX, int nVerticeZ);
    void AlteraVerticesDoTerreno();
    void AlteraVerticesDoTerreno(int xCentro, int zCentro, int altura, int raio);
    void GeraFacesDoTerreno(int nVerticeX, int nVerticeZ);
    void DesenhaTerreno();
};


A ideia central é gerar um conjunto de triângulos a partir de uma malha vértices dispostos no plano XZ.  Inicialmente, é preciso gerar os vértices e na sequência, gerar os triângulos que compõem o terreno.
Ao executar o projeto, pressione a tecla espaço para exibir
, ou não, as linhas que formam o terreno.


Parâmetros para a Geração do Terreno

Para gerar um terreno, é preciso definir os seguites parâmetros, que são passados na construtora da classe:
As imagens a seguir, apresentam um terreno de largura=20, profundidade=20. À esquerda, pode-se ver um terreno de 5 vértices em X e 5 vértices em Z. Já à direita, tem-se um terreno de 20 vértices em X e 20 vértices em Z.


Terreno de 5 x 5 vértices
Terreno de 20 x 20 vértices
Figura - Exemplos de Terrenos

No código fornecido nesta aula, a criação do terreno é feita na função init(), a partir do seguinte trecho:

  // *****************
    // Inicializa os parâmetros do terreno
    int DimX = 20; // numero de vértices ao longo do eixo X
    int DimZ = 20; // numero de vértices ao longo do eixo Z
    float Larg = 20;
    float Prof = 20;
    float AltMax = 3;
    //Terreno(int nVerticeX, int nVerticeZ, float Largura, float Profundidade, float AlturaMaxima)
    T1 = new Terreno (DimX, DimZ, Larg, Prof, AltMax);


Geração dos Vértices do Terreno

Observe o método GeraVerticesDoTerreno da classe Terreno. Este código gera uma malha de nVerticeX * nVerticeZ pontos e armazena estes pontos na estrutura

TPoint PontosDoTerreno[MAX][MAX];

O espaçamento entre os pontos, no eixo X é dado pelas dividindo-se a largura do terreno pelo número de vértices ao longo do eixo X. De forma análoga, o espaçamento entre os pontos, no eixo Z é dado pelas dividindo-se a profundidade do terreno pelo número de vértices ao longo do eixo Z.
O código a seguir demonstra como isto é feito.

void GeraVerticesDoTerreno(int nVerticeX, int nVerticeZ)
    {
        TPoint meio(nVerticeX/2.0, 0, nVerticeZ/2.0);
       
        float MaxDist = sqrt(meio.X*meio.X + meio.Z*meio.Z);
        float deltaX = Largura/nVerticeX;
        float deltaZ = Profundidade/nVerticeZ;
       
        for (int x=0;x<nVerticeX;x++)
        {
            for (int z=0;z<nVerticeZ;z++)
            {
                float dx, dz;
                dx = meio.X-x;
                dz = meio.Z-z;
                float distancia = sqrt(dx*dx + dz*dz); // calcula a distancia do vértice até o centro do terreno
                // Gera uma altura entre 0 e 1. Quanto mais distante do centro, maior o valor
                float y = (MaxDist - distancia)/MaxDist;
                // Calcula a altura como uma função da altura máxima
                y *= AlturaMaxima;

                // Calcula o X e o Z em função da distância entre cada vértice
                PontosDoTerreno[x][z] = TPoint(x*deltaX, y, z*deltaZ);
            }
        }
    }

Na estrutura PontosDoTerreno,
os vértices são dispostos conforme o desenho a seguir.

Figura - Disposição do Vértices na estrutura
PontosDoTerreno

A coordenada Z do vértice é calculada a partir da distância do vértice até o centro da malha, sendo que, quanto maior a distância, menor a altura. O tercho de código em azul demonstra o cálculo.

A imagem a seguir mostra algumas coordenadas de um terreno.

Figura - Coordenadas dos Extremos de um Terreno




Alteração dos Vértices do Terreno

Após a geração dos vértices, ao final do método GeraVerticesDoTerreno é chamado o método AlteraVerticesDoTerreno, que permite alterar as coordenadas dos vértices da forma que se quiser. O código a seguir, por exemplo, cria regiões com altura zero na bordas do terreno. À direita do código é mostrada a imagem gerada pelo código. No código fornecido, a chamada do método AlteraVerticesDoTerreno está comentada.
void AlteraVerticesDoTerreno()
{
    for (int x=0;x<nVerticeX;x++)
    {
        for (int z=0;z<nVerticeZ;z++)
        {
            if ((x<5) || (z < 5))
                PontosDoTerreno[x][z].Y = 0;
            if (x>(nVerticeX-5) || z >(nVerticeZ-5))
                PontosDoTerreno[x][z].Y = 0;
        }
    }
}

Figura - Exemplo de alterações no terreno



Geração dos Triângulos do Terreno

Observe o método GeraTriangulosDoTerreno da classe Terreno. Este código gera uma malha de triângulos e armazena estes na estrutura

TTriangle FacesDoTerreno[MAX+1][(MAX-1)*2];

Dada uma posição X,Z da malha de vértices, são gerados dois triângulos conforme a imagem a seguir.
Estes triângulos são armazenados na estrutura através do seguinte código:


      // Gera os quatro vertices que compoem a célula (X,Z)
      A = PontosDoTerreno[x][z];
      B = PontosDoTerreno[x][z+1];
      C = PontosDoTerreno[x+1][z+1];
      D = PontosDoTerreno[x+1][z];
      // ***************************
      // Monta o primeiro triângulo
      FacesDoTerreno[x][z*2].P1 = A;
      FacesDoTerreno[x][z*2].P2 = B;
      FacesDoTerreno[x][z*2].P3 = C;

     


Triângulo 1: A,B,C
Triângulo 2: A,C,D


Figura - Geração de um Par de Triângulos a partir de um vértice

A partir destes triângulos são geradas as respectivas normais através do código

      // Calcula a normal do primeiro triangulo
     TPoint VAB, VBC, V1;
     VAB.Set(B.X-A.X, B.Y-A.Y, B.Z-A.Z );
     VBC.Set(C.X-B.X, C.Y-B.Y, C.Z-B.Z );
     ProdVetorial(VAB, VBC, V1);
     VetUnitario(V1);
     // Armazena as normais de cada vertice. As 3 normais nos vértices serao iguais
     FacesDoTerreno[x][z*2].N1 = V1;
     FacesDoTerreno[x][z*2].N2 = V1;
     FacesDoTerreno[x][z*2].N3 = V1;


Note que a estrutura FacesDoTerreno tem o dobro de colunas da estrutura de vértices, pois a cada colun são gerados dois triângulos.


Exercícios

1) Altere o código do método GeraVerticesDoTerreno de forma que a altura de cada vértice seja determinada por uma imagem em tons de cinza. A estas imagens, se dá o nome de Mapas de Altura, ou Height Maps.

A figura a seguir mostra duas imagens e os respectivos terrenos. Salve as imagens da esquerda para realizar seus testes.





Figura - Geração de um Terrenos a partir de Imagens


Primeiramente, configure o terreno de forma que este tenha o mesmo número de linhas(
nVerticeZ) e colunas(nVerticeX) que a largura e a altura da imagem.
Para carregar a imagem, altere a função init() e configure o nome da imagem através da chamada

ImagemDoTerreno.Load("Mapa-B-100x100.jpg");


Para realizar o mapeamento entre o tom de cinza e a altura, modifique o método
GeraVerticesDoTerreno. Lembre-se que o tom de cinza varia entre 0 e 255, e pode ser obtido pela chamada

ImagemDoTerreno.GetPointIntensity(x,z)


2) Crie uma nova versão do método AlteraVerticesDoTerreno de forma que este passe a ter a seguinte assinatura:

void AlteraVerticesDoTerreno(int xCentro, int zCentro, int altura, int raio).

A partir disto, crie um código que modifique a altura dos vértices ao redor do vértice
(xCentro,zCentro). A ideia é que o método altere a altura do vértice em função de sua distância até o ponto (xCentro,zCentro). Isto é semelhante ao que já está no método GeraVerticesDoTerreno, mas neste caso, a criação do pico fica restrita à circunferência ao redor de (xCentro, zCentro).
O alcance desta modificação deve ser definido pelo parâmetro raio.
Procure garantir que a combinação Centro + Raio não exceda os limites dos vértices. O código a seguir faz este trabalho.

void AlteraVerticesDoTerreno(int xCentro, int zCentro, int altura, int raio)
{
   if ((xCentro+raio >= nVerticeX) || (zCentro+raio >= nVerticeZ))
   {
      cout << "Erro: Circulo passa dos limites da malha." << endl;
      exit(1);
   }
   .....
}

A seguir,
passe novamente pelos vértices recém alterados aplicando pequenas variações randômicas nos vértices. Isto decve dar uma aparência de  uma montanha ao redor do ponto (xCentro,zCentro).


3) Exiba um cubo sobre o terreno de forma a garantir que a altura do cubo sobre o terreno seja a mesma do terreno, na posição onde o cubo for colocado.

Bom trabalho !