PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

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