@bitbybit-dev/base
Version:
Bit By Bit Developers Base CAD Library to Program Geometry
802 lines (801 loc) • 26.3 kB
JavaScript
import * as Inputs from "../inputs";
/**
* Contains various math methods.
*/
export class MathBitByBit {
/**
* Creates and returns a number value (pass-through for number input).
* Example: Input 42 → 42, Input 3.14 → 3.14
* @param inputs a number to be created
* @returns number
* @group create
* @shortname number
* @drawable false
*/
number(inputs) {
return inputs.number;
}
/**
* Performs basic arithmetic operations on two numbers (add, subtract, multiply, divide, power, modulus).
* Example: 5 + 3 → 8, 10 % 3 → 1, 2 ^ 3 → 8
* @param inputs two numbers and operator
* @returns Result of math operation action
* @group operations
* @shortname two numbers
* @drawable false
*/
twoNrOperation(inputs) {
let result;
switch (inputs.operation) {
case Inputs.Math.mathTwoNrOperatorEnum.add:
result = inputs.first + inputs.second;
break;
case Inputs.Math.mathTwoNrOperatorEnum.subtract:
result = inputs.first - inputs.second;
break;
case Inputs.Math.mathTwoNrOperatorEnum.multiply:
result = inputs.first * inputs.second;
break;
case Inputs.Math.mathTwoNrOperatorEnum.divide:
result = inputs.first / inputs.second;
break;
case Inputs.Math.mathTwoNrOperatorEnum.power:
result = Math.pow(inputs.first, inputs.second);
break;
case Inputs.Math.mathTwoNrOperatorEnum.modulus:
result = inputs.first % inputs.second;
break;
default:
break;
}
return result;
}
/**
* Calculates the remainder after division (modulus operation).
* Example: 10 % 3 → 1, 17 % 5 → 2
* @param inputs two numbers and operator
* @returns Result of modulus operation
* @group operations
* @shortname modulus
* @drawable false
*/
modulus(inputs) {
return this.twoNrOperation({ first: inputs.number, second: inputs.modulus, operation: Inputs.Math.mathTwoNrOperatorEnum.modulus });
}
/**
* Rounds a number to specified decimal places.
* Example: 1.32156 with 3 decimals returns 1.322
* @param inputs a number and decimal places
* @returns Result of rounding
* @group operations
* @shortname round to decimals
* @drawable false
*/
roundToDecimals(inputs) {
return Math.round(inputs.number * Math.pow(10, inputs.decimalPlaces)) / Math.pow(10, inputs.decimalPlaces);
}
/**
* Rounds a number to specified decimal places and removes trailing zeros.
* Example: 1.32156 with 3 decimals returns 1.322, but 1.320000001 returns 1.32, and 1.000 returns 1
* @param inputs a number and decimal places
* @returns Result of rounding as a number without trailing zeros
* @group operations
* @shortname round trim zeros
* @drawable false
*/
roundAndRemoveTrailingZeros(inputs) {
const rounded = Math.round(inputs.number * Math.pow(10, inputs.decimalPlaces)) / Math.pow(10, inputs.decimalPlaces);
return parseFloat(rounded.toFixed(inputs.decimalPlaces));
}
/**
* Performs mathematical operations on a single number (absolute, negate, sqrt, trig functions, logarithms, etc.).
* Example: sqrt(5) → 2.236, abs(-3) → 3, sin(π/2) → 1
* @param inputs one number and operator action
* @returns Result of math operation
* @group operations
* @shortname one number
* @drawable false
*/
oneNrOperation(inputs) {
let result;
switch (inputs.operation) {
case Inputs.Math.mathOneNrOperatorEnum.absolute:
result = Math.abs(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.negate:
result = -inputs.number;
break;
case Inputs.Math.mathOneNrOperatorEnum.ln:
result = Math.log(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.log10:
result = Math.log10(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.tenPow:
result = Math.pow(10, inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.round:
result = Math.round(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.floor:
result = Math.floor(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.ceil:
result = Math.ceil(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.sqrt:
result = Math.sqrt(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.sin:
result = Math.sin(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.cos:
result = Math.cos(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.tan:
result = Math.tan(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.asin:
result = Math.asin(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.acos:
result = Math.acos(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.atan:
result = Math.atan(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.log:
result = Math.log(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.exp:
result = Math.exp(inputs.number);
break;
case Inputs.Math.mathOneNrOperatorEnum.degToRad:
result = inputs.number * Math.PI / 180;
break;
case Inputs.Math.mathOneNrOperatorEnum.radToDeg:
result = inputs.number * 180 / Math.PI;
break;
default:
break;
}
return result;
}
/**
* Maps a number from one range to another range proportionally.
* Example: 5 from [0,10] to [0,100] → 50, 0.5 from [0,1] to [-10,10] → 0
* @param inputs one number and operator action
* @returns Result of mapping
* @group operations
* @shortname remap
* @drawable false
*/
remap(inputs) {
return (inputs.number - inputs.fromLow) * (inputs.toHigh - inputs.toLow) / (inputs.fromHigh - inputs.fromLow) + inputs.toLow;
}
/**
* Generates a random decimal number between 0 (inclusive) and 1 (exclusive).
* Example: Outputs like 0.342, 0.891, or any value in [0, 1)
* @returns A random number between 0 and 1
* @group generate
* @shortname random 0 - 1
* @drawable false
*/
random() {
return Math.random();
}
/**
* Generates a random number within a specified range (low to high).
* Example: Range [0, 10] → outputs like 3.7, 8.2, or any value between 0 and 10
* @param inputs low and high numbers
* @returns A random number
* @group generate
* @shortname random number
* @drawable false
*/
randomNumber(inputs) {
return Math.random() * (inputs.high - inputs.low) + inputs.low;
}
/**
* Generates multiple random numbers within a specified range.
* Example: Range [0, 10] with 3 items → [2.5, 7.1, 4.8]
* @param inputs low and high numbers
* @returns A list of random numbers
* @group generate
* @shortname random numbers
* @drawable false
*/
randomNumbers(inputs) {
const result = [];
for (let i = 0; i < inputs.count; i++) {
result.push(this.randomNumber(inputs));
}
return result;
}
/**
* Returns the mathematical constant π (pi) ≈ 3.14159.
* Example: Outputs 3.141592653589793
* @returns A number PI
* @group generate
* @shortname π
* @drawable false
*/
pi() {
return Math.PI;
}
/**
* Formats a number as a string with a fixed number of decimal places (always shows trailing zeros).
* Example: 3.14159 with 2 decimals → '3.14', 5 with 3 decimals → '5.000'
* @param inputs a number to be rounded to decimal places
* @returns number
* @group operations
* @shortname to fixed
* @drawable false
*/
toFixed(inputs) {
return inputs.number.toFixed(inputs.decimalPlaces);
}
/**
* Adds two numbers together.
* Example: 5 + 3 → 8, -2 + 7 → 5
* @param inputs two numbers
* @returns number
* @group basics
* @shortname add
* @drawable false
*/
add(inputs) {
return inputs.first + inputs.second;
}
/**
* Subtracts the second number from the first.
* Example: 10 - 3 → 7, 5 - 8 → -3
* @param inputs two numbers
* @returns number
* @group basics
* @shortname subtract
* @drawable false
*/
subtract(inputs) {
return inputs.first - inputs.second;
}
/**
* Multiplies two numbers together.
* Example: 5 × 3 → 15, -2 × 4 → -8
* @param inputs two numbers
* @returns number
* @group basics
* @shortname multiply
* @drawable false
*/
multiply(inputs) {
return inputs.first * inputs.second;
}
/**
* Divides the first number by the second.
* Example: 10 ÷ 2 → 5, 7 ÷ 2 → 3.5
* @param inputs two numbers
* @returns number
* @group basics
* @shortname divide
* @drawable false
*/
divide(inputs) {
return inputs.first / inputs.second;
}
/**
* Raises the first number to the power of the second (exponentiation).
* Example: 2³ → 8, 5² → 25, 10⁻¹ → 0.1
* @param inputs two numbers
* @returns number
* @group basics
* @shortname power
* @drawable false
*/
power(inputs) {
return Math.pow(inputs.first, inputs.second);
}
/**
* Calculates the square root of a number.
* Example: √9 → 3, √2 → 1.414, √16 → 4
* @param inputs a number
* @returns number
* @group basics
* @shortname sqrt
* @drawable false
*/
sqrt(inputs) {
return Math.sqrt(inputs.number);
}
/**
* Returns the absolute value (removes negative sign, always positive or zero).
* Example: |-5| → 5, |3| → 3, |0| → 0
* @param inputs a number
* @returns number
* @group basics
* @shortname abs
* @drawable false
*/
abs(inputs) {
return Math.abs(inputs.number);
}
/**
* Rounds a number to the nearest integer.
* Example: 3.7 → 4, 2.3 → 2, 5.5 → 6
* @param inputs a number
* @returns number
* @group basics
* @shortname round
* @drawable false
*/
round(inputs) {
return Math.round(inputs.number);
}
/**
* Rounds a number down to the nearest integer (toward negative infinity).
* Example: 3.7 → 3, -2.3 → -3, 5 → 5
* @param inputs a number
* @returns number
* @group basics
* @shortname floor
* @drawable false
*/
floor(inputs) {
return Math.floor(inputs.number);
}
/**
* Rounds a number up to the nearest integer (toward positive infinity).
* Example: 3.2 → 4, -2.8 → -2, 5 → 5
* @param inputs a number
* @returns number
* @group basics
* @shortname ceil
* @drawable false
*/
ceil(inputs) {
return Math.ceil(inputs.number);
}
/**
* Negates a number (flips its sign: positive becomes negative, negative becomes positive).
* Example: 5 → -5, -3 → 3, 0 → 0
* @param inputs a number
* @returns number
* @group basics
* @shortname negate
* @drawable false
*/
negate(inputs) {
return -inputs.number;
}
/**
* Calculates the natural logarithm (base e) of a number.
* Example: ln(2.718) → ~1, ln(7.389) → ~2, ln(1) → 0
* @param inputs a number
* @returns number
* @group basics
* @shortname ln
* @drawable false
*/
ln(inputs) {
return Math.log(inputs.number);
}
/**
* Calculates the base 10 logarithm of a number.
* Example: log₁₀(100) → 2, log₁₀(1000) → 3, log₁₀(10) → 1
* @param inputs a number
* @returns number
* @group basics
* @shortname log10
* @drawable false
*/
log10(inputs) {
return Math.log10(inputs.number);
}
/**
* Raises 10 to the power of the input number.
* Example: 10² → 100, 10³ → 1000, 10⁻¹ → 0.1
* @param inputs a number
* @returns number
* @group basics
* @shortname ten pow
* @drawable false
*/
tenPow(inputs) {
return Math.pow(10, inputs.number);
}
/**
* Calculates the sine of an angle in radians.
* Example: sin(0) → 0, sin(π/2) → 1, sin(π) → ~0
* @param inputs a number
* @returns number
* @group basics
* @shortname sin
* @drawable false
*/
sin(inputs) {
return Math.sin(inputs.number);
}
/**
* Calculates the cosine of an angle in radians.
* Example: cos(0) → 1, cos(π/2) → ~0, cos(π) → -1
* @param inputs a number
* @returns number
* @group basics
* @shortname cos
* @drawable false
*/
cos(inputs) {
return Math.cos(inputs.number);
}
/**
* Calculates the tangent of an angle in radians.
* Example: tan(0) → 0, tan(π/4) → ~1, tan(π/2) → infinity
* @param inputs a number
* @returns number
* @group basics
* @shortname tan
* @drawable false
*/
tan(inputs) {
return Math.tan(inputs.number);
}
/**
* Calculates the arcsine (inverse sine) in radians, returns angle whose sine is the input.
* Example: asin(0) → 0, asin(1) → π/2 (~1.57), asin(0.5) → π/6 (~0.524)
* @param inputs a number
* @returns number
* @group basics
* @shortname asin
* @drawable false
*/
asin(inputs) {
return Math.asin(inputs.number);
}
/**
* Calculates the arccosine (inverse cosine) in radians, returns angle whose cosine is the input.
* Example: acos(1) → 0, acos(0) → π/2 (~1.57), acos(-1) → π (~3.14)
* @param inputs a number
* @returns number
* @group basics
* @shortname acos
* @drawable false
*/
acos(inputs) {
return Math.acos(inputs.number);
}
/**
* Calculates the arctangent (inverse tangent) in radians, returns angle whose tangent is the input.
* Example: atan(0) → 0, atan(1) → π/4 (~0.785), atan(-1) → -π/4
* @param inputs a number
* @returns number
* @group basics
* @shortname atan
* @drawable false
*/
atan(inputs) {
return Math.atan(inputs.number);
}
/**
* Calculates e raised to the power of the input (exponential function).
* Example: e⁰ → 1, e¹ → ~2.718, e² → ~7.389
* @param inputs a number
* @returns number
* @group basics
* @shortname exp
* @drawable false
*/
exp(inputs) {
return Math.exp(inputs.number);
}
/**
* Converts an angle from degrees to radians.
* Example: 180° → π (~3.14159), 90° → π/2 (~1.5708), 360° → 2π
* @param inputs a number in degrees
* @returns number
* @group basics
* @shortname deg to rad
* @drawable false
*/
degToRad(inputs) {
return inputs.number * Math.PI / 180;
}
/**
* Converts an angle from radians to degrees.
* Example: π → 180°, π/2 → 90°, 2π → 360°
* @param inputs a number in radians
* @returns number
* @group basics
* @shortname rad to deg
* @drawable false
*/
radToDeg(inputs) {
return inputs.number * 180 / Math.PI;
}
/**
* Applies an easing function to interpolate smoothly between min and max values.
* Example: x=0.5 from [0,100] with easeInQuad → applies quadratic acceleration curve
* Useful for smooth animations with various acceleration/deceleration curves.
* @param inputs a number, min and max values, and ease type
* @returns number
* @group operations
* @shortname ease
* @drawable false
*/
ease(inputs) {
const x = inputs.x;
const min = inputs.min;
const max = inputs.max;
const y = this[inputs.ease](x);
const res = this.remap({ number: y, fromLow: 0, fromHigh: 1, toLow: min, toHigh: max });
return res;
}
/**
* Constrains a value between a minimum and maximum value.
* Example: clamp(5, 0, 3) returns 3, clamp(-1, 0, 3) returns 0, clamp(1.5, 0, 3) returns 1.5
* @param inputs a number, min and max values
* @returns number clamped between min and max
* @group operations
* @shortname clamp
* @drawable false
*/
clamp(inputs) {
return Math.max(inputs.min, Math.min(inputs.max, inputs.number));
}
/**
* Linear interpolation between two values using parameter t (0 to 1).
* Example: From 0 to 100 at t=0.5 → 50, From 10 to 20 at t=0.25 → 12.5
* When t=0 returns start, when t=1 returns end. Useful for smooth transitions.
* @param inputs start value, end value, and interpolation parameter t
* @returns interpolated value
* @group operations
* @shortname lerp
* @drawable false
*/
lerp(inputs) {
return inputs.start + (inputs.end - inputs.start) * inputs.t;
}
/**
* Calculates the interpolation parameter t for a value between start and end (reverse of lerp).
* Example: Value 5 in range [0,10] → t=0.5, Value 2.5 in range [0,10] → t=0.25
* Returns what t value would produce the given value in a lerp. Useful for finding relative position.
* @param inputs start value, end value, and the value to find t for
* @returns interpolation parameter (typically 0-1)
* @group operations
* @shortname inverse lerp
* @drawable false
*/
inverseLerp(inputs) {
if (inputs.start === inputs.end) {
return 0;
}
return (inputs.value - inputs.start) / (inputs.end - inputs.start);
}
/**
* Hermite interpolation with smooth acceleration and deceleration (smoother than linear lerp).
* Example: x=0 → 0, x=0.5 → 0.5, x=1 → 1 (but with smooth S-curve in between)
* Input is automatically clamped to [0,1]. Output eases in and out smoothly. Great for animations.
* @param inputs a number between 0 and 1
* @returns smoothly interpolated value
* @group operations
* @shortname smoothstep
* @drawable false
*/
smoothstep(inputs) {
const t = Math.max(0, Math.min(1, inputs.number));
return t * t * (3 - 2 * t);
}
/**
* Returns the sign of a number: -1 for negative, 0 for zero, 1 for positive.
* Example: -5 → -1, 0 → 0, 3.14 → 1
* Useful for determining direction or polarity.
* @param inputs a number
* @returns -1, 0, or 1
* @group operations
* @shortname sign
* @drawable false
*/
sign(inputs) {
return Math.sign(inputs.number);
}
/**
* Returns the fractional part of a number (removes integer part, keeps decimals).
* Example: 3.14 → 0.14, 5.9 → 0.9, -2.3 → 0.7
* Useful for wrapping values and creating repeating patterns.
* @param inputs a number
* @returns fractional part (always positive)
* @group operations
* @shortname fract
* @drawable false
*/
fract(inputs) {
return inputs.number - Math.floor(inputs.number);
}
/**
* Wraps a number within a specified range (creates repeating cycle).
* Example: 1.5 in range [0,1) → 0.5, -0.3 in range [0,1) → 0.7, 370° in range [0,360) → 10°
* Useful for angles, UVs, or any repeating domain. Like modulo but handles negatives properly.
* @param inputs a number, min and max values
* @returns wrapped value within range
* @group operations
* @shortname wrap
* @drawable false
*/
wrap(inputs) {
const range = inputs.max - inputs.min;
if (range === 0) {
return inputs.min;
}
const normalized = (inputs.number - inputs.min) % range;
return normalized < 0 ? normalized + inputs.max : normalized + inputs.min;
}
/**
* Creates a ping-pong (back-and-forth) effect that bounces a value between 0 and length.
* The value goes from 0→length, then back length→0, repeating this cycle.
* Example: With length=1: t=0→0, t=0.5→0.5, t=1→1 (peak), t=1.5→0.5, t=2→0, t=2.5→0.5 (repeats)
* Useful for creating bouncing animations like a ball or oscillating motion.
* @param inputs time value t and length
* @returns value bouncing between 0 and length
* @group operations
* @shortname ping pong
* @drawable false
*/
pingPong(inputs) {
const t = Math.abs(inputs.t) % (inputs.length * 2);
return t > inputs.length ? inputs.length * 2 - t : t;
}
/**
* Moves a value toward a target by a maximum delta amount (never overshooting).
* Example: From 0 toward 10 by max 3 → 3, From 8 toward 10 by max 3 → 10 (reached)
* Useful for smooth movement with maximum speed limits.
* @param inputs current value, target value, and maximum delta
* @returns new value moved toward target
* @group operations
* @shortname move towards
* @drawable false
*/
moveTowards(inputs) {
const delta = inputs.target - inputs.current;
if (Math.abs(delta) <= inputs.maxDelta) {
return inputs.target;
}
return inputs.current + Math.sign(delta) * inputs.maxDelta;
}
easeInSine(x) {
return 1 - Math.cos((x * Math.PI) / 2);
}
easeOutSine(x) {
return Math.sin((x * Math.PI) / 2);
}
easeInOutSine(x) {
return -(Math.cos(Math.PI * x) - 1) / 2;
}
easeInQuad(x) {
return x * x;
}
easeOutQuad(x) {
return 1 - (1 - x) * (1 - x);
}
easeInOutQuad(x) {
return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2;
}
easeInCubic(x) {
return x * x * x;
}
easeOutCubic(x) {
return 1 - Math.pow(1 - x, 3);
}
easeInOutCubic(x) {
return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
}
easeInQuart(x) {
return x * x * x * x;
}
easeOutQuart(x) {
return 1 - Math.pow(1 - x, 4);
}
easeInOutQuart(x) {
return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2;
}
easeInQuint(x) {
return x * x * x * x * x;
}
easeOutQuint(x) {
return 1 - Math.pow(1 - x, 5);
}
easeInOutQuint(x) {
return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2;
}
easeInExpo(x) {
return x === 0 ? 0 : Math.pow(2, 10 * x - 10);
}
easeOutExpo(x) {
return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
}
easeInOutExpo(x) {
return x === 0
? 0
: x === 1
? 1
: x < 0.5
? Math.pow(2, 20 * x - 10) / 2
: (2 - Math.pow(2, -20 * x + 10)) / 2;
}
easeInCirc(x) {
return 1 - Math.sqrt(1 - x * x);
}
easeOutCirc(x) {
return Math.sqrt(1 - Math.pow(x - 1, 2));
}
easeInOutCirc(x) {
return x < 0.5
? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2
: (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2;
}
easeInBack(x) {
const c1 = 1.70158;
const c3 = c1 + 1;
return c3 * x * x * x - c1 * x * x;
}
easeOutBack(x) {
const c1 = 1.70158;
const c3 = c1 + 1;
return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
}
easeInOutBack(x) {
const c1 = 1.70158;
const c2 = c1 * 1.525;
return x < 0.5
? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2
: (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
}
easeInElastic(x) {
const c4 = (2 * Math.PI) / 3;
return x === 0
? 0
: x === 1
? 1
: -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4);
}
easeOutElastic(x) {
const c4 = (2 * Math.PI) / 3;
return x === 0
? 0
: x === 1
? 1
: Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1;
}
easeInOutElastic(x) {
const c5 = (2 * Math.PI) / 4.5;
return x === 0
? 0
: x === 1
? 1
: x < 0.5
? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2
: (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1;
}
easeInBounce(x) {
return 1 - this.easeOutBounce(1 - x);
}
easeOutBounce(x) {
const n1 = 7.5625;
const d1 = 2.75;
if (x < 1 / d1) {
return n1 * x * x;
}
else if (x < 2 / d1) {
return n1 * (x -= 1.5 / d1) * x + 0.75;
}
else if (x < 2.5 / d1) {
return n1 * (x -= 2.25 / d1) * x + 0.9375;
}
else {
return n1 * (x -= 2.625 / d1) * x + 0.984375;
}
}
easeInOutBounce(x) {
return x < 0.5
? (1 - this.easeOutBounce(1 - 2 * x)) / 2
: (1 + this.easeOutBounce(2 * x - 1)) / 2;
}
}