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
+ }
+ }
+}