/*
* 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 ),
};
}
}
}