game / src /utils /math.js
riddhiman's picture
Create src/utils/math.js
f746a82 verified
// ==========================================
// SEEDED RANDOMNESS (LCG)
// ==========================================
const LCG_MULTIPLIER = 16807;
const LCG_MODULUS = 2147483647; // 2^31 - 1
let _seed = 123456789;
export const normalizeSeed = (newSeed) => {
const numeric = Math.floor(Number(newSeed) || 0);
const normalized = Math.abs(numeric) % (LCG_MODULUS - 1);
return normalized + 1;
};
export const setSeed = (newSeed) => {
_seed = normalizeSeed(newSeed);
};
export const createSeededRandom = (seed) => {
let localSeed = normalizeSeed(seed);
return () => {
localSeed = (localSeed * LCG_MULTIPLIER) % LCG_MODULUS;
return (localSeed - 1) / (LCG_MODULUS - 1);
};
};
export const createRandomRange = (rng) => (min, max) => min + rng() * (max - min);
export const createRandomStream = (seed) => {
const next = createSeededRandom(seed);
return {
next,
range: createRandomRange(next),
};
};
export const mixSeed = (...parts) => {
let hash = 2166136261;
for (const part of parts) {
const chunk = String(part ?? '');
for (let i = 0; i < chunk.length; i += 1) {
hash ^= chunk.charCodeAt(i);
hash = Math.imul(hash, 16777619);
}
hash ^= 124;
hash = Math.imul(hash, 16777619);
}
return normalizeSeed(hash >>> 0);
};
export const createRandomSeed = () => {
const cryptoObj = globalThis.crypto;
if (cryptoObj?.getRandomValues) {
const buffer = new Uint32Array(1);
cryptoObj.getRandomValues(buffer);
return normalizeSeed(buffer[0]);
}
return normalizeSeed(Math.floor(Math.random() * (LCG_MODULUS - 1)));
};
export const seededRandom = () => {
_seed = (_seed * LCG_MULTIPLIER) % LCG_MODULUS;
return (_seed - 1) / (LCG_MODULUS - 1);
};
export const randomRange = (min, max) => min + seededRandom() * (max - min);
// ==========================================
// MATH UTILITIES
// ==========================================
export const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
export const lerp = (a, b, t) => a + (b - a) * t;
export const damp = (current, target, smoothing, dt) =>
lerp(current, target, 1 - Math.exp(-smoothing * dt));
// Avoids branching (if statements) using continuous modulo logic
export const wrap01 = (value) => ((value % 1) + 1) % 1;
// ==========================================
// ANGLE UTILITIES
// ==========================================
const PI2 = Math.PI * 2;
export const angleDifference = (a, b) => {
let diff = (b - a) % PI2;
diff = (diff + PI2) % PI2;
return diff > Math.PI ? diff - PI2 : diff;
};
export const signedAngleXZ = (from, to) => {
const cross = from.x * to.z - from.z * to.x;
const dot = from.x * to.x + from.z * to.z;
return Math.atan2(cross, dot);
};
// ==========================================
// FORMATTING UTILITIES
// ==========================================
export const formatOrdinal = (index) => {
const n = Math.max(1, index);
const mod100 = n % 100;
if (mod100 >= 11 && mod100 <= 13) return `${n}th`;
const suffixes = ['th', 'st', 'nd', 'rd'];
const mod10 = n % 10;
return `${n}${suffixes[mod10 < 4 ? mod10 : 0]}`;
};
export const formatRaceTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60).toString().padStart(2, '0');
const millis = Math.floor((seconds % 1) * 1000).toString().padStart(3, '0');
return `${mins}:${secs}.${millis}`;
};