math/Utils.js

/**
 * Utils
 * @module Utils
 */

/**
 * Linear interpolation between to values
 * @param {Number} a from
 * @param {Number} b to
 * @param {Number} p value between 0 and 1
 * @returns {Number}
 */
function lerp(a, b, p) {
    var _p = Number(p);
    _p = Math.max(0, Math.min(1, _p));
    return a + _p * (b - a);
}

/**
 * Framerate independent linear interpolation
 * @param {Number} a from
 * @param {Number} b to
 * @param {Number} p value between 0 and 1
 * @param {Number} dt delta time in ms since last frame
 * @param {Number} targetFPS target (context) fps
 * @returns {Number}
 */
function fi_lerp(a, b, p, dt, targetFPS = 60) {
    return lerp(a, b, 1 - Math.pow(1 - p, dt / (1000 / targetFPS)));
}

/**
 * Limit the number to a given number of digits
 * @param {Number} n 
 * @param {Number} digits 
 * @returns {Number}
 */
function fixed(n, digits) {
    digits = digits || 3;
    return parseFloat(n.toFixed(digits));
}

/**
 * Limit the range of a number
 * @param Number} n the number
 * @param Number} min minimum
 * @param Number} max maximum
 * @returns {Number} the clamped number
 */
function clamp(n, min, max) {
    return n <= min ? min : n >= max ? max : n;
}

/**
 * Check if the given Object can be used as a vector, this simply checks if the object has a x and y property
 * @param {Object|Vector} v 
 * @returns {Boolean}
 */
function isVector(v) {
    return v && typeof v.x !== "undefined" && typeof v.y !== "undefined";
}

/**
 * Check if the two lines intersect
 * @param {Number} x1 start x coordinate of line 1
 * @param {Number} y1 start y coordinate of line 1
 * @param {Number} x2 end x coordinate of line 1
 * @param {Number} y2 end y coordinate of line 1
 * @param {Number} x3 start x coordinate of line 2
 * @param {Number} y3 start y coordinate of line 2
 * @param {Number} x4 end x coordinate of line 2
 * @param {Number} y4 end y coordinate of line 2
 * @returns {Boolean}
 */
function lineIntersectsLine(x1, y1, x2, y2, x3, y3, x4, y4) {
    let uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
    let uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
    return uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1;
}

/**
 * Get the point where the two lines intersect
 * @param {Number} x1 start x coordinate of line 1
 * @param {Number} y1 start y coordinate of line 1
 * @param {Number} x2 end x coordinate of line 1
 * @param {Number} y2 end y coordinate of line 1
 * @param {Number} x3 start x coordinate of line 2
 * @param {Number} y3 start y coordinate of line 2
 * @param {Number} x4 end x coordinate of line 2
 * @param {Number} y4 end y coordinate of line 2
 * @returns {Boolean} false if there is no intersection
 * @returns {Object} object with x and y if there is an intersection
 */
function lineIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
    // Check if none of the lines are of length 0
    if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) {
        return false
    }

    denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))

    // Lines are parallel
    if (denominator === 0) {
        return false
    }

    let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator
    let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator

    // is the intersection along the segments
    if (ua < 0 || ua > 1 || ub < 0 || ub > 1) {
        return false
    }

    // Return a object with the x and y coordinates of the intersection
    let x = x1 + ua * (x2 - x1)
    let y = y1 + ua * (y2 - y1)

    return { x, y }
}

/**
 * convert the given variable to a point in array form, this is used in some internal math functions
 * 
 * @example
 * point(new Vector(1,2)) => [1,2]
 * point({x:1, y:2}) => [1,2]
 * point([1,2]) => [1,2]
 * 
 * @param {Vector|Object|Array} p a point as object, vector or array 
 * @returns {Array} Array with two entries `[x, y]` never a reference to the original point
 */
function point(p) {
    if (isVector(p)) {
        return [p.x, p.y];
    }

    if (Array.isArray(p) && p.length === 2) {
        return [p[0], p[1]];
    }

    throw new Error("provided variable is not a point");
}

/**
 * get the quadrant of the position
 * 1 = positive x, positive y  
 * 2 = negative x, positive y  
 * 3 = negative x, negative y  
 * 4 = positive x, negative y  
 * @param {Number} x 
 * @param {Number} y 
 * @returns {Number} the quadrant 
 */
function quadrant(x, y) {
    if (x > 0) {
        return y >= 0 ? 1 : 4;
    } else if (x < 0) {
        return y >= 0 ? 2 : 3;
    } else if (y > 0) {
        return 1;
    } else if (y < 0) {
        return 3;
    }

    return 0;
}

/**
 * triangle circumcenter
 * @param {Number} a 
 * @param {Number} b 
 * @param {Number} c 
 * @returns {Array}
 */
function circumcenter(a, b, c) {
    a = point(a);
    b = point(b);
    c = point(c);

    const ad = a[0] * a[0] + a[1] * a[1];
    const bd = b[0] * b[0] + b[1] * b[1];
    const cd = c[0] * c[0] + c[1] * c[1];
    const D = 2 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1]));
    return [
        1 / D * (ad * (b[1] - c[1]) + bd * (c[1] - a[1]) + cd * (a[1] - b[1])),
        1 / D * (ad * (c[0] - b[0]) + bd * (a[0] - c[0]) + cd * (b[0] - a[0])),
    ];
}

function rangeIntersect(a1, a2, b1, b2) {
    let min = a1 < b1 ? [a1, a2] : [b1, b2];
    let max = a1 < b1 ? [b1, b2] : [a1, a2];
    if (min[1] < max[0]) return null;

    return [
        max[0],
        min[1] < max[1] ? min[1] : max[1]
    ]
}

export {
    lerp,
    fi_lerp,
    fixed,
    clamp,
    isVector,
    lineIntersectsLine,
    lineIntersection,
    point,
    quadrant,
    circumcenter,
    rangeIntersect
}