Spaces:
Sleeping
Sleeping
File size: 9,133 Bytes
4771fc6 d9d0b0f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
class OrbRenderer {
constructor(container, opts = {}) {
this.container = container;
this.hue = opts.hue ?? 0;
this.hoverIntensity = opts.hoverIntensity ?? 0.2;
this.bgColor = opts.backgroundColor ?? [0.02, 0.02, 0.06];
this.targetHover = 0;
this.currentHover = 0;
this.currentRot = 0;
this.lastTs = 0;
this.canvas = document.createElement('canvas');
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.container.appendChild(this.canvas);
this.gl = this.canvas.getContext('webgl', { alpha: true, premultipliedAlpha: false, antialias: false });
if (!this.gl) { console.warn('WebGL not available'); return; }
this._build();
this._resize();
this._onResize = this._resize.bind(this);
window.addEventListener('resize', this._onResize);
this._raf = requestAnimationFrame(this._loop.bind(this));
}
static VERT = `
precision highp float;
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main(){ vUv=uv; gl_Position=vec4(position,0.0,1.0); }`;
static FRAG = `
precision highp float;
uniform float iTime;
uniform vec3 iResolution;
uniform float hue;
uniform float hover;
uniform float rot;
uniform float hoverIntensity;
uniform vec3 backgroundColor;
varying vec2 vUv;
vec3 rgb2yiq(vec3 c){float y=dot(c,vec3(.299,.587,.114));float i=dot(c,vec3(.596,-.274,-.322));float q=dot(c,vec3(.211,-.523,.312));return vec3(y,i,q);}
vec3 yiq2rgb(vec3 c){return vec3(c.x+.956*c.y+.621*c.z,c.x-.272*c.y-.647*c.z,c.x-1.106*c.y+1.703*c.z);}
vec3 adjustHue(vec3 color,float hueDeg){float h=hueDeg*3.14159265/180.0;vec3 yiq=rgb2yiq(color);float cosA=cos(h);float sinA=sin(h);float i2=yiq.y*cosA-yiq.z*sinA;float q2=yiq.y*sinA+yiq.z*cosA;yiq.y=i2;yiq.z=q2;return yiq2rgb(yiq);}
vec3 hash33(vec3 p3){p3=fract(p3*vec3(.1031,.11369,.13787));p3+=dot(p3,p3.yxz+19.19);return -1.0+2.0*fract(vec3(p3.x+p3.y,p3.x+p3.z,p3.y+p3.z)*p3.zyx);}
float snoise3(vec3 p){const float K1=.333333333;const float K2=.166666667;vec3 i=floor(p+(p.x+p.y+p.z)*K1);vec3 d0=p-(i-(i.x+i.y+i.z)*K2);vec3 e=step(vec3(0.0),d0-d0.yzx);vec3 i1=e*(1.0-e.zxy);vec3 i2=1.0-e.zxy*(1.0-e);vec3 d1=d0-(i1-K2);vec3 d2=d0-(i2-K1);vec3 d3=d0-0.5;vec4 h=max(0.6-vec4(dot(d0,d0),dot(d1,d1),dot(d2,d2),dot(d3,d3)),0.0);vec4 n=h*h*h*h*vec4(dot(d0,hash33(i)),dot(d1,hash33(i+i1)),dot(d2,hash33(i+i2)),dot(d3,hash33(i+1.0)));return dot(vec4(31.316),n);}
vec4 extractAlpha(vec3 c){float a=max(max(c.r,c.g),c.b);return vec4(c/(a+1e-5),a);}
const vec3 baseColor1=vec3(.611765,.262745,.996078);
const vec3 baseColor2=vec3(.298039,.760784,.913725);
const vec3 baseColor3=vec3(.062745,.078431,.600000);
const float innerRadius=0.6;
const float noiseScale=0.65;
float light1(float i,float a,float d){return i/(1.0+d*a);}
float light2(float i,float a,float d){return i/(1.0+d*d*a);}
vec4 draw(vec2 uv){
vec3 c1=adjustHue(baseColor1,hue);vec3 c2=adjustHue(baseColor2,hue);vec3 c3=adjustHue(baseColor3,hue);
float ang=atan(uv.y,uv.x);float len=length(uv);float invLen=len>0.0?1.0/len:0.0;
float bgLum=dot(backgroundColor,vec3(.299,.587,.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,bgLum*0.7);
float cl=cos(ang+iTime*2.0)*0.5+0.5;
float a2=iTime*-1.0;vec2 pos=vec2(cos(a2),sin(a2))*r0;float d=distance(uv,pos);
float v1=light2(1.5,5.0,d);v1*=light1(1.0,50.0,d0);
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 colBase=mix(c1,c2,cl);
float fadeAmt=mix(1.0,0.1,bgLum);
vec3 darkCol=mix(c3,colBase,v0);darkCol=(darkCol+v1)*v2*v3;darkCol=clamp(darkCol,0.0,1.0);
vec3 lightCol=(colBase+v1)*mix(1.0,v2*v3,fadeAmt);lightCol=mix(backgroundColor,lightCol,v0);lightCol=clamp(lightCol,0.0,1.0);
vec3 fc=mix(darkCol,lightCol,bgLum);
return extractAlpha(fc);
}
vec4 mainImage(vec2 fragCoord){
vec2 center=iResolution.xy*0.5;float sz=min(iResolution.x,iResolution.y);
vec2 uv=(fragCoord-center)/sz*2.0;
float s2=sin(rot);float c2=cos(rot);uv=vec2(c2*uv.x-s2*uv.y,s2*uv.x+c2*uv.y);
uv.x+=hover*hoverIntensity*0.1*sin(uv.y*10.0+iTime);
uv.y+=hover*hoverIntensity*0.1*sin(uv.x*10.0+iTime);
return draw(uv);
}
void main(){
vec2 fc=vUv*iResolution.xy;vec4 col=mainImage(fc);
gl_FragColor=vec4(col.rgb*col.a,col.a);
}`;
_compile(type, src) {
const gl = this.gl;
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
console.error('Shader compile error:', gl.getShaderInfoLog(s));
gl.deleteShader(s);
return null;
}
return s;
}
_build() {
const gl = this.gl;
const vs = this._compile(gl.VERTEX_SHADER, OrbRenderer.VERT);
const fs = this._compile(gl.FRAGMENT_SHADER, OrbRenderer.FRAG);
if (!vs || !fs) return;
this.pgm = gl.createProgram();
gl.attachShader(this.pgm, vs);
gl.attachShader(this.pgm, fs);
gl.linkProgram(this.pgm);
if (!gl.getProgramParameter(this.pgm, gl.LINK_STATUS)) {
console.error('Program link error:', gl.getProgramInfoLog(this.pgm));
return;
}
gl.useProgram(this.pgm);
const posLoc = gl.getAttribLocation(this.pgm, 'position');
const uvLoc = gl.getAttribLocation(this.pgm, 'uv');
const posBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
const uvBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0, 2,0, 0,2]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(uvLoc);
gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 0, 0);
this.u = {};
['iTime','iResolution','hue','hover','rot','hoverIntensity','backgroundColor'].forEach(name => {
this.u[name] = gl.getUniformLocation(this.pgm, name);
});
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.clearColor(0,0,0,0);
}
_resize() {
const dpr = window.devicePixelRatio || 1;
const w = this.container.clientWidth;
const h = this.container.clientHeight;
this.canvas.width = w * dpr;
this.canvas.height = h * dpr;
if (this.gl) this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
}
_loop(ts) {
this._raf = requestAnimationFrame(this._loop.bind(this));
if (!this.pgm) return;
const gl = this.gl;
const t = ts * 0.001;
const dt = this.lastTs ? t - this.lastTs : 0.016;
this.lastTs = t;
this.currentHover += (this.targetHover - this.currentHover) * Math.min(dt * 4, 1);
if (this.currentHover > 0.5) this.currentRot += dt * 0.3;
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(this.pgm);
gl.uniform1f(this.u.iTime, t);
gl.uniform3f(this.u.iResolution, this.canvas.width, this.canvas.height, this.canvas.width / this.canvas.height);
gl.uniform1f(this.u.hue, this.hue);
gl.uniform1f(this.u.hover, this.currentHover);
gl.uniform1f(this.u.rot, this.currentRot);
gl.uniform1f(this.u.hoverIntensity, this.hoverIntensity);
gl.uniform3f(this.u.backgroundColor, this.bgColor[0], this.bgColor[1], this.bgColor[2]);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
setActive(active) {
this.targetHover = active ? 1.0 : 0.0;
const ctn = this.container;
if (active) ctn.classList.add('active');
else ctn.classList.remove('active');
}
destroy() {
cancelAnimationFrame(this._raf);
window.removeEventListener('resize', this._onResize);
if (this.canvas.parentNode) this.canvas.parentNode.removeChild(this.canvas);
const ext = this.gl.getExtension('WEBGL_lose_context');
if (ext) ext.loseContext();
}
} |