terça-feira, 27 de setembro de 2011

Segue

Até o final do ano não haverão novas postagens.

Achei que no blog estava um pouco confuso, vcs podem encontrar mais materias no forum:

http://forum.outerspace.terra.com.br/showthread.php?t=366412&highlight=XNA+4.0

quarta-feira, 3 de agosto de 2011

XNA 4.0 - Modelos - SkyDome

O que aborda esse tutorial:
1 – Criação de Uma classe chamada World, que nos ajudará a gerenciar nossos objetos.
2 – Criação da Classe SkyDome.
3 – Mudanças no arquivo Game1.cs para vermos nosso SkyDome. Mudança no arquivo Terrain.cs para adicionar a variável World

O arquivo .rar com os exemplos usados :

O tutorial na pasta raiz.

Contem um SkyDome(Skydome.x) e uma Textura(sky_v2b_tex.dds) usadas pelo SDK do DirectX. No diretório: Parte 6 -Modelos – SkyDome\Tutorial1Content\Models\SkyDome

O arquivo Terrain.cs com as mudanças para usa o gerenciador World e o arquivo SkyDome.cs – No
diretório: Parte 6 -Modelos - SkyDome\Tutorial1\GameBase\Shapes

O arquivo Game1.se com as mudanças para visualização do SkyDome – No diretório: Parte 6 -Modelos – SkyDome\Tutorial1

Download: http://www.megaupload.com/?d=999G84V4

sábado, 30 de julho de 2011

XNA 4.0 - Recuperando Altura de um terreno.

Vamos agora recuperar a altura de nosso terreno, é relativamente fácil.
Fornecemos as posições em forma de um vetor do tipo Vector3, Obtemos a posição na malha, comparamos com a posição do bloco inicial e verificamos se essa posição está mesmo dentro de nossa malha, caso sim calcula o quadrado que estamos, pega os 4 indices que formam esse quadrado, faz alguns calculos vetoriais e mostra exatamente a posição. O código é esse:

public float GetHeight(Vector3 position)
{
return GetHeight(position.X, position.Z);
}

private float GetHeight(float positionX, float positionZ)
{
float height = -999999.0f;
if (heightMap == null) return height;

// Obter a posicao do objeto na grade do terreno
Vector2 positionInGrid = new Vector2(
positionX - (StartPosition.X),
positionZ - (StartPosition.Y));

// Calcula a posicao do bloco inicial
Vector2 blockPosition = new Vector2(
positionInGrid.X / blockScale,
positionInGrid.Y / blockScale);

// Verifica se o objeto esta dentro da grade
if (blockPosition.X >= 0 && blockPosition.X < (vertexCountX - 1) && blockPosition.Y >= 0 && blockPosition.Y < (vertexCountZ - 1)) { Vector2 blockOffset = new Vector2( blockPosition.X - (int)blockPosition.X, blockPosition.Y - (int)blockPosition.Y); // Obteem a altura dos quatro vertices do bloco int vertexIndex = (int)blockPosition.X + (int)blockPosition.Y * vertexCountX; float height1 = heightMap[vertexIndex + 1]; float height2 = heightMap[vertexIndex]; float height3 = heightMap[vertexIndex + vertexCountX + 1]; float height4 = heightMap[vertexIndex + vertexCountX]; // Triangule de baixo float heightIncX, heightIncY; if (blockOffset.X > blockOffset.Y)
{
heightIncX = height1 - height2;
heightIncY = height3 - height1;
}
// Triangulo de cima
else
{
heightIncX = height3 - height4;
heightIncY = height4 - height2;
}

// Interpolacao linear para encontrar a altura do triangulo
float lerpHeight = height2 + heightIncX * blockOffset.X + heightIncY * blockOffset.Y;
height = lerpHeight * heightScale;
}

return height;
}

Criamos dois métodos, um para retornar a altura em uma determinada posição e outro cuida de todos os calculos envovendo isso.

Vamos inserir uma propriedade para que a posição inicial seja calculada tempo como base os vértices que escolhemos para ser o inicial. Adicione entre as variáveis e o mé todo Initialize():

public Vector2 StartPosition
{
get
{
float terrainHalfWidth = (vertexCountX - 1) * blockScale * 0.5f;
float terrainHalfDepth = (vertexCountZ - 1) * blockScale * 0.5f;

return new Vector2(-terrainHalfWidth, -terrainHalfDepth);
}
}

Agora um código para que a nossa câmera siga a altura do mapa, para termos a noção de relevo do mapa, vá ao arquivo "Game1.cs" no método Update() e insira, depois do CameraManager.ActiveCamera.addToPosition()

cameraManager.ActiveCamera.Position = new Vector3(cameraManager.ActiveCamera.Position.X,
terrain.GetHeight(cameraManager.ActiveCamera.Position) + 1.86f,
cameraManager.ActiveCamera.Position.Z
);

Esse código atualiza a posição do eixo Y tendo como relação a posição fornecida e é acrescida de 1.86f enquando as posições X e Z são mantidas.
1.86f é a minha altura, se você quiser uma escala de 1:1 ou seja, 1.0f em nosso mundo equivale a 1 metro de altura então temos que 1.86f representa 1 metro e 86 centimentros e que o nosso mapa tem 256m2, o que é espaço pra caramba para brincar.

XNA 4.0 - Criando Tangente e Binormal

A principio ia falar sobre efeitos em XNA bem superficialmente, mas decidi que seria legal colocar uma série separada, onde até eu mesmo irei aprender mais sobre isso.

Nós estamos usando efeitos básicos, que já vem com o XNA, e são muito simples, não permitem usar luzes pelo mapa e nada mais avançado, então se queremos usar efeitos avançado em nosso terreno teremos de calcular algumas coisas a mais. Efeitos em sua maioria requerem Tangente, BiNormal, Normal, Coordenada da Textura e Posição. Nós já fizemos e achamos a posição, a coordenada das texturas e a normal e para isso nós usamos o vertex: VertexPositiomNormal mas agora precisamos usar um outro tipo de vértice que junte tudo isso pra gente. Mas o XNA não fornece isso para gente, então vamos criar o VertexPositionNormalTangentBinormal. Na raiz do projeto crie uma pasta e nomeie-a como "Helpers", adicione um novo arquivo nessa parata, chamado de : "VertexPositionNormalTangentBinormal". O código é a estrutura interna do vértice, pelo nome e pelo o que contem é possível saber qual a estrutura dos outros tipos de Vertexs. O código do arquivo completo:

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

namespace Tutorial1.Helpers
{
public struct VertexPositionNormalTangentBinormal
{

#region Variaveis
public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
public Vector3 Tanget;
public Vector3 Binormal;
#endregion

#region Propriedades
public static int SizeInBytes
{
get
{
return (3 + 3 + 2 + 3 + 3 ) * sizeof(float);
}
}
#endregion

#region Construtor
public VertexPositionNormalTangentBinormal(Vector3 position, Vector3 normal, Vector2 textureCoordinate, Vector3 tanget, Vector3 binormal)
{
Position = position;
Normal = normal;
TextureCoordinate = textureCoordinate;
Tanget = tanget;
Binormal = binormal;
}
#endregion

#region Elementos do Vertex
public static VertexElement[] VertexElements = new VertexElement[] {
new VertexElement(0, VertexElementFormat.Vector3,
VertexElementUsage.Position, 0),
new VertexElement(12, VertexElementFormat.Vector3,
VertexElementUsage.Normal, 0),
new VertexElement(24, VertexElementFormat.Vector2,
VertexElementUsage.TextureCoordinate, 0),
new VertexElement(32, VertexElementFormat.Vector3,
VertexElementUsage.Tangent, 0),
new VertexElement(44, VertexElementFormat.Vector3,
VertexElementUsage.Binormal, 0),

};
#endregion
}
}

Volte no arquivo "Terrain.cs" aprete CTRL+H e substitua todas as ocorrências de VertexPositiomNormal para VertexPositionNormalTangentBinormal

No método GenerateTerrainMesh() substitua a declaração do vértice para essa

VertexDeclaration decl = new VertexDeclaration(VertexPositionNormalTangentBinormal.VertexElements);

No método GenerateTerrainVértices adicione a linha, a linha fica depois de "GenerateTerrainNormals(vertices, terrainIndices);"

GenerateTerrainTangentBinormal(vertices, terrainIndices);

Agora vamos criar o método GenerateTerrainTangentBinormal, a tangent é um vetor que aponta do atual vértice para o próximo. e a BiNormal é um vetor que é obtido atrávez do produto vetorial da Normal com a tangente, o código fica assim:
public void GenerateTerrainTangentBinormal(VertexPositionNormalTangentBinormal[] vertices, int[] indices)
{
for (int i = 0; i < vertexCountZ; i++) { for (int j = 0; j < vertexCountX; j++) { int vertexIndex = j + i * vertexCountX; Vector3 v1 = vertices[vertexIndex].Position; if (j < vertexCountX - 1) { Vector3 v2 = vertices[vertexIndex + 1].Position; vertices[vertexIndex].Tanget = (v2 - v1); } else { Vector3 v2 = vertices[vertexIndex - 1].Position; vertices[vertexIndex].Tanget = (v1 - v2); } vertices[vertexIndex].Tanget.Normalize(); vertices[vertexIndex].Binormal = Vector3.Cross(vertices[vertexIndex].Tanget, vertices[vertexIndex].Normal); } } }


