diff --git a/playground/src/pages/LensflareDemo.vue b/playground/src/pages/LensflareDemo.vue
index 416954db..afd642db 100644
--- a/playground/src/pages/LensflareDemo.vue
+++ b/playground/src/pages/LensflareDemo.vue
@@ -1,9 +1,8 @@
-
-
-
-
diff --git a/src/core/abstractions/index.ts b/src/core/abstractions/index.ts
index 26c836f1..6df7809d 100644
--- a/src/core/abstractions/index.ts
+++ b/src/core/abstractions/index.ts
@@ -4,7 +4,7 @@ import { Environment } from './useEnvironment/component'
import Stars from './Stars.vue'
import Precipitation from './Precipitation.vue'
import Smoke from './Smoke.vue'
-import Lensflare from './Lensflare.vue'
+import Lensflare from './Lensflare/component.vue'
import Levioso from './Levioso.vue'
import ContactShadows from './ContactShadows.vue'
import MouseParallax from './MouseParallax.vue'
diff --git a/src/utils/RandUtils.ts b/src/core/abstractions/lensflare/RandUtils.ts
similarity index 100%
rename from src/utils/RandUtils.ts
rename to src/core/abstractions/lensflare/RandUtils.ts
diff --git a/src/core/abstractions/lensflare/component.vue b/src/core/abstractions/lensflare/component.vue
new file mode 100644
index 00000000..c52d56d4
--- /dev/null
+++ b/src/core/abstractions/lensflare/component.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
diff --git a/src/core/abstractions/lensflare/easing.ts b/src/core/abstractions/lensflare/easing.ts
new file mode 100644
index 00000000..d69a7649
--- /dev/null
+++ b/src/core/abstractions/lensflare/easing.ts
@@ -0,0 +1,30 @@
+export function linear(x: number): number {
+ return x;
+}
+
+export function easeInCubic(x: number): number {
+ return x * x * x;
+}
+
+export function easeInOutCubic(x: number): number {
+ return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
+}
+
+export function easeInQuart(x: number): number {
+ return x * x * x * x;
+}
+
+export function easeOutBounce(x: number): number {
+ const n1 = 7.5625;
+ const d1 = 2.75;
+
+ if (x < 1 / d1) {
+ return n1 * x * x;
+ } else if (x < 2 / d1) {
+ return n1 * (x -= 1.5 / d1) * x + 0.75;
+ } else if (x < 2.5 / d1) {
+ return n1 * (x -= 2.25 / d1) * x + 0.9375;
+ } else {
+ return n1 * (x -= 2.625 / d1) * x + 0.984375;
+ }
+}
\ No newline at end of file
diff --git a/src/core/abstractions/lensflare/index.ts b/src/core/abstractions/lensflare/index.ts
new file mode 100644
index 00000000..bfafc49e
--- /dev/null
+++ b/src/core/abstractions/lensflare/index.ts
@@ -0,0 +1,209 @@
+import { Texture, MathUtils, Color } from 'three'
+import { TresColor } from '@tresjs/core'
+import RandUtils from './RandUtils'
+import { linear, easeInCubic, easeInOutCubic, easeInQuart, easeOutBounce } from './easing'
+import Lensflare from './component.vue'
+
+export { Lensflare };
+
+export const partialLensflarePropsArrayToLensflarePropsArray = (
+ partialProps: Partial[] | undefined,
+ seed: number | undefined,
+) =>
+ partialPropsArrayToPropsArray(
+ partialProps,
+ seed,
+ getSeededLensflareElementProps,
+ defaultElement,
+ )
+
+const TEXTURE_PATH =
+ 'https://raw.githubusercontent.com/andretchen0/tresjs_assets/' +
+ 'b1bc3780de73a9328a530767c9a7f4cbab060396/textures/lensflare/'
+
+const easingFunctions = [linear, easeInCubic, easeInOutCubic, easeInQuart, easeOutBounce]
+
+const lerp = MathUtils.lerp
+
+export type LensflareElementProps = {
+ texture: Texture | string
+ size: number
+ distance: number
+ color: TresColor
+}
+
+/**
+ * Fill in missing values for each element in an array of partial props.
+ * Depending on arguments, missing values are filled in as follows:
+ * | has partialProps | has seed | Effect |
+ * |------------------|-----------|---------------------------------------------------------------------------------|
+ * | `true` | `true` | return seeded elements with partialProps as tweaks; ignore excess partialProps. |
+ * | `true` | `false` | return copy of partialProps; missing values are filled in with defaultProps. |
+ * | `false` | `true` | Use the seeded props. |
+ * | `false` | `false` | Use seeded props with seed == 0. |
+ *
+ * @param partialProps - `undefined` or an array of (potentially) incomplete props
+ * @param seed - `undefined` or a number to seed random prop generation
+ * @param getSeededPropsArray - function that returns an array of pseudorandom props
+ * @param defaultProps - props used to fill in missing fields, if seeded random props are not used
+ * @returns T[] - An array of complete props
+ **/
+function partialPropsArrayToPropsArray(
+ partialProps: Partial[] | undefined,
+ seed: number | undefined,
+ getSeededPropsArray: (seed: number) => T[],
+ defaultProps: T,
+): T[] {
+ const hasPartialProps = Array.isArray(partialProps)
+ const hasSeed = typeof seed === 'number'
+
+ if (hasPartialProps) {
+ if (hasSeed) {
+ return getSeededPropsArray(seed).map((seededProps, i) => Object.assign({}, seededProps, partialProps[i]))
+ } else {
+ return partialProps.map(props => Object.assign({}, defaultProps, props))
+ }
+ } else {
+ if (hasSeed) {
+ return getSeededPropsArray(seed)
+ } else {
+ return getSeededPropsArray(0)
+ }
+ }
+}
+
+const defaultElement: LensflareElementProps = {
+ texture: `${TEXTURE_PATH}cirlceBlur.png`,
+ size: 64,
+ distance: 0,
+ color: new Color('white'),
+}
+
+const getSeededLensflareElementProps = (seed: number): LensflareElementProps[] => {
+ const rand: RandUtils = new RandUtils(seed)
+
+ const numPoints = rand.choice([4, 6, 8]) as 4 | 6 | 8
+
+ const circle = `${TEXTURE_PATH}circle.png`
+ const circleBlur = `${TEXTURE_PATH}cirlceBlur.png`
+ const circleRainbow = `${TEXTURE_PATH}circleRainbow.png`
+ const line = `${TEXTURE_PATH}line.png`
+ const poly = `${TEXTURE_PATH}poly${numPoints}.png`
+ const polyStroke = `${TEXTURE_PATH}polyStroke${numPoints}.png`
+ const rays = `${TEXTURE_PATH}rays${numPoints}.png`
+ const ring = `${TEXTURE_PATH}ring.png`
+ const starThin = `${TEXTURE_PATH}starThin${numPoints}.png`
+
+ // NOTE:
+ // Flare elements are divided into back, oversize, body, front.
+ // They are arranged as such, relative to the light source and camera:
+ //
+ // [distance < 0] [distance == 0] [distance > 0]
+ // light camera
+ // back body, oversize front
+
+ const oversizeTexturesOptional = [line, ring]
+ const oversizeColors = ['white']
+ const oversizeSizeMin = 750
+ const oversizeSizeMax = 1024
+ const oversizeElementsNumMin = 0
+ const oversizeElementsNumMax = 2
+
+ const bodyTexturesRequired = [circleBlur, rays]
+ const bodyTexturesOptional = [circle, circleRainbow, ring, starThin]
+ const bodyColors = ['white']
+ const bodySizeMin = 180
+ const bodySizeMax = 512
+ const bodyTexturesOptionalNumMin = 2
+ const bodyTexturesOptionalNumMax = 3
+
+ const frontTexturesOptional = [circleBlur, circle, ring, poly, polyStroke]
+ const [darkPurple, darkBlue] = [0x38235f, 0x02055a]
+ const frontColors = ['dimgray', 'gray', 'darkgray', darkPurple, darkBlue]
+ const frontTexturesNumMin = 2
+ const frontTexturesNumMax = 4
+ const frontSizeMin = 20
+ const frontSizeMax = 180
+ const frontOffsetMin = 0.5
+ const frontOffsetMax = 1
+ const frontLengthMin = 0.75
+ const frontLengthMax = 2.5
+ const frontElementsNumMin = 5
+ const frontElementsNumMax = 21
+
+ const backTexturesOptional = frontTexturesOptional
+ const backColors = frontColors
+ const backTexturesNumMin = 2
+ const backTexturesNumMax = 4
+ const backSizeMin = 180
+ const backSizeMax = 360
+ const backOffsetMin = 0.1
+ const backOffsetMax = 0.2
+ const backLengthMin = 0.5
+ const backLengthMax = 0.6
+ const backElementsNumMin = 0
+ const backElementsNumMax = 5
+
+ const easingFn = rand.choice(easingFunctions) as (n: number) => number
+
+ const oversizeTexturesSelected = rand.sample(
+ oversizeTexturesOptional,
+ rand.int(oversizeElementsNumMin, oversizeElementsNumMax),
+ )
+ const oversizeElementProps: LensflareElementProps[] = oversizeTexturesSelected.map(texture => ({
+ texture,
+ size: rand.int(oversizeSizeMin, oversizeSizeMax),
+ distance: 0,
+ color: rand.defaultChoice(oversizeColors, 'white'),
+ }))
+
+ const bodyTexturesOptionalSelected = rand.sample(
+ bodyTexturesOptional,
+ rand.int(bodyTexturesOptionalNumMin, bodyTexturesOptionalNumMax),
+ )
+
+ const bodyElementProps: LensflareElementProps[] = [
+ ...bodyTexturesRequired.map(texture => ({
+ texture,
+ size: rand.int(bodySizeMin, bodySizeMax),
+ distance: 0,
+ color: rand.defaultChoice(bodyColors, defaultElement.color),
+ })),
+ ...bodyTexturesOptionalSelected.map(texture => ({
+ texture,
+ size: rand.int(bodySizeMin, bodySizeMax),
+ distance: 0,
+ color: rand.defaultChoice(bodyColors, defaultElement.color),
+ })),
+ ]
+
+ const frontTexturesSelected = rand.sample(frontTexturesOptional, rand.int(frontTexturesNumMin, frontTexturesNumMax))
+ const frontNumElements = rand.int(frontElementsNumMin, frontElementsNumMax)
+ const frontDistanceStart = rand.float(frontOffsetMin, frontOffsetMax)
+ const frontDistanceEnd = frontDistanceStart + rand.float(frontLengthMin, frontLengthMax)
+ const frontElementProps: LensflareElementProps[] = new Array(frontNumElements).fill(0).map(() => {
+ const progress = easingFn(rand.rand())
+ return {
+ texture: rand.defaultChoice(frontTexturesSelected, defaultElement.texture),
+ size: lerp(frontSizeMin, frontSizeMax, easingFn(1 - progress)),
+ distance: lerp(frontDistanceStart, frontDistanceEnd, progress),
+ color: rand.defaultChoice(frontColors, defaultElement.color),
+ }
+ })
+
+ const backTexturesSelected = rand.sample(backTexturesOptional, rand.int(backTexturesNumMin, backTexturesNumMax))
+ const backNumElements = rand.int(backElementsNumMin, backElementsNumMax)
+ const backDistanceStart = rand.float(backOffsetMin, backOffsetMax)
+ const backDistanceEnd = backDistanceStart + rand.float(backLengthMin, backLengthMax)
+ const backElementProps = new Array(backNumElements).fill(0).map(() => {
+ const progress = easingFn(rand.rand())
+ return {
+ texture: rand.defaultChoice(backTexturesSelected, defaultElement.texture),
+ size: lerp(backSizeMin, backSizeMax, easingFn(1 - progress)),
+ distance: -lerp(backDistanceStart, backDistanceEnd, progress),
+ color: rand.defaultChoice(backColors, defaultElement.color),
+ }
+ })
+
+ return [...oversizeElementProps, ...bodyElementProps, ...frontElementProps, ...backElementProps]
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 07b43b48..bbfb7d96 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -29,7 +29,4 @@ export function pick(obj: T, props: K[]): P
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 }
+}
\ No newline at end of file