API Docs for: v0.1.0
Show:

File: src\math\math.js

var support = require('../utils/support'),
    PIXI = require('../vendor/pixi');

/**
 * The grapefruit math library, used to abstract commonly used math operations
 *
 * @class math
 * @extends Object
 * @static
 */
var math = {
    /**
     * The factor to multiply by to convert Degrees into Radians. The value is π/180
     *
     * @property DEG_TO_RAD
     * @type Number
     * @readOnly
     */
    DEG_TO_RAD: Math.PI / 180,
    /**
     * The factor to multiply by to convert Radians into Degrees. The value is 180/π
     *
     * @property RAD_TO_DEG
     * @type Number
     * @readOnly
     */
    RAD_TO_DEG: 180 / Math.PI,
    /**
     * The RNG seed that allows for deterministic random numbers. Change this to a certain value
     * to ensure you will have the same sequence of "random" numbers. Useful for playbacks, save files,
     * procedural generation, etc.
     *
     * Note: Deterministic randomness is not yet implemented. Scheduled for v0.2.0
     *
     * @property SEED
     * @type Number
     * @default Math.random()
     */
    SEED: Math.random(),
    /**
     * A Matrix class, directory exposes PIXI.Matrix.
     *
     * @property Matrix
     * @type Matrix
     */
    Matrix: PIXI.Matrix,
    /**
     * A 3x3 Matrix namespace, directory exposes PIXI.mat3. You can use this to create 3x3 Matrix classes.
     *
     * @property mat3
     * @type Object
     */
    mat3: PIXI.mat3,
    /**
     * A 4x4 Matrix namespace, directory exposes PIXI.mat4. You can use this to create 4x4 Matrix classes.
     *
     * @property mat3
     * @type Object
     */
    mat4: PIXI.mat4,
    /**
     * Alias some native functions for great justice (or incase we want to override)
     *
     */
    /**
     * Applys a Floor operation to a value, currently uses native Math.floor
     * since it deals with all edge cases that quicker solutions like `~~value`
     * or `value | 0` do not.
     *
     * @method floor
     * @param num {Number} The number to floor
     * @return {Number} The floored value
     */
    floor: Math.floor,
    /**
     * Applys a Ceiling operation to a value, currently uses native Math.ceil
     * since it deals with all edge cases
     *
     * @method ceil
     * @param num {Number} The number to ceil
     * @return {Number} The ceiling value
     */
    ceil: Math.ceil,
    /**
     * Generates a random number between 0 and 1, NON DETERMINISTIC
     *
     * @method random
     * @return {Number} The random value
     */
    random: Math.random,
    /**
     * Returns the absolute value of a number, currently uses native Math.abs
     * since it is more performant than tricks you can use.
     * see:
     *      http://jsperf.com/math-abs-vs-bitwise/7
     *      http://jsperf.com/abs-value
     *      http://jsperf.com/math-abs-vs-bitwise/3
     *
     * @method abs
     * @param num {Number} The number to get the absolute value for
     * @return {Number} The absolute value
     */
    abs: Math.abs,
    /**
     * Returns the square root of a number, currently uses native Math.sqrt
     *
     * @method sqrt
     * @param num {Number} The number to get the sqrt of
     * @return {Number} The sqrt value
     */
    sqrt: Math.sqrt,
    /**
     * Returns the min of the values passed
     *
     * @method min
     * @param num* {Number...}
     * @return {Number} The min value
     */
    min: Math.min,
    /**
     * Returns the max of the values passed
     *
     * @method max
     * @param num* {Number...}
     * @return {Number} The max value
     */
    max: Math.max,
    /**
     * Quickly rounds a number. This is about twice as fast as Math.round()
     *
     * @method round
     * @param num {Number} The number to round
     * @return {Number} The rounded value
     */
    round: function(n) {
        return ~~(n + (n > 0 ? 0.5 : -0.5));
    },
    /**
     * Clamps a number between two values.
     *
     * @method clamp
     * @param num {Number} The number to clamp
     * @param min {Number} The minimum value the number is allowed to be
     * @param max {Number} The maximum value the number is allowed to be
     * @return {Number} The clamped value
     */
    clamp: function(n, min, max) {
        return Math.max(min, Math.min(max, n));
    },
    /**
     * Truncates the decimal from a number
     *
     * @method truncate
     * @param num {Number} The number to truncate
     * @return {Number} The truncated value
     */
    truncate: function(n) {
        return (n > 0) ? Math.floor(n) : Math.ceil(n);
    },
    /**
     * Snaps a number to a grid value.
     * For example, if you have a grid with gaps the size of 10 horizontally, and
     * a position of 11, it would snap to 10; a position of 18 would snap to 20
     *
     * @method snap
     * @param num {Number} The number to snap
     * @param gap {Number} The gap size of the grid (the tile size)
     * @param [offset=0] {Number} The starting offset of a grid slice (aka tile)
     * @return {Number} The snapped value
     */
    snap: function(n, gap, offset) {
        if(gap === 0) return n;

        offset = offset || 0;

        n -= offset;
        n = gap * Math.round(n / gap);

        return offset + n;
    },
    /**
     * Snaps a number to a grid value, using floor.
     * For example, if you have a grid with gaps the size of 10 horizontally, and
     * a position of 11, it would snap to 10; a position of 18 would also snap to 10
     *
     * @method snapFloor
     * @param num {Number} The number to snap
     * @param gap {Number} The gap size of the grid (the tile size)
     * @param [offset=0] {Number} The starting offset of a grid slice (aka tile)
     * @return {Number} The snapped value
     */
    snapFloor: function(n, gap, offset) {
        if(gap === 0) return n;

        offset = offset || 0;

        n -= offset;
        n = gap * Math.floor(n / gap);

        return offset + n;
    },
    /**
     * Snaps a number to a grid value, using ceiling.
     * For example, if you have a grid with gaps the size of 10 horizontally, and
     * a position of 11, it would snap to 20; a position of 18 would also snap to 20
     *
     * @method snapCeil
     * @param num {Number} The number to snap
     * @param gap {Number} The gap size of the grid (the tile size)
     * @param [offset=0] {Number} The starting offset of a grid slice (aka tile)
     * @return {Number} The snapped value
     */
    snapCeil: function(n, gap, offset) {
        if(gap === 0) return n;

        offset = offset || 0;

        n -= offset;
        n = gap * Math.ceil(n / gap);

        return offset + n;
    },
    /**
     * Convert radians to degrees
     *
     * @method radiansToDegrees
     * @param angle {Number} The angle in radians to convert
     * @return {Number} The angle in degrees
     */
    radiansToDegrees: function(angle) {
        return angle * math.RAD_TO_DEG;
    },
    /**
     * Convert radians to degrees
     *
     * @method degreesToRadians
     * @param angle {Number} The angle in degrees to convert
     * @return {Number} The angle in radians
     */
    degreesToRadians: function(angle) {
        return angle * math.DEG_TO_RAD;
    },
    /**
     * Calculates the angle between two points
     *
     * @method angleBetween
     * @param pos1 {Vector|Point} The first position
     * @param pos2 {Vector|Point} The second position
     * @return {Number} The angle in radians
     */
    angleBetween: function(pos1, pos2) {
        return Math.atan2(pos2.y - pos1.y, pos2.x - pos1.x);
    },
    /**
     * Returns a random boolean based on the provided chance. The chance represents the
     * percentage chance of returning: true.
     *
     * @method randomBool
     * @param [chance=50] {Number} The % chance of getting true (0 - 100), defaults to 50%
     * @return {Boolean}
     */
    randomBool: function(chance) {
        if(chance === undefined)
            chance = 50;

        //no chance of true
        if(chance <= 0)
            return false;

        //must always be true
        if(chance >= 100)
            return true;

        //if roll is less than change, return true
        return (Math.random() * 100 < chance);
    },
    /**
     * Returns a random int between min and max.
     *
     * @method randomInt
     * @param [min=0] {Number} The minimun number that the result can be
     * @param [max=100] {Number} The maximun number that the result can be
     * @return {Number}
     */
    randomInt: function(min, max) {
        if(min !== undefined && min === max)
            return min;

        min = min || 0;
        max = max || 100;

        return Math.floor(Math.random() * (max - min + 1) + min);
    },
    /**
     * Returns a random real number between min and max.
     *
     * @method randomReal
     * @param [min=0] {Number} The minimun number that the result can be
     * @param [max=1] {Number} The maximun number that the result can be
     * @return {Number}
     */
    randomReal: function(min, max) {
        if(min !== undefined && min === max)
            return min;

        min = min || 0;
        max = max || 1;

        return math.random() * (max - min) + min;
    },
    /**
     * Returns a random sign based on the provided chance. The chance represents the
     * percentage chance of returning 1 (positive).
     *
     * @method randomSign
     * @param chance {Number} The % chance of getting positive (0 - 100), defaults to 50%
     * @return {Number} either 1 or -1
     */
    randomSign: function(chance) {
        return math.randomBool(chance) ? 1 : -1;
    },
    /**
     * Returns a random string based on a random value between 0 and 1, multiplied
     * by the current date. Ex: "1158014093337", "86371874178", etc
     *
     * @method randomString
     * @return {String} A random string
     */
    randomString: function() {
        return Math.floor(Date.now() * Math.random()).toString();
    },
    /**
     * Generates a random RFC4122 compliant (v4) UUID
     *
     * @method randomUuid
     * @return {String} A random guid
     */
    randomUuid: function() {
        //collect some random bytes
        var buf = math.randomBytes(math.__uuidBytes);

        // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
        buf[6] = (buf[6] & 0x0f) | 0x40;
        buf[8] = (buf[8] & 0x3f) | 0x80;

        var i = 0,
            bth = math.__byteToHex;

        //convert bytes to string
        return bth[buf[i++]] + bth[buf[i++]] +
                bth[buf[i++]] + bth[buf[i++]] + '-' +
                bth[buf[i++]] + bth[buf[i++]] + '-' +
                bth[buf[i++]] + bth[buf[i++]] + '-' +
                bth[buf[i++]] + bth[buf[i++]] + '-' +
                bth[buf[i++]] + bth[buf[i++]] +
                bth[buf[i++]] + bth[buf[i++]] +
                bth[buf[i++]] + bth[buf[i++]];
    },
    __uuidBytes: new Uint8Array(16),
    __byteToHex: (function() {
        var bth = [],
            htb = {};
        for (var i = 0; i < 256; i++) {
            bth[i] = (i + 0x100).toString(16).substr(1);
            htb[bth[i]] = i;
        }

        return bth;
    })(),
    /**
     * Fills a Typed Array with random bytes. If you do not pass an output param, then a default
     * Uint8Array(16) is created and returned for you.
     *
     * @method randomBytes
     * @param [output] {TypedArray} The output array for the random data, if none specified a new Uint8Array(16) is created
     */
    randomBytes: function(ary) {
        ary = ary || new Uint8Array(16);
        window.crypto.getRandomValues(ary);
        return ary;
    },
    /**
     * Returns a random element of an array.
     *
     * @method randomElement
     * @param array {Array} The array to choose from
     * @param start {Number} The index of the first element to include, defaults to 0
     * @param end {Number} The index of the last element to include, defaults to array.length - 1
     * @return {Number} either 1 or -1
     */
    randomElement: function(array, start, end) {
        //ensure we have an array, and there are elements to check
        if(!array || !array.length)
            return null;

        //special case for 1 element
        if(array.length === 1)
            return array[0];

        //default for start
        if(!start || start < 0)
            start = start || 0;

        //default for end
        if(!end || end < 0)
            end = array.length - 1;

        return array[math.randomInt(start, end)];
    }
};

