sexta-feira, 29 de julho de 2011

XNA 4.0 - Parte 6 - Terrenos

Vamos agora criar terrenos de verdade, terrenos grandes, com altura. Nesse primeiro momento apenas o básico. No final terá o código completo.

Abra o arquivo "Terrain.cs" e apague todos os métodos, deixe apenas a declaração da classe e os usings:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Tutorial1.GameBase.Cameras;

namespace Tutorial1.GameBase.Shapes
{
public class Terrain : DrawableGameComponent
{
}
}

Vamos definir nossas variáveis, a primera uma array do tipo byte para guardar a altura de cada vértice de nosso mapa, seguido da declaração de nosso vertex e indice buffer.

//Variável que guardará a altura do mapa
byte[] heightMap;

//Variáveis de renderização
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;

Agora variáveis do mundo, são elas

//Guarda o número de Vértices e de triângulos
int numVertices;
int numTriangles;

//Quantidade de vértices nos eixos X e Z
int vertexCountX;
int vertexCountZ;

//Escala do nosso mapa
float blockScale;
float heightScale;

E pra terminar Variáveis de efeito e de serviços

//Efeito que será usado na cena
BasicEffect effect;

//Serviços necessários
bool isInitialized;
CameraManager cameraManager;

Terminado as declarações vamos fazer o método construtor:

public Terrain(Game game)
: base(game)
{
isInitialized = false;
}

Agora vamos fazer o método Initialize() dentro dele terá uma coisa nova, o FillMode e o RenderState, o primeiro,o FillMode, diz como a cena será desenhada, em Modo Solid ou WireFrame, sólido ou em triângulo, vamos definir como triangulos, você pode altera-lo depois para FillMode.Solid;

//Inicializa todos os nossos serviços e depois de iniciado defini nossa flag como true, permitindo que outros
//métodos possam se executar sem perda de dados
public override void Initialize()
{
cameraManager = Game.Services.GetService(typeof(CameraManager)) as CameraManager;

//Define o nosso RenderState
//Rasterize.FillMode =
//Pode variar entre FillMode.Solid - Para mostrar os objetos da cena de momo completo
//FillMode.WireFrame - Para mostrar os triângulod que formam os objetos na cena
RasterizerState rasterizeState = new RasterizerState();
rasterizeState.FillMode = FillMode.WireFrame;
Game.GraphicsDevice.RasterizerState = rasterizeState;

isInitialized = true;

base.Initialize();
}

Vamos partir direto para o método Load, ele vai receber o tamanho do mapa, X e Z e tambem as escala, escala de tamanho e de altura:

public void Load(int vertexCountX, int vertexCountZ, float blockScale, float heightScale)
{
//Cuida para que tudo seja iniciado
if (!isInitialized)
Initialize();

Random random = new Random();
//Passamos alguns parametros para definir o tamanho do Mapa em X e Z e tambem a escala de
//Tamanho e de altura
this.vertexCountX = vertexCountX;
this.vertexCountZ = vertexCountZ;
this.blockScale = blockScale;
this.heightScale = heightScale;

//Definimos o tamanho total do mapa, usando parametros anteriormente declarados
int heightMapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightMapSize];

//Geramos valores aleatórios para a alutura de nooso mapa
for (int i = 0; i < heightMapSize; i++) heightMap[i] = (byte)random.Next(0, 30); GenerateTerrainMesh(); effect = new BasicEffect(Game.GraphicsDevice); effect.VertexColorEnabled = true; }




O código acima chama o método GenerateTerrainmesh(), vamos criá-lo agora, nós ja usamos ele quando criamos nosso Eixos3D lá atrás, então não há muita novidade:

private void GenerateTerrainMesh()
{
//Vamos definir o número de vértices e triângulo para renderizar todos eles
numVertices = vertexCountX * vertexCountZ;
numTriangles = (vertexCountX - 1) * (vertexCountZ - 1) * 2;

//Criando nosso índices
int[] indices = GenerateTerrainIndices();

VertexPositionColor[] vertices = GenerateTerrainVertices(indices);

//Vamos criar a declaração dos vértices
VertexDeclaration decl = new VertexDeclaration(VertexPositionColor.VertexDeclaration.GetVertexElements());

// Cria o buffer de vertices
vertexBuffer = new VertexBuffer(GraphicsDevice, decl, numVertices, BufferUsage.WriteOnly);

//Adiciona nossos vértices ao Buffer
vertexBuffer.SetData(vertices);

//Cria o Buffer de índices
indexBuffer = new IndexBuffer(Game.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);

//Adiciona nossos índices ao Buffer
indexBuffer.SetData(indices);
}

