Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 80 additions & 9 deletions src/content/Backgrounds/Orb/Orb.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { Mesh, Program, Renderer, Triangle, Vec3 } from 'ogl';
import { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle, Vec3 } from 'ogl';
import './Orb.css';

export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = true, forceHoverState = false }) {
export default function Orb({
hue = 0,
hoverIntensity = 0.2,
rotateOnHover = true,
forceHoverState = false,
backgroundColor = '#000000'
}) {
const ctnDom = useRef(null);

const vert = /* glsl */ `
Expand All @@ -25,6 +31,7 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
uniform float hover;
uniform float rot;
uniform float hoverIntensity;
uniform vec3 backgroundColor;
varying vec2 vUv;

vec3 rgb2yiq(vec3 c) {
Expand Down Expand Up @@ -115,12 +122,17 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
float ang = atan(uv.y, uv.x);
float len = length(uv);
float invLen = len > 0.0 ? 1.0 / len : 0.0;

float bgLuminance = dot(backgroundColor, vec3(0.299, 0.587, 0.114));

float n0 = snoise3(vec3(uv * noiseScale, iTime * 0.5)) * 0.5 + 0.5;
float r0 = mix(mix(innerRadius, 1.0, 0.4), mix(innerRadius, 1.0, 0.6), n0);
float d0 = distance(uv, (r0 * invLen) * uv);
float v0 = light1(1.0, 10.0, d0);

v0 *= smoothstep(r0 * 1.05, r0, len);
float innerFade = smoothstep(r0 * 0.8, r0 * 0.95, len);
v0 *= mix(innerFade, 1.0, bgLuminance * 0.7);
float cl = cos(ang + iTime * 2.0) * 0.5 + 0.5;

float a = iTime * -1.0;
Expand All @@ -132,12 +144,20 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
float v2 = smoothstep(1.0, mix(innerRadius, 1.0, n0 * 0.5), len);
float v3 = smoothstep(innerRadius, mix(innerRadius, 1.0, 0.5), len);

vec3 col = mix(color1, color2, cl);
col = mix(color3, col, v0);
col = (col + v1) * v2 * v3;
col = clamp(col, 0.0, 1.0);
vec3 colBase = mix(color1, color2, cl);
float fadeAmount = mix(1.0, 0.1, bgLuminance);

vec3 darkCol = mix(color3, colBase, v0);
darkCol = (darkCol + v1) * v2 * v3;
darkCol = clamp(darkCol, 0.0, 1.0);

vec3 lightCol = (colBase + v1) * mix(1.0, v2 * v3, fadeAmount);
lightCol = mix(backgroundColor, lightCol, v0);
lightCol = clamp(lightCol, 0.0, 1.0);

return extractAlpha(col);
vec3 finalCol = mix(darkCol, lightCol, bgLuminance);

return extractAlpha(finalCol);
}

vec4 mainImage(vec2 fragCoord) {
Expand Down Expand Up @@ -184,7 +204,8 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
hue: { value: hue },
hover: { value: 0 },
rot: { value: 0 },
hoverIntensity: { value: hoverIntensity }
hoverIntensity: { value: hoverIntensity },
backgroundColor: { value: hexToVec3(backgroundColor) }
}
});

Expand Down Expand Up @@ -242,6 +263,7 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
program.uniforms.iTime.value = t * 0.001;
program.uniforms.hue.value = hue;
program.uniforms.hoverIntensity.value = hoverIntensity;
program.uniforms.backgroundColor.value = hexToVec3(backgroundColor);

const effectiveHover = forceHoverState ? 1 : targetHover;
program.uniforms.hover.value += (effectiveHover - program.uniforms.hover.value) * 0.1;
Expand All @@ -264,7 +286,56 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hue, hoverIntensity, rotateOnHover, forceHoverState]);
}, [hue, hoverIntensity, rotateOnHover, forceHoverState, backgroundColor]);

return <div ref={ctnDom} className="orb-container" />;
}

function hslToRgb(h, s, l) {
let r, g, b;

if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};

const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}

return new Vec3(r, g, b);
}

function hexToVec3(color) {
if (color.startsWith('#')) {
const r = parseInt(color.slice(1, 3), 16) / 255;
const g = parseInt(color.slice(3, 5), 16) / 255;
const b = parseInt(color.slice(5, 7), 16) / 255;
return new Vec3(r, g, b);
}

const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (rgbMatch) {
return new Vec3(parseInt(rgbMatch[1]) / 255, parseInt(rgbMatch[2]) / 255, parseInt(rgbMatch[3]) / 255);
}

const hslMatch = color.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%/);
if (hslMatch) {
const h = parseInt(hslMatch[1]) / 360;
const s = parseInt(hslMatch[2]) / 100;
const l = parseInt(hslMatch[3]) / 100;
return hslToRgb(h, s, l);
}

return new Vec3(0, 0, 0);
}
32 changes: 26 additions & 6 deletions src/demo/Backgrounds/OrbDemo.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { Box, Flex, Input, Text } from '@chakra-ui/react';
import { useState } from 'react';
import { CodeTab, PreviewTab, TabsLayout } from '../../components/common/TabsLayout';
import { Box } from '@chakra-ui/react';
import { useDebounce } from 'react-haiku';
import { CodeTab, PreviewTab, TabsLayout } from '../../components/common/TabsLayout';

import CodeExample from '../../components/code/CodeExample';

import PropTable from '../../components/common/Preview/PropTable';
import Dependencies from '../../components/code/Dependencies';
import BackgroundContent from '../../components/common/Preview/BackgroundContent';
import Customize from '../../components/common/Preview/Customize';
import PreviewSlider from '../../components/common/Preview/PreviewSlider';
import PreviewSwitch from '../../components/common/Preview/PreviewSwitch';
import Customize from '../../components/common/Preview/Customize';
import BackgroundContent from '../../components/common/Preview/BackgroundContent';
import PropTable from '../../components/common/Preview/PropTable';

import Orb from '../../content/Backgrounds/Orb/Orb';
import { orb } from '../../constants/code/Backgrounds/orbCode';
import Orb from '../../content/Backgrounds/Orb/Orb';

const OrbDemo = () => {
const [hue, setHue] = useState(0);
const [hoverIntensity, setHoverIntensity] = useState(2);
const [rotateOnHover, setRotateOnHover] = useState(true);
const [forceHoverState, setForceHoverState] = useState(false);
const [backgroundColor, setBackgroundColor] = useState('#000000');

const debouncedHue = useDebounce(hue, 300);
const debouncedHoverIntensity = useDebounce(hoverIntensity, 300);
Expand Down Expand Up @@ -48,6 +49,12 @@ const OrbDemo = () => {
type: 'boolean',
default: 'false',
description: 'Force hover animations even when the orb is not actually hovered.'
},
{
name: 'backgroundColor',
type: 'string',
default: '#000000',
description: 'The background color of the container.'
}
];

Expand All @@ -60,6 +67,7 @@ const OrbDemo = () => {
rotateOnHover={rotateOnHover}
hue={debouncedHue}
forceHoverState={forceHoverState}
backgroundColor={backgroundColor}
/>

{/* For Demo Purposes Only */}
Expand Down Expand Up @@ -89,6 +97,18 @@ const OrbDemo = () => {
isChecked={forceHoverState}
onChange={checked => setForceHoverState(checked)}
/>

<Flex alignItems="center" mb={4}>
<Text fontSize="sm" mr={2}>
Orb Background Color
</Text>
<Input
type="color"
value={backgroundColor}
onChange={e => setBackgroundColor(e.target.value)}
width="50px"
/>
</Flex>
</Customize>

<PropTable data={propData} />
Expand Down
88 changes: 79 additions & 9 deletions src/tailwind/Backgrounds/Orb/Orb.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Mesh, Program, Renderer, Triangle, Vec3 } from 'ogl';
import { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle, Vec3 } from 'ogl';

export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = true, forceHoverState = false }) {
export default function Orb({
hue = 0,
hoverIntensity = 0.2,
rotateOnHover = true,
forceHoverState = false,
backgroundColor = '#000000'
}) {
const ctnDom = useRef(null);

const vert = /* glsl */ `
Expand All @@ -24,6 +30,7 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
uniform float hover;
uniform float rot;
uniform float hoverIntensity;
uniform vec3 backgroundColor;
varying vec2 vUv;

vec3 rgb2yiq(vec3 c) {
Expand Down Expand Up @@ -114,12 +121,16 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
float ang = atan(uv.y, uv.x);
float len = length(uv);
float invLen = len > 0.0 ? 1.0 / len : 0.0;

float bgLuminance = dot(backgroundColor, vec3(0.299, 0.587, 0.114));

float n0 = snoise3(vec3(uv * noiseScale, iTime * 0.5)) * 0.5 + 0.5;
float r0 = mix(mix(innerRadius, 1.0, 0.4), mix(innerRadius, 1.0, 0.6), n0);
float d0 = distance(uv, (r0 * invLen) * uv);
float v0 = light1(1.0, 10.0, d0);
v0 *= smoothstep(r0 * 1.05, r0, len);
float innerFade = smoothstep(r0 * 0.8, r0 * 0.95, len);
v0 *= mix(innerFade, 1.0, bgLuminance * 0.7);
float cl = cos(ang + iTime * 2.0) * 0.5 + 0.5;

float a = iTime * -1.0;
Expand All @@ -131,12 +142,20 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
float v2 = smoothstep(1.0, mix(innerRadius, 1.0, n0 * 0.5), len);
float v3 = smoothstep(innerRadius, mix(innerRadius, 1.0, 0.5), len);

vec3 col = mix(color1, color2, cl);
col = mix(color3, col, v0);
col = (col + v1) * v2 * v3;
col = clamp(col, 0.0, 1.0);
vec3 colBase = mix(color1, color2, cl);
float fadeAmount = mix(1.0, 0.1, bgLuminance);

vec3 darkCol = mix(color3, colBase, v0);
darkCol = (darkCol + v1) * v2 * v3;
darkCol = clamp(darkCol, 0.0, 1.0);

vec3 lightCol = (colBase + v1) * mix(1.0, v2 * v3, fadeAmount);
lightCol = mix(backgroundColor, lightCol, v0);
lightCol = clamp(lightCol, 0.0, 1.0);

vec3 finalCol = mix(darkCol, lightCol, bgLuminance);

return extractAlpha(col);
return extractAlpha(finalCol);
}

vec4 mainImage(vec2 fragCoord) {
Expand Down Expand Up @@ -183,7 +202,8 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
hue: { value: hue },
hover: { value: 0 },
rot: { value: 0 },
hoverIntensity: { value: hoverIntensity }
hoverIntensity: { value: hoverIntensity },
backgroundColor: { value: hexToVec3(backgroundColor) }
}
});

