using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; using System.IO; namespace XNAGame { public class Terrain { GraphicsDevice device; Effect effect; //Texture variables Texture2D grassTexture; Texture2D sandTexture; Texture2D rockTexture; Texture2D snowTexture; VertexMultitextured[] vertices; private VertexBuffer vb; private IndexBuffer ib; private int WIDTH; private int HEIGHT; Texture2D heightMap; //Boundaries for determining minimum and maximum heights of the loaded terrain private float MINIMUMHEIGHT = 255; private float MAXIMUMHEIGHT = 0; private float[,] heightData; private Matrix worldMatrix; // Structure definition for the terrain containing info about the position, normal and color per vertex. public struct VertexPositionNormalColored { public Vector3 Position; public Color Color; public Vector3 Normal; public static int SizeInBytes = 7 * 4; public static VertexElement[] VertexElements = new VertexElement[] { new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ), new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0 ), new VertexElement( 0, sizeof(float) * 4, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ) }; } //Structure definition to enable smooth transition between multiple textures. public struct VertexMultitextured { public Vector3 Position; public Vector3 Normal; public Vector4 TextureCoordinate; public Vector4 TextureWeights; public static int SizeInBytes = (3 + 3 + 4 + 4) * 4; public static VertexElement[] VertexElements = new VertexElement[] { new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ), new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ), new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ), new VertexElement( 0, sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ), }; } public Terrain() { } public void Initialize(GraphicsDevice gd, ContentManager cm) { heightMap = cm.Load("Models\\terrain\\heightmapa512"); LoadHeightData(); SetUpXNADevice(gd); SetUpEffects(cm); SetUpVertices(); SetUpIndices(); } private void SetUpXNADevice(GraphicsDevice gd) { device = gd; } private void SetUpEffects(ContentManager cm) { effect = cm.Load("effect"); grassTexture = cm.Load("Models\\textures\\grass"); sandTexture = cm.Load("Models\\textures\\sand"); rockTexture = cm.Load("Models\\textures\\rock"); snowTexture = cm.Load("Models\\textures\\snow"); } private void SetUpVertices() { float maximumheight = 0; vertices = new VertexMultitextured[WIDTH * HEIGHT]; for (int x = 0; x < WIDTH; x++) { for (int y = 0; y < HEIGHT; y++) { if (maximumheight < heightData[x, y]) { maximumheight = heightData[x, y]; } vertices[x + y * WIDTH].Position = new Vector3(x, y, heightData[x, y]); vertices[x + y * WIDTH].Normal = new Vector3(0, 0, 1); vertices[x + y * WIDTH].TextureCoordinate.X = (float)x / 30.0f; vertices[x + y * WIDTH].TextureCoordinate.Y = (float)y / 30.0f; vertices[x + y * WIDTH].TextureWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 0) / 20.0f, 0, 1); vertices[x + y * WIDTH].TextureWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 30) / 20.0f, 0, 1); vertices[x + y * WIDTH].TextureWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 60) / 20.0f, 0, 1); vertices[x + y * WIDTH].TextureWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 95) / 20.0f, 0, 1); float totalWeight = vertices[x + y * WIDTH].TextureWeights.X + vertices[x + y * WIDTH].TextureWeights.Y + vertices[x + y * WIDTH].TextureWeights.Z + vertices[x + y * WIDTH].TextureWeights.W; vertices[x + y * WIDTH].TextureWeights.X /= totalWeight; vertices[x + y * WIDTH].TextureWeights.Y /= totalWeight; vertices[x + y * WIDTH].TextureWeights.Z /= totalWeight; vertices[x + y * WIDTH].TextureWeights.W /= totalWeight; } } for (int x = 1; x < WIDTH - 1; x++) { for (int y = 1; y < HEIGHT - 1; y++) { Vector3 normX = new Vector3((vertices[x - 1 + y * WIDTH].Position.Z - vertices[x + 1 + y * WIDTH].Position.Z) / 2, 0, 1); Vector3 normY = new Vector3(0, (vertices[x + (y - 1) * WIDTH].Position.Z - vertices[x + y + 1 * WIDTH].Position.Z) / 2, 1); vertices[x + y * WIDTH].Normal = normX + normY; vertices[x + y * WIDTH].Normal.Normalize(); } } Console.Out.Write(maximumheight); vb = new VertexBuffer(device, VertexMultitextured.SizeInBytes * WIDTH * HEIGHT, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic); vb.SetData(vertices); } private void SetUpIndices() { int[] indices = new int[(WIDTH - 1) * (HEIGHT - 1) * 6]; for (int x = 0; x < WIDTH - 1; x++) { for (int y = 0; y < HEIGHT - 1; y++) { indices[(x + y * (WIDTH - 1)) * 6] = (x + 1) + (y + 1) * WIDTH; indices[(x + y * (WIDTH - 1)) * 6 + 1] = (x + 1) + y * WIDTH; indices[(x + y * (WIDTH - 1)) * 6 + 2] = x + y * WIDTH; indices[(x + y * (WIDTH - 1)) * 6 + 3] = (x + 1) + (y + 1) * WIDTH; indices[(x + y * (WIDTH - 1)) * 6 + 4] = x + y * WIDTH; indices[(x + y * (WIDTH - 1)) * 6 + 5] = x + (y + 1) * WIDTH; } } ib = new IndexBuffer(device, typeof(int), (WIDTH - 1) * (HEIGHT - 1) * 6, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic); ib.SetData(indices); } public void updateCamera(Matrix viewMatrix, Matrix projectionMatrix) { worldMatrix = Matrix.Identity; effect.Parameters["xCameraViewProjection"].SetValue(viewMatrix * projectionMatrix); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xWorld"].SetValue(worldMatrix); } private void LoadHeightData() { WIDTH = heightMap.Width; HEIGHT = heightMap.Height; Color[] heightMapColors = new Color[WIDTH * HEIGHT]; heightMap.GetData(heightMapColors); heightData = new float[WIDTH, HEIGHT]; for (int i = 0; i < HEIGHT; i++) { for (int j = 0; j < WIDTH; j++) { //set minimum, maximum height values heightData[i, j] = heightMapColors[i + j * WIDTH].R; if (heightData[i, j] < MINIMUMHEIGHT) { MINIMUMHEIGHT = heightData[i, j]; } if (heightData[i, j] > MAXIMUMHEIGHT) { MAXIMUMHEIGHT = heightData[i, j]; } } } for (int i = 0; i < WIDTH; i++) for (int j = 0; j < HEIGHT; j++) { heightData[i, j] = (heightData[i, j] - MINIMUMHEIGHT) / (MAXIMUMHEIGHT - MINIMUMHEIGHT) * 30; } } //Gets the corresponding Z value for a given point on the map //This method is used to draw objects on the corresponding height on the terrain public Vector3 getHeightValue(float x, float y) { //We want to pass the x and y values as percentages of the terrain position float z = heightData[(Int32)(x / 100 * WIDTH), (Int32)(y / 100 * HEIGHT)]; return new Vector3(x / 100 * WIDTH, y / 100 * HEIGHT, z); } public void Draw(GameTime gameTime, Matrix viewMatrix, Matrix projectionMatrix) { effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xSandTexture"].SetValue(sandTexture); effect.Parameters["xGrassTexture"].SetValue(grassTexture); effect.Parameters["xRockTexture"].SetValue(rockTexture); effect.Parameters["xSnowTexture"].SetValue(snowTexture); worldMatrix = Matrix.Identity; effect.Parameters["xWorld"].SetValue(worldMatrix); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xEnableLighting"].SetValue(true); effect.Parameters["xLightDirection"].SetValue(new Vector3(-0.5f, -0.5f, -1)); effect.Parameters["xAmbient"].SetValue(0.8f); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); device.VertexDeclaration = new VertexDeclaration(device, VertexMultitextured.VertexElements); device.Vertices[0].SetSource(vb, 0, VertexMultitextured.SizeInBytes); device.Indices = ib; device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, WIDTH * HEIGHT, 0, (WIDTH - 1) * (HEIGHT - 1) * 2); pass.End(); } effect.End(); } } }