using System;
using System.Xml.Serialization;
using UnityEngine;
namespace LibNoise
{
///
/// Provides a two-dimensional noise map.
///
/// This covers most of the functionality from LibNoise's noiseutils library, but
/// the method calls might not be the same. See the tutorials project if you're wondering
/// which calls are equivalent.
public class Noise2D : IDisposable
{
#region Constants
public static readonly double South = -90.0;
public static readonly double North = 90.0;
public static readonly double West = -180.0;
public static readonly double East = 180.0;
public static readonly double AngleMin = -180.0;
public static readonly double AngleMax = 180.0;
public static readonly double Left = -1.0;
public static readonly double Right = 1.0;
public static readonly double Top = -1.0;
public static readonly double Bottom = 1.0;
#endregion
#region Fields
private int _width;
private int _height;
private float[,] _data;
private readonly int _ucWidth;
private readonly int _ucHeight;
private int _ucBorder = 1; // Border size of extra noise for uncropped data.
private readonly float[,] _ucData;
// Uncropped data. This has a border of extra noise data used for calculating normal map edges.
private float _borderValue = float.NaN;
private ModuleBase _generator;
#endregion
#region Constructors
///
/// Initializes a new instance of Noise2D.
///
protected Noise2D()
{
}
///
/// Initializes a new instance of Noise2D.
///
/// The width and height of the noise map.
public Noise2D(int size)
: this(size, size, null)
{
}
///
/// Initializes a new instance of Noise2D.
///
/// The width and height of the noise map.
/// The generator module.
public Noise2D(int size, ModuleBase generator)
: this(size, size, generator)
{
}
///
/// Initializes a new instance of Noise2D.
///
/// The width of the noise map.
/// The height of the noise map.
/// The generator module.
public Noise2D(int width, int height, ModuleBase generator = null)
{
_generator = generator;
_width = width;
_height = height;
_data = new float[width, height];
_ucWidth = width + _ucBorder * 2;
_ucHeight = height + _ucBorder * 2;
_ucData = new float[width + _ucBorder * 2, height + _ucBorder * 2];
}
#endregion
#region Indexers
///
/// Gets or sets a value in the noise map by its position.
///
/// The position on the x-axis.
/// The position on the y-axis.
/// Indicates whether to select the cropped (default) or uncropped noise map data.
/// The corresponding value.
public float this[int x, int y, bool isCropped = true]
{
get
{
if (isCropped)
{
if (x < 0 && x >= _width)
{
throw new ArgumentOutOfRangeException("Invalid x position");
}
if (y < 0 && y >= _height)
{
throw new ArgumentOutOfRangeException("Invalid y position");
}
return _data[x, y];
}
if (x < 0 && x >= _ucWidth)
{
throw new ArgumentOutOfRangeException("Invalid x position");
}
if (y < 0 && y >= _ucHeight)
{
throw new ArgumentOutOfRangeException("Invalid y position");
}
return _ucData[x, y];
}
set
{
if (isCropped)
{
if (x < 0 && x >= _width)
{
throw new ArgumentOutOfRangeException("Invalid x position");
}
if (y < 0 && y >= _height)
{
throw new ArgumentOutOfRangeException("Invalid y position");
}
_data[x, y] = value;
}
else
{
if (x < 0 && x >= _ucWidth)
{
throw new ArgumentOutOfRangeException("Invalid x position");
}
if (y < 0 && y >= _ucHeight)
{
throw new ArgumentOutOfRangeException("Invalid y position");
}
_ucData[x, y] = value;
}
}
}
#endregion
#region Properties
///
/// Gets or sets the constant value at the noise maps borders.
///
public float Border
{
get { return _borderValue; }
set { _borderValue = value; }
}
///
/// Gets or sets the generator module.
///
public ModuleBase Generator
{
get { return _generator; }
set { _generator = value; }
}
///
/// Gets the height of the noise map.
///
public int Height
{
get { return _height; }
}
///
/// Gets the width of the noise map.
///
public int Width
{
get { return _width; }
}
#endregion
#region Methods
///
/// Gets normalized noise map data with all values in the set of {0..1}.
///
/// Indicates whether to select the cropped (default) or uncropped noise map data.
/// This value crops off data from the right of the noise map data.
/// This value crops off data from the bottom of the noise map data.
/// The normalized noise map data.
public float[,] GetNormalizedData(bool isCropped = true, int xCrop = 0, int yCrop = 0)
{
return GetData(isCropped, xCrop, yCrop, true);
}
///
/// Gets noise map data.
///
/// Indicates whether to select the cropped (default) or uncropped noise map data.
/// This value crops off data from the right of the noise map data.
/// This value crops off data from the bottom of the noise map data.
/// Indicates whether to normalize noise map data.
/// The noise map data.
public float[,] GetData(bool isCropped = true, int xCrop = 0, int yCrop = 0, bool isNormalized = false)
{
int width, height;
float[,] data;
if (isCropped)
{
width = _width;
height = _height;
data = _data;
}
else
{
width = _ucWidth;
height = _ucHeight;
data = _ucData;
}
width -= xCrop;
height -= yCrop;
var result = new float[width, height];
for (var x = 0; x < width; x++)
{
for (var y = 0; y < height; y++)
{
float sample;
if (isNormalized)
{
sample = (data[x, y] + 1) / 2;
}
else
{
sample = data[x, y];
}
result[x, y] = sample;
}
}
return result;
}
///
/// Clears the noise map.
///
/// The constant value to clear the noise map with.
public void Clear(float value = 0f)
{
for (var x = 0; x < _width; x++)
{
for (var y = 0; y < _height; y++)
{
_data[x, y] = value;
}
}
}
///
/// Generates a planar projection of a point in the noise map.
///
/// The position on the x-axis.
/// The position on the y-axis.
/// The corresponding noise map value.
private double GeneratePlanar(double x, double y)
{
return _generator.GetValue(x, 0.0, y);
}
///
/// Generates a non-seamless planar projection of the noise map.
///
/// The clip region to the left.
/// The clip region to the right.
/// The clip region to the top.
/// The clip region to the bottom.
/// Indicates whether the resulting noise map should be seamless.
public void GeneratePlanar(double left, double right, double top, double bottom, bool isSeamless = true)
{
if (right <= left || bottom <= top)
{
throw new ArgumentException("Invalid right/left or bottom/top combination");
}
if (_generator == null)
{
throw new ArgumentNullException("Generator is null");
}
var xe = right - left;
var ze = bottom - top;
var xd = xe / ((double) _width - _ucBorder);
var zd = ze / ((double) _height - _ucBorder);
var xc = left;
for (var x = 0; x < _ucWidth; x++)
{
var zc = top;
for (var y = 0; y < _ucHeight; y++)
{
float fv;
if (isSeamless)
{
fv = (float) GeneratePlanar(xc, zc);
}
else
{
var swv = GeneratePlanar(xc, zc);
var sev = GeneratePlanar(xc + xe, zc);
var nwv = GeneratePlanar(xc, zc + ze);
var nev = GeneratePlanar(xc + xe, zc + ze);
var xb = 1.0 - ((xc - left) / xe);
var zb = 1.0 - ((zc - top) / ze);
var z0 = Utils.InterpolateLinear(swv, sev, xb);
var z1 = Utils.InterpolateLinear(nwv, nev, xb);
fv = (float) Utils.InterpolateLinear(z0, z1, zb);
}
_ucData[x, y] = fv;
if (x >= _ucBorder && y >= _ucBorder && x < _width + _ucBorder &&
y < _height + _ucBorder)
{
_data[x - _ucBorder, y - _ucBorder] = fv; // Cropped data
}
zc += zd;
}
xc += xd;
}
}
///
/// Generates a cylindrical projection of a point in the noise map.
///
/// The angle of the point.
/// The height of the point.
/// The corresponding noise map value.
private double GenerateCylindrical(double angle, double height)
{
var x = Math.Cos(angle * Mathf.Deg2Rad);
var y = height;
var z = Math.Sin(angle * Mathf.Deg2Rad);
return _generator.GetValue(x, y, z);
}
///
/// Generates a cylindrical projection of the noise map.
///
/// The maximum angle of the clip region.
/// The minimum angle of the clip region.
/// The minimum height of the clip region.
/// The maximum height of the clip region.
public void GenerateCylindrical(double angleMin, double angleMax, double heightMin, double heightMax)
{
if (angleMax <= angleMin || heightMax <= heightMin)
{
throw new ArgumentException("Invalid angle or height parameters");
}
if (_generator == null)
{
throw new ArgumentNullException("Generator is null");
}
var ae = angleMax - angleMin;
var he = heightMax - heightMin;
var xd = ae / ((double) _width - _ucBorder);
var yd = he / ((double) _height - _ucBorder);
var ca = angleMin;
for (var x = 0; x < _ucWidth; x++)
{
var ch = heightMin;
for (var y = 0; y < _ucHeight; y++)
{
_ucData[x, y] = (float) GenerateCylindrical(ca, ch);
if (x >= _ucBorder && y >= _ucBorder && x < _width + _ucBorder &&
y < _height + _ucBorder)
{
_data[x - _ucBorder, y - _ucBorder] = (float) GenerateCylindrical(ca, ch);
// Cropped data
}
ch += yd;
}
ca += xd;
}
}
///
/// Generates a spherical projection of a point in the noise map.
///
/// The latitude of the point.
/// The longitude of the point.
/// The corresponding noise map value.
private double GenerateSpherical(double lat, double lon)
{
var r = Math.Cos(Mathf.Deg2Rad * lat);
return _generator.GetValue(r * Math.Cos(Mathf.Deg2Rad * lon), Math.Sin(Mathf.Deg2Rad * lat),
r * Math.Sin(Mathf.Deg2Rad * lon));
}
///
/// Generates a spherical projection of the noise map.
///
/// The clip region to the south.
/// The clip region to the north.
/// The clip region to the west.
/// The clip region to the east.
public void GenerateSpherical(double south, double north, double west, double east)
{
if (east <= west || north <= south)
{
throw new ArgumentException("Invalid east/west or north/south combination");
}
if (_generator == null)
{
throw new ArgumentNullException("Generator is null");
}
var loe = east - west;
var lae = north - south;
var xd = loe / ((double) _width - _ucBorder);
var yd = lae / ((double) _height - _ucBorder);
var clo = west;
for (var x = 0; x < _ucWidth; x++)
{
var cla = south;
for (var y = 0; y < _ucHeight; y++)
{
_ucData[x, y] = (float) GenerateSpherical(cla, clo);
if (x >= _ucBorder && y >= _ucBorder && x < _width + _ucBorder &&
y < _height + _ucBorder)
{
_data[x - _ucBorder, y - _ucBorder] = (float) GenerateSpherical(cla, clo);
// Cropped data
}
cla += yd;
}
clo += xd;
}
}
///
/// Creates a grayscale texture map for the current content of the noise map.
///
/// The created texture map.
public Texture2D GetTexture()
{
return GetTexture(GradientPresets.Grayscale);
}
///
/// Creates a texture map for the current content of the noise map.
///
/// The gradient to color the texture map with.
/// The created texture map.
public Texture2D GetTexture(Gradient gradient)
{
var texture = new Texture2D(_width, _height);
var pixels = new Color[_width * _height];
for (var x = 0; x < _width; x++)
{
for (var y = 0; y < _height; y++)
{
float sample;
if (!float.IsNaN(_borderValue) &&
(x == 0 || x == _width - _ucBorder || y == 0 || y == _height - _ucBorder))
{
sample = _borderValue;
}
else
{
sample = _data[x, y];
}
pixels[x + y * _width] = gradient.Evaluate((sample + 1) / 2);
}
}
texture.SetPixels(pixels);
texture.wrapMode = TextureWrapMode.Clamp;
texture.Apply();
return texture;
}
///
/// Creates a normal map for the current content of the noise map.
///
/// The scaling of the normal map values.
/// The created normal map.
public Texture2D GetNormalMap(float intensity)
{
var texture = new Texture2D(_width, _height);
var pixels = new Color[_width * _height];
for (var x = 0; x < _ucWidth; x++)
{
for (var y = 0; y < _ucHeight; y++)
{
var xPos = (_ucData[Mathf.Max(0, x - _ucBorder), y] -
_ucData[Mathf.Min(x + _ucBorder, _height + _ucBorder), y]) / 2;
var yPos = (_ucData[x, Mathf.Max(0, y - _ucBorder)] -
_ucData[x, Mathf.Min(y + _ucBorder, _width + _ucBorder)]) / 2;
var normalX = new Vector3(xPos * intensity, 0, 1);
var normalY = new Vector3(0, yPos * intensity, 1);
// Get normal vector
var normalVector = normalX + normalY;
normalVector.Normalize();
// Get color vector
var colorVector = Vector3.zero;
colorVector.x = (normalVector.x + 1) / 2;
colorVector.y = (normalVector.y + 1) / 2;
colorVector.z = (normalVector.z + 1) / 2;
// Start at (x + _ucBorder, y + _ucBorder) so that resulting normal map aligns with cropped data
if (x >= _ucBorder && y >= _ucBorder && x < _width + _ucBorder &&
y < _height + _ucBorder)
{
pixels[(x - _ucBorder) + (y - _ucBorder) * _width] = new Color(colorVector.x,
colorVector.y, colorVector.z);
}
}
}
texture.SetPixels(pixels);
texture.wrapMode = TextureWrapMode.Clamp;
texture.Apply();
return texture;
}
#endregion
#region IDisposable Members
[XmlIgnore]
#if !XBOX360 && !ZUNE
[NonSerialized]
#endif
private bool _disposed;
///
/// Gets a value whether the object is disposed.
///
public bool IsDisposed
{
get { return _disposed; }
}
///
/// Immediately releases the unmanaged resources used by this object.
///
public void Dispose()
{
if (!_disposed)
{
_disposed = Disposing();
}
GC.SuppressFinalize(this);
}
///
/// Immediately releases the unmanaged resources used by this object.
///
/// True if the object is completely disposed.
protected virtual bool Disposing()
{
_data = null;
_width = 0;
_height = 0;
return true;
}
#endregion
}
}