/// <summary> /// Creates an height map using the Fault Formation algorithm. /// Produces smooth terrain by adding a random line to a blank height field, and add random height to one of the two sides. /// </summary> /// <param name="size">Size of square HeightData to be created</param> /// <param name="numberIteration">The number of iteration of the algorithm. More iteration yields more spaces</param> /// <param name="minHeight">Min value of height produced from the algorithm</param> /// <param name="maxHeight">Max value of height produced from the algorithm</param> /// <param name="scaleHeight"></param> /// <param name="filter"></param> /// <returns>HeightData created with Fault Formation algorithm that has "size" of size</returns> public static HeightMap GenerateFaultFormation(int size, int numberIteration, float minHeight, float maxHeight, float scaleHeight, float filter) { var heightMap = new HeightMap(size, scaleHeight); var random = new Random(); for (var i = 0; i < numberIteration; ++i) { // Calculate height for this iteration var height = maxHeight - (maxHeight - minHeight) * i / numberIteration; var currentPassHeight = height * ((float)random.NextDouble() - 0.1f); // Find the line mark a half space for this iteration var point1 = new Point(random.Next(size), random.Next(size)); var point2 = new Point(random.Next(size), random.Next(size)); var halfSpaceLineVector = new Vector2(point2.X - point1.X, point2.Y - point1.Y); for (var iX = 0; iX < size; ++iX) { for (var iZ = 0; iZ < size; ++iZ) { var currentPointLine = new Vector2(iX - point1.X, iZ - point1.Y); float sign; Vector2.Dot(ref halfSpaceLineVector, ref currentPointLine, out sign); if (sign > 0) heightMap[iX, iZ] += currentPassHeight; } } heightMap.FilterHeightField(filter); } heightMap.NormalizeHeightMap(); heightMap.CalculateMedian(); return heightMap; }
private static void NormalizeHeightMap(HeightMap heightMap) { var maxHeight = float.MinValue; var minHeight = float.MaxValue; for (var i = 0; i < heightMap.DataSize; ++i) { if (maxHeight < heightMap[i]) maxHeight = heightMap[i]; if (minHeight > heightMap[i]) minHeight = heightMap[i]; } maxHeight -= minHeight; for (var i = 0; i < heightMap.DataSize; ++i) heightMap[i] = (heightMap[i] - minHeight) / maxHeight; }
private static void FilterHeightField(HeightMap data, float filter) { var size = data.Size; // Erode left to right for (var i = 0; i < size; i++) FilterHeightBand(data, size * i, 1, size, filter); //erode right to left for (var i = 0; i < size; i++) FilterHeightBand(data, size * i + size - 1, -1, size, filter); //erode top to bottom for (var i = 0; i < data.Size; i++) FilterHeightBand(data, i, size, size, filter); //erode from bottom to top for (var i = 0; i < data.Size; i++) FilterHeightBand(data, size * (size - 1) + i, -size, size, filter); }
/// <summary> /// Filters HeightData using band-based filter. by "Jason Shankel" /// It simulates terrain erosion. /// Throws an ArgumentOutOfRangeException if heightData is empty. /// </summary> /// <param name="heightData">In: Already created HeightData, Out: Smoothed HeightData</param> /// <param name="startIndex"></param> /// <param name="stride"></param> /// <param name="count"></param> /// <param name="filter"></param> public static void FilterHeightBand(HeightMap heightData, int startIndex, int stride, int count, float filter) { var v = heightData[startIndex]; var j = stride; //go through the height band and apply the erosion filter for (var i = 0; i < count - 1; ++i) { heightData[startIndex + j] = filter * v + (1 - filter) * heightData[startIndex + j]; v = heightData[startIndex + j]; j += stride; } }
/// <summary> /// Gets a normal vector for a given x, z coordinate and the corresponding heightmap /// </summary> /// <param name="heightMap"></param> /// <param name="x"></param> /// <param name="z"></param> /// <returns></returns> private static Vector4 GetNormalVector(HeightMap heightMap, int x, int z) { var currentP = new Vector3(x, heightMap.GetHeight(x, z), z); Vector3 p1; Vector3 p2; if (x == heightMap.Size - 1 && z == heightMap.Size - 1) // Bottom right pixel { p1 = new Vector3(x, heightMap.GetHeight(x, z - 1), z - 1); p2 = new Vector3(x - 1, heightMap.GetHeight(x - 1, z), z); } else if (x == heightMap.Size - 1) // Right border { p1 = new Vector3(x - 1, heightMap.GetHeight(x - 1, z), z); p2 = new Vector3(x, heightMap.GetHeight(x, z + 1), z + 1); } else if (z == heightMap.Size - 1) // Bottom border { p1 = new Vector3(x + 1, heightMap.GetHeight(x + 1, z), z); p2 = new Vector3(x, heightMap.GetHeight(x, z - 1), z - 1); } else // The rest of pixels { p1 = new Vector3(x, heightMap.GetHeight(x, z + 1), z + 1); p2 = new Vector3(x + 1, heightMap.GetHeight(x + 1, z), z); } return new Vector4(Vector3.Normalize(Vector3.Cross(p1 - currentP, p2 - currentP)), 1); }
/// <summary> /// Initializes Vertex buffer data by a given height map /// </summary> /// <param name="heightMap"></param> /// <param name="vertexBuffer"></param> private static unsafe void SetVertexDataFromHeightMap(HeightMap heightMap, IntPtr vertexBuffer) { var vb = (VertexNormalTexture*)vertexBuffer; var halfSize = heightMap.Size * 0.5f; for (var iZ = 0; iZ < heightMap.Size; ++iZ) for (var iX = 0; iX < heightMap.Size; ++iX) { vb[iZ * heightMap.Size + iX] = new VertexNormalTexture { Position = new Vector4(iX - halfSize, heightMap.GetHeight(iX, iZ), -iZ + halfSize, 1), Normal = GetNormalVector(heightMap, iX, iZ), TextureCoordinate = new Vector2((float)iX / heightMap.Size, (float)iZ / heightMap.Size) }; } }
/// <summary> /// Initializes Vertex and Index buffers with a given height map /// </summary> /// <param name="heightMap"></param> private void InitializeBuffersFromTerrain(HeightMap heightMap) { var commandList = Game.GraphicsContext.CommandList; // Set data in VertexBuffer var mappedSubResource = commandList.MapSubresource(terrainVertexBuffer, 0, MapMode.WriteDiscard); SetVertexDataFromHeightMap(heightMap, mappedSubResource.DataBox.DataPointer); commandList.UnmapSubresource(mappedSubResource); // Set data in IndexBuffer mappedSubResource = commandList.MapSubresource(terrainIndexBuffer, 0, MapMode.WriteDiscard); var elementCount = SetIndexDataForTerrain(heightMap.Size, mappedSubResource.DataBox.DataPointer); commandList.UnmapSubresource(mappedSubResource); terrainMesh.Draw.DrawCount = elementCount; }