canvas-sketch-util
Version:
Utilities for sketching in Canvas, WebGL and generative art
329 lines (284 loc) • 8.51 kB
JavaScript
var seedRandom = require('seed-random');
var SimplexNoise = require('simplex-noise');
var defined = require('defined');
function createRandom (defaultSeed) {
defaultSeed = defined(defaultSeed, null);
var defaultRandom = Math.random;
var currentSeed;
var currentRandom;
var noiseGenerator;
var _nextGaussian = null;
var _hasNextGaussian = false;
setSeed(defaultSeed);
return {
value: value,
createRandom: function (defaultSeed) {
return createRandom(defaultSeed);
},
setSeed: setSeed,
getSeed: getSeed,
getRandomSeed: getRandomSeed,
valueNonZero: valueNonZero,
permuteNoise: permuteNoise,
noise1D: noise1D,
noise2D: noise2D,
noise3D: noise3D,
noise4D: noise4D,
sign: sign,
boolean: boolean,
chance: chance,
range: range,
rangeFloor: rangeFloor,
pick: pick,
shuffle: shuffle,
onCircle: onCircle,
insideCircle: insideCircle,
onSphere: onSphere,
insideSphere: insideSphere,
quaternion: quaternion,
weighted: weighted,
weightedSet: weightedSet,
weightedSetIndex: weightedSetIndex,
gaussian: gaussian
};
function setSeed (seed, opt) {
if (typeof seed === 'number' || typeof seed === 'string') {
currentSeed = seed;
currentRandom = seedRandom(currentSeed, opt);
} else {
currentSeed = undefined;
currentRandom = defaultRandom;
}
noiseGenerator = createNoise();
_nextGaussian = null;
_hasNextGaussian = false;
}
function value () {
return currentRandom();
}
function valueNonZero () {
var u = 0;
while (u === 0) u = value();
return u;
}
function getSeed () {
return currentSeed;
}
function getRandomSeed () {
var seed = String(Math.floor(Math.random() * 1000000));
return seed;
}
function createNoise () {
return new SimplexNoise(currentRandom);
}
function permuteNoise () {
noiseGenerator = createNoise();
}
function noise1D (x, frequency, amplitude) {
if (!isFinite(x)) throw new TypeError('x component for noise() must be finite');
frequency = defined(frequency, 1);
amplitude = defined(amplitude, 1);
return amplitude * noiseGenerator.noise2D(x * frequency, 0);
}
function noise2D (x, y, frequency, amplitude) {
if (!isFinite(x)) throw new TypeError('x component for noise() must be finite');
if (!isFinite(y)) throw new TypeError('y component for noise() must be finite');
frequency = defined(frequency, 1);
amplitude = defined(amplitude, 1);
return amplitude * noiseGenerator.noise2D(x * frequency, y * frequency);
}
function noise3D (x, y, z, frequency, amplitude) {
if (!isFinite(x)) throw new TypeError('x component for noise() must be finite');
if (!isFinite(y)) throw new TypeError('y component for noise() must be finite');
if (!isFinite(z)) throw new TypeError('z component for noise() must be finite');
frequency = defined(frequency, 1);
amplitude = defined(amplitude, 1);
return amplitude * noiseGenerator.noise3D(
x * frequency,
y * frequency,
z * frequency
);
}
function noise4D (x, y, z, w, frequency, amplitude) {
if (!isFinite(x)) throw new TypeError('x component for noise() must be finite');
if (!isFinite(y)) throw new TypeError('y component for noise() must be finite');
if (!isFinite(z)) throw new TypeError('z component for noise() must be finite');
if (!isFinite(w)) throw new TypeError('w component for noise() must be finite');
frequency = defined(frequency, 1);
amplitude = defined(amplitude, 1);
return amplitude * noiseGenerator.noise4D(
x * frequency,
y * frequency,
z * frequency,
w * frequency
);
}
function sign () {
return boolean() ? 1 : -1;
}
function boolean () {
return value() > 0.5;
}
function chance (n) {
n = defined(n, 0.5);
if (typeof n !== 'number') throw new TypeError('expected n to be a number');
return value() < n;
}
function range (min, max) {
if (max === undefined) {
max = min;
min = 0;
}
if (typeof min !== 'number' || typeof max !== 'number') {
throw new TypeError('Expected all arguments to be numbers');
}
return value() * (max - min) + min;
}
function rangeFloor (min, max) {
if (max === undefined) {
max = min;
min = 0;
}
if (typeof min !== 'number' || typeof max !== 'number') {
throw new TypeError('Expected all arguments to be numbers');
}
return Math.floor(range(min, max));
}
function pick (array) {
if (array.length === 0) return undefined;
return array[rangeFloor(0, array.length)];
}
function shuffle (arr) {
if (!Array.isArray(arr)) {
throw new TypeError('Expected Array, got ' + typeof arr);
}
var rand;
var tmp;
var len = arr.length;
var ret = arr.slice();
while (len) {
rand = Math.floor(value() * len--);
tmp = ret[len];
ret[len] = ret[rand];
ret[rand] = tmp;
}
return ret;
}
function onCircle (radius, out) {
radius = defined(radius, 1);
out = out || [];
var theta = value() * 2.0 * Math.PI;
out[0] = radius * Math.cos(theta);
out[1] = radius * Math.sin(theta);
return out;
}
function insideCircle (radius, out) {
radius = defined(radius, 1);
out = out || [];
onCircle(1, out);
var r = radius * Math.sqrt(value());
out[0] *= r;
out[1] *= r;
return out;
}
function onSphere (radius, out) {
radius = defined(radius, 1);
out = out || [];
var u = value() * Math.PI * 2;
var v = value() * 2 - 1;
var phi = u;
var theta = Math.acos(v);
out[0] = radius * Math.sin(theta) * Math.cos(phi);
out[1] = radius * Math.sin(theta) * Math.sin(phi);
out[2] = radius * Math.cos(theta);
return out;
}
function insideSphere (radius, out) {
radius = defined(radius, 1);
out = out || [];
var u = value() * Math.PI * 2;
var v = value() * 2 - 1;
var k = value();
var phi = u;
var theta = Math.acos(v);
var r = radius * Math.cbrt(k);
out[0] = r * Math.sin(theta) * Math.cos(phi);
out[1] = r * Math.sin(theta) * Math.sin(phi);
out[2] = r * Math.cos(theta);
return out;
}
function quaternion (out) {
out = out || [];
var u1 = value();
var u2 = value();
var u3 = value();
var sq1 = Math.sqrt(1 - u1);
var sq2 = Math.sqrt(u1);
var theta1 = Math.PI * 2 * u2;
var theta2 = Math.PI * 2 * u3;
var x = Math.sin(theta1) * sq1;
var y = Math.cos(theta1) * sq1;
var z = Math.sin(theta2) * sq2;
var w = Math.cos(theta2) * sq2;
out[0] = x;
out[1] = y;
out[2] = z;
out[3] = w;
return out;
}
function weightedSet (set) {
set = set || [];
if (set.length === 0) return null;
return set[weightedSetIndex(set)].value;
}
function weightedSetIndex (set) {
set = set || [];
if (set.length === 0) return -1;
return weighted(set.map(function (s) {
return s.weight;
}));
}
function weighted (weights) {
weights = weights || [];
if (weights.length === 0) return -1;
var totalWeight = 0;
var i;
for (i = 0; i < weights.length; i++) {
totalWeight += weights[i];
}
if (totalWeight <= 0) throw new Error('Weights must sum to > 0');
var random = value() * totalWeight;
for (i = 0; i < weights.length; i++) {
if (random < weights[i]) {
return i;
}
random -= weights[i];
}
return 0;
}
function gaussian (mean, standardDerivation) {
mean = defined(mean, 0);
standardDerivation = defined(standardDerivation, 1);
// https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/Random.java#L496
if (_hasNextGaussian) {
_hasNextGaussian = false;
var result = _nextGaussian;
_nextGaussian = null;
return mean + standardDerivation * result;
} else {
var v1 = 0;
var v2 = 0;
var s = 0;
do {
v1 = value() * 2 - 1; // between -1 and 1
v2 = value() * 2 - 1; // between -1 and 1
s = v1 * v1 + v2 * v2;
} while (s >= 1 || s === 0);
var multiplier = Math.sqrt(-2 * Math.log(s) / s);
_nextGaussian = (v2 * multiplier);
_hasNextGaussian = true;
return mean + standardDerivation * (v1 * multiplier);
}
}
}
module.exports = createRandom();