Skip to content
Snippets Groups Projects
math.js 7.45 KiB
Newer Older
  • Learn to ignore specific revisions
  • /**
     * Interpolates between two objects, obj1 and obj2, based on a given factor
     * If a property is a number and exists in both objects, it interpolates the values
     * The result includes all properties from both objects
     * Non-numeric properties and properties not shared between objects are copied as is
     *
     * @param {object} obj1 The first object
     * @param {object} obj2 The second object
     * @param {number} factor The interpolation factor (0 to 1).
     * @returns {object} A new object with interpolated number values and other properties from both obj1 and obj2
     */
    function interpolateObject(obj1, obj2, factor) {
    	const result = {};
    
    	for (const key in obj1) {
    		if (Object.hasOwn(obj1, key)) {
    			if (typeof obj1[key] === "number" && Object.hasOwn(obj2, key) && typeof obj2[key] === "number") {
    				result[key] = interpolate(obj1[key], obj2[key], factor);
    			} else if (typeof obj1[key] === "object" && Object.hasOwn(obj2, key) && typeof obj2[key] === "object") {
    				result[key] = interpolateObject(obj1[key], obj2[key], factor);
    			} else {
    				result[key] = obj1[key];
    			}
    		}
    	}
    
    	for (const key in obj2) {
    		if (Object.hasOwn(obj2, key) && !Object.hasOwn(result, key)) {
    			result[key] = obj2[key];
    		}
    	}
    	return result;
    }
    
    window.interpolateObject = interpolateObject;
    
    /**
     * Linearly interpolates between two values based on a given factor
     *
     * @param {number} value1
     * @param {number} value2
     * @param {number} factor Interpolation factor (0 to 1)
     * @returns {number}
     */
    function interpolate(value1, value2, factor) {
    	return value1 + (value2 - value1) * factor;
    }
    window.interpolate = interpolate;
    
    function lerp(percent, start, end) {
    	return Math.clamp(start + (end - start) * percent, start, end);
    }
    window.lerp = lerp;
    
    function inverseLerp(value, start, end) {
    	return Math.clamp((value - start) / (end - start), 0, 1);
    }
    window.inverseLerp = inverseLerp;
    
    function formatDecimals(value, decimalPlaces) {
    	return Number(Math.round(parseFloat(value + "e" + decimalPlaces)) + "e-" + decimalPlaces);
    }
    window.formatDecimals = formatDecimals;
    
    function nCr(n, r) {
    	// https://stackoverflow.com/questions/11809502/which-is-better-way-to-calculate-ncr
    	if (r > n - r) {
    		// because C(n, r) == C(n, n - r)
    		r = n - r;
    	}
    
    	let ans = 1;
    	for (let i = 1; i <= r; ++i) {
    		ans *= n - r + i;
    		ans /= i;
    	}
    
    	return ans;
    }
    window.nCr = nCr;
    
    /**
     * Checks if x is equal or higher than min and lower or equal to max
     *
     * @param {number} x
     * @param {any} min
     * @param {any} max
     * @returns {boolean}
     */
    function between(x, min, max) {
    	return typeof x === "number" && x >= min && x <= max;
    }
    window.between = between;
    
    /**
     * This function takes a value, and weights it by exponential curve.
     *
     * Value should be between 0.0 and 1.0 (use normalise to get a percentage of a max).
     *
     * An exponent of 1.0 returns 1 every time.
     *
     * Exponents between 1.0 and 2.0 return a curve favoring higher results (closer to 1)
     *
     * An exponent of 2.0 will return a flat line distribution, and is identical to random()
     *
     * Exponents greater than 2.0 return a curve favoring lower results (closer to 0), reaching to 0 at infinity.
     *
     * For example, see:
     * https://www.desmos.com/calculator/87hhrjfixi
     *
     * @param {number} value Value to be weighted
     * @param {number} exp Exponent used to generate the curve
     * @returns {number} value weighted against exponential curve
     */
    function expCurve(value, exp) {
    	return value ** exp / value;
    }
    window.expCurve = expCurve;
    
    /**
     * This function creates a random float 0.0-1.0, weighted by exponential curve.
     *
     * A value of 1.0 returns 1 every time.
     *
     * Values between 1.0 and 2.0 return a curve favoring higher results (closer to 1)
     *
     * A value of 2.0 will return a flat line distribution, and is identical to random()
     *
     * Values greater than 2.0 return a curve favoring lower results (closer to 0), reaching to 0 at infinity.
     *
     * For example, see:
     * https://www.desmos.com/calculator/87hhrjfixi
     *
     * @param {number} exp Exponent used to generate the curve
     * @returns {number} random number weighted against exponential curve
     */
    function randomExp(exp) {
    	return expCurve(State.random(), exp);
    }
    window.randomExp = randomExp;
    
    /**
     * Normalises value to a decimal number 0.0-1.0, a percentage of the range specified in min and max.
     *
     * @param {number} value The value to be normalised
     * @param {number} max The highest value of the range
     * @param {number} min The lowest value of the range, default 0
     * @returns {number} Normalised value
     */
    function normalise(value, max, min = 0) {
    	const denominator = max - min;
    	if (denominator === 0) {
    		Errors.report("[normalise]: min and max params must be different.", { value, max, min });
    		return 0;
    	}
    	if (denominator < 0) {
    		Errors.report("[normalise]: max param must be greater than min param.", { value, max, min });
    		return 0;
    	}
    	return Math.clamp((value - min) / denominator, 0, 1);
    }
    window.normalise = normalise;
    
    /**
     * Returns a rounded number, with number of decimals based on the second parameter
     *
     * @param {number} number
     * @param {number} decimals
     * @returns {number} new number
     */
    function round(number, decimals) {
    	const multiplier = 10 ** decimals;
    	return Math.round(number * multiplier) / multiplier;
    }
    window.round = round;
    
     * Categorises a value into a specified number of categories based on its position within a given range.
    
     * The function automatically handles both ascending (min < max) and descending (min > max) ranges.
     *
     * @param {number} value The value to be categorized.
     * @param {number} min The start of the range.
     * @param {number} max The end of the range.
     * @param {number} parts The number of categories into which the range should be divided.
     * @returns {number} Returns the category index, ranging from 0 to parts-1.
    
     * Examples:
     *  categorise(15, 10, 20, 4);
     *  Result: 2
     *  Divides the range 10-20 into 4 parts, and 15 falls into the third part. (First part is 0)
     *
     *  categorise(15, 20, 10, 4);
     *  Result: 1
     *  Divides the range 20-10 into 4 parts, and 15 falls into the second part.
     *
     *  categorise(5, 0, 10, 5);
     *  Result: 0
     *  Divides the range 0-10 into 5 parts, and 5 falls right on the border of the first and second part but is rounded down.
     *
     *  categorise(18, 20, 10, 5);
     *  Result: 3
     *  Divides the range 20-10 into 5 parts, and 18 falls into the fourth part.
    
     */
    function categorise(value, min, max, parts) {
    	const normalised = normalise(value, Math.max(min, max), Math.min(min, max));
    	const category = Math.floor(normalised * parts);
    	return Math.clamp(min > max ? parts - 1 - category : category, 0, parts - 1);
    }
    window.categorise = categorise;
    
    
    /**
     * Generates a random number within a specified range around a given base number.
     *
     * @param {number} num The base number.
     * @param {number} min The minimum offset subtracted from the base number.
     * @param {number} max The maximum offset added to the base number.
    
    xao's avatar
    xao committed
     * @param rngInstance
    
     * @returns {number} A random number between `num - min` and `num + max`.
     */
    
    xao's avatar
    xao committed
    function boundedRandom(num, min, max = min, rngInstance) {
    
    majou's avatar
    majou committed
    	const result = rngInstance ? rngInstance.randomFloat(num - min, num + max) : randomFloat(num - min, num + max);
    	return round(result, 2);
    
    }
    window.boundedRandom = boundedRandom;
    
    /**
     * Generates a random integer based on chance and max value
     *
     * @param {number} chance Value between 0 and 1
     * @param {number} max Integer
     */
    function calculateBinomial(chance, max) {
    	let result = 0;
    	for (let i = 0; i < max; i++) {
    
    		if (State.random() < chance) {
    
    			result++;
    		}
    	}
    	return result;
    }
    window.calculateBinomial = calculateBinomial;