/Utilities/Math/Noise.cs
C# | 471 lines | 290 code | 46 blank | 135 comment | 18 complexity | 28d5008cf132a376ee5f291922ac4367 MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.IO;
- using Delta.Utilities.Datatypes;
- using NUnit.Framework;
-
- namespace Delta.Utilities.Math
- {
- /// <summary>
- /// Helper class to generate noise and allow to recreate the same conditions
- /// over and over again to generate the exact same random noise again. Each
- /// noise entry is a Vector4 and will be interpolated in the GetNoise method.
- /// </summary>
- public class Noise : ISaveLoadBinary, IEquatable<Noise>
- {
- #region Constants
- /// <summary>
- /// Noise bias = 1024
- /// </summary>
- protected const int NoiseBias = 1024;
- #endregion
-
- #region Protected
-
- #region noiseSize (Protected)
- /// <summary>
- /// Size of the noise array, the gradient and permutation seed. All these
- /// values can be saved to a stream (see Save) and loaded again later
- /// to reconstruct the exact same noise table again (with Load).
- /// </summary>
- protected int noiseSize;
- #endregion
-
- #region gradientSeed (Protected)
- /// <summary>
- /// Size of the noise array, the gradient and permutation seed. All these
- /// values can be saved to a stream (see Save) and loaded again later
- /// to reconstruct the exact same noise table again (with Load).
- /// </summary>
- protected int gradientSeed;
- #endregion
-
- #region permutationSeed (Protected)
- /// <summary>
- /// Size of the noise array, the gradient and permutation seed. All these
- /// values can be saved to a stream (see Save) and loaded again later
- /// to reconstruct the exact same noise table again (with Load).
- /// </summary>
- protected int permutationSeed;
- #endregion
-
- #region noiseTable (Protected)
- /// <summary>
- /// Noise table values, use GetNoise to get to the values.
- /// Note: Quaternions are just used here as a Vector4 replacement.
- /// </summary>
- protected Quaternion[] noiseTable;
- #endregion
-
- #region permutationTable (Protected)
- /// <summary>
- /// Permutation table to jump around like crazy in the noiseTable array.
- /// </summary>
- protected uint[] permutationTable;
- #endregion
-
- #region gradientRange (Protected)
- /// <summary>
- /// Gradient range
- /// </summary>
- protected Random gradientRange;
- #endregion
-
- #region permutationRange (Protected)
- /// <summary>
- /// Permutation range
- /// </summary>
- protected Random permutationRange;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create noise with given table size, gradient seed and permutation seed
- /// values (this way you can easily recreate this noise table with the
- /// same values again). Each noise value is stored as a Vector4
- /// (Quaternion in our engine).
- /// </summary>
- /// <param name="setNoiseTableSize">Size for the noise table</param>
- /// <param name="setGradientSeed">
- /// Seed value to regenerate the same random seed values as before.
- /// </param>
- /// <param name="setPermutationSeed">
- /// And another seed for the permutation, must also fit to generate the
- /// same values again.
- /// </param>
- public Noise(int setNoiseTableSize, int setGradientSeed,
- int setPermutationSeed)
- {
- noiseSize = setNoiseTableSize;
- gradientSeed = setGradientSeed;
- // Permutation seed
- permutationSeed = setPermutationSeed;
-
- BuildNoise();
- }
-
- /// <summary>
- /// Create noise parameters from a stream and rebuild the same noise table
- /// that was used when this Noise was saved.
- /// </summary>
- public Noise(BinaryReader reader)
- {
- Load(reader);
- }
- #endregion
-
- #region IEquatable<Noise> Members
- /// <summary>
- /// Equals
- /// </summary>
- /// <param name="other">Other</param>
- /// <returns>Value indicating the equality of two vectors</returns>
- public bool Equals(Noise other)
- {
- return other != null &&
- noiseSize == other.noiseSize &&
- gradientSeed == other.gradientSeed &&
- permutationSeed == other.permutationSeed;
- }
- #endregion
-
- #region ISaveLoadBinary Members
- /// <summary>
- /// Load Noise parameters from a stream and rebuild the same noise table
- /// that was used when this Noise was saved.
- /// </summary>
- public void Load(BinaryReader reader)
- {
- noiseSize = reader.ReadInt32();
- gradientSeed = reader.ReadInt32();
- permutationSeed = reader.ReadInt32();
-
- BuildNoise();
- }
-
- /// <summary>
- /// Save Noise parameter out to a stream so we can reconstruct the exact
- /// same values again when loading these parameters.
- /// </summary>
- public void Save(BinaryWriter writer)
- {
- writer.Write(noiseSize);
- writer.Write(gradientSeed);
- writer.Write(permutationSeed);
- }
- #endregion
-
- #region op_Equality (Operator)
- /// <summary>
- /// Check for equality
- /// </summary>
- /// <param name="value1">Value 1</param>
- /// <param name="value2">Value 2</param>
- /// <returns>True if the values are equal, false otherwise</returns>
- public static bool operator ==(Noise value1, Noise value2)
- {
- return value1 != null &&
- value2 != null &&
- value1.noiseSize == value2.noiseSize &&
- value1.gradientSeed == value2.gradientSeed &&
- value1.permutationSeed == value2.permutationSeed;
- }
- #endregion
-
- #region op_Inequality (Operator)
- /// <summary>
- /// Check for inequality
- /// </summary>
- /// <param name="value1">Value 1</param>
- /// <param name="value2">Value 2</param>
- /// <returns>Bool</returns>
- public static bool operator !=(Noise value1, Noise value2)
- {
- return value1 == null ||
- value2 == null ||
- value1.noiseSize != value2.noiseSize ||
- value1.gradientSeed != value2.gradientSeed ||
- value1.permutationSeed != value2.permutationSeed;
- }
- #endregion
-
- #region GetNoise (Public)
- /// <summary>
- /// Get noise at a specific Vector4 (as Quternion) position. Each of the
- /// 4 values (x, y, z, w) should be between 0.0 and 1.0. When all values
- /// are 0.0, 0.0 is always returned.
- /// </summary>
- public float GetNoise(Quaternion pos)
- {
- float x = pos.X + NoiseBias;
- float y = pos.Y + NoiseBias;
- float z = pos.Z + NoiseBias;
- float w = pos.W + NoiseBias;
-
- int floorX = (int)System.Math.Floor(x);
- float rx = x - floorX;
- float rxc = rx - 1.0f;
-
- int floorY = (int)System.Math.Floor(y);
- float ry = y - floorY;
- float ryc = ry - 1.0f;
-
- int floorZ = (int)System.Math.Floor(z);
- float rz = z - floorZ;
- float rzc = rz - 1.0f;
-
- int floorW = (int)System.Math.Floor(w);
- float rw = w - floorW;
- float rwc = rw - 1.0f;
-
- int dwBoundX0 = floorX % noiseSize;
- int dwBoundX1 = (dwBoundX0 + 1) % noiseSize;
- int dwBoundY0 = floorY % noiseSize;
- int dwBoundY1 = (dwBoundY0 + 1) % noiseSize;
- int dwBoundZ0 = floorZ % noiseSize;
- int dwBoundZ1 = (dwBoundZ0 + 1) % noiseSize;
- int dwBoundW0 = floorW % noiseSize;
- int dwBoundW1 = (dwBoundW0 + 1) % noiseSize;
-
- float sx = SCurve(rx);
- float sy = SCurve(ry);
- float sz = SCurve(rz);
- float sw = SCurve(rw);
-
- float f0000 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX0] + dwBoundY0] + dwBoundZ0] + dwBoundW0],
- rx, ry, rz, rw);
- float f1000 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX1] + dwBoundY0] + dwBoundZ0] + dwBoundW0],
- rxc, ry, rz, rw);
- float f1100 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX1] + dwBoundY1] + dwBoundZ0] + dwBoundW0],
- rxc, ryc, rz, rw);
- float f0100 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX0] + dwBoundY1] + dwBoundZ0] + dwBoundW0],
- rx, ryc, rz, rw);
- float f0010 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX0] + dwBoundY0] + dwBoundZ1] + dwBoundW0],
- rx, ry, rzc, rw);
- float f1010 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX1] + dwBoundY0] + dwBoundZ1] + dwBoundW0],
- rxc, ry, rzc, rw);
- float f1110 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX1] + dwBoundY1] + dwBoundZ1] + dwBoundW0],
- rxc, ryc, rzc, rw);
- float f0110 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX0] + dwBoundY1] + dwBoundZ1] + dwBoundW0],
- rx, ryc, rzc, rw);
- float f0001 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX0] + dwBoundY0] + dwBoundZ0] + dwBoundW1],
- rx, ry, rz, rwc);
- float f1001 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX1] + dwBoundY0] + dwBoundZ0] + dwBoundW1],
- rxc, ry, rz, rwc);
- float f1101 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX1] + dwBoundY1] + dwBoundZ0] + dwBoundW1],
- rxc, ryc, rz, rwc);
- float f0101 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX0] + dwBoundY1] + dwBoundZ0] + dwBoundW1],
- rx, ryc, rz, rwc);
- float f0011 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX0] + dwBoundY0] + dwBoundZ1] + dwBoundW1],
- rx, ry, rzc, rwc);
- float f1011 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX1] + dwBoundY0] + dwBoundZ1] + dwBoundW1],
- rxc, ry, rzc, rwc);
- float f1111 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX1] + dwBoundY1] + dwBoundZ1] + dwBoundW1],
- rxc, ryc, rzc, rwc);
- float f0111 = Dot(noiseTable[permutationTable[permutationTable[
- permutationTable[dwBoundX0] + dwBoundY1] + dwBoundZ1] + dwBoundW1],
- rx, ryc, rzc, rwc);
-
- // Use the helper methods below :)
- float a = TriLerp(f0000, f1000, f1100, f0100, f0010, f1010, f1110,
- f0110, sx, sy, sz);
- float b = TriLerp(f0001, f1001, f1101, f0101, f0011, f1011, f1111,
- f0111, sx, sy, sz);
- return Lerp(a, b, sw);
- }
- #endregion
-
- #region GetHashCode (Public)
- /// <summary>
- /// Get hash code
- /// </summary>
- public override int GetHashCode()
- {
- return noiseSize.GetHashCode() ^
- gradientSeed.GetHashCode() ^
- permutationSeed.GetHashCode();
- }
- #endregion
-
- #region Equals (Public)
- /// <summary>
- /// Equals
- /// </summary>
- public override bool Equals(object obj)
- {
- if (obj is Noise)
- {
- return Equals((Noise)obj);
- }
- return base.Equals(obj);
- }
- #endregion
-
- #region Methods (Private)
-
- #region BuildNoise
- /// <summary>
- /// Build noise with the given parameters: noiseSize, gradientSeed and
- /// permutationSeed. The resulting tables are always the same for the
- /// same parameters.
- /// </summary>
- private void BuildNoise()
- {
- gradientRange = new Random(gradientSeed);
- permutationRange = new Random(permutationSeed);
-
- // Build the noise and permutation tables
- noiseTable = new Quaternion[2 * noiseSize];
- permutationTable = new uint[2 * noiseSize];
-
- for (uint slotIndex = 0; slotIndex < noiseSize; slotIndex++)
- {
- noiseTable[slotIndex] =
- noiseTable[noiseSize + slotIndex] =
- new Quaternion(
- 2.0f * (float)gradientRange.NextDouble() - 1.0f,
- 2.0f * (float)gradientRange.NextDouble() - 1.0f,
- 2.0f * (float)gradientRange.NextDouble() - 1.0f,
- 2.0f * (float)gradientRange.NextDouble() - 1.0f);
- permutationTable[slotIndex] = slotIndex;
- }
-
- // Mix the permutation table by exchanging indices at random
- for (uint slotIndex = 0; slotIndex < noiseSize; slotIndex++)
- {
- uint randomIndex = (uint)permutationRange.Next(noiseSize);
-
- uint temp = permutationTable[randomIndex];
- permutationTable[randomIndex] = permutationTable[slotIndex];
- permutationTable[slotIndex] = temp;
- }
-
- // Finalize the permutation table by doubling its size
- for (uint slotIndex = 0; slotIndex < noiseSize; slotIndex++)
- {
- permutationTable[noiseSize + slotIndex] =
- permutationTable[slotIndex];
- }
- }
- #endregion
-
- #region SCurve
- /// <summary>
- /// SCurve helper method for GetNoise.
- /// 3 t^2 - 2 t^3 ==> Gives some sort of S-Shaped curve
- /// </summary>
- protected float SCurve(float t)
- {
- return t * t * (3.0f - 2.0f * t);
- }
- #endregion
-
- #region Dot
- /// <summary>
- /// Dot helper method for GetNoise
- /// </summary>
- protected float Dot(Quaternion op0, float op1X, float op1Y,
- float op1Z, float op1W)
- {
- return op0.X * op1X + op0.Y * op1Y + op0.Z * op1Z + op0.W * op1W;
- }
- #endregion
-
- #region TriLerp
- /// <summary>
- /// Linear, Bilinear and Trilinear interpolation helper for GetNoise.
- /// The cube is designed like this:
- ///
- /// p3 p2
- /// o--------o
- /// /: /| Y
- /// p7/ : p6/ | |
- /// o--------o | |
- /// | :p0 | |p1 |
- /// | o.....|..o o------X
- /// | ' | / /
- /// |' |/ /
- /// o--------o Z
- /// p4 p5
- /// </summary>
- protected float TriLerp(float p0, float p1, float p2, float p3,
- float p4, float p5, float p6, float p7, float x, float y,
- float z)
- {
- return Lerp(BiLerp(p0, p1, p2, p3, x, y),
- BiLerp(p4, p5, p6, p7, x, y), z);
- }
- #endregion
-
- #region BiLerp
- /// <summary>
- /// BiLerp helper method used in GetNoise and TriLerp
- /// </summary>
- protected float BiLerp(float p0, float p1, float p2, float p3,
- float x, float y)
- {
- return Lerp(Lerp(p0, p1, x), Lerp(p3, p2, x), y);
- }
- #endregion
-
- #region Lerp
- /// <summary>
- /// Lerp helper method used in GetNoise.
- /// </summary>
- protected float Lerp(float p0, float p1, float x)
- {
- return p0 + (p1 - p0) * x;
- }
- #endregion
-
- #endregion
-
- internal class NoiseTests
- {
- #region GenerateNoise
- [Test]
- public void GenerateNoise()
- {
- Noise exampleNoise = new Noise(10, 1, 2);
- float firstValue = exampleNoise.GetNoise(
- new Quaternion(0, 0, 0, 0));
- float anotherValue = exampleNoise.GetNoise(
- new Quaternion(0.1f, 0.2f, 0.3f, 0.4f));
- // Those values should not be equal normally.
- Assert.NotEqual(firstValue, anotherValue);
-
- // Now generate the same noise again and check if we get the same
- // values again.
- Noise secondNoise = new Noise(10, 1, 2);
- Assert.Equal(firstValue,
- secondNoise.GetNoise(new Quaternion(0, 0, 0, 0)));
- Assert.Equal(anotherValue,
- secondNoise.GetNoise(new Quaternion(0.1f, 0.2f, 0.3f, 0.4f)));
-
- // And finally generate a noise with different values, which should
- // result in different values.
- Noise differentNoise = new Noise(10, 3, 4);
- // Note: 0, 0, 0, 0 always returns the same value: 0.0f
- Assert.NotEqual(anotherValue,
- differentNoise.GetNoise(new Quaternion(0.1f, 0.2f, 0.3f, 0.4f)));
- }
- #endregion
- }
- }
- }