//these polyfills are separated and exposed so that they can get tested

//if we support typed arrays we can do a good approximation of crypto.getRandomValues
math._getRandomValuesTyped = function(ary) {
    //get a Uint8 view into the buffer
    var buf = ary.buffer,
        len = buf.byteLength,
        view = new Uint8Array(buf);

    //fill the buffer one random byte at a time
    for(var i = 0, r; i < len; ++i) {
        //we only need a new random when we have pulled all the bytes out of the last one
        //which means every fourth byte we get a new random 32-bit value
        if((i & 0x03) === 0) {
            r = math.random() * 0x100000000;
        }

        //pull the next byte out of the random number
        view[i] = r >>> ((i & 0x03) << 3) & 0xff;
    }

    //return the original view which now has the data we put into the buffer
    return ary;
};

//without typed array support we can do one that returns an array of values
//but you would need to use `new Array(num)`, so there is a length
//or something like `var a = []; a[num - 1] = undefined;` so length is expanded
math._getRandomValuesArray = function(ary) {
    //fill the array with random values
    for(var i = 0; i < ary.length; ++i) {
        ary[i] = math.random() * 0x100000000;
    }

    return ary;
};

//polyfill crypto.getRandomValues if necessary
//crypto spec: http://wiki.whatwg.org/wiki/Crypto
if(!support.crypto) {
    window.crypto = window.crypto || {};

    if(support.typedArrays) {
        window.crypto.getRandomValues = math._getRandomValuesTyped;
    } else {
        window.crypto.getRandomValues = math._getRandomValuesArray;
    }
}

module.exports = math;