| |
| |
| |
| const LCG_MULTIPLIER = 16807; |
| const LCG_MODULUS = 2147483647; |
| 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); |
|
|
| |
| |
| |
| 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)); |
|
|
| |
| export const wrap01 = (value) => ((value % 1) + 1) % 1; |
|
|
| |
| |
| |
| 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); |
| }; |
|
|
| |
| |
| |
| 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}`; |
| }; |