Isso em nada vai mudar a nossa cena mas como usaremos efeitos avançados mais a frente é bom terminar a classe Terrain logo de uma vez;

sexta-feira, 29 de julho de 2011

XNA 4.0 - Parte 6 Terrenos

Baixe esse arquivo: http://www.megaupload.com/?d=MEROS15K

Ele possui algumas texturas que usaremos para criar nosso terreno e depois com efeitos avançados.

Extraia as imagens, Crie uma Pasta chamada "Textures" no content e adicione essas 4 imagens para dentro da pasta.

Para criarmos uma textura e ilumina-la precusamos calcular a "Normal" de cada vértice para que ele possa ser iluminado. Geralmente um Vector3.Up é o suficiente para criar a normal do vértice mas vamos fazer algo um pouco mais trabalhado. Vamos Criar um novo método antes do método Update(), o método se chamara GenerateTerrainNormals() ele vai receber uma lista de vértices do Tipo VertexPositionColor.

private void GenerateTerrainNormals(VertexPositionColor[] vertices, int[] indices)
{
for (int i = 0; i < indices.Length; i += 3) { Vector3 v1 = vertices[indices[i]].Position; Vector3 v2 = vertices[indices[i + 1]].Position; Vector3 v3 = vertices[indices[i + 2]].Position; Vector3 vu = v3 - v1; Vector3 vt = v2 - v1; Vector3 normal = Vector3.Cross(vu, vt); normal.Normalize(); vertices[indices[i]].Normal += normal; vertices[indices[i + 1]].Normal += normal; vertices[indices[i + 2]].Normal += normal; } }



Ele pega as "normais" dos outros dois vértices e criar algo mais elaborado.

Agora vá no método CreateTerrainVertices e adicione os eguinte código antes de "return vertices"

// Cria a normal do vertice
GenerateTerrainNormals(vertices, terrainIndices);

Fazendo algumas alterações no código, atualmente usamos "VertexPositionColor", foi legal enquanto durou mas queremos usar texturas e luzes, vamos alterar todas as chamadas de VertexPositionColor para VertexPositionNormalTexture, vamos usar o atalho CRTL+H, substitua todas, no total são 7. Vamos Substituir todas as ocorrências de BasicEffect para EnvironmentMapEffect, no total serão 2.

Ainda no método CreateTerrainVertices() altere a referência de cores para coordenada de textura. Então tinhamos:

vertices[vertexCount].Color = Color.Yellow;
if((int)heightMap[vertexCount] < 80) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 200)
vertices[vertexCount].Color = Color.Red;

E agora teremos:

vertices[vertexCount].TextureCoordinate = new Vector2(j, i);

Vou colocar o método inteiro para ficar mais claro:

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

int vertexCount = 0;
VertexPositionNormalTexture[] vertices =
new VertexPositionNormalTexture[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].TextureCoordinate = new Vector2(j, i); vertexCount++; } } // Gera as normais dos indices GenerateTerrainNormals(vertices, terrainIndices); return vertices; }



No método Load(), na ultima linha substitua o "effect.VertexColorEnabled = true;" para "effect.EnableDefaultLighting();" e logo após adicione a se guinte linha:

effect.Texture = Game.Content.Load("Textures/Terrain2");

Você pode alterar essa linha para:

effect.Texture = Game.Content.Load("Textures/Terrain1");

mudando o ultimo parametro, você pode adicionar as suas próprias texturas, adicionando-as a pasta Textures e depois substituindo o nome dele no ultimo parametro:

effect.Texture = Game.Content.Load("Textures/nomeSuaTexturaSemOTipoDeArquivo");

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;
using System.IO;

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
EnvironmentMapEffect 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.Solid;
Game.GraphicsDevice.RasterizerState = rasterizeState;

isInitialized = true;

base.Initialize();
}
#endregion

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


//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;

// Carrega o arquivo do mapa de altura
FileStream fileStream = File.OpenRead(Game.Content.RootDirectory + "/Terrains/" + heightmapFileName + ".raw");

int heightmapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightmapSize];
fileStream.Read(heightMap, 0, heightmapSize);
fileStream.Close();

GenerateTerrainMesh();

effect = new EnvironmentMapEffect(Game.GraphicsDevice);
effect.EnableDefaultLighting();
effect.Texture = Game.Content.Load("Textures/Terrain2");
}
#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();

VertexPositionNormalTexture[] vertices = GenerateTerrainVertices(indices);

//Vamos criar a declaração dos vértices
VertexDeclaration decl = new VertexDeclaration(VertexPositionNormalTexture.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 VertexPositionNormalTexture[] GenerateTerrainVertices(int[] terrainIndices)
{
float halfTerrainWidth = (vertexCountX - 1) * blockScale * 0.5f;
float halfTerrainDepth = (vertexCountZ - 1) * blockScale * 0.5f;

int vertexCount = 0;
VertexPositionNormalTexture[] vertices =
new VertexPositionNormalTexture[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].TextureCoordinate = new Vector2(j, i);

vertexCount++;
}

}
// Gera as normais dos indices
GenerateTerrainNormals(vertices, terrainIndices);

return vertices;
}
#endregion

#region GenerateTerrainNormals()
private void GenerateTerrainNormals(VertexPositionNormalTexture[] vertices, int[] indices)
{
for (int i = 0; i < indices.Length; i += 3)
{
Vector3 v1 = vertices[indices[i]].Position;
Vector3 v2 = vertices[indices[i + 1]].Position;
Vector3 v3 = vertices[indices[i + 2]].Position;

Vector3 vu = v3 - v1;
Vector3 vt = v2 - v1;
Vector3 normal = Vector3.Cross(vu, vt);
normal.Normalize();

vertices[indices[i]].Normal += normal;
vertices[indices[i + 1]].Normal += normal;
vertices[indices[i + 2]].Normal += normal;
}
}
#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
}
}
Baixe esse arquivo: http://www.megaupload.com/?d=JB5UCFGU

Nele tem dois arquivos de 8-Bits no formato .raw - Você pode usar o google imagens, procurar por imagens e preto e branco e depois salvar o arquivo no tipo .raw, eu uso o PhotoShop mas existem algumas ferramentas que fazer mapas para você, TerraGen e EathSculptor.

Extraia os arquivos onde quiser. Em tutorial1.Content(Content) crie uma nova pasta, chame-a de "Terrains", Clique com o botão direito sobre a pasta criada -> Add -> Existing itens e selecione as duas imagens.

Selecione cada uma de uma vez, abra a aba de propeiedades. Mude Build Action para "none" e CopyToOutputDirectory para Copy if newer. Isso acontece porque o XNA transforma as imagens para 32Bits, o que não queremos.

Volte no arquivo "Terrain.cs" adicione a seguinte using "using System.IO;", no Método Load() apague as seguintes linhas


"Random random = new Random();" e

"//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);" e cole o seguinte código:
// Carrega o arquivo do mapa de altura
FileStream fileStream = File.OpenRead(Game.Content.RootDirectory + "/Terrains/" + heightmapFileName + ".raw");

int heightmapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightmapSize];
fileStream.Read(heightMap, 0, heightmapSize);
fileStream.Close();

Agora altere os parametros do método Load para
public void Load(string heightmapFileName, int vertexCountX, int vertexCountZ, float blockScale, float heightScale) - Adicionamos o

"string heightmapFileName"

No inicio dos parametros.

No arquivo "Game1.cs" Ache novamente o método terrain.Load() e mude-o para:

terrain.Load("Terrain1", 256, 256, 1.0f, 0.1f); ou
terrain.Load("Australia", 256, 256, 1.0f, 0.1f);

Por Ultimo vamos alterar a cor, no método CreateTerrainVertices mude a altura de cada cor para

if((int)heightMap[vertexCount] < 80) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 200)
vertices[vertexCount].Color = Color.Red;

Você pose mudar o FillMode para Solid no método Initialize();

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;
using System.IO;

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(string heightmapFileName, int vertexCountX, int vertexCountZ, float blockScale, float heightScale)
{
//Cuida para que tudo seja iniciado
if (!isInitialized)
Initialize();


//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;

// Carrega o arquivo do mapa de altura
FileStream fileStream = File.OpenRead(Game.Content.RootDirectory + "/Terrains/" + heightmapFileName + ".raw");

int heightmapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightmapSize];
fileStream.Read(heightMap, 0, heightmapSize);
fileStream.Close();

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] < 80) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 200)
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
}
}

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
}
}