diff --git a/src/core/abstractions/Lensflare.vue b/src/core/abstractions/Lensflare.vue new file mode 100644 index 00000000..a0d2aa7c --- /dev/null +++ b/src/core/abstractions/Lensflare.vue @@ -0,0 +1,305 @@ + + + diff --git a/src/utils/RandUtils.ts b/src/utils/RandUtils.ts new file mode 100644 index 00000000..399a1948 --- /dev/null +++ b/src/utils/RandUtils.ts @@ -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 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 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(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(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(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 + } + } +}