using UnityEngine;
using System;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEditor;
namespace Gaia
{
///
/// Utility class for managing unity heightmaps. Height maps have allowable value range of 0..1f.
///
public class HeightMap
{
protected int m_widthX;
protected int m_depthZ;
protected float[,] m_heights;
protected bool m_isPowerOf2;
protected float m_widthInvX;
protected float m_depthInvZ;
protected float m_statMinVal;
protected float m_statMaxVal;
protected double m_statSumVals;
protected bool m_isDirty;
protected byte[] m_metaData = new byte[0];
#region Constructors
///
/// Default constructor
///
public HeightMap()
{
Reset();
}
///
/// Construct a heightmap with given width and height
///
/// Width of constructed heightmap
/// Height of constructed heightmap
public HeightMap(int width, int depth)
{
m_widthX = width;
m_depthZ = depth;
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_heights = new float[m_widthX, m_depthZ];
m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ);
m_statMinVal = m_statMaxVal = 0f;
m_statSumVals = 0;
m_metaData = new byte[0];
m_isDirty = false;
}
///
/// Create a heightmap from a float array
///
/// Source array
public HeightMap(float[,] source)
{
m_widthX = source.GetLength(0);
m_depthZ = source.GetLength(1);
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_heights = new float[m_widthX, m_depthZ];
m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ);
m_statMinVal = m_statMaxVal = 0f;
m_statSumVals = 0;
m_metaData = new byte[0];
Buffer.BlockCopy(source, 0, m_heights, 0, m_widthX * m_depthZ * sizeof(float));
m_isDirty = false;
}
///
/// Create a height mape from the particular slice passed in
///
/// Height map arrays
/// The slice to use
public HeightMap(float[,,] source, int slice)
{
m_widthX = source.GetLength(0);
m_depthZ = source.GetLength(1);
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_heights = new float[m_widthX, m_depthZ];
m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ);
m_statMinVal = m_statMaxVal = 0f;
m_statSumVals = 0;
m_metaData = new byte[0];
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = source[x, z, slice];
}
}
m_isDirty = false;
}
///
/// Create a heightmap from an int array - beware of precision issues
///
/// Source array
public HeightMap(int[,] source)
{
m_widthX = source.GetLength(0);
m_depthZ = source.GetLength(1);
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_heights = new float[m_widthX, m_depthZ];
m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ);
m_statMinVal = m_statMaxVal = 0f;
m_statSumVals = 0;
m_metaData = new byte[0];
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = (float)source[x, z];
}
}
m_isDirty = false;
}
///
/// Create a height map that is a copy of another heightmap
///
/// Source heightmap
public HeightMap(HeightMap source)
{
Reset();
m_widthX = source.m_widthX;
m_depthZ = source.m_depthZ;
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_heights = new float[m_widthX, m_depthZ];
m_isPowerOf2 = source.m_isPowerOf2;
SetMetaData(source.m_metaData);
Buffer.BlockCopy(source.m_heights, 0, m_heights, 0, m_widthX * m_depthZ * sizeof(float));
m_isDirty = false;
}
///
/// Create a heightmap by reading in and processing a binary file
///
/// File to read in
public HeightMap(string sourceFile)
{
Reset();
LoadFromBinaryFile(sourceFile);
m_isDirty = false;
}
///
/// Create a heightmap by reading in and processing the byte array
///
/// Source as a byte array
public HeightMap(byte[] sourceBytes)
{
Reset();
LoadFromByteArray(sourceBytes);
m_isDirty = false;
}
#endregion
#region Data access
///
/// Get width of the height map (x component)
///
/// Height map width
public int Width()
{
return m_widthX;
}
///
/// Get depth or height of the height map (z component)
///
/// Height map depth
public int Depth()
{
return m_depthZ;
}
///
/// Get min value - need to call update stats before calling this
///
/// Min value
public float MinVal()
{
return m_statMinVal;
}
///
/// Get max value - need to call update stats before calling this
///
/// Max value
public float MaxVal()
{
return m_statMaxVal;
}
///
/// Get sum of values - need to call update stats before calling this
///
/// Sum of values
public double SumVal()
{
return m_statSumVals;
}
///
/// Get metadata
///
///
public byte[] GetMetaData()
{
return m_metaData;
}
///
/// Get dirty flag ie we have been modified
///
///
public bool IsDirty()
{
return m_isDirty;
}
///
/// Set dirty flag
///
///
///
public void SetDirty(bool dirty = true)
{
m_isDirty = dirty;
}
///
/// Clear the dirty flag
///
public void ClearDirty()
{
m_isDirty = false;
}
///
/// Set metadata
///
/// The metadata to set
public void SetMetaData(byte[] metadata)
{
m_metaData = new byte[metadata.Length];
Buffer.BlockCopy(metadata, 0, m_metaData, 0, metadata.Length);
m_isDirty = true;
}
///
/// Get height map heights
///
/// Height map heights
public float[,] Heights()
{
return m_heights;
}
///
/// Get the heights as a 1D Array
///
/// Heights as a 1D array
public float[] Heights1D()
{
float[] result = new float[m_widthX * m_depthZ];
Buffer.BlockCopy(m_heights, 0, result, 0, result.Length * sizeof(float));
return result;
}
///
/// Set the array from the content of the supplied 1D array = assume its set up to be correct length
///
///
public void SetHeights(float[] heights)
{
int size = (int)Mathf.Sqrt((float)heights.Length);
if (size != m_widthX || size != m_depthZ)
{
Debug.LogError("SetHeights: Heights do not match. Aborting.");
return;
}
Buffer.BlockCopy(heights, 0, m_heights, 0, heights.Length * sizeof(float));
m_isDirty = true;
}
///
/// Copy the content of the supplied array into this array
///
///
public void SetHeights(float[,] heights)
{
if (m_widthX != heights.GetLength(0) || m_depthZ != heights.GetLength(1))
{
Debug.LogError("SetHeights: Sizes do not match. Aborting.");
return;
}
int size = heights.GetLength(0) * heights.GetLength(1);
Buffer.BlockCopy(heights, 0, m_heights, 0, size * sizeof(float));
m_isDirty = true;
}
///
/// Get height at the given location. If out of bounds will return nearest border.
///
/// x location
/// z location
///
public float GetSafeHeight(int x, int z)
{
if (x < 0) x = 0;
if (z < 0) z = 0;
if (x >= m_widthX) x = m_widthX - 1;
if (z >= m_depthZ) z = m_depthZ - 1;
return m_heights[x, z];
}
///
/// Set height at the given location. If out of bounds will set at nearest border.
///
/// x location
/// z location
///
public void SetSafeHeight(int x, int z, float height)
{
if (x < 0) x = 0;
if (z < 0) z = 0;
if (x >= m_widthX) x = m_widthX - 1;
if (z >= m_depthZ) z = m_depthZ - 1;
m_heights[x, z] = height;
m_isDirty = true;
}
///
/// Get the interpolated height at the given location
///
/// x location, range 0..1
/// z location, range 0..1
/// Interpolated height
protected float GetInterpolatedHeight(float x, float z)
{
//Scale it
x *= m_widthX;
z *= m_depthZ;
//Convert and handle the '1.0' scenario
int xR0 = (int)(x);
if (xR0 == m_widthX)
{
xR0 = m_widthX - 1;
}
int zR0 = (int)(z);
if (zR0 == m_depthZ)
{
zR0 = m_depthZ - 1;
}
int xR1 = xR0 + 1;
int zR1 = zR0 + 1;
if (xR1 == m_widthX)
{
xR1 = xR0;
}
if (zR1 == m_depthZ)
{
zR1 = zR0;
}
float dx = x - xR0;
float dz = z - zR0;
float omdx = 1f - dx;
float omdz = 1f - dz;
return omdx * omdz * m_heights[xR0, zR0] +
omdx * dz * m_heights[xR0, zR1] +
dx * omdz * m_heights[xR1, zR0] +
dx * dz * m_heights[xR1, zR1];
}
///
/// Get and set the height at the given location
///
/// x location
/// z location
/// Height at that location
public float this[int x, int z]
{
get
{
return m_heights[x, z];
}
set
{
m_heights[x, z] = value;
m_isDirty = true;
}
}
///
/// Get and set the height at the location
///
/// x location in 0..1f
/// z location in 0..1f
/// Height at that location
public float this[float x, float z]
{
get
{
return GetInterpolatedHeight(x, z);
}
set
{
//Convert and handle the '1.0' scenario
x *= m_widthX;
z *= m_depthZ;
int xR = (int)(x);
if (xR == m_widthX)
{
xR = m_widthX-1;
}
int zR = (int)(z);
if (zR == m_depthZ)
{
zR = m_depthZ - 1;
}
m_heights[xR, zR] = value;
m_isDirty = true;
}
}
///
/// Set the level of the entire map to the supplied value
///
/// This
public HeightMap SetHeight(float height)
{
float newLevel = Gaia.GaiaUtils.Math_Clamp(0f, 1f, height);
for (int hmX = 0; hmX < m_widthX; hmX++)
{
for (int hmZ = 0; hmZ < m_depthZ; hmZ++)
{
m_heights[hmX, hmZ] = newLevel;
}
}
m_isDirty = true;
return this;
}
///
/// Get the height rnage for this map
///
/// Minimum height
/// Maximum height
public void GetHeightRange(ref float minHeight, ref float maxHeight)
{
float currHeight;
maxHeight = float.MinValue;
minHeight = float.MaxValue;
for (int hmX = 0; hmX < m_widthX; hmX++)
{
for (int hmZ = 0; hmZ < m_depthZ; hmZ++)
{
currHeight = m_heights[hmX, hmZ];
if (currHeight > maxHeight)
{
maxHeight = currHeight;
}
if (currHeight < minHeight)
{
minHeight = currHeight;
}
}
}
}
///
/// Get the slope at the designated location
///
/// x location
/// z location
/// Steepness at tha location
public float GetSlope(int x, int z)
{
float height = m_heights[x, z];
// Compute the differentials by stepping 1 in both directions.
float dx = m_heights[x + 1, z] - height;
float dy = m_heights[x, z + 1] - height;
// The "steepness" is the magnitude of the gradient vector,
// For a faster but not as accurate computation, you can just use abs(dx) + abs(dy)
return (float)Math.Sqrt(dx * dx + dy * dy);
}
///
/// Get the slope at the designated location
///
/// x location in range 0..1
/// z location in range 0..1
/// Steepness
public float GetSlope(float x, float z)
{
float dX = (GetInterpolatedHeight(x + (m_widthInvX * 0.9f), z) - GetInterpolatedHeight(x - (m_widthInvX * 0.9f), z));
float dZ = (GetInterpolatedHeight(x, z + (m_depthInvZ * 0.9f)) - GetInterpolatedHeight(x, (z - m_depthInvZ * 0.9f)));
//float direction = (float)Math.Atan2(deltaZ, deltaX);
return Gaia.GaiaUtils.Math_Clamp(0f, 90f, (float)(Math.Sqrt((dX * dX) + (dZ * dZ)) * 10000));
}
///
/// Get the slope at the designated location
///
/// x location in range 0..1
/// z location in range 0..1
/// Steepness
public float GetSlope_a(float x, float z)
{
float center = GetInterpolatedHeight(x, z);
float dTop = Math.Abs(GetInterpolatedHeight(x - m_widthInvX, z) - center);
float dBot = Math.Abs(GetInterpolatedHeight(x + m_widthInvX, z) - center);
float dLeft = Math.Abs(GetInterpolatedHeight(x, z - m_depthInvZ) - center);
float dRight = Math.Abs(GetInterpolatedHeight(x, z + m_depthInvZ) - center);
return ((dTop + dBot + dLeft + dRight) / 4f) * 400f;
}
///
/// Get the highest point around the edges of the heightmap - this is used as base level by scanner
///
/// Base level
public float GetBaseLevel()
{
float baseLevel = 0f;
for (int x = 0; x < m_widthX; x++)
{
if (m_heights[x, 0] > baseLevel)
{
baseLevel = m_heights[x, 0];
}
if (m_heights[x, m_depthZ-1] > baseLevel)
{
baseLevel = m_heights[x, m_depthZ - 1];
}
}
for (int z = 0; z < m_depthZ; z++)
{
if (m_heights[0, z] > baseLevel)
{
baseLevel = m_heights[0, z];
}
if (m_heights[m_widthX-1, z] > baseLevel)
{
baseLevel = m_heights[m_widthX - 1, z];
}
}
#if UNITY_EDITOR
SceneView.lastActiveSceneView.Repaint();
#endif
return baseLevel;
}
///
/// Return true if we have data, false otherwise
///
/// True if we have data, false otehrwise
public bool HasData()
{
if (m_widthX <= 0 || m_depthZ <= 0)
{
return false;
}
if (m_heights == null)
{
return false;
}
if (m_heights.GetLength(0) != m_widthX || m_heights.GetLength(1) != m_depthZ)
{
return false;
}
return true;
}
///
/// Get a specific row
///
/// Row to get
/// Content of the row
public float[] GetRow(int rowX)
{
float [] row = new float[m_depthZ];
for (int z = 0; z < m_depthZ; z++)
{
row[z] = m_heights[rowX, z];
}
return row;
}
///
/// Set the content of a specific row
///
/// Row to set
/// Values to set
public void SetRow(int rowX, float [] values)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[rowX, z] = values[z];
}
}
///
/// Get a specific column
///
/// Column to get
/// Content of the column
public float[] GetColumn(int columnZ)
{
float[] col = new float[m_widthX];
for (int x = 0; x < m_widthX; x++)
{
col[x] = m_heights[x, columnZ];
}
return col;
}
///
/// Set the content of a specific column
///
/// Column to set
/// Values to set
public void SetColumn(int columnZ, float[] values)
{
for (int x = 0; x< m_widthX; x++)
{
m_heights[x, columnZ] = values[x];
}
}
#endregion
#region Operations
///
/// Reset the heightmap including all stats
///
public void Reset()
{
m_widthX = m_depthZ = 0;
m_widthInvX = m_depthInvZ = 0f;
m_heights = null;
m_statMinVal = m_statMaxVal = 0f;
m_statSumVals = 0;
m_metaData = new byte[0];
m_heights = new float[0, 0];
m_isDirty = false;
}
///
/// Update heightmap stats
///
public void UpdateStats()
{
m_statMinVal = 1f;
m_statMaxVal = 0f;
m_statSumVals = 0;
float height = 0f;
for (int hmX = 0; hmX < m_widthX; hmX++)
{
for (int hmZ = 0; hmZ < m_depthZ; hmZ++)
{
height = m_heights[hmX, hmZ];
if (height < m_statMinVal)
{
m_statMinVal = height;
}
if (height > m_statMaxVal)
{
m_statMaxVal = height;
}
m_statSumVals += height;
}
}
}
///
/// Smooth the height map
///
/// Number of iterations of smoothing to run
/// This
public HeightMap Smooth(int iterations)
{
for (int i = 0; i < iterations; i++ )
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = Gaia.GaiaUtils.Math_Clamp(0f, 1f, (GetSafeHeight(x - 1, z) + GetSafeHeight(x + 1, z) + GetSafeHeight(x, z - 1) + GetSafeHeight(x, z + 1)) / 4f);
}
}
}
m_isDirty = true;
return this;
}
///
/// Smooth in a given radius
///
/// Smoothing radius
/// This
public HeightMap SmoothRadius(int radius)
{
radius = Mathf.Max(5, radius);
HeightMap filter = new HeightMap(m_widthX, m_depthZ);
float factor = 1f / ((2 * radius + 1) * (2 * radius + 1));
for (int y = 0; y < m_depthZ; y++)
{
for (int x = 0; x < m_widthX; x++)
{
filter[x, y] = factor * m_heights[x,y];
}
}
for (int x = radius; x < m_widthX - radius; x++)
{
int y = radius;
float sum = 0f;
for (int i = -radius; i < radius + 1; i++)
{
for (int j = -radius; j < radius + 1; j++)
{
sum += filter[x + j, y + i];
}
}
for (y++; y < m_depthZ - radius; y++)
{
for (int j = -radius; j < radius + 1; j++)
{
sum -= filter[x + j, y - radius - 1];
sum += filter[x + j, y + radius];
}
m_heights[x, y] = sum;
}
}
m_isDirty = true;
return this;
}
///
/// Applies the kernel to the array
///
/// The kernel to apply
/// Convolved array
public HeightMap Convolve(float[,] kernel)
{
int xr, yj;
float a, d, divisor = 0f;
int radius = Mathf.FloorToInt(kernel.GetLength(0) / 2f);
int kernelWidth = kernel.GetLength(0);
int kernelHeight = kernel.GetLength(1);
int kernelSize = kernelWidth * kernelHeight;
int processedKernelSize = 0;
//Calculate the divisor
for (int r = 0; r < kernelWidth; r++)
{
for (int j = 0; j < kernelHeight; j++)
{
divisor += kernel[r, j];
}
}
if (Gaia.GaiaUtils.Math_ApproximatelyEqual(divisor, 0f))
{
divisor = 1f;
}
for (int x = 0; x < m_widthX; x++)
{
for (int y = 0; y < m_depthZ; y++)
{
a = 0f;
d = 0f;
processedKernelSize = 0;
for (int r = -radius; r <= radius; r++)
{
xr = x + r;
if (xr < 0 || xr >= m_widthX)
{
continue;
}
for (int j = -radius; j <= radius; j++)
{
yj = y + j;
if (yj < 0 || yj >= m_depthZ)
{
continue;
}
float k = kernel[r + radius, j + radius];
d += k;
a += (m_heights[xr, yj] * k);
processedKernelSize++;
}
}
if (processedKernelSize != kernelSize)
{
// if (!PWCommon.Utils.Math_ApproximatelyEqual(d, 0f))
// {
// m_heights[x, y] = a / d;
// }
// else
// {
// m_heights[x, y] = a / divisor;
// }
}
else
{
m_heights[x, y] = Mathf.Clamp01(a / divisor);
}
}
}
m_isDirty = true;
return this;
}
///
/// Denoises the array - removes small lows and highs - becomes less sensitive the greater
/// the radius is - 1 is generlly good usage.
///
/// Radius of the denoise check
/// Denoised array
public HeightMap DeNoise(int radius)
{
float min, max, v = 0f;
for (int x = radius; x < m_widthX - radius; x++)
{
for (int y = radius; y < m_depthZ - radius; y++)
{
min = float.MaxValue;
max = float.MinValue;
for (int r = -radius; r <= radius; r++)
{
for (int j = -radius; j <= radius; j++)
{
if (!(r == 0 && j == 0))
{
v = m_heights[x + r, y + j];
if (v < min)
{
min = v;
}
if (v > max)
{
max = v;
}
}
}
}
v = m_heights[x, y];
if (v > max)
{
m_heights[x, y] = max;
}
else if (v < min)
{
m_heights[x, y] = min;
}
}
}
m_isDirty = true;
return this;
}
///
/// Grows the features in the array
///
/// Radius of the growth check
/// Grown array
public HeightMap GrowEdges(int radius)
{
int xr, yj;
float min, max, v = 0f;
for (int x = 0; x < m_widthX; x++)
{
for (int y = 0; y < m_depthZ; y++)
{
min = float.MaxValue;
max = float.MinValue;
for (int r = -radius; r <= radius; r++)
{
for (int j = -radius; j <= radius; j++)
{
xr = x + r;
if (xr < 0 || xr >= m_widthX)
{
continue;
}
if (!(r == 0 && j == 0))
{
yj = y + j;
if (yj < 0 || yj >= m_depthZ)
{
continue;
}
v = m_heights[xr, yj];
if (v < min)
{
min = v;
}
if (v > max)
{
max = v;
}
}
}
}
v = m_heights[x, y];
if (max > v)
{
m_heights[x, y] = (max + v) / 2f;
}
}
}
m_isDirty = true;
return this;
}
///
/// Shrinks the features in the array - also makes nice erosion effect
///
/// Radius of the shrink check
/// Shrunk array
public HeightMap ShrinkEdges(int radius)
{
int xr, yj;
float min, max, v = 0f;
for (int x = 0; x < m_widthX; x++)
{
for (int y = 0; y < m_depthZ; y++)
{
min = float.MaxValue;
max = float.MinValue;
for (int r = -radius; r <= radius; r++)
{
xr = x + r;
if (xr < 0 || xr == m_widthX)
{
continue;
}
for (int j = -radius; j <= radius; j++)
{
if (!(r == 0 && j == 0))
{
yj = y + j;
if (yj < 0 || yj >= m_depthZ)
{
continue;
}
v = m_heights[xr, yj];
if (v < min)
{
min = v;
}
if (v > max)
{
max = v;
}
}
}
}
v = m_heights[x, y];
if (min < v)
{
m_heights[x, y] = (min + v) / 2f;
}
}
}
m_isDirty = true;
return this;
}
///
/// Return a new heightmap where each point at contains the slopes of this heightmap at that point
///
///
public HeightMap GetSlopeMap()
{
HeightMap slopeMap = new HeightMap(this);
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
slopeMap[x, z] = GetSlope(x, z);
}
}
return slopeMap;
}
public enum CopyType { AlwaysCopy, CopyIfLessThan, CopyIfGreaterThan}
///
/// Copy the supplied heightmap
///
/// Heightmap to copy
/// Always - always copy, CopyIfLessThan - copy new value if less than current value, CopyIfGreaterThan - copy new value if greater than current value.
/// /// This
public HeightMap Copy(HeightMap heightMap, CopyType copyType = CopyType.AlwaysCopy)
{
if (copyType == CopyType.AlwaysCopy)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = heightMap[m_widthInvX * x, m_depthInvZ * z];
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = heightMap.m_heights[x, z];
}
}
}
}
else if (copyType == CopyType.CopyIfLessThan)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float h0 = m_heights[x, z];
float h1 = heightMap[m_widthInvX * x, m_depthInvZ * z];
if (h1 < h0)
{
m_heights[x, z] = h1;
}
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float h0 = m_heights[x, z];
float h1 = heightMap[x, z];
if (h1 < h0)
{
m_heights[x, z] = h1;
}
}
}
}
}
else if (copyType == CopyType.CopyIfGreaterThan)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float h0 = m_heights[x, z];
float h1 = heightMap[m_widthInvX * x, m_depthInvZ * z];
if (h1 > h0)
{
m_heights[x, z] = h1;
}
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float h0 = m_heights[x, z];
float h1 = heightMap[x, z];
if (h1 > h0)
{
m_heights[x, z] = h1;
}
}
}
}
}
m_isDirty = true;
return this;
}
///
/// Copy the source heightmap and clamp it
///
/// Heightmap to copy
/// Min value to clamp it to
/// Max value to clamp it to
/// This
public HeightMap CopyClamped(HeightMap heightMap, float min, float max)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = heightMap[m_widthInvX * x, m_depthInvZ * z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = heightMap.m_heights[x, z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
m_isDirty = true;
return this;
}
///
/// Duplicate this heightmap and return a new onw
///
/// A new heightmap which is a direct duplicate of this one
public HeightMap Duplicate()
{
return new HeightMap(this);
}
///
/// Invert the heightmap
///
/// This
public HeightMap Invert()
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = 1f - m_heights[x, z];
}
}
m_isDirty = true;
return this;
}
///
/// Flip the heightmap
///
/// This
public HeightMap Flip()
{
float[,] heights = new float[m_depthZ, m_widthX];
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
heights[z, x] = m_heights[x, z];
}
}
m_heights = heights;
m_widthX = heights.GetLength(0);
m_depthZ = heights.GetLength(1);
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ);
m_statMinVal = m_statMaxVal = 0f;
m_statSumVals = 0;
m_isDirty = true;
return this;
}
///
/// Normalise the heightmap
///
/// This
public HeightMap Normalise()
{
float height;
float maxHeight = float.MinValue;
float minHeight = float.MaxValue;
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
height = m_heights[x, z];
if (height > maxHeight)
{
maxHeight = height;
}
if (height < minHeight)
{
minHeight = height;
}
}
}
float heightRange = maxHeight - minHeight;
if (heightRange > 0f)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = (m_heights[x, z] - minHeight) / heightRange;
}
}
m_isDirty = true;
}
return this;
}
///
/// Export the given heightmap as a texture
///
///
public Texture2D ToTexture()
{
Texture2D returnTexture = new Texture2D(m_widthX, m_depthZ, TextureFormat.RGBAFloat, false);
var pixels = returnTexture.GetPixels(0, 0, returnTexture.width, returnTexture.height);
int i = 0;
for (int hmZ = 0; hmZ < m_depthZ; hmZ++)
{
for (int hmX = 0; hmX < m_widthX; hmX++)
{
float colorValue = m_heights[hmX, hmZ];
pixels[i] = new Color(colorValue, colorValue, colorValue);
i++;
}
}
returnTexture.SetPixels(pixels);
returnTexture.Apply();
GaiaUtils.MakeTextureReadable(returnTexture);
return returnTexture;
}
/*
///
/// Thermally erode the heightmap
///
///
///
///
public HeightMap Erode(float min_threshold, float max_threshold, int iterations)
{
if (min_threshold < 0f || min_threshold >= 1f || max_threshold <= 0f && max_threshold > 1f || min_threshold >= max_threshold)
{
Debug.Log("Invalid erosion values");
return this;
}
HeightMap diff = new HeightMap(m_widthX, m_depthZ);
for (int i = 0; i < iterations; i++)
{
// material transport
for (int x = 1; x < m_widthX - 1; x++)
{
for (int z = 1; z < m_depthZ - 1; z++)
{
// calculate height differences
float h = m_heights[x, z];
float h1 = m_heights[x, z + 1];
float h2 = m_heights[x - 1, z];
float h3 = m_heights[x + 1, z];
float h4 = m_heights[x, z - 1];
float d1 = h - h1;
float d2 = h - h2;
float d3 = h - h3;
float d4 = h - h4;
// find greatest height difference
float max_diff = float.MinValue;
float total_diff = 0f;
if (d1 > 0f)
{
total_diff += d1;
if (d1 > max_diff)
{
max_diff = d1;
}
}
if (d2 > 0f)
{
total_diff += d2;
if (d2 > max_diff)
{
max_diff = d2;
}
}
if (d3 > 0f)
{
total_diff += d3;
if (d3 > max_diff)
{
max_diff = d3;
}
}
if (d4 > 0f)
{
total_diff += d4;
if (d4 > max_diff)
{
max_diff = d4;
}
}
// skip if outside talus threshold
if (max_diff < min_threshold || max_diff > max_threshold)
{
continue;
}
max_diff = max_diff / 2f;
float factor = max_diff / total_diff;
diff[x, z] = diff[x, z] - max_diff;
// transport material
if (d1 > 0)
diff[x, z + 1] = diff[x, z + 1] + factor * d1;
if (d2 > 0)
diff[x - 1, z] = diff[x - 1, z] + factor * d2;
if (d3 > 0)
diff[x + 1, z] = diff[x + 1, z] + factor * d3;
if (d4 > 0)
diff[x, z - 1] = diff[x, z - 1] + factor * d4;
}
}
// apply changes
Add(diff);
diff.SetHeight(0f);
//Set dirty
m_isDirty = true;
}
return this;
}
///
/// Thermally erode the heightmap
///
/// Number of iterations
/// Minimum erosion threshold
/// Maximum erosion threshold
public HeightMap ErodeThermal_1(int iterations, float min_threshold, float max_threshold)
{
//Check thresholds
if (min_threshold < 0f || min_threshold > 1f || max_threshold < 0f && max_threshold > 1f || min_threshold >= max_threshold)
{
Debug.Log("Invalid erosion thresholds");
return this;
}
HeightMap diff = new HeightMap(m_widthX, m_depthZ);
for (int i = 0; i < iterations; i++)
{
// material transport
for (int x = 1; x < m_widthX - 1; x++)
{
for (int z = 1; z < m_depthZ - 1; z++)
{
// calculate height differences
float h = m_heights[x, z];
float h1 = m_heights[x, z + 1];
float h2 = m_heights[x - 1, z];
float h3 = m_heights[x + 1, z];
float h4 = m_heights[x, z - 1];
float d1 = h - h1;
float d2 = h - h2;
float d3 = h - h3;
float d4 = h - h4;
// find greatest height difference
float max_diff = float.MinValue;
float total_diff = 0f;
if (d1 > 0f)
{
total_diff += d1;
if (d1 > max_diff)
{
max_diff = d1;
}
}
if (d2 > 0f)
{
total_diff += d2;
if (d2 > max_diff)
{
max_diff = d2;
}
}
if (d3 > 0f)
{
total_diff += d3;
if (d3 > max_diff)
{
max_diff = d3;
}
}
if (d4 > 0f)
{
total_diff += d4;
if (d4 > max_diff)
{
max_diff = d4;
}
}
// skip if outside talus threshold
if (max_diff < min_threshold || max_diff > max_threshold)
{
continue;
}
max_diff = max_diff / 2f;
float factor = max_diff / total_diff;
diff[x, z] = diff[x, z] - max_diff;
// transport material
if (d1 > 0)
diff[x, z + 1] = diff[x, z + 1] + factor * d1;
if (d2 > 0)
diff[x - 1, z] = diff[x - 1, z] + factor * d2;
if (d3 > 0)
diff[x + 1, z] = diff[x + 1, z] + factor * d3;
if (d4 > 0)
diff[x, z - 1] = diff[x, z - 1] + factor * d4;
}
}
// apply changes
AddClamped(diff, 0f, 1f);
// reset diff
diff.SetHeight(0f);
//Set dirty
m_isDirty = true;
}
return this;
}
*/
///
/// Run a thermal erosion pass
///
///
///
///
///
///
public HeightMap ErodeThermal(int iterations, float talusMin, float talusMax, HeightMap hardnessMask)
{
float h, h1, h2, h3, h4, d1, d2, d3, d4, max_d;
int i, j;
for (int iter = 0; iter < iterations; iter++)
{
for (int x = 1; x < m_widthX - 1; x++)
{
for (int z = 1; z < m_depthZ - 1; z++)
{
h = m_heights[x, z];
h1 = m_heights[x, z + 1];
h2 = m_heights[x - 1, z];
h3 = m_heights[x + 1, z];
h4 = m_heights[x, z - 1];
d1 = h - h1;
d2 = h - h2;
d3 = h - h3;
d4 = h - h4;
i = 0;
j = 0;
max_d = 0f;
if (d1 > max_d)
{
max_d = d1;
j = 1;
}
if (d2 > max_d)
{
max_d = d2;
i = -1;
j = 0;
}
if (d3 > max_d)
{
max_d = d3;
i = 1;
j = 0;
}
if (d4 > max_d)
{
max_d = d4;
i = 0;
j = -1;
}
if (max_d < talusMin || max_d > talusMax)
{
continue;
}
max_d *= (1f - hardnessMask[m_widthInvX * x, m_depthInvZ * z]);
max_d *= 0.5f;
m_heights[x, z] = m_heights[x, z] - max_d;
m_heights[x + i, z + j] = m_heights[x + i, z + j] + max_d;
m_isDirty = true;
}
}
}
return this;
}
/*
///
/// Hydraulic erosion
///
/// Number of iterations to run
/// Rain map - added every rain freq intervc
/// Frequency between iterations that rain will fall
/// Max amount of sediment disolved from underlying heightmap every iteration - 0f..1f = 1f = all height moved
/// This
public HeightMap ErodeHydraulic_0(int iterations, HeightMap rainMap, int rainFrequency, float sedimentDisolveRate)
{
HeightMap waterMap = new HeightMap(m_widthX, m_depthZ);
HeightMap waterMapDiff = new HeightMap(m_widthX, m_depthZ);
HeightMap sedimentMap = new HeightMap(m_widthX, m_depthZ);
HeightMap sedimentMapDiff = new HeightMap(m_widthX, m_depthZ);
//Consume all water over the duration of the rain fall
float transferRate = 1f / (float)rainFrequency;
for (int i = 0; i < iterations; i++)
{
// simulate rain - add water to water map
if (i % rainFrequency == 0)
{
waterMap.Add(rainMap);
}
//Sediment and rain - energy must be conserved - so rain in == rain out, hm new = hm orig + sediment map-> therefore move sediment in 1 step based on erosion and height
// calculate water and sediment
for (int x = 1; x < m_widthX - 1; x++)
{
for (int z = 1; z < m_depthZ - 1; z++)
{
// get relative values at this location
float cellHeight = m_heights[x, z];
float cellWater = waterMap[x, z];
//float cellSediment = sedimentMap[x, z];
//float totalCellHeight = cellHeight + cellWater + cellSediment;
// determine the magnitude of the drop around the location
float deltaHeight = 0;
int affectedCells = 0;
for (int dX = x-1; dX <= x+1; dX++)
{
for (int dZ = z-1; dZ <= z+1; dZ++)
{
float localDelta = cellHeight - m_heights[dX, dZ];
if (localDelta > 0f)
{
deltaHeight += localDelta;
affectedCells++;
}
}
}
//Move water and sediment if we can
if (affectedCells > 0)
{
// add in a batch of newly dissolved sediment - theory - greater height difference = greater water flow = more sediment generation
float newSediment = cellWater * deltaHeight * sedimentDisolveRate;
// remove sediment from current location
sedimentMapDiff[x, z] -= newSediment;
// remove water from current location
waterMapDiff[x, z] -= cellWater * transferRate;
// push sediment and water to new locations
for (int dX = x - 1; dX <= x + 1; dX++)
{
for (int dZ = z - 1; dZ <= z + 1; dZ++)
{
float localDelta = cellHeight - m_heights[dX, dZ];
if (localDelta > 0f)
{
float flowStrength = localDelta/deltaHeight;
//Move a percentge of water from current location to new location
waterMapDiff[dX, dZ] += cellWater * flowStrength * transferRate;
//Move the sediment to its new location
sedimentMapDiff[dX, dZ] += newSediment * flowStrength;
}
}
}
}
}
}
// apply changes to water map
waterMap.Add(waterMapDiff);
// apply changes to sediment map
sedimentMap.Add(sedimentMapDiff);
// apply changes to height map
AddClamped(sedimentMapDiff, 0f, 1f);
// water vaporization
waterMap.SubtractClamped(transferRate, 0f, 1f);
// clear diff maps
waterMapDiff.SetHeight(0f);
sedimentMapDiff.SetHeight(0f);
//Set dirty
m_isDirty = true;
}
return this;
}
///
/// Hydraulic erosion
///
/// Number of iterations to run
/// Rain map - added every rain freq intervc
/// Frequency between iterations that rain will fall
/// Max amount of sediment disolved from underlying heightmap every iteration - 0f..1f = 1f = all height moved
/// This
public HeightMap ErodeHydraulic_1(int iterations, HeightMap rainMap, int rainFrequency, float sedimentDisolveRate)
{
HeightMap waterMap = new HeightMap(m_widthX, m_depthZ);
HeightMap velocityMap = new HeightMap(m_widthX, m_depthZ);
HeightMap hardnessMap = new HeightMap(m_widthX, m_depthZ);
HeightMap waterMapDiff = new HeightMap(m_widthX, m_depthZ);
HeightMap sedimentMap = new HeightMap(m_widthX, m_depthZ);
HeightMap sedimentMapDiff = new HeightMap(m_widthX, m_depthZ);
//Consume all water over the duration of the rain fall
float transferRate = 1f / (float)rainFrequency;
for (int i = 0; i < iterations; i++)
{
// simulate rain - add water to water map
if (i % rainFrequency == 0)
{
waterMap.Add(rainMap);
}
//Sediment and rain - energy must be conserved - so rain in == rain out,
//hm new = hm orig + sediment map-> therefore move sediment in 1 step based on erosion and height
//caclulate velocity
// calculate water and sediment
for (int x = 1; x < m_widthX - 1; x++)
{
for (int z = 1; z < m_depthZ - 1; z++)
{
// get relative values at this location
float cellHeight = m_heights[x, z];
float cellWater = waterMap[x, z];
//float cellSediment = sedimentMap[x, z];
//float totalCellHeight = cellHeight + cellWater + cellSediment;
// determine the magnitude of the drop around the location
float deltaHeight = 0;
int affectedCells = 0;
for (int dX = x - 1; dX <= x + 1; dX++)
{
for (int dZ = z - 1; dZ <= z + 1; dZ++)
{
float localDelta = cellHeight - m_heights[dX, dZ];
if (localDelta > 0f)
{
deltaHeight += localDelta;
affectedCells++;
}
}
}
//Move water and sediment if we can
if (affectedCells > 0)
{
// add in a batch of newly dissolved sediment - theory - greater height difference = greater water flow = more sediment generation
float newSediment = cellWater * deltaHeight * sedimentDisolveRate;
// remove sediment from current location
sedimentMapDiff[x, z] -= newSediment;
// remove water from current location
waterMapDiff[x, z] -= cellWater * transferRate;
// push sediment and water to new locations
for (int dX = x - 1; dX <= x + 1; dX++)
{
for (int dZ = z - 1; dZ <= z + 1; dZ++)
{
float localDelta = cellHeight - m_heights[dX, dZ];
if (localDelta > 0f)
{
float flowStrength = localDelta / deltaHeight;
//Move a percentge of water from current location to new location
waterMapDiff[dX, dZ] += cellWater * flowStrength * transferRate;
//Move the sediment to its new location
sedimentMapDiff[dX, dZ] += newSediment * flowStrength;
}
}
}
}
}
}
// apply changes to water map
waterMap.Add(waterMapDiff);
// apply changes to sediment map
sedimentMap.Add(sedimentMapDiff);
// apply changes to height map
AddClamped(sedimentMapDiff, 0f, 1f);
// water vaporization
waterMap.SubtractClamped(transferRate, 0f, 1f);
// clear diff maps
waterMapDiff.SetHeight(0f);
sedimentMapDiff.SetHeight(0f);
//Set dirty
m_isDirty = true;
}
return this;
}
*/
///
/// Quantize the heightmap to mod's of this value
///
///
///
public HeightMap Quantize(float divisor)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = Mathf.Round(m_heights[x, z] / divisor) * divisor;
}
}
m_isDirty = true;
return this;
}
///
/// Quantize the heightmap to mod's of this value
///
///
///
///
public HeightMap Quantize(float [] startHeights, AnimationCurve [] curves)
{
int numTerraces = startHeights.GetLength(0);
if (numTerraces == 0)
{
Debug.LogWarning("Quantize : must supply heights!");
return this;
}
if (curves.GetLength(0) != numTerraces)
{
Debug.LogWarning("Quantize : startHeights and curves do not match!");
return this;
}
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
int terraceIdx = 0;
float startHeight = 0f;
float nextHeight = 1f;
float currHeight = m_heights[x, z];
for (terraceIdx = numTerraces-1; terraceIdx >= 0; terraceIdx--)
{
startHeight = startHeights[terraceIdx];
if (terraceIdx == numTerraces-1)
{
nextHeight = 1f;
}
else
{
nextHeight = startHeights[terraceIdx+1];
}
if (startHeight <= currHeight && currHeight <= nextHeight)
{
break;
}
}
m_heights[x, z] = startHeight + ((currHeight-startHeight) * curves[terraceIdx].Evaluate((currHeight - startHeight) / (nextHeight - startHeight)));
}
}
m_isDirty = true;
return this;
}
///
/// Calculate the curvature of this heightmap
///
/// Type of curvature to calculate
/// Curvature map
public HeightMap CurvatureMap(GaiaConstants.CurvatureType curvatureType)
{
float limit = 10000;
int height = m_depthZ;
int width = m_widthX;
float ux = 1.0f / (width - 1.0f);
float uy = 1.0f / (height - 1.0f);
float[] heights = this.Heights1D();
HeightMap cMap = this.Duplicate();
for (int z = 0; z < height; z++)
{
for (int x = 0; x < width; x++)
{
int xp1 = (x == width - 1) ? x : x + 1;
int xn1 = (x == 0) ? x : x - 1;
int yp1 = (z == height - 1) ? z : z + 1;
int yn1 = (z == 0) ? z : z - 1;
float v = heights[x + z * width];
float l = heights[xn1 + z * width];
float r = heights[xp1 + z * width];
float b = heights[x + yn1 * width];
float t = heights[x + yp1 * width];
float lb = heights[xn1 + yn1 * width];
float lt = heights[xn1 + yp1 * width];
float rb = heights[xp1 + yn1 * width];
float rt = heights[xp1 + yp1 * width];
float dx = (r - l) / (2.0f * ux);
float dy = (t - b) / (2.0f * uy);
float dxx = (r - 2.0f * v + l) / (ux * ux);
float dyy = (t - 2.0f * v + b) / (uy * uy);
float dxy = (rt - rb - lt + lb) / (4.0f * ux * uy);
float curve = 0.0f;
switch (curvatureType)
{
case GaiaConstants.CurvatureType.Horizontal:
curve = HorizontalCurve(limit, dx, dy, dxx, dyy, dxy);
break;
case GaiaConstants.CurvatureType.Vertical:
curve = VerticalCurve(limit, dx, dy, dxx, dyy, dxy);
break;
case GaiaConstants.CurvatureType.Average:
curve = AverageCurve(limit, dx, dy, dxx, dyy, dxy);
break;
}
cMap[x, z] = curve;
}
}
return cMap;
}
///
/// Horizontal curvature
///
private float HorizontalCurve(float limit, float dx, float dy, float dxx, float dyy, float dxy)
{
float kh = -2.0f * (dy * dy * dxx + dx * dx * dyy - dx * dy * dxy);
kh /= dx * dx + dy * dy;
if (float.IsInfinity(kh) || float.IsNaN(kh)) kh = 0.0f;
if (kh < -limit) kh = -limit;
if (kh > limit) kh = limit;
kh /= limit;
kh = kh * 0.5f + 0.5f;
return kh;
}
///
/// Vertical curvature
///
private float VerticalCurve(float limit, float dx, float dy, float dxx, float dyy, float dxy)
{
float kv = -2.0f * (dx * dx * dxx + dy * dy * dyy + dx * dy * dxy);
kv /= dx * dx + dy * dy;
if (float.IsInfinity(kv) || float.IsNaN(kv)) kv = 0.0f;
if (kv < -limit) kv = -limit;
if (kv > limit) kv = limit;
kv /= limit;
kv = kv * 0.5f + 0.5f;
return kv;
}
///
/// Average curvature
///
private float AverageCurve(float limit, float dx, float dy, float dxx, float dyy, float dxy)
{
float kh = HorizontalCurve(limit, dx, dy, dxx, dyy, dxy);
float kv = VerticalCurve(limit, dx, dy, dxx, dyy, dxy);
return (kh + kv) * 0.5f;
}
///
/// Create an aspect map
///
/// Type of aspect to create
/// Aspect map
public HeightMap Aspect(GaiaConstants.AspectType aspectType)
{
int height = m_depthZ;
int width = m_widthX;
float[] heights = this.Heights1D();
float ux = 1.0f / (width - 1.0f);
float uy = 1.0f / (height - 1.0f);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int xp1 = (x == width - 1) ? x : x + 1;
int xn1 = (x == 0) ? x : x - 1;
int yp1 = (y == height - 1) ? y : y + 1;
int yn1 = (y == 0) ? y : y - 1;
float l = heights[xn1 + y * width];
float r = heights[xp1 + y * width];
float b = heights[x + yn1 * width];
float t = heights[x + yp1 * width];
float dx = (r - l) / (2.0f * ux);
float dy = (t - b) / (2.0f * uy);
float m = Mathf.Sqrt(dx * dx + dy * dy);
float a = Mathf.Acos(-dy / m) * Mathf.Rad2Deg;
if (float.IsInfinity(a) || float.IsNaN(a))
a = 0.0f;
float aspect = 180.0f * (1.0f + Sign(dx)) - Sign(dx) * a;
switch (aspectType)
{
case GaiaConstants.AspectType.Northerness:
aspect = Mathf.Cos(aspect * Mathf.Deg2Rad);
aspect = aspect * 0.5f + 0.5f;
break;
case GaiaConstants.AspectType.Easterness:
aspect = Mathf.Sin(aspect * Mathf.Deg2Rad);
aspect = aspect * 0.5f + 0.5f;
break;
default:
aspect /= 360.0f;
break;
}
m_heights[x, y] = aspect;
}
}
m_isDirty = true;
return this;
}
///
/// Value based on sign
///
/// Value to check
///
private float Sign(float v)
{
if (v > 0) return 1;
if (v < 0) return -1;
return 0;
}
///
///
/// Number of iterations to run
/// Hardness map - influences rate of erosion
/// Rain map - added every rain freq intervc
/// Frequency between iterations that rain will fall
/// Max amount of sediment disolved from underlying heightmap every iteration - 0f..1f = 1f = all height moved
/// Sediment passed back to caller
/// This
public HeightMap ErodeHydraulic(int iterations, HeightMap hardnessMap, HeightMap rainMap, int rainFrequency, float sedimentDisolveRate, ref HeightMap sedimentMap)
{
HeightMap waterMap = new HeightMap(m_widthX, m_depthZ);
float[,,] waterOutFlow = new float[m_widthX, m_depthZ, 4];
HeightMap waterMapDiff = new HeightMap(m_widthX, m_depthZ);
HeightMap sedimentMapDiff = new HeightMap(m_widthX, m_depthZ);
//Consume all water over the duration of the rain fall
float transferRate = 1f / (float)rainFrequency;
for (int i = 0; i < iterations; i++)
{
// simulate rain - add water to water map
if (i % rainFrequency == 0)
{
waterMap.Add(rainMap);
}
// caculate outflow
CalculateWaterOutflow(this, waterMap, waterOutFlow);
// update the water map
UpdateWaterMap(waterMap, waterOutFlow);
// erode and move sediment
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
// get relative values at this location
float height = m_heights[x, z];
float water = waterMap[x, z];
float hardness = hardnessMap[x, z];
// determine the magnitude of the drop around the location
float deltaHeight = 0;
int affectedCells = 0;
for (int dX = x - 1; dX <= x + 1; dX++)
{
if (dX < 0 || dX == m_widthX)
{
continue;
}
for (int dZ = z - 1; dZ <= z + 1; dZ++)
{
if (dZ < 0 || dZ == m_depthZ)
{
continue;
}
float localDelta = height - m_heights[dX, dZ];
if (localDelta > 0f)
{
deltaHeight += localDelta;
affectedCells++;
}
}
}
//Move water and sediment if we can
if (affectedCells > 0)
{
// add in a batch of newly dissolved sediment - theory - greater height difference = greater water flow = more sediment generation
float newSediment = water * deltaHeight * sedimentDisolveRate * (1f - hardness);
// remove sediment from current location
sedimentMapDiff[x, z] -= newSediment;
// push sediment to new locations
for (int dX = x - 1; dX <= x + 1; dX++)
{
if (dX < 0 || dX == m_widthX)
{
continue;
}
for (int dZ = z - 1; dZ <= z + 1; dZ++)
{
if (dZ < 0 || dZ == m_depthZ)
{
continue;
}
float localDelta = height - m_heights[dX, dZ];
if (localDelta > 0f)
{
float flow = newSediment * (localDelta / deltaHeight);
sedimentMapDiff[dX, dZ] += flow;
//sedimentMap[dX, dZ] += flow;
}
}
}
}
}
}
// apply changes to sediment map
sedimentMap.Add(sedimentMapDiff);
// apply changes to height map
AddClamped(sedimentMapDiff, 0f, 1f);
// water vaporization
waterMap.SubtractClamped(transferRate, 0f, 1f);
// clear diff maps
waterMapDiff.SetHeight(0f);
sedimentMapDiff.SetHeight(0f);
//Set dirty
m_isDirty = true;
}
return this;
}
///
/// Calculate outflow
///
/// Water map
/// Outflow
/// Heights
/// Width
/// Height
private void CalculateWaterOutflow(HeightMap heightMap, HeightMap waterMap, float[,,] outFlow)
{
int width = heightMap.Width();
int height = heightMap.Depth();
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
int xLeft = (x == 0) ? 0 : x - 1;
int xRight = (x == width - 1) ? width - 1 : x + 1;
int yUp = (y == 0) ? 0 : y - 1;
int yDown = (y == height - 1) ? height - 1 : y + 1;
float waterHt = waterMap[x, y];
float waterHts0 = waterMap[xLeft, y];
float waterHts1 = waterMap[xRight, y];
float waterHts2 = waterMap[x, yUp];
float waterHts3 = waterMap[x, yDown];
float landHt = heightMap[x, y];
float landHts0 = heightMap[xLeft, y];
float landHts1 = heightMap[xRight, y];
float landHts2 = heightMap[x, yUp];
float landHts3 = heightMap[x, yDown];
float diff0 = (waterHt + landHt) - (waterHts0 + landHts0);
float diff1 = (waterHt + landHt) - (waterHts1 + landHts1);
float diff2 = (waterHt + landHt) - (waterHts2 + landHts2);
float diff3 = (waterHt + landHt) - (waterHts3 + landHts3);
//out flow is previous flow plus flow for this time step.
float flow0 = Mathf.Max(0, outFlow[x, y, 0] + diff0);
float flow1 = Mathf.Max(0, outFlow[x, y, 1] + diff1);
float flow2 = Mathf.Max(0, outFlow[x, y, 2] + diff2);
float flow3 = Mathf.Max(0, outFlow[x, y, 3] + diff3);
float sum = flow0 + flow1 + flow2 + flow3;
if (sum > 0.0f)
{
//If the sum of the outflow flux exceeds the amount in the cell
//flow value will be scaled down by a factor K to avoid negative update.
float K = waterHt / (sum * TIME);
if (K > 1.0f)
{
K = 1.0f;
}
if (K < 0.0f)
{
K = 0.0f;
}
outFlow[x, y, 0] = flow0 * K;
outFlow[x, y, 1] = flow1 * K;
outFlow[x, y, 2] = flow2 * K;
outFlow[x, y, 3] = flow3 * K;
}
else
{
outFlow[x, y, 0] = 0.0f;
outFlow[x, y, 1] = 0.0f;
outFlow[x, y, 2] = 0.0f;
outFlow[x, y, 3] = 0.0f;
}
}
}
}
///
/// Update the water map
///
/// Water map
/// Outflow
/// Width
/// Height
private void UpdateWaterMap(HeightMap waterMap, float[,,] outFlow)
{
int width = waterMap.Width();
int height = waterMap.Depth();
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
float flowOUT = outFlow[x, y, 0] + outFlow[x, y, 1] + outFlow[x, y, 2] + outFlow[x, y, 3];
float flowIN = 0.0f;
//Flow in is inflow from neighour cells. Note for the cell on the left you need
//thats cells flow to the right (ie it flows into this cell)
flowIN += (x == 0) ? 0.0f : outFlow[x - 1, y, RIGHT];
flowIN += (x == width - 1) ? 0.0f : outFlow[x + 1, y, LEFT];
flowIN += (y == 0) ? 0.0f : outFlow[x, y - 1, TOP];
flowIN += (y == height - 1) ? 0.0f : outFlow[x, y + 1, BOTTOM];
float ht = waterMap[x, y] + (flowIN - flowOUT) * TIME;
if (ht < 0.0f)
{
ht = 0.0f;
}
//Result is net volume change over time
waterMap[x, y] = ht;
}
}
}
private const int LEFT = 0;
private const int RIGHT = 1;
private const int BOTTOM = 2;
private const int TOP = 3;
private const float TIME = 0.2f;
///
/// Calculate the a flow map for the heightmap
///
/// Number of iterations to run
/// Flowmap
public HeightMap FlowMap(int iterations)
{
int height = m_depthZ;
int width = m_widthX;
float[] heights = this.Heights1D();
float[,] waterMap = new float[width, height];
float[,,] outFlow = new float[width, height, 4];
FillWaterMap(0.0001f, waterMap, width, height);
for (int i = 0; i < iterations; i++)
{
ComputeOutflow(waterMap, outFlow, heights, width, height);
UpdateWaterMap(waterMap, outFlow, width, height);
}
float[,] velocityMap = new float[width, height];
CalculateVelocityField(velocityMap, outFlow, width, height);
NormalizeMap(velocityMap, width, height);
return new HeightMap(velocityMap);
}
///
/// Fill the water map
///
/// Amount to fill it
/// Watermap
/// Width
/// Height
private void FillWaterMap(float amount, float[,] waterMap, int width, int height)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
waterMap[x, y] = amount;
}
}
}
///
/// Compute outflow
///
/// Water map
/// Outflow
/// Heights
/// Width
/// Height
private void ComputeOutflow(float[,] waterMap, float[,,] outFlow, float[] heightMap, int width, int height)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int xn1 = (x == 0) ? 0 : x - 1;
int xp1 = (x == width - 1) ? width - 1 : x + 1;
int yn1 = (y == 0) ? 0 : y - 1;
int yp1 = (y == height - 1) ? height - 1 : y + 1;
float waterHt = waterMap[x, y];
float waterHts0 = waterMap[xn1, y];
float waterHts1 = waterMap[xp1, y];
float waterHts2 = waterMap[x, yn1];
float waterHts3 = waterMap[x, yp1];
float landHt = heightMap[x + y * width];
float landHts0 = heightMap[xn1 + y * width];
float landHts1 = heightMap[xp1 + y * width];
float landHts2 = heightMap[x + yn1 * width];
float landHts3 = heightMap[x + yp1 * width];
float diff0 = (waterHt + landHt) - (waterHts0 + landHts0);
float diff1 = (waterHt + landHt) - (waterHts1 + landHts1);
float diff2 = (waterHt + landHt) - (waterHts2 + landHts2);
float diff3 = (waterHt + landHt) - (waterHts3 + landHts3);
//out flow is previous flow plus flow for this time step.
float flow0 = Mathf.Max(0, outFlow[x, y, 0] + diff0);
float flow1 = Mathf.Max(0, outFlow[x, y, 1] + diff1);
float flow2 = Mathf.Max(0, outFlow[x, y, 2] + diff2);
float flow3 = Mathf.Max(0, outFlow[x, y, 3] + diff3);
float sum = flow0 + flow1 + flow2 + flow3;
if (sum > 0.0f)
{
//If the sum of the outflow flux exceeds the amount in the cell
//flow value will be scaled down by a factor K to avoid negative update.
float K = waterHt / (sum * TIME);
if (K > 1.0f) K = 1.0f;
if (K < 0.0f) K = 0.0f;
outFlow[x, y, 0] = flow0 * K;
outFlow[x, y, 1] = flow1 * K;
outFlow[x, y, 2] = flow2 * K;
outFlow[x, y, 3] = flow3 * K;
}
else
{
outFlow[x, y, 0] = 0.0f;
outFlow[x, y, 1] = 0.0f;
outFlow[x, y, 2] = 0.0f;
outFlow[x, y, 3] = 0.0f;
}
}
}
}
///
/// Update the water map
///
/// Water map
/// Outflow
/// Width
/// Height
private void UpdateWaterMap(float[,] waterMap, float[,,] outFlow, int width, int height)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float flowOUT = outFlow[x, y, 0] + outFlow[x, y, 1] + outFlow[x, y, 2] + outFlow[x, y, 3];
float flowIN = 0.0f;
//Flow in is inflow from neighour cells. Note for the cell on the left you need
//thats cells flow to the right (ie it flows into this cell)
flowIN += (x == 0) ? 0.0f : outFlow[x - 1, y, RIGHT];
flowIN += (x == width - 1) ? 0.0f : outFlow[x + 1, y, LEFT];
flowIN += (y == 0) ? 0.0f : outFlow[x, y - 1, TOP];
flowIN += (y == height - 1) ? 0.0f : outFlow[x, y + 1, BOTTOM];
float ht = waterMap[x, y] + (flowIN - flowOUT) * TIME;
if (ht < 0.0f) ht = 0.0f;
//Result is net volume change over time
waterMap[x, y] = ht;
}
}
}
///
/// Calculate water flow velocity field
///
/// Velocity map
/// Outlflow map
/// Width
/// Height
private void CalculateVelocityField(float[,] velocityMap, float[,,] outFlow, int width, int height)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float dl = (x == 0) ? 0.0f : outFlow[x - 1, y, RIGHT] - outFlow[x, y, LEFT];
float dr = (x == width - 1) ? 0.0f : outFlow[x, y, RIGHT] - outFlow[x + 1, y, LEFT];
float dt = (y == height - 1) ? 0.0f : outFlow[x, y + 1, BOTTOM] - outFlow[x, y, TOP];
float db = (y == 0) ? 0.0f : outFlow[x, y, BOTTOM] - outFlow[x, y - 1, TOP];
float vx = (dl + dr) * 0.5f;
float vy = (db + dt) * 0.5f;
velocityMap[x, y] = Mathf.Sqrt(vx * vx + vy * vy);
}
}
}
///
/// Normalize array of floats
///
/// Array to normalize
/// Width
/// Height
private void NormalizeMap(float[,] map, int width, int height)
{
float min = float.PositiveInfinity;
float max = float.NegativeInfinity;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float v = map[x, y];
if (v < min) min = v;
if (v > max) max = v;
}
}
float size = max - min;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float v = map[x, y];
if (size < 1e-12f)
v = 0;
else
v = (v - min) / size;
map[x, y] = v;
}
}
}
///
/// Calculate slope map for this height map
///
///
public HeightMap SlopeMap()
{
int height = m_depthZ;
int width = m_widthX;
float[] heights = this.Heights1D();
float ux = 1.0f / (width - 1.0f);
float uy = 1.0f / (height - 1.0f);
float scaleX = 0.5f;
float scaleY = 0.5f;
HeightMap sMap = new HeightMap(width, height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int xp1 = (x == width - 1) ? x : x + 1;
int xn1 = (x == 0) ? x : x - 1;
int yp1 = (y == height - 1) ? y : y + 1;
int yn1 = (y == 0) ? y : y - 1;
float l = heights[xn1 + y * width] * scaleX;
float r = heights[xp1 + y * width] * scaleX;
float b = heights[x + yn1 * width] * scaleY;
float t = heights[x + yp1 * width] * scaleY;
float dx = (r - l) / (2.0f * ux);
float dy = (t - b) / (2.0f * uy);
float g = Mathf.Sqrt(dx * dx + dy * dy);
float slope = g / Mathf.Sqrt(1.0f + g * g);
sMap[x, y] = slope;
}
}
return sMap;
}
///
/// Add value
///
/// Value to add
public HeightMap Add(float value)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] += value;
}
}
m_isDirty = true;
return this;
}
///
/// Add heightmap
///
/// Heightmap to add
public HeightMap Add(HeightMap heightMap)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] += heightMap[m_widthInvX * x, m_depthInvZ * z];
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] += heightMap.m_heights[x, z];
}
}
}
m_isDirty = true;
return this;
}
///
/// Add value and clamp result
///
/// Value to add
/// Min value to clamp it to
/// Max value to clamp it to
public HeightMap AddClamped(float value, float min, float max)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] + value;
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
m_isDirty = true;
return this;
}
///
/// Add heightmap and clamp result
///
/// Heightmap to add
/// Min value to clamp it to
/// Max value to clamp it to
public HeightMap AddClamped(HeightMap heightMap, float min, float max)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] + heightMap[m_widthInvX * x, m_depthInvZ * z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] + heightMap.m_heights[x, z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
m_isDirty = true;
return this;
}
///
/// Subtract value
///
/// Value to subtract
public HeightMap Subtract(float value)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] -= value;
}
}
m_isDirty = true;
return this;
}
///
/// Subtract heightmap
///
/// Heightmap to subtract
public HeightMap Subtract(HeightMap heightMap)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] -= heightMap[m_widthInvX * x, m_depthInvZ * z];
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] -= heightMap.m_heights[x, z];
}
}
}
m_isDirty = true;
return this;
}
///
/// Subtract value and clamp result
///
/// Value to subtract
/// Min value to clamp it to
/// Max value to clamp it to
public HeightMap SubtractClamped(float value, float min, float max)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] - value;
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
m_isDirty = true;
return this;
}
///
/// Subtract heightmap and clamp result
///
/// Heightmap to subtract
/// Min value to clamp it to
/// Max value to clamp it to
public HeightMap SubtractClamped(HeightMap heightMap, float min, float max)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] - heightMap[m_widthInvX * x, m_depthInvZ * z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] - heightMap.m_heights[x, z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
m_isDirty = true;
return this;
}
///
/// Multiply by value
///
/// Value to multiply
public HeightMap Multiply(float value)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] *= value;
}
}
m_isDirty = true;
return this;
}
///
/// Multiply by heightmap
///
/// Heightmap to multiply
public HeightMap Multiply(HeightMap heightMap)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] *= heightMap[m_widthInvX * x, m_depthInvZ * z];
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] *= heightMap.m_heights[x, z];
}
}
}
m_isDirty = true;
return this;
}
///
/// Multiply by value and clamp result
///
/// Value to multiply it by
/// Min value to clamp it to
/// Max value to clamp it to
public HeightMap MultiplyClamped(float value, float min, float max)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] * value;
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
m_isDirty = true;
return this;
}
///
/// Multiply by heightmap and clamp result
///
/// Heightmap to multiply
/// Min value to clamp it to
/// Max value to clamp it to
public HeightMap MultiplyClamped(HeightMap heightMap, float min, float max)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] * heightMap[m_widthInvX * x, m_depthInvZ * z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] * heightMap.m_heights[x, z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
m_isDirty = true;
return this;
}
///
/// Divide by value
///
/// Value to divide
public HeightMap Divide(float value)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] /= value;
}
}
m_isDirty = true;
return this;
}
///
/// Divide by heightmap
///
/// Heightmap to divide
public HeightMap Divide(HeightMap heightMap)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] /= heightMap[m_widthInvX * x, m_depthInvZ * z];
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] /= heightMap.m_heights[x, z];
}
}
}
m_isDirty = true;
return this;
}
///
/// Divide by value and clamp result
///
/// Value to divide
/// Min value to clamp it to
/// Max value to clamp it to
public HeightMap DivideClamped(float value, float min, float max)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] / value;
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
m_isDirty = true;
return this;
}
///
/// Divide by heightmap and clamp result
///
/// Heightmap to multiply
/// Min value to clamp it to
/// Max value to clamp it to
public HeightMap DivideClamped(HeightMap heightMap, float min, float max)
{
if (m_widthX != heightMap.m_widthX || m_depthZ != heightMap.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] / heightMap[m_widthInvX * x, m_depthInvZ * z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
float newValue = m_heights[x, z] / heightMap.m_heights[x, z];
if (newValue < min)
{
newValue = min;
}
else if (newValue > max)
{
newValue = max;
}
m_heights[x, z] = newValue;
}
}
}
m_isDirty = true;
return this;
}
///
/// Lerp to new values supplied based on mask
///
/// New values to lerp to
/// Mask that controls degree of interpolation - 1 = take target, 0 = keep original
public HeightMap Lerp(HeightMap hmNewValues, HeightMap hmLerpMask)
{
if (m_widthX != hmNewValues.m_widthX || m_depthZ != hmNewValues.m_depthZ)
{
if (m_widthX != hmLerpMask.m_widthX || m_depthZ != hmLerpMask.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = Mathf.Lerp(m_heights[x, z], hmNewValues[m_widthInvX * x, m_depthInvZ * z], hmLerpMask[m_widthInvX * x, m_depthInvZ * z]);
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = Mathf.Lerp(m_heights[x, z], hmNewValues[m_widthInvX * x, m_depthInvZ * z], hmLerpMask[x, z]);
}
}
}
}
else
{
if (m_widthX != hmLerpMask.m_widthX || m_depthZ != hmLerpMask.m_depthZ)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = Mathf.Lerp(m_heights[x, z], hmNewValues[x, z], hmLerpMask[m_widthInvX * x, m_depthInvZ * z]);
}
}
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = Mathf.Lerp(m_heights[x, z], hmNewValues[x, z], hmLerpMask[x, z]);
}
}
}
}
m_isDirty = true;
return this;
}
///
/// Sum the content of the heightmap
///
/// Sum of heightmap content
public float Sum()
{
float sum = 0f;
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
sum += m_heights[x, z];
}
}
return sum;
}
///
/// Average the content of the heightmap
///
/// Average of heightmap content
public float Average()
{
return Sum() / (m_widthX * m_depthZ);
}
///
/// Adjust to the power of the exponent provided
///
/// Exponent to power of
public HeightMap Power(float exponent)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = Mathf.Pow(m_heights[x, z], exponent);
}
}
m_isDirty = true;
return this;
}
///
/// Adjust contrast by the value provided
///
/// Contrast value
public HeightMap Contrast(float contrast)
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = ((m_heights[x, z] - 0.5f) * contrast) + 0.5f;
}
}
m_isDirty = true;
return this;
}
#endregion
#region File Operations
///
/// Save ourselves into the file provided
///
///
///
public void SaveToBinaryFile(string fileName)
{
BinaryFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, m_widthX);
formatter.Serialize(stream, m_depthZ);
formatter.Serialize(stream, m_metaData);
formatter.Serialize(stream, m_heights);
stream.Close();
m_isDirty = false;
}
///
/// Load ourselves from the file provided
///
///
public void LoadFromBinaryFile(string fileName)
{
if (!System.IO.File.Exists(fileName))
{
Debug.LogError("Could not locate file : " + fileName);
return;
}
Reset();
BinaryFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
m_widthX = (int)formatter.Deserialize(stream);
m_depthZ = (int)formatter.Deserialize(stream);
m_metaData = (byte[])formatter.Deserialize(stream);
m_heights = (float[,])formatter.Deserialize(stream);
stream.Close();
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ);
m_isDirty = false;
}
/*
///
/// Save to a system bitmap - note - you will lose precision as we are going to from 32 bit to 8 bit values
///
/// System bitmap
public System.Drawing.Bitmap SaveToBitmap(PixelFormat format)
{
System.Drawing.Bitmap bm = new Bitmap(m_widthX, m_depthZ, format);
if (format == PixelFormat.Format8bppIndexed)
{
BitmapData data = bm.LockBits(new Rectangle(0, 0, m_widthX, m_depthZ), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
byte[] bytes = new byte[data.Height * data.Stride];
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
bytes[x * data.Stride + z] = (byte)(m_heights[x, z] * 255f);
}
}
// Copy the bytes from the byte array into the image
Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
bm.UnlockBits(data);
}
else
{
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
int value = (int)(m_heights[x, z] * 255f);
bm.SetPixel(x, z, System.Drawing.Color.FromArgb(value, value, value, value));
}
}
}
return bm;
}
public void LoadFromBitmap(System.Drawing.Bitmap bitmap)
{
if (bitmap == null)
{
Debug.LogError("No data provided");
return;
}
Reset();
m_widthX = bitmap.Width;
m_depthZ = bitmap.Height;
m_widthXOpt = m_widthX - 1f;
m_depthZOpt = m_depthZ - 1f;
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_heights = new float[m_widthX, m_depthZ];
m_isPowerOf2 = PWCommon.Utils.Math_IsPowerOf2(m_widthX) && PWCommon.Utils.Math_IsPowerOf2(m_depthZ);
m_statMinVal = m_statMaxVal = 0f;
m_statSumVals = 0;
m_metaData = new byte[0];
m_isDirty = false;
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
System.Drawing.Color c = bitmap.GetPixel(x, z);
//Convert to greyscale
m_heights[x, z] = (float)(0.29899999499321 * (double)((float)c.R/255f) + 0.587000012397766 * (double)((float)c.G/255f) + 57.0 / 500.0 * (double)((float)c.B/255f));
}
}
}
*/
///
/// Load ourselves from the byte array provided
///
///
public void LoadFromByteArray(byte[] source)
{
if (source == null)
{
Debug.LogError("No data provided");
return;
}
Reset();
BinaryFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream(source);
m_widthX = (int)formatter.Deserialize(stream);
m_depthZ = (int)formatter.Deserialize(stream);
m_metaData = (byte[])formatter.Deserialize(stream);
m_heights = (float[,])formatter.Deserialize(stream);
stream.Close();
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ);
m_isDirty = false;
}
///
/// Load ourselves from the raw file provided
///
///
public void LoadFromRawFile(string fileName, GaiaConstants.RawByteOrder byteOrder, ref GaiaConstants.RawBitDepth bitDepth, ref int resolution)
{
if (!System.IO.File.Exists(fileName))
{
Debug.LogError("Could not locate raw file : " + fileName);
return;
}
Reset();
using (FileStream fileStream = File.OpenRead(fileName))
{
using (BinaryReader br = (byteOrder == GaiaConstants.RawByteOrder.IBM) ? new BinaryReader(fileStream) : new BinaryReaderMac(fileStream))
{
if (bitDepth == GaiaConstants.RawBitDepth.Sixteen)
{
if (fileStream.Length % 2 == 0)
{
resolution = m_widthX = m_depthZ = Mathf.CeilToInt(Mathf.Sqrt(fileStream.Length / 2));
m_heights = new float[m_widthX, m_depthZ];
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = (float)br.ReadUInt16() / 65535.0f; //Should consider doing the unity HM switch here
}
}
}
// Can't be 16 bit raw, try to process as 8bit
else
{
m_widthX = m_depthZ = Mathf.CeilToInt(Mathf.Sqrt(fileStream.Length));
#if UNITY_EDITOR
if (UnityEditor.EditorUtility.DisplayDialog("OOPS!",
"The file received is not 16-bit RAW.\n" +
"If processed as an 8-bit RAW its resolution would be " + m_widthX + " x " + m_widthX + ".\n" +
"Do you want to attempt to scan it as an 8-bit RAW?\n" +
"(Tip: Check if the resolution matches the resolution of what you are trying to scan)\n\n" +
"WARNING: 8-bit RAW files have very poor precision and result in terraced stamps.\n\n",
"YES", "NO"))
{
bitDepth = GaiaConstants.RawBitDepth.Eight;
resolution = m_widthX;
m_heights = new float[m_widthX, m_depthZ];
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = (float)br.ReadByte() / 255.0f; //Should consider doing the unity HM switch here
}
}
}
#endif
}
}
else
{
resolution = m_widthX = m_depthZ = Mathf.CeilToInt(Mathf.Sqrt(fileStream.Length));
m_heights = new float[m_widthX, m_depthZ];
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
m_heights[x, z] = (float)br.ReadByte() / 255.0f; //Should consider doing the unity HM switch here
}
}
}
}
fileStream.Close();
}
m_widthInvX = 1f / (float)(m_widthX);
m_depthInvZ = 1f / (float)(m_depthZ);
m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ);
m_isDirty = false;
}
#endregion
#region Debug
///
/// A handy utility to dump the content of a heightmap.
/// Example: for unity terrain heightmaps use DumpMap(9f, 0, "", true).
///
/// Amount to scale the value by
/// The number of decimal points to show
/// The spacer to show (or not)
/// Whether or not to flip the lookup
public void DumpMap(float scaleValue, int precision, string spacer, bool flip)
{
StringBuilder debugStr = new StringBuilder();
string format = "";
if (precision == 0)
{
format = "{0:0}";
}
else
{
format = "{0:0.";
for (int p = 0; p < precision; p++)
{
format += "0";
}
format += "}";
}
if (!string.IsNullOrEmpty(spacer))
{
format += spacer;
}
for (int x = 0; x < m_widthX; x++)
{
for (int z = 0; z < m_depthZ; z++)
{
if (!flip)
{
debugStr.AppendFormat(format, m_heights[x, z] * scaleValue);
}
else
{
debugStr.AppendFormat(format, m_heights[z, x] * scaleValue);
}
}
debugStr.AppendLine();
}
Debug.Log(debugStr.ToString());
}
///
/// Dump a specific row
///
/// The row to dump
/// Amount to scale the value by
/// The number of decimal points to show
/// The spacer to show (or not)
public void DumpRow(int rowX, float scaleValue, int precision, string spacer)
{
StringBuilder debugStr = new StringBuilder();
string format = "";
if (precision == 0)
{
format = "{0:0}";
}
else
{
format = "{0:0.";
for (int p = 0; p < precision; p++)
{
format += "0";
}
format += "}";
}
if (!string.IsNullOrEmpty(spacer))
{
format += spacer;
}
float [] values = GetRow(rowX);
for (int v = 0; v < values.Length; v++)
{
debugStr.AppendFormat(format, values[v] * scaleValue);
}
Debug.Log(debugStr.ToString());
}
///
/// Dump a specific column
///
/// The column to dump
/// Amount to scale the value by
/// The number of decimal points to show
/// The spacer to show (or not)
public void DumpColumn(int columnZ, float scaleValue, int precision, string spacer)
{
StringBuilder debugStr = new StringBuilder();
string format = "";
if (precision == 0)
{
format = "{0:0}";
}
else
{
format = "{0:0.";
for (int p = 0; p < precision; p++)
{
format += "0";
}
format += "}";
}
if (!string.IsNullOrEmpty(spacer))
{
format += spacer;
}
float[] values = GetColumn(columnZ);
for (int v = 0; v < values.Length; v++)
{
debugStr.AppendFormat(format, values[v] * scaleValue);
}
Debug.Log(debugStr.ToString());
}
#endregion
}
}