Skip to content

Instantly share code, notes, and snippets.

@MAKIO135
Last active April 26, 2016 15:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MAKIO135/2361cf1727ec3d53e733 to your computer and use it in GitHub Desktop.
Save MAKIO135/2361cf1727ec3d53e733 to your computer and use it in GitHub Desktop.
Maths functions, mostly from Processing.js
// Pseudo-random generator
function Marsaglia(i1, i2) {
// from http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c
var z = i1 || 362436069, w = i2 || 521288629;
var intGenerator = function() {
z = (36969 * (z & 65535) + (z >>> 16)) & 0xFFFFFFFF;
w = (18000 * (w & 65535) + (w >>> 16)) & 0xFFFFFFFF;
return (((z & 0xFFFF) << 16) | (w & 0xFFFF)) & 0xFFFFFFFF;
};
this.doubleGenerator = function() {
var i = intGenerator() / 4294967296;
return i < 0 ? 1 + i : i;
};
this.intGenerator = intGenerator;
}
Marsaglia.createRandomized = function() {
var now = new Date();
return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF);
};
//////////////////////////////////////////////////////////////////////////////
// Noise functions and helpers
function PerlinNoise(seed) {
var rnd = seed !== undefined ? new Marsaglia(seed) : Marsaglia.createRandomized();
var i, j;
// http://www.noisemachine.com/talk1/17b.html
// http://mrl.nyu.edu/~perlin/noise/
// generate permutation
var perm = new Uint8Array(512);
for(i=0;i<256;++i) { perm[i] = i; }
for(i=0;i<256;++i) { var t = perm[j = rnd.intGenerator() & 0xFF]; perm[j] = perm[i]; perm[i] = t; }
// copy to avoid taking mod in perm[0];
for(i=0;i<256;++i) { perm[i + 256] = perm[i]; }
function grad3d(i,x,y,z) {
var h = i & 15; // convert into 12 gradient directions
var u = h<8 ? x : y,
v = h<4 ? y : h===12||h===14 ? x : z;
return ((h&1) === 0 ? u : -u) + ((h&2) === 0 ? v : -v);
}
function grad2d(i,x,y) {
var v = (i & 1) === 0 ? x : y;
return (i&2) === 0 ? -v : v;
}
function grad1d(i,x) {
return (i&1) === 0 ? -x : x;
}
function lerp(t,a,b) { return a + t * (b - a); }
this.noise3d = function(x, y, z) {
var X = Math.floor(x)&255, Y = Math.floor(y)&255, Z = Math.floor(z)&255;
x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z);
var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y, fz = (3-2*z)*z*z;
var p0 = perm[X]+Y, p00 = perm[p0] + Z, p01 = perm[p0 + 1] + Z,
p1 = perm[X + 1] + Y, p10 = perm[p1] + Z, p11 = perm[p1 + 1] + Z;
return lerp(fz,
lerp(fy, lerp(fx, grad3d(perm[p00], x, y, z), grad3d(perm[p10], x-1, y, z)),
lerp(fx, grad3d(perm[p01], x, y-1, z), grad3d(perm[p11], x-1, y-1,z))),
lerp(fy, lerp(fx, grad3d(perm[p00 + 1], x, y, z-1), grad3d(perm[p10 + 1], x-1, y, z-1)),
lerp(fx, grad3d(perm[p01 + 1], x, y-1, z-1), grad3d(perm[p11 + 1], x-1, y-1,z-1))));
};
this.noise2d = function(x, y) {
var X = Math.floor(x)&255, Y = Math.floor(y)&255;
x -= Math.floor(x); y -= Math.floor(y);
var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y;
var p0 = perm[X]+Y, p1 = perm[X + 1] + Y;
return lerp(fy,
lerp(fx, grad2d(perm[p0], x, y), grad2d(perm[p1], x-1, y)),
lerp(fx, grad2d(perm[p0 + 1], x, y-1), grad2d(perm[p1 + 1], x-1, y-1)));
};
this.noise1d = function(x) {
var X = Math.floor(x)&255;
x -= Math.floor(x);
var fx = (3-2*x)*x*x;
return lerp(fx, grad1d(perm[X], x), grad1d(perm[X+1], x-1));
};
}
//////////////////////////////////////////////////////////////////////////////
var Math2 = function(){
this.internalRandomGenerator = function() { return Math.random(); };
this.noiseProfile = { generator: undefined, octaves: 4, fallout: 0.5, seed: undefined};
};
/**
* Generates random numbers. Each time the random() function is called, it returns an unexpected value within
* the specified range. If one parameter is passed to the function it will return a float between zero and the
* value of the high parameter. The function call random(5) returns values between 0 and 5 (starting at zero,
* up to but not including 5). If two parameters are passed, it will return a float with a value between the
* parameters. The function call random(-5, 10.2) returns values starting at -5 up to (but not including) 10.2.
* To convert a floating-point random number to an integer, use the int() function.
*
* @param {int|float} value1 if one parameter is used, the top end to random from, if two params the low end
* @param {int|float} value2 the top end of the random range
*
* @returns {float}
*
* @see randomSeed
* @see noise
*/
Math2.prototype.random = function() {
if(arguments.length === 0) {
return this.internalRandomGenerator();
}
if(arguments.length === 1) {
return this.internalRandomGenerator() * arguments[0];
}
var aMin = arguments[0], aMax = arguments[1];
return this.internalRandomGenerator() * (aMax - aMin) + aMin;
};
/**
* Sets the seed value for random(). By default, random() produces different results each time the
* program is run. Set the value parameter to a constant to return the same pseudo-random numbers
* each time the software is run.
*
* @param {int|float} seed int
*
* @see random
* @see noise
* @see noiseSeed
*/
Math2.prototype.randomSeed = function(seed) {
this.internalRandomGenerator = (new Marsaglia(seed)).doubleGenerator;
this.haveNextNextGaussian = false;
};
/**
* Returns a float from a random series of numbers having a mean of 0 and standard deviation of 1. Each time
* the randomGaussian() function is called, it returns a number fitting a Gaussian, or normal, distribution.
* There is theoretically no minimum or maximum value that randomGaussian() might return. Rather, there is just a
* very low probability that values far from the mean will be returned; and a higher probability that numbers
* near the mean will be returned.
*
* @returns {float}
*
* @see random
* @see noise
*/
Math2.prototype.randomGaussian = function() {
if (this.haveNextNextGaussian) {
this.haveNextNextGaussian = false;
return this.nextNextGaussian;
}
var v1, v2, s;
do {
v1 = 2 * this.internalRandomGenerator() - 1; // between -1.0 and 1.0
v2 = 2 * this.internalRandomGenerator() - 1; // between -1.0 and 1.0
s = v1 * v1 + v2 * v2;
}
while (s >= 1 || s === 0);
var multiplier = Math.sqrt(-2 * Math.log(s) / s);
this.nextNextGaussian = v2 * multiplier;
this.haveNextNextGaussian = true;
return v1 * multiplier;
};
/**
* Returns the Perlin noise value at specified coordinates. Perlin noise is a random sequence
* generator producing a more natural ordered, harmonic succession of numbers compared to the
* standard random() function. It was invented by Ken Perlin in the 1980s and been used since
* in graphical applications to produce procedural textures, natural motion, shapes, terrains etc.
* The main difference to the random() function is that Perlin noise is defined in an infinite
* n-dimensional space where each pair of coordinates corresponds to a fixed semi-random value
* (fixed only for the lifespan of the program). The resulting value will always be between 0.0
* and 1.0. Processing can compute 1D, 2D and 3D noise, depending on the number of coordinates
* given. The noise value can be animated by moving through the noise space as demonstrated in
* the example above. The 2nd and 3rd dimension can also be interpreted as time.
* The actual noise is structured similar to an audio signal, in respect to the function's use
* of frequencies. Similar to the concept of harmonics in physics, perlin noise is computed over
* several octaves which are added together for the final result.
* Another way to adjust the character of the resulting sequence is the scale of the input
* coordinates. As the function works within an infinite space the value of the coordinates
* doesn't matter as such, only the distance between successive coordinates does (eg. when using
* noise() within a loop). As a general rule the smaller the difference between coordinates, the
* smoother the resulting noise sequence will be. Steps of 0.005-0.03 work best for most applications,
* but this will differ depending on use.
*
* @param {float} x x coordinate in noise space
* @param {float} y y coordinate in noise space
* @param {float} z z coordinate in noise space
*
* @returns {float}
*
* @see random
* @see noiseDetail
*/
Math2.prototype.noise = function(x, y, z) {
if(this.noiseProfile.generator === undefined) {
// caching
this.noiseProfile.generator = new PerlinNoise(this.noiseProfile.seed);
}
var generator = this.noiseProfile.generator;
var effect = 1, k = 1, sum = 0;
for(var i = 0; i < this.noiseProfile.octaves; ++i) {
effect *= this.noiseProfile.fallout;
switch (arguments.length) {
case 1:
sum += effect * (1 + generator.noise1d(k*x))/2; break;
case 2:
sum += effect * (1 + generator.noise2d(k*x, k*y))/2; break;
case 3:
sum += effect * (1 + generator.noise3d(k*x, k*y, k*z))/2; break;
}
k *= 2;
}
return sum;
};
/**
* Adjusts the character and level of detail produced by the Perlin noise function.
* Similar to harmonics in physics, noise is computed over several octaves. Lower octaves
* contribute more to the output signal and as such define the overal intensity of the noise,
* whereas higher octaves create finer grained details in the noise sequence. By default,
* noise is computed over 4 octaves with each octave contributing exactly half than its
* predecessor, starting at 50% strength for the 1st octave. This falloff amount can be
* changed by adding an additional function parameter. Eg. a falloff factor of 0.75 means
* each octave will now have 75% impact (25% less) of the previous lower octave. Any value
* between 0.0 and 1.0 is valid, however note that values greater than 0.5 might result in
* greater than 1.0 values returned by noise(). By changing these parameters, the signal
* created by the noise() function can be adapted to fit very specific needs and characteristics.
*
* @param {int} octaves number of octaves to be used by the noise() function
* @param {float} falloff falloff factor for each octave
*
* @see noise
*/
Math2.prototype.noiseDetail = function(octaves, fallout) {
this.noiseProfile.octaves = octaves;
if(fallout !== undefined) {
this.noiseProfile.fallout = fallout;
}
};
/**
* Sets the seed value for noise(). By default, noise() produces different results each
* time the program is run. Set the value parameter to a constant to return the same
* pseudo-random numbers each time the software is run.
*
* @param {int} seed int
*
* @returns {float}
*
* @see random
* @see radomSeed
* @see noise
* @see noiseDetail
*/
Math2.prototype.noiseSeed = function(seed) {
this.noiseProfile.seed = seed;
this.noiseProfile.generator = undefined;
};
/**
* Constrains a value to not exceed a maximum and minimum value.
*
* @param {int|float} value the value to constrain
* @param {int|float} value minimum limit
* @param {int|float} value maximum limit
*
* @returns {int|float}
*/
Math2.prototype.constrain = function(aNumber, aMin, aMax) {
return aNumber > aMax ? aMax : aNumber < aMin ? aMin : aNumber;
};
/**
* Calculates the distance between two points.
*
* @param {int|float} x1 int or float: x-coordinate of the first point
* @param {int|float} y1 int or float: y-coordinate of the first point
* @param {int|float} z1 int or float: z-coordinate of the first point
* @param {int|float} x2 int or float: x-coordinate of the second point
* @param {int|float} y2 int or float: y-coordinate of the second point
* @param {int|float} z2 int or float: z-coordinate of the second point
*
* @returns {float}
*/
Math2.prototype.dist = function() {
var dx, dy, dz;
if (arguments.length === 4) {
dx = arguments[0] - arguments[2];
dy = arguments[1] - arguments[3];
return Math.sqrt(dx * dx + dy * dy);
}
if (arguments.length === 6) {
dx = arguments[0] - arguments[3];
dy = arguments[1] - arguments[4];
dz = arguments[2] - arguments[5];
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
};
/**
* Calculates a number between two numbers at a specific increment. The amt parameter is the
* amount to interpolate between the two values where 0.0 equal to the first point, 0.1 is very
* near the first point, 0.5 is half-way in between, etc. The lerp function is convenient for
* creating motion along a straight path and for drawing dotted lines.
*
* @param {int|float} value1 float or int: first value
* @param {int|float} value2 float or int: second value
* @param {int|float} amt float: between 0.0 and 1.0
*
* @returns {float}
*
* @see curvePoint
* @see bezierPoint
*/
Math2.prototype.lerp = function(value1, value2, amt) {
return ((value2 - value1) * amt) + value1;
};
/**
* Calculates the magnitude (or length) of a vector. A vector is a direction in space commonly
* used in computer graphics and linear algebra. Because it has no "start" position, the magnitude
* of a vector can be thought of as the distance from coordinate (0,0) to its (x,y) value.
* Therefore, mag() is a shortcut for writing "dist(0, 0, x, y)".
*
* @param {int|float} a float or int: first value
* @param {int|float} b float or int: second value
* @param {int|float} c float or int: third value
*
* @returns {float}
*
* @see dist
*/
Math2.prototype.mag = function(a, b, c) {
if (c) {
return Math.sqrt(a * a + b * b + c * c);
}
return Math.sqrt(a * a + b * b);
};
/**
* Re-maps a number from one range to another. In the example above, the number '25' is converted from
* a value in the range 0..100 into a value that ranges from the left edge (0) to the right edge (width) of the screen.
* Numbers outside the range are not clamped to 0 and 1, because out-of-range values are often intentional and useful.
*
* @param {float} value The incoming value to be converted
* @param {float} istart Lower bound of the value's current range
* @param {float} istop Upper bound of the value's current range
* @param {float} ostart Lower bound of the value's target range
* @param {float} ostop Upper bound of the value's target range
*
* @returns {float}
*
* @see norm
* @see lerp
*/
Math2.prototype.map = function(value, istart, istop, ostart, ostop) {
return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
};
/**
* Determines the largest value in a sequence of numbers.
*
* @param {int|float} value1 int or float
* @param {int|float} value2 int or float
* @param {int|float} value3 int or float
* @param {int|float} array int or float array
*
* @returns {int|float}
*
* @see min
*/
Math2.prototype.max = function() {
if (arguments.length === 2) {
return arguments[0] < arguments[1] ? arguments[1] : arguments[0];
}
var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used
if (! ("length" in numbers && numbers.length > 0)) {
throw "Non-empty array is expected";
}
var max = numbers[0],
count = numbers.length;
for (var i = 1; i < count; ++i) {
if (max < numbers[i]) {
max = numbers[i];
}
}
return max;
};
/**
* Determines the smallest value in a sequence of numbers.
*
* @param {int|float} value1 int or float
* @param {int|float} value2 int or float
* @param {int|float} value3 int or float
* @param {int|float} array int or float array
*
* @returns {int|float}
*
* @see max
*/
Math2.prototype.min = function() {
if (arguments.length === 2) {
return arguments[0] < arguments[1] ? arguments[0] : arguments[1];
}
var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used
if (! ("length" in numbers && numbers.length > 0)) {
throw "Non-empty array is expected";
}
var min = numbers[0],
count = numbers.length;
for (var i = 1; i < count; ++i) {
if (min > numbers[i]) {
min = numbers[i];
}
}
return min;
};
/**
* Normalizes a number from another range into a value between 0 and 1.
* Identical to map(value, low, high, 0, 1);
* Numbers outside the range are not clamped to 0 and 1, because out-of-range
* values are often intentional and useful.
*
* @param {float} aNumber The incoming value to be converted
* @param {float} low Lower bound of the value's current range
* @param {float} high Upper bound of the value's current range
*
* @returns {float}
*
* @see map
* @see lerp
*/
Math2.prototype.norm = function(aNumber, low, high) {
return (aNumber - low) / (high - low);
};
/**
* Squares a number (multiplies a number by itself). The result is always a positive number,
* as multiplying two negative numbers always yields a positive result. For example, -1 * -1 = 1.
*
* @param {float} value int or float
*
* @returns {float}
*
* @see sqrt
*/
Math2.prototype.sq = function(aNumber) {
return aNumber * aNumber;
};
/**
* Converts a radian measurement to its corresponding value in degrees. Radians and degrees are two ways of
* measuring the same thing. There are 360 degrees in a circle and 2*PI radians in a circle. For example,
* 90 degrees = PI/2 = 1.5707964. All trigonometric methods in Processing require their parameters to be specified in radians.
*
* @param {int|float} value an angle in radians
*
* @returns {float}
*
* @see radians
*/
Math2.prototype.degrees = function(aAngle) {
return (aAngle * 180) / Math.PI;
};
/**
* Converts a degree measurement to its corresponding value in radians. Radians and degrees are two ways of
* measuring the same thing. There are 360 degrees in a circle and 2*PI radians in a circle. For example,
* 90 degrees = PI/2 = 1.5707964. All trigonometric methods in Processing require their parameters to be specified in radians.
*
* @param {int|float} value an angle in radians
*
* @returns {float}
*
* @see degrees
*/
Math2.prototype.radians = function(aAngle) {
return (aAngle / 180) * Math.PI;
};
/**
* Interpolate a value to a target with ease using an easing coefficient ( x = ease(x,width, 0.01); )
*
* @param {float} aNumber The incoming value to be converted
* @param {float} target The target value
* @param {float} easingVal The easing coefficient from 0.001 to 1.0, the lower, the better
*
* @returns {float}
*/
Math2.prototype.ease = function(aNumber, target, easingVal) {
var d = target - aNumber;
if(Math.abs(d) > 1) aNumber += d * easingVal;
return aNumber;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment