/* * Terrain.cs * Authors: August Zinsser * * Copyright August Zinsser 2007 * This program is distributed under the terms of the GNU General Public License */ 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; namespace Pina3D { /// /// A mesh based on a height map. Typically used for the ground in a world/level. /// public class Terrain : Renderable { /// /// Samples the given height map to produce a 2D array of floats /// /// Source field /// Rows to sample /// Columns to sample /// Number of samples between whole coordinates in the heightMap public Terrain(PinaState spawnState, PerlinNoise heightMap, int rows, int cols, float resolution, float amplitude) : base (spawnState, new SampledHeightMap(heightMap, rows, cols, resolution, amplitude)) { } /// /// Returns the value of the sampled height map /// public float LookUp(int x, int y) { return ((SampledHeightMap)((RenderableState)mState).SourceData).Mesh[x,y]; } protected class TerrainState : RenderableState { public TerrainState(PinaState pinaState, PerlinNoise heightMap, int rows, int cols, float resolution, float amplitude) : base(pinaState, new SampledHeightMap(heightMap, rows, cols, resolution, amplitude)) { } } /// /// Stores a mesh consisting of regular sampling from a PerlinNoise field /// protected class SampledHeightMap : IRenderableSourceData { protected float[,] mMesh; protected VertexBuffer mVertexBuffer; protected IndexBuffer mIndexBuffer; protected int mNumVertices; protected int mNumIndices; protected float mAmplitude; public float[,] Mesh { get { return mMesh; } } public VertexBuffer VertexBuffer { get { return mVertexBuffer; } } public IndexBuffer IndexBuffer { get { return mIndexBuffer; } } public int NumVertices { get { return mNumVertices; } } public int NumIndices { get { return mNumIndices; } } public float Amplitdue { get { return mAmplitude; } } public SampledHeightMap(PerlinNoise heightMap, int rows, int cols, float resolution, float amplitude) { mMesh = new float[rows, cols]; mAmplitude = amplitude; // Sample the PerlinNoise field for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) { mMesh[i, j] = heightMap.LookUp((float)i / resolution, (float)j / resolution) * amplitude; } FillVertexBuffer(); FillIndexBuffer(); } /// /// Creates a vertex buffer based on the height map and sampling parameters /// protected void FillVertexBuffer() { int rows = mMesh.GetLength(0); int cols = mMesh.GetLength(1); VertexMultitextured[] verts = new VertexMultitextured[rows * cols]; for (int x = 0; x < rows; x++) { for (int y = 0; y < cols; y++) { verts[x + y * rows].Position = new Vector3(x, y, mMesh[x, y]); verts[x + y * rows].Normal = new Vector3(0, 0, 1); verts[x + y * rows].TextureCoordinate.X = (float)x / 30.0f; verts[x + y * rows].TextureCoordinate.Y = (float)y / 30.0f; verts[x + y * rows].TextureWeights.X = MathHelper.Clamp(1.0f - Math.Abs(mMesh[x, y] - 0) / 8.0f, 0, 1); verts[x + y * rows].TextureWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(mMesh[x, y] - 12) / 6.0f, 0, 1); verts[x + y * rows].TextureWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(mMesh[x, y] - 20) / 6.0f, 0, 1); verts[x + y * rows].TextureWeights.W = MathHelper.Clamp(1.0f - Math.Abs(mMesh[x, y] - 30) / 6.0f, 0, 1); float totalWeight = verts[x + y * rows].TextureWeights.X; totalWeight += verts[x + y * rows].TextureWeights.Y; totalWeight += verts[x + y * rows].TextureWeights.Z; totalWeight += verts[x + y * rows].TextureWeights.W; verts[x + y * rows].TextureWeights.X /= totalWeight; verts[x + y * rows].TextureWeights.Y /= totalWeight; verts[x + y * rows].TextureWeights.Z /= totalWeight; verts[x + y * rows].TextureWeights.W /= totalWeight; } } for (int x = 1; x < rows - 1; x++) { for (int y = 1; y < cols - 1; y++) { Vector3 normX = new Vector3((verts[x - 1 + y * rows].Position.Z - verts[x + 1 + y * rows].Position.Z) / 2, 0, 1); Vector3 normY = new Vector3(0, (verts[x + (y - 1) * rows].Position.Z - verts[x + (y + 1) * rows].Position.Z) / 2, 1); verts[x + y * rows].Normal = normX + normY; verts[x + y * rows].Normal.Normalize(); } } mVertexBuffer = new VertexBuffer(Pina.Graphics.GraphicsDevice, VertexMultitextured.SizeInBytes * rows * cols, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic); mVertexBuffer.SetData(verts); mNumVertices = rows * cols; } /// /// Creates a vertex buffer based on the height map and sampling parameters /// protected void FillIndexBuffer() { int rows = mMesh.GetLength(0); int cols = mMesh.GetLength(1); int[] inds = new int[(rows - 1) * (cols - 1) * 6]; for (int x = 0; x < rows - 1; x++) { for (int y = 0; y < cols - 1; y++) { inds[(x + y * (rows - 1)) * 6] = (x + 1) + (y + 1) * rows; inds[(x + y * (rows - 1)) * 6 + 1] = (x + 1) + y * rows; inds[(x + y * (rows - 1)) * 6 + 2] = x + y * rows; inds[(x + y * (rows - 1)) * 6 + 3] = (x + 1) + (y + 1) * rows; inds[(x + y * (rows - 1)) * 6 + 4] = x + y * rows; inds[(x + y * (rows - 1)) * 6 + 5] = x + (y + 1) * rows; } } mIndexBuffer = new IndexBuffer(Pina.Graphics.GraphicsDevice, typeof(int), (rows - 1) * (cols - 1) * 6, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic); mIndexBuffer.SetData(inds); mNumIndices = (rows - 1) * (cols - 1) * 2; } /// /// Render the given height map with the given shader at the given state /// public void Render(IRenderableSourceData sourceData, Shader shader, PinaState state) { // Set the world matrix for this instance Pina.WorldMatrix = state.Matrix; // Get the necessary data List shaderPasses = shader.EffectPasses; // Make sure to use the correct vertex declaration for the shader. // Since the vertex declaration is stored in unmanaged space, the managed reference to it may get garbage collected. // To prevent this from happening, keep a reference to the VD in managed memory until the model is done rendering. VertexDeclaration vd = new VertexDeclaration(Pina.Graphics.GraphicsDevice, VertexMultitextured.VertexElements); Pina.Graphics.GraphicsDevice.VertexDeclaration = vd; shader.Begin(); for (int i = 0; i < shaderPasses.Count; i++) { shaderPasses[i].Begin(); RenderVertices( ((SampledHeightMap)sourceData).mVertexBuffer, ((SampledHeightMap)sourceData).mIndexBuffer, ((SampledHeightMap)sourceData).NumVertices, ((SampledHeightMap)sourceData).NumIndices); shaderPasses[i].End(); } shader.End(); } /// /// Render vertices /// private static void RenderVertices(VertexBuffer vertexBuffer, IndexBuffer indexBuffer, int numVertices, int numIndices) { Pina.Graphics.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexMultitextured.SizeInBytes); Pina.Graphics.GraphicsDevice.Indices = indexBuffer; Pina.Graphics.GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, numVertices, 0, numIndices); } } 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 ), }; } } }