PageRenderTime 221ms CodeModel.GetById 80ms app.highlight 42ms RepoModel.GetById 95ms app.codeStats 0ms

/Utilities/Math/Noise.cs

#
C# | 471 lines | 290 code | 46 blank | 135 comment | 18 complexity | 28d5008cf132a376ee5f291922ac4367 MD5 | raw file
  1using System;
  2using System.IO;
  3using Delta.Utilities.Datatypes;
  4using NUnit.Framework;
  5
  6namespace Delta.Utilities.Math
  7{
  8	/// <summary>
  9	/// Helper class to generate noise and allow to recreate the same conditions
 10	/// over and over again to generate the exact same random noise again. Each
 11	/// noise entry is a Vector4 and will be interpolated in the GetNoise method.
 12	/// </summary>
 13	public class Noise : ISaveLoadBinary, IEquatable<Noise>
 14	{
 15		#region Constants
 16		/// <summary>
 17		/// Noise bias = 1024
 18		/// </summary>
 19		protected const int NoiseBias = 1024;
 20		#endregion
 21
 22		#region Protected
 23
 24		#region noiseSize (Protected)
 25		/// <summary>
 26		/// Size of the noise array, the gradient and permutation seed. All these
 27		/// values can be saved to a stream (see Save) and loaded again later
 28		/// to reconstruct the exact same noise table again (with Load).
 29		/// </summary>
 30		protected int noiseSize;
 31		#endregion
 32
 33		#region gradientSeed (Protected)
 34		/// <summary>
 35		/// Size of the noise array, the gradient and permutation seed. All these
 36		/// values can be saved to a stream (see Save) and loaded again later
 37		/// to reconstruct the exact same noise table again (with Load).
 38		/// </summary>
 39		protected int gradientSeed;
 40		#endregion
 41
 42		#region permutationSeed (Protected)
 43		/// <summary>
 44		/// Size of the noise array, the gradient and permutation seed. All these
 45		/// values can be saved to a stream (see Save) and loaded again later
 46		/// to reconstruct the exact same noise table again (with Load).
 47		/// </summary>
 48		protected int permutationSeed;
 49		#endregion
 50
 51		#region noiseTable (Protected)
 52		/// <summary>
 53		/// Noise table values, use GetNoise to get to the values.
 54		/// Note: Quaternions are just used here as a Vector4 replacement.
 55		/// </summary>
 56		protected Quaternion[] noiseTable;
 57		#endregion
 58
 59		#region permutationTable (Protected)
 60		/// <summary>
 61		/// Permutation table to jump around like crazy in the noiseTable array.
 62		/// </summary>
 63		protected uint[] permutationTable;
 64		#endregion
 65
 66		#region gradientRange (Protected)
 67		/// <summary>
 68		/// Gradient range
 69		/// </summary>
 70		protected Random gradientRange;
 71		#endregion
 72
 73		#region permutationRange (Protected)
 74		/// <summary>
 75		/// Permutation range
 76		/// </summary>
 77		protected Random permutationRange;
 78		#endregion
 79
 80		#endregion
 81
 82		#region Constructors
 83		/// <summary>
 84		/// Create noise with given table size, gradient seed and permutation seed
 85		/// values (this way you can easily recreate this noise table with the
 86		/// same values again). Each noise value is stored as a Vector4
 87		/// (Quaternion in our engine).
 88		/// </summary>
 89		/// <param name="setNoiseTableSize">Size for the noise table</param>
 90		/// <param name="setGradientSeed">
 91		/// Seed value to regenerate the same random seed values as before.
 92		/// </param>
 93		/// <param name="setPermutationSeed">
 94		/// And another seed for the permutation, must also fit to generate the
 95		/// same values again.
 96		/// </param>
 97		public Noise(int setNoiseTableSize, int setGradientSeed,
 98			int setPermutationSeed)
 99		{
100			noiseSize = setNoiseTableSize;
101			gradientSeed = setGradientSeed;
102			// Permutation seed
103			permutationSeed = setPermutationSeed;
104
105			BuildNoise();
106		}
107
108		/// <summary>
109		/// Create noise parameters from a stream and rebuild the same noise table
110		/// that was used when this Noise was saved.
111		/// </summary>
112		public Noise(BinaryReader reader)
113		{
114			Load(reader);
115		}
116		#endregion
117
118		#region IEquatable<Noise> Members
119		/// <summary>
120		/// Equals
121		/// </summary>
122		/// <param name="other">Other</param>
123		/// <returns>Value indicating the equality of two vectors</returns>
124		public bool Equals(Noise other)
125		{
126			return other != null &&
127			       noiseSize == other.noiseSize &&
128			       gradientSeed == other.gradientSeed &&
129			       permutationSeed == other.permutationSeed;
130		}
131		#endregion
132
133		#region ISaveLoadBinary Members
134		/// <summary>
135		/// Load Noise parameters from a stream and rebuild the same noise table
136		/// that was used when this Noise was saved.
137		/// </summary>
138		public void Load(BinaryReader reader)
139		{
140			noiseSize = reader.ReadInt32();
141			gradientSeed = reader.ReadInt32();
142			permutationSeed = reader.ReadInt32();
143
144			BuildNoise();
145		}
146
147		/// <summary>
148		/// Save Noise parameter out to a stream so we can reconstruct the exact
149		/// same values again when loading these parameters.
150		/// </summary>
151		public void Save(BinaryWriter writer)
152		{
153			writer.Write(noiseSize);
154			writer.Write(gradientSeed);
155			writer.Write(permutationSeed);
156		}
157		#endregion
158
159		#region op_Equality (Operator)
160		/// <summary>
161		/// Check for equality
162		/// </summary>
163		/// <param name="value1">Value 1</param>
164		/// <param name="value2">Value 2</param>
165		/// <returns>True if the values are equal, false otherwise</returns>
166		public static bool operator ==(Noise value1, Noise value2)
167		{
168			return value1 != null &&
169			       value2 != null &&
170			       value1.noiseSize == value2.noiseSize &&
171			       value1.gradientSeed == value2.gradientSeed &&
172			       value1.permutationSeed == value2.permutationSeed;
173		}
174		#endregion
175
176		#region op_Inequality (Operator)
177		/// <summary>
178		/// Check for inequality
179		/// </summary>
180		/// <param name="value1">Value 1</param>
181		/// <param name="value2">Value 2</param>
182		/// <returns>Bool</returns>	
183		public static bool operator !=(Noise value1, Noise value2)
184		{
185			return value1 == null ||
186			       value2 == null ||
187			       value1.noiseSize != value2.noiseSize ||
188			       value1.gradientSeed != value2.gradientSeed ||
189			       value1.permutationSeed != value2.permutationSeed;
190		}
191		#endregion
192
193		#region GetNoise (Public)
194		/// <summary>
195		/// Get noise at a specific Vector4 (as Quternion) position. Each of the
196		/// 4 values (x, y, z, w) should be between 0.0 and 1.0. When all values
197		/// are 0.0, 0.0 is always returned.
198		/// </summary>
199		public float GetNoise(Quaternion pos)
200		{
201			float x = pos.X + NoiseBias;
202			float y = pos.Y + NoiseBias;
203			float z = pos.Z + NoiseBias;
204			float w = pos.W + NoiseBias;
205
206			int floorX = (int)System.Math.Floor(x);
207			float rx = x - floorX;
208			float rxc = rx - 1.0f;
209
210			int floorY = (int)System.Math.Floor(y);
211			float ry = y - floorY;
212			float ryc = ry - 1.0f;
213
214			int floorZ = (int)System.Math.Floor(z);
215			float rz = z - floorZ;
216			float rzc = rz - 1.0f;
217
218			int floorW = (int)System.Math.Floor(w);
219			float rw = w - floorW;
220			float rwc = rw - 1.0f;
221
222			int dwBoundX0 = floorX % noiseSize;
223			int dwBoundX1 = (dwBoundX0 + 1) % noiseSize;
224			int dwBoundY0 = floorY % noiseSize;
225			int dwBoundY1 = (dwBoundY0 + 1) % noiseSize;
226			int dwBoundZ0 = floorZ % noiseSize;
227			int dwBoundZ1 = (dwBoundZ0 + 1) % noiseSize;
228			int dwBoundW0 = floorW % noiseSize;
229			int dwBoundW1 = (dwBoundW0 + 1) % noiseSize;
230
231			float sx = SCurve(rx);
232			float sy = SCurve(ry);
233			float sz = SCurve(rz);
234			float sw = SCurve(rw);
235
236			float f0000 = Dot(noiseTable[permutationTable[permutationTable[
237				permutationTable[dwBoundX0] + dwBoundY0] + dwBoundZ0] + dwBoundW0],
238				rx, ry, rz, rw);
239			float f1000 = Dot(noiseTable[permutationTable[permutationTable[
240				permutationTable[dwBoundX1] + dwBoundY0] + dwBoundZ0] + dwBoundW0],
241				rxc, ry, rz, rw);
242			float f1100 = Dot(noiseTable[permutationTable[permutationTable[
243				permutationTable[dwBoundX1] + dwBoundY1] + dwBoundZ0] + dwBoundW0],
244				rxc, ryc, rz, rw);
245			float f0100 = Dot(noiseTable[permutationTable[permutationTable[
246				permutationTable[dwBoundX0] + dwBoundY1] + dwBoundZ0] + dwBoundW0],
247				rx, ryc, rz, rw);
248			float f0010 = Dot(noiseTable[permutationTable[permutationTable[
249				permutationTable[dwBoundX0] + dwBoundY0] + dwBoundZ1] + dwBoundW0],
250				rx, ry, rzc, rw);
251			float f1010 = Dot(noiseTable[permutationTable[permutationTable[
252				permutationTable[dwBoundX1] + dwBoundY0] + dwBoundZ1] + dwBoundW0],
253				rxc, ry, rzc, rw);
254			float f1110 = Dot(noiseTable[permutationTable[permutationTable[
255				permutationTable[dwBoundX1] + dwBoundY1] + dwBoundZ1] + dwBoundW0],
256				rxc, ryc, rzc, rw);
257			float f0110 = Dot(noiseTable[permutationTable[permutationTable[
258				permutationTable[dwBoundX0] + dwBoundY1] + dwBoundZ1] + dwBoundW0],
259				rx, ryc, rzc, rw);
260			float f0001 = Dot(noiseTable[permutationTable[permutationTable[
261				permutationTable[dwBoundX0] + dwBoundY0] + dwBoundZ0] + dwBoundW1],
262				rx, ry, rz, rwc);
263			float f1001 = Dot(noiseTable[permutationTable[permutationTable[
264				permutationTable[dwBoundX1] + dwBoundY0] + dwBoundZ0] + dwBoundW1],
265				rxc, ry, rz, rwc);
266			float f1101 = Dot(noiseTable[permutationTable[permutationTable[
267				permutationTable[dwBoundX1] + dwBoundY1] + dwBoundZ0] + dwBoundW1],
268				rxc, ryc, rz, rwc);
269			float f0101 = Dot(noiseTable[permutationTable[permutationTable[
270				permutationTable[dwBoundX0] + dwBoundY1] + dwBoundZ0] + dwBoundW1],
271				rx, ryc, rz, rwc);
272			float f0011 = Dot(noiseTable[permutationTable[permutationTable[
273				permutationTable[dwBoundX0] + dwBoundY0] + dwBoundZ1] + dwBoundW1],
274				rx, ry, rzc, rwc);
275			float f1011 = Dot(noiseTable[permutationTable[permutationTable[
276				permutationTable[dwBoundX1] + dwBoundY0] + dwBoundZ1] + dwBoundW1],
277				rxc, ry, rzc, rwc);
278			float f1111 = Dot(noiseTable[permutationTable[permutationTable[
279				permutationTable[dwBoundX1] + dwBoundY1] + dwBoundZ1] + dwBoundW1],
280				rxc, ryc, rzc, rwc);
281			float f0111 = Dot(noiseTable[permutationTable[permutationTable[
282				permutationTable[dwBoundX0] + dwBoundY1] + dwBoundZ1] + dwBoundW1],
283				rx, ryc, rzc, rwc);
284
285			// Use the helper methods below :)
286			float a = TriLerp(f0000, f1000, f1100, f0100, f0010, f1010, f1110,
287				f0110, sx, sy, sz);
288			float b = TriLerp(f0001, f1001, f1101, f0101, f0011, f1011, f1111,
289				f0111, sx, sy, sz);
290			return Lerp(a, b, sw);
291		}
292		#endregion
293
294		#region GetHashCode (Public)
295		/// <summary>
296		/// Get hash code
297		/// </summary>
298		public override int GetHashCode()
299		{
300			return noiseSize.GetHashCode() ^
301			       gradientSeed.GetHashCode() ^
302			       permutationSeed.GetHashCode();
303		}
304		#endregion
305
306		#region Equals (Public)
307		/// <summary>
308		/// Equals
309		/// </summary>
310		public override bool Equals(object obj)
311		{
312			if (obj is Noise)
313			{
314				return Equals((Noise)obj);
315			}
316			return base.Equals(obj);
317		}
318		#endregion
319
320		#region Methods (Private)
321
322		#region BuildNoise
323		/// <summary>
324		/// Build noise with the given parameters: noiseSize, gradientSeed and
325		/// permutationSeed. The resulting tables are always the same for the
326		/// same parameters.
327		/// </summary>
328		private void BuildNoise()
329		{
330			gradientRange = new Random(gradientSeed);
331			permutationRange = new Random(permutationSeed);
332
333			// Build the noise and permutation tables
334			noiseTable = new Quaternion[2 * noiseSize];
335			permutationTable = new uint[2 * noiseSize];
336
337			for (uint slotIndex = 0; slotIndex < noiseSize; slotIndex++)
338			{
339				noiseTable[slotIndex] =
340					noiseTable[noiseSize + slotIndex] =
341					new Quaternion(
342						2.0f * (float)gradientRange.NextDouble() - 1.0f,
343						2.0f * (float)gradientRange.NextDouble() - 1.0f,
344						2.0f * (float)gradientRange.NextDouble() - 1.0f,
345						2.0f * (float)gradientRange.NextDouble() - 1.0f);
346				permutationTable[slotIndex] = slotIndex;
347			}
348
349			// Mix the permutation table by exchanging indices at random
350			for (uint slotIndex = 0; slotIndex < noiseSize; slotIndex++)
351			{
352				uint randomIndex = (uint)permutationRange.Next(noiseSize);
353
354				uint temp = permutationTable[randomIndex];
355				permutationTable[randomIndex] = permutationTable[slotIndex];
356				permutationTable[slotIndex] = temp;
357			}
358
359			// Finalize the permutation table by doubling its size
360			for (uint slotIndex = 0; slotIndex < noiseSize; slotIndex++)
361			{
362				permutationTable[noiseSize + slotIndex] =
363					permutationTable[slotIndex];
364			}
365		}
366		#endregion
367
368		#region SCurve
369		/// <summary>
370		/// SCurve helper method for GetNoise.
371		/// 3 t^2 - 2 t^3  ==> Gives some sort of S-Shaped curve
372		/// </summary>
373		protected float SCurve(float t)
374		{
375			return t * t * (3.0f - 2.0f * t);
376		}
377		#endregion
378
379		#region Dot
380		/// <summary>
381		/// Dot helper method for GetNoise
382		/// </summary>
383		protected float Dot(Quaternion op0, float op1X, float op1Y,
384			float op1Z, float op1W)
385		{
386			return op0.X * op1X + op0.Y * op1Y + op0.Z * op1Z + op0.W * op1W;
387		}
388		#endregion
389
390		#region TriLerp
391		/// <summary>
392		/// Linear, Bilinear and Trilinear interpolation helper for GetNoise.
393		/// The cube is designed like this:
394		///
395		///        p3         p2
396		///         o--------o
397		///        /:       /|          Y
398		///     p7/ :    p6/ |          |
399		///      o--------o  |          |
400		///      |  :p0   |  |p1        |
401		///      |  o.....|..o          o------X
402		///      | '      | /          /
403		///      |'       |/          /
404		///      o--------o          Z
405		///     p4        p5
406		/// </summary>
407		protected float TriLerp(float p0, float p1, float p2, float p3,
408			float p4, float p5, float p6, float p7, float x, float y,
409			float z)
410		{
411			return Lerp(BiLerp(p0, p1, p2, p3, x, y),
412				BiLerp(p4, p5, p6, p7, x, y), z);
413		}
414		#endregion
415
416		#region BiLerp
417		/// <summary>
418		/// BiLerp helper method used in GetNoise and TriLerp
419		/// </summary>
420		protected float BiLerp(float p0, float p1, float p2, float p3,
421			float x, float y)
422		{
423			return Lerp(Lerp(p0, p1, x), Lerp(p3, p2, x), y);
424		}
425		#endregion
426
427		#region Lerp
428		/// <summary>
429		/// Lerp helper method used in GetNoise.
430		/// </summary>
431		protected float Lerp(float p0, float p1, float x)
432		{
433			return p0 + (p1 - p0) * x;
434		}
435		#endregion
436
437		#endregion
438
439		internal class NoiseTests
440		{
441			#region GenerateNoise
442			[Test]
443			public void GenerateNoise()
444			{
445				Noise exampleNoise = new Noise(10, 1, 2);
446				float firstValue = exampleNoise.GetNoise(
447					new Quaternion(0, 0, 0, 0));
448				float anotherValue = exampleNoise.GetNoise(
449					new Quaternion(0.1f, 0.2f, 0.3f, 0.4f));
450				// Those values should not be equal normally.
451				Assert.NotEqual(firstValue, anotherValue);
452
453				// Now generate the same noise again and check if we get the same
454				// values again.
455				Noise secondNoise = new Noise(10, 1, 2);
456				Assert.Equal(firstValue,
457					secondNoise.GetNoise(new Quaternion(0, 0, 0, 0)));
458				Assert.Equal(anotherValue,
459					secondNoise.GetNoise(new Quaternion(0.1f, 0.2f, 0.3f, 0.4f)));
460
461				// And finally generate a noise with different values, which should
462				// result in different values.
463				Noise differentNoise = new Noise(10, 3, 4);
464				// Note: 0, 0, 0, 0 always returns the same value: 0.0f
465				Assert.NotEqual(anotherValue,
466					differentNoise.GetNoise(new Quaternion(0.1f, 0.2f, 0.3f, 0.4f)));
467			}
468			#endregion
469		}
470	}
471}