Expand Down Expand Up @@ -241,6 +261,7 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
program.uniforms.iTime.value = t * 0.001;
program.uniforms.hue.value = hue;
program.uniforms.hoverIntensity.value = hoverIntensity;
program.uniforms.backgroundColor.value = hexToVec3(backgroundColor);

const effectiveHover = forceHoverState ? 1 : targetHover;
program.uniforms.hover.value += (effectiveHover - program.uniforms.hover.value) * 0.1;
Expand All @@ -263,7 +284,56 @@ export default function Orb({ hue = 0, hoverIntensity = 0.2, rotateOnHover = tru
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hue, hoverIntensity, rotateOnHover, forceHoverState]);
}, [hue, hoverIntensity, rotateOnHover, forceHoverState, backgroundColor]);

return <div ref={ctnDom} className="w-full h-full" />;
}

function hslToRgb(h, s, l) {
let r, g, b;

if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};

const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}

return new Vec3(r, g, b);
}

function hexToVec3(color) {
if (color.startsWith('#')) {
const r = parseInt(color.slice(1, 3), 16) / 255;
const g = parseInt(color.slice(3, 5), 16) / 255;
const b = parseInt(color.slice(5, 7), 16) / 255;
return new Vec3(r, g, b);
}

const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (rgbMatch) {
return new Vec3(parseInt(rgbMatch[1]) / 255, parseInt(rgbMatch[2]) / 255, parseInt(rgbMatch[3]) / 255);
}

const hslMatch = color.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%/);
if (hslMatch) {
const h = parseInt(hslMatch[1]) / 360;
const s = parseInt(hslMatch[2]) / 100;
const l = parseInt(hslMatch[3]) / 100;
return hslToRgb(h, s, l);
}

return new Vec3(0, 0, 0);
}
Loading