Skip to content

Commit

Permalink
feat(RandUtils): add RandUtils
Browse files Browse the repository at this point in the history
  • Loading branch information
andretchen0 committed Sep 2, 2023
1 parent e059fe7 commit e1693c1
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './core'
export * from './utils'
120 changes: 120 additions & 0 deletions src/utils/RandUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { MathUtils } from 'THREE'
const clamp = MathUtils.clamp

/**
* Seedable pseudorandom number tools
*/
export default class RandUtils {
private _getNext: () => number
private _getGenerator: (seed: number) => () => number

/**
* Create a new seeded pseudorandom number generator.
* @param [seed=0] - the seed for the generator
* @param [getSeededRandomGenerator=getMulberry32] - a function that returns a pseudorandom number generator
* @constructor
*/
constructor(seed = 0, getSeededRandomGenerator?: (seed: number) => () => number) {
this._getGenerator = getSeededRandomGenerator ?? this.getMulberry32
this._getNext = this._getGenerator(seed)
}

/**
* Reseed the pseudorandom number generator
*/
seed(s: number) {
this._getNext = this._getGenerator(s)
}

/**
* Return the next pseudorandom number in the interval [0, 1]
*/
rand(): number {
return this._getNext()
}

/**
* Random float from <low, high> interval
* @param low - Low value of the interval
* @param high - High value of the interval
*/
float(low: number, high: number): number {
return low + this._getNext() * (high - low)
}

/**
* Random float from <-range/2, range/2> interval
* @param range - Interval range
*/
floatSpread(range: number): number {
return this.float(-0.5 * range, 0.5 * range)
}

/**
* Random integer from <low, high> interval
* @param low Low value of the interval
* @param high High value of the interval
*/
int(low: number, high: number): number {
return low + Math.floor(this._getNext() * (high - low + 1))
}

/**
* Choose an element from an array.
* @param array The array to choose from
* @returns An element from the array or null if the array is empty
*/
choice<T>(array: T[]): T | null {
if (!array.length) {
return null
}
return array[Math.floor(this._getNext() * array.length)]
}

/**
* Return n elements from an array.
* @param array The array to sample
* @param sampleSizeMin The minimum sample size
* @param sampleSizeMax The maximum sample size
*/
sample<T>(array: T[], sampleSizeMin: number, sampleSizeMax?: number): T[] {
const len = array.length
sampleSizeMin = clamp(sampleSizeMin, 0, len - 1)
sampleSizeMax = clamp(sampleSizeMax ?? len - 1, 0, len - 1)
const sampleSize = this.int(sampleSizeMin, sampleSizeMax)
const indicies = this.shuffle(array.map((_, i) => i))
const n = Math.min(array.length, sampleSize)
return indicies
.slice(0, n)
.sort()
.map(i => array[i])
}

/**
* Shuffle an array. Not in-place.
* @param array The array to shuffle
*/
shuffle<T>(array: T[]): T[] {
return array
.map(value => ({ value, sort: this._getNext() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value)
}

/**
* The default pseudorandom generator.
*/
private getMulberry32(seed = 0): () => number {
if (0 < seed && seed < 1) {
seed = Math.floor(seed * 2 ** 16)
}
return () => {
// NOTE: Mulberry32 generator
seed += 0x6d2b79f5
let t = seed
t = Math.imul(t ^ (t >>> 15), t | 1)
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
}
3 changes: 3 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ export function hasSetter(obj: any, prop: string): boolean {
const setterName = `set${prop[0].toUpperCase()}${prop.slice(1)}`
return obj[setterName] !== undefined
}

import RandUtils from './RandUtils'
export { RandUtils }

0 comments on commit e1693c1

Please sign in to comment.