o código acima chama mais 2 outros métodos, CreateTerrainindice e CreateTerrainIndices. O primeiro CreateTerrainindice() vai gerar nossos índices, o que ele faz é pegar os indices de 2 quadrados por vez, pegando 2 indices de um segmento e depois outro do próximo segmento, e a segunda parte pega para formar um único quadrado.

private int[] GenerateTerrainIndices()
{
//Cria todos os índices, como cada triângulo possui 3 vértices
int numIndices = numTriangles * 3;
int[] indices = new int[numIndices];

int indicesCount = 0;

//Cria dois indices no mesmo segmento de indices e outro indice no próximo segmento
//Faz isso 2 vezes, com dois triângulos formando um quadrado
for (int i = 0; i < (vertexCountZ - 1); i++) { for (int j = 0; j < (vertexCountX - 1); j++) { int index = j + i * vertexCountZ; indices[indicesCount++] = index; indices[indicesCount++] = index + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX; indices[indicesCount++] = index; } } return indices; }




E agora o método CreateTerrainVertices() - Que vai criar toda a malha, usando os índices já criados e pegando a altura de cada vértice.

private VertexPositionColor[] GenerateTerrainVertices(int[] terrainIndices)
{
float halfTerrainWidth = (vertexCountX - 1) * blockScale * 0.5f;
float halfTerrainDepth = (vertexCountZ - 1) * blockScale * 0.5f;

int vertexCount = 0;
VertexPositionColor[] vertices =
new VertexPositionColor[vertexCountX * vertexCountZ];

// Define a posicao dos vertices e a coordenada de textura
for (float i = -halfTerrainDepth; i <= halfTerrainDepth; i += blockScale) { for (float j = -halfTerrainWidth; j <= halfTerrainWidth; j += blockScale) { vertices[vertexCount].Position = new Vector3(j, heightMap[vertexCount] * heightScale, i); vertices[vertexCount].Color = Color.Yellow; if((int)heightMap[vertexCount] < 10) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 20)
vertices[vertexCount].Color = Color.Red;

vertexCount++;
}

}

return vertices;
}

E pra terminar os já conhecidos métodos Update() e Draw().

public override void Update(GameTime gameTime)
{
effect.View = cameraManager.ActiveCamera.View;
effect.Projection = cameraManager.ActiveCamera.Projection;
base.Update(gameTime);
}

public override void Draw(GameTime gameTime)
{

//Adicionamos o nossos indices e vertices ao nosso dispositivso para desenhar
Game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
Game.GraphicsDevice.Indices = indexBuffer;

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
//Adiciona e aplica o efeito
pass.Apply();
//Desenha a lista de linhas
Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numVertices, 0, numTriangles);
}

base.Draw(gameTime);
}

Como estamos usando classes e como eu já disse que classes cuidam de tudo que se refere a ela nós só precisaremos umar uma única coisa no arquivo "Game1.cs", no método load complemente o terrain.Load e vai ficar assim:

terrain.Load(256, 256, 1.0f, 0.1f);

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Tutorial1.GameBase.Cameras;

namespace Tutorial1.GameBase.Shapes
{
public class Terrain : DrawableGameComponent
{
#region Variáveis
//Variável que guardará a altura do mapa
byte[] heightMap;

//Variáveis de renderização
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;

//Guarda o número de Vértices e de triângulos
int numVertices;
int numTriangles;

//Quantidade de vértices nos eixos X e Z
int vertexCountX;
int vertexCountZ;

//Escala do nosso mapa
float blockScale;
float heightScale;

//Efeito que será usado na cena
BasicEffect effect;

//Serviços necessários
bool isInitialized;
CameraManager cameraManager;

#endregion

#region Construtor()
public Terrain(Game game)
: base(game)
{
isInitialized = false;
}
#endregion

#region initialize()
//Inicializa todos os nossos serviços e depois de iniciado defini nossa flag como true, permitindo que outros
//métodos possam se executar sem perda de dados
public override void Initialize()
{
cameraManager = Game.Services.GetService(typeof(CameraManager)) as CameraManager;

//Define o nosso RenderState
//Rasterize.FillMode =
//Pode variar entre FillMode.Solid - Para mostrar os objetos da cena de momo completo
//FillMode.WireFrame - Para mostrar os triângulod que formam os objetos na cena
RasterizerState rasterizeState = new RasterizerState();
rasterizeState.FillMode = FillMode.WireFrame;
Game.GraphicsDevice.RasterizerState = rasterizeState;

isInitialized = true;

base.Initialize();
}
#endregion

#region Load()
public void Load(int vertexCountX, int vertexCountZ, float blockScale, float heightScale)
{
//Cuida para que tudo seja iniciado
if (!isInitialized)
Initialize();

Random random = new Random();
//Passamos alguns parametros para definir o tamanho do Mapa em X e Z e tambem a escala de
//Tamanho e de altura
this.vertexCountX = vertexCountX;
this.vertexCountZ = vertexCountZ;
this.blockScale = blockScale;
this.heightScale = heightScale;

//Definimos o tamanho total do mapa, usando parametros anteriormente declarados
int heightMapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightMapSize];

//Geramos valores aleatórios para a alutura de nooso mapa
for (int i = 0; i < heightMapSize; i++) heightMap[i] = (byte)random.Next(0, 30); GenerateTerrainMesh(); effect = new BasicEffect(Game.GraphicsDevice); effect.VertexColorEnabled = true; } #endregion #region GenerateTerrainMesh() private void GenerateTerrainMesh() { //Vamos definir o número de vértices e triângulo para renderizar todos eles numVertices = vertexCountX * vertexCountZ; numTriangles = (vertexCountX - 1) * (vertexCountZ - 1) * 2; //Criando nosso índices int[] indices = GenerateTerrainIndices(); VertexPositionColor[] vertices = GenerateTerrainVertices(indices); //Vamos criar a declaração dos vértices VertexDeclaration decl = new VertexDeclaration(VertexPositionColor.VertexDeclaration.GetVertexElements()); // Cria o buffer de vertices vertexBuffer = new VertexBuffer(GraphicsDevice, decl, numVertices, BufferUsage.WriteOnly); //Adiciona nossos vértices ao Buffer vertexBuffer.SetData(vertices);

//Cria o Buffer de índices
indexBuffer = new IndexBuffer(Game.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);

//Adiciona nossos índices ao Buffer
indexBuffer.SetData(indices);
}
#endregion

#region GenerateTerrainIndices()
private int[] GenerateTerrainIndices()
{
//Cria todos os índices, como cada triângulo possui 3 vértices
int numIndices = numTriangles * 3;
int[] indices = new int[numIndices];

int indicesCount = 0;

//Cria dois indices no mesmo segmento de indices e outro indice no próximo segmento
//Faz isso 2 vezes, com dois triângulos formando um quadrado
for (int i = 0; i < (vertexCountZ - 1); i++) { for (int j = 0; j < (vertexCountX - 1); j++) { int index = j + i * vertexCountZ; indices[indicesCount++] = index; indices[indicesCount++] = index + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX; indices[indicesCount++] = index; } } return indices; } #endregion #region GenerateTerrainVertices() private VertexPositionColor[] GenerateTerrainVertices(int[] terrainIndices) { float halfTerrainWidth = (vertexCountX - 1) * blockScale * 0.5f; float halfTerrainDepth = (vertexCountZ - 1) * blockScale * 0.5f; int vertexCount = 0; VertexPositionColor[] vertices = new VertexPositionColor[vertexCountX * vertexCountZ]; // Define a posicao dos vertices e a coordenada de textura for (float i = -halfTerrainDepth; i <= halfTerrainDepth; i += blockScale) { for (float j = -halfTerrainWidth; j <= halfTerrainWidth; j += blockScale) { vertices[vertexCount].Position = new Vector3(j, heightMap[vertexCount] * heightScale, i); vertices[vertexCount].Color = Color.Yellow; if((int)heightMap[vertexCount] < 10) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 20)
vertices[vertexCount].Color = Color.Red;

vertexCount++;
}

}

return vertices;
}
#endregion

#region Update()
public override void Update(GameTime gameTime)
{
effect.View = cameraManager.ActiveCamera.View;
effect.Projection = cameraManager.ActiveCamera.Projection;
base.Update(gameTime);
}
#endregion

#region Draw()
public override void Draw(GameTime gameTime)
{

//Adicionamos o nossos indices e vertices ao nosso dispositivso para desenhar
Game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
Game.GraphicsDevice.Indices = indexBuffer;

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
//Adiciona e aplica o efeito
pass.Apply();
//Desenha a lista de linhas
Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numVertices, 0, numTriangles);
}

base.Draw(gameTime);
}
#endregion
}
}

Nenhum comentário:

Postar um comentário