/random.js

https://github.com/mattdesl/canvas-sketch-util · JavaScript · 328 lines · 283 code · 44 blank · 1 comment · 53 complexity · 45d315f58ff6fccddc06829bc36d814e MD5 · raw file

  1. var seedRandom = require('seed-random');
  2. var SimplexNoise = require('simplex-noise');
  3. var defined = require('defined');
  4. function createRandom (defaultSeed) {
  5. defaultSeed = defined(defaultSeed, null);
  6. var defaultRandom = Math.random;
  7. var currentSeed;
  8. var currentRandom;
  9. var noiseGenerator;
  10. var _nextGaussian = null;
  11. var _hasNextGaussian = false;
  12. setSeed(defaultSeed);
  13. return {
  14. value: value,
  15. createRandom: function (defaultSeed) {
  16. return createRandom(defaultSeed);
  17. },
  18. setSeed: setSeed,
  19. getSeed: getSeed,
  20. getRandomSeed: getRandomSeed,
  21. valueNonZero: valueNonZero,
  22. permuteNoise: permuteNoise,
  23. noise1D: noise1D,
  24. noise2D: noise2D,
  25. noise3D: noise3D,
  26. noise4D: noise4D,
  27. sign: sign,
  28. boolean: boolean,
  29. chance: chance,
  30. range: range,
  31. rangeFloor: rangeFloor,
  32. pick: pick,
  33. shuffle: shuffle,
  34. onCircle: onCircle,
  35. insideCircle: insideCircle,
  36. onSphere: onSphere,
  37. insideSphere: insideSphere,
  38. quaternion: quaternion,
  39. weighted: weighted,
  40. weightedSet: weightedSet,
  41. weightedSetIndex: weightedSetIndex,
  42. gaussian: gaussian
  43. };
  44. function setSeed (seed, opt) {
  45. if (typeof seed === 'number' || typeof seed === 'string') {
  46. currentSeed = seed;
  47. currentRandom = seedRandom(currentSeed, opt);
  48. } else {
  49. currentSeed = undefined;
  50. currentRandom = defaultRandom;
  51. }
  52. noiseGenerator = createNoise();
  53. _nextGaussian = null;
  54. _hasNextGaussian = false;
  55. }
  56. function value () {
  57. return currentRandom();
  58. }
  59. function valueNonZero () {
  60. var u = 0;
  61. while (u === 0) u = value();
  62. return u;
  63. }
  64. function getSeed () {
  65. return currentSeed;
  66. }
  67. function getRandomSeed () {
  68. var seed = String(Math.floor(Math.random() * 1000000));
  69. return seed;
  70. }
  71. function createNoise () {
  72. return new SimplexNoise(currentRandom);
  73. }
  74. function permuteNoise () {
  75. noiseGenerator = createNoise();
  76. }
  77. function noise1D (x, frequency, amplitude) {
  78. if (!isFinite(x)) throw new TypeError('x component for noise() must be finite');
  79. frequency = defined(frequency, 1);
  80. amplitude = defined(amplitude, 1);
  81. return amplitude * noiseGenerator.noise2D(x * frequency, 0);
  82. }
  83. function noise2D (x, y, frequency, amplitude) {
  84. if (!isFinite(x)) throw new TypeError('x component for noise() must be finite');
  85. if (!isFinite(y)) throw new TypeError('y component for noise() must be finite');
  86. frequency = defined(frequency, 1);
  87. amplitude = defined(amplitude, 1);
  88. return amplitude * noiseGenerator.noise2D(x * frequency, y * frequency);
  89. }
  90. function noise3D (x, y, z, frequency, amplitude) {
  91. if (!isFinite(x)) throw new TypeError('x component for noise() must be finite');
  92. if (!isFinite(y)) throw new TypeError('y component for noise() must be finite');
  93. if (!isFinite(z)) throw new TypeError('z component for noise() must be finite');
  94. frequency = defined(frequency, 1);
  95. amplitude = defined(amplitude, 1);
  96. return amplitude * noiseGenerator.noise3D(
  97. x * frequency,
  98. y * frequency,
  99. z * frequency
  100. );
  101. }
  102. function noise4D (x, y, z, w, frequency, amplitude) {
  103. if (!isFinite(x)) throw new TypeError('x component for noise() must be finite');
  104. if (!isFinite(y)) throw new TypeError('y component for noise() must be finite');
  105. if (!isFinite(z)) throw new TypeError('z component for noise() must be finite');
  106. if (!isFinite(w)) throw new TypeError('w component for noise() must be finite');
  107. frequency = defined(frequency, 1);
  108. amplitude = defined(amplitude, 1);
  109. return amplitude * noiseGenerator.noise4D(
  110. x * frequency,
  111. y * frequency,
  112. z * frequency,
  113. w * frequency
  114. );
  115. }
  116. function sign () {
  117. return boolean() ? 1 : -1;
  118. }
  119. function boolean () {
  120. return value() > 0.5;
  121. }
  122. function chance (n) {
  123. n = defined(n, 0.5);
  124. if (typeof n !== 'number') throw new TypeError('expected n to be a number');
  125. return value() < n;
  126. }
  127. function range (min, max) {
  128. if (max === undefined) {
  129. max = min;
  130. min = 0;
  131. }
  132. if (typeof min !== 'number' || typeof max !== 'number') {
  133. throw new TypeError('Expected all arguments to be numbers');
  134. }
  135. return value() * (max - min) + min;
  136. }
  137. function rangeFloor (min, max) {
  138. if (max === undefined) {
  139. max = min;
  140. min = 0;
  141. }
  142. if (typeof min !== 'number' || typeof max !== 'number') {
  143. throw new TypeError('Expected all arguments to be numbers');
  144. }
  145. return Math.floor(range(min, max));
  146. }
  147. function pick (array) {
  148. if (array.length === 0) return undefined;
  149. return array[rangeFloor(0, array.length)];
  150. }
  151. function shuffle (arr) {
  152. if (!Array.isArray(arr)) {
  153. throw new TypeError('Expected Array, got ' + typeof arr);
  154. }
  155. var rand;
  156. var tmp;
  157. var len = arr.length;
  158. var ret = arr.slice();
  159. while (len) {
  160. rand = Math.floor(value() * len--);
  161. tmp = ret[len];
  162. ret[len] = ret[rand];
  163. ret[rand] = tmp;
  164. }
  165. return ret;
  166. }
  167. function onCircle (radius, out) {
  168. radius = defined(radius, 1);
  169. out = out || [];
  170. var theta = value() * 2.0 * Math.PI;
  171. out[0] = radius * Math.cos(theta);
  172. out[1] = radius * Math.sin(theta);
  173. return out;
  174. }
  175. function insideCircle (radius, out) {
  176. radius = defined(radius, 1);
  177. out = out || [];
  178. onCircle(1, out);
  179. var r = radius * Math.sqrt(value());
  180. out[0] *= r;
  181. out[1] *= r;
  182. return out;
  183. }
  184. function onSphere (radius, out) {
  185. radius = defined(radius, 1);
  186. out = out || [];
  187. var u = value() * Math.PI * 2;
  188. var v = value() * 2 - 1;
  189. var phi = u;
  190. var theta = Math.acos(v);
  191. out[0] = radius * Math.sin(theta) * Math.cos(phi);
  192. out[1] = radius * Math.sin(theta) * Math.sin(phi);
  193. out[2] = radius * Math.cos(theta);
  194. return out;
  195. }
  196. function insideSphere (radius, out) {
  197. radius = defined(radius, 1);
  198. out = out || [];
  199. var u = value() * Math.PI * 2;
  200. var v = value() * 2 - 1;
  201. var k = value();
  202. var phi = u;
  203. var theta = Math.acos(v);
  204. var r = radius * Math.cbrt(k);
  205. out[0] = r * Math.sin(theta) * Math.cos(phi);
  206. out[1] = r * Math.sin(theta) * Math.sin(phi);
  207. out[2] = r * Math.cos(theta);
  208. return out;
  209. }
  210. function quaternion (out) {
  211. out = out || [];
  212. var u1 = value();
  213. var u2 = value();
  214. var u3 = value();
  215. var sq1 = Math.sqrt(1 - u1);
  216. var sq2 = Math.sqrt(u1);
  217. var theta1 = Math.PI * 2 * u2;
  218. var theta2 = Math.PI * 2 * u3;
  219. var x = Math.sin(theta1) * sq1;
  220. var y = Math.cos(theta1) * sq1;
  221. var z = Math.sin(theta2) * sq2;
  222. var w = Math.cos(theta2) * sq2;
  223. out[0] = x;
  224. out[1] = y;
  225. out[2] = z;
  226. out[3] = w;
  227. return out;
  228. }
  229. function weightedSet (set) {
  230. set = set || [];
  231. if (set.length === 0) return null;
  232. return set[weightedSetIndex(set)].value;
  233. }
  234. function weightedSetIndex (set) {
  235. set = set || [];
  236. if (set.length === 0) return -1;
  237. return weighted(set.map(function (s) {
  238. return s.weight;
  239. }));
  240. }
  241. function weighted (weights) {
  242. weights = weights || [];
  243. if (weights.length === 0) return -1;
  244. var totalWeight = 0;
  245. var i;
  246. for (i = 0; i < weights.length; i++) {
  247. totalWeight += weights[i];
  248. }
  249. if (totalWeight <= 0) throw new Error('Weights must sum to > 0');
  250. var random = value() * totalWeight;
  251. for (i = 0; i < weights.length; i++) {
  252. if (random < weights[i]) {
  253. return i;
  254. }
  255. random -= weights[i];
  256. }
  257. return 0;
  258. }
  259. function gaussian (mean, standardDerivation) {
  260. mean = defined(mean, 0);
  261. standardDerivation = defined(standardDerivation, 1);
  262. // https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/Random.java#L496
  263. if (_hasNextGaussian) {
  264. _hasNextGaussian = false;
  265. var result = _nextGaussian;
  266. _nextGaussian = null;
  267. return mean + standardDerivation * result;
  268. } else {
  269. var v1 = 0;
  270. var v2 = 0;
  271. var s = 0;
  272. do {
  273. v1 = value() * 2 - 1; // between -1 and 1
  274. v2 = value() * 2 - 1; // between -1 and 1
  275. s = v1 * v1 + v2 * v2;
  276. } while (s >= 1 || s === 0);
  277. var multiplier = Math.sqrt(-2 * Math.log(s) / s);
  278. _nextGaussian = (v2 * multiplier);
  279. _hasNextGaussian = true;
  280. return mean + standardDerivation * (v1 * multiplier);
  281. }
  282. }
  283. }
  284. module.exports = createRandom();