using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace GunTest2 { class Terrain : SceneObject { uint vertexWidth; uint vertexHeight; float[] heights; Vector3[] verts; Vector3[] normals; Vector2[] texcoords; Color[] colors; short[] tris; int minHeight; int maxHeight; float tileScale; VertexDeclaration decl; VertexPositionNormalColor[] buffer; VertexBuffer vbuf; IndexBuffer ibuf; BoundingBox bounding; public BoundingBox Bounding { get { return bounding; } } public float TileScale { get { return tileScale; } } public float[] HeightMap { get { return heights; } } public float TileWidth { get { return vertexWidth; } } public float TileHeight { get { return (float)vertexHeight; } } public static Terrain Instance; public Terrain(uint width, uint height) { minHeight = 0; maxHeight = 50; tileScale = 10.0f; vertexWidth = width; vertexHeight = height; heights = new float[width * height]; verts = new Vector3[width * height]; normals = new Vector3[width * height]; texcoords = new Vector2[width * height]; colors = new Color[width * height]; // Two tris per tile tris = new short[(width - 1) * (height - 1) * 2 * 3]; bounding.Min = new Vector3(0, minHeight, 0); bounding.Max = new Vector3(vertexWidth * tileScale, maxHeight, vertexHeight * tileScale); GenerateHeightmap(); CalculateMeshVerts(0, 0, vertexWidth, vertexHeight); CalculateMapNormals(); GenerateVertexBuffer(); Instance = this; } private void GenerateHeightmap() { Random r = new Random(); float h = GetHeightFromNormal((float)r.NextDouble()); int nSpots = 64; while (nSpots-- > 0) { int x = (int)(vertexWidth * r.NextDouble());//((r.NextDouble() + r.NextDouble() + r.NextDouble()) * 0.33f)); int y = (int)(vertexHeight * r.NextDouble());//((r.NextDouble() + r.NextDouble() + r.NextDouble()) * 0.33f)); // Write center WriteHeight(x, y, GetHeightFromNormal((float)r.NextDouble())); Blend(x, y, r); } } private void Blend(int x, int y, Random r) { float h = ReadHeight(x, y); if (h - minHeight <= tileScale) { return; } if(h - ReadHeight(x + 1, y) > tileScale) { WriteHeight(x + 1, y, h - ((float)r.NextDouble() * tileScale)); Blend(x + 1, y, r); } else if (ReadHeight(x + 1, y) - h > tileScale) { WriteHeight(x + 1, y, h + ((float)r.NextDouble() * tileScale)); Blend(x + 1, y, r); } if(h - ReadHeight(x - 1, y) > tileScale) { WriteHeight(x - 1, y, h - ((float)r.NextDouble() * tileScale)); Blend(x - 1, y, r); } else if (ReadHeight(x - 1, y) - h > tileScale) { WriteHeight(x - 1, y, h + ((float)r.NextDouble() * tileScale)); Blend(x - 1, y, r); } if(h - ReadHeight(x, y + 1) > tileScale) { WriteHeight(x, y + 1, h - ((float)r.NextDouble() * tileScale)); Blend(x, y + 1, r); } else if (ReadHeight(x - 1, y) - h > tileScale) { WriteHeight(x - 1, y, h + ((float)r.NextDouble() * tileScale)); Blend(x - 1, y, r); } if(h - ReadHeight(x, y - 1) > tileScale) { WriteHeight(x, y - 1, h - ((float)r.NextDouble() * tileScale)); Blend(x, y - 1, r); } else if(h - ReadHeight(x - 1, y) > tileScale) { WriteHeight(x - 1, y, h - ((float)r.NextDouble() * tileScale)); Blend(x - 1, y, r); } } private float GetHeightFromNormal(float normalizedHeight) { return ((float)(maxHeight - minHeight) * normalizedHeight) + (float)minHeight; } private float ReadHeight(int x, int y) { if (x >= 0 && x < vertexWidth && y >= 0 && y < vertexHeight) { return heights[(y * vertexWidth) + x]; } return 0.0f; } private void WriteHeight(int x, int y, float h) { if (x >= 0 && x < vertexWidth && y >= 0 && y < vertexHeight) { heights[(y * vertexWidth) + x] = h; } } private void CalculateMeshVerts(uint offx, uint offy, uint width, uint height) { // texture mapping float texscale = 2.0f / vertexWidth; // height mapping uint colorIndex; float h, hmin, hmax, realx, realy; Color[] colormap = new Color[8] { Color.Black, Color.Green, Color.Green, Color.Green, Color.Brown, Color.Gray, Color.Gray, Color.White }; hmin = (float)minHeight; hmax = (float)(maxHeight - minHeight); // current vert uint i = (offy * vertexWidth) + offx; // currently tri uint tri = ((offy * vertexWidth) + offx) * 2; // convert the heightmap to vertexes for (uint y = offy; y < height; y++) { for (uint x = offx; x < width; x++) { h = heights[(y * width) + x]; // create the vertexes realx = (float)(x * tileScale); realy = (float)(y * tileScale); // map a color onto the vertex colorIndex = (uint)((h - hmin) / hmax) * 7; if (colorIndex >= colormap.Length) { colorIndex = (uint)(colormap.Length - 1); } colors[i] = colormap[colorIndex]; verts[i] = new Vector3(realx, h, realy); texcoords[i] = new Vector2(x * texscale, y * texscale); // define the two triangles for the sample if (x < (vertexWidth - 1) && y < (vertexHeight - 1)) { // one indexed triangle tris[tri + 0] = (short)i; tris[tri + 1] = (short)(i + vertexWidth); tris[tri + 2] = (short)(i + 1); tri += 3; // second indexed triangle tris[tri + 0] = (short)(i + 1); tris[tri + 1] = (short)(i + vertexWidth); tris[tri + 2] = (short)(i + vertexWidth + 1); tri += 3; } i++; } } } private void CalculateMapNormals() { ushort x, y; for (y = 0; y < vertexHeight; ++y) { // setup edge normals if (y == 0 || y == vertexHeight - 1) { for (x = 0; x < vertexWidth; ++x) { normals[(y * vertexWidth) + x] = new Vector3(0, 1, 0); } continue; } // First normal in row normals[(y * vertexWidth)] = new Vector3(0, 1, 0); // Last normal in row normals[(y * vertexWidth) + (vertexWidth - 1)] = new Vector3(0, 1, 0); for (x = 1; x < vertexWidth - 1; ++x) { Vector3 n = new Vector3(); n.X = verts[(y * vertexWidth) + (x - 1)].Y - verts[(y * vertexWidth) + (x + 1)].Y; n.Y = 2 * tileScale; n.Z = verts[(y * vertexWidth) + (x - vertexWidth)].Y - verts[(y * vertexWidth) + (x + vertexWidth)].Y; n.Normalize(); normals[(y * vertexWidth) + x] = n; } } } private void GenerateVertexBuffer() { buffer = new VertexPositionNormalColor[vertexWidth * vertexHeight]; for (uint i = 0, c = vertexWidth * vertexHeight; i < c; ++i) { if (0.0f == verts[i].Y) { buffer[i] = new VertexPositionNormalColor( verts[i], normals[i], Color.DarkBlue); } else { float l = verts[i].Y / maxHeight; buffer[i] = new VertexPositionNormalColor( verts[i], normals[i], new Color( new Vector3( MathHelper.Lerp(Color.DarkGreen.R, Color.Green.R, l), MathHelper.Lerp(Color.DarkGreen.G, Color.Green.G, l), MathHelper.Lerp(Color.DarkGreen.B, Color.Green.B, l)))); } } } // From SceneObject public override void UpdateState(Scene scene, float seconds) { } public override void SubmitPrimitives(Scene scene, GraphicsDevice gd) { if (null == vbuf) { vbuf = scene.GetVertexBuffer(VertexPositionNormalColor.SizeInBytes * this.verts.Length); ibuf = scene.GetIndexBuffer16(2 * tris.Length); vbuf.SetData(buffer); ibuf.SetData(tris); decl = new VertexDeclaration(gd, VertexPositionNormalColor.VertexElements); } gd.VertexDeclaration = decl; gd.Vertices[0].SetSource(vbuf, 0, VertexPositionNormalColor.SizeInBytes); gd.Indices = ibuf; gd.RenderState.CullMode = CullMode.CullClockwiseFace; gd.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, (int)(vertexWidth * vertexHeight), 0, (int)((vertexWidth - 1) * (vertexHeight - 1) * 2)); } public Vector4 FindFreeTileCenterAt(float z) { Random r = new Random(); while (z > Bounding.Min.Z) { for (float x = ((float)r.Next((int)vertexWidth) * tileScale); x < bounding.Max.X; x += tileScale) { float tileX = x - bounding.Min.X; float tileY = z - bounding.Min.Z; if (tileX < 0.0f) { tileX = 0.0f; } else if (tileX > bounding.Max.X) { tileX = bounding.Max.X; } if (tileY < 0.0f) { tileY = 0.0f; } else if (tileY > bounding.Max.Z) { tileY = bounding.Max.Z; } tileX = (float)Math.Floor(tileX / tileScale); tileY = (float)Math.Floor(tileY / tileScale); float h = heights[(int)((tileY * vertexWidth) + tileX)]; if (h > 0.0f) { return new Vector4(x, h, z, 1); } } z -= tileScale; } return Vector4.Zero; } } }