import * as THREE from 'three'; export const tessVertexShader = ` void main() { gl_Position = vec4(position.xy, 0.0, 1.0); } `; export const tessFragmentShader = ` precision highp float; uniform float u_time; uniform vec2 u_vertices[4]; uniform int u_vertexCount; uniform float u_amplitude; uniform vec2 u_resolution; // --- Complex arithmetic (from WaveformVisualizer) --- vec2 cmul(vec2 a, vec2 b) { return vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); } vec2 cdiv(vec2 a, vec2 b) { float d = dot(b, b); return vec2(dot(a, b), a.y * b.x - a.x * b.y) / d; } vec2 conj(vec2 z) { return vec2(z.x, -z.y); } vec2 mobius(vec2 z, vec2 a) { return cdiv(z - a, vec2(1.0, 0.0) - cmul(conj(a), z)); } float hdist(vec2 z) { float r = length(z); if (r >= 1.0) return 10.0; return log((1.0 + r) / (1.0 - r)); } vec2 rot(vec2 p, float a) { float c = cos(a), s = sin(a); return vec2(c * p.x - s * p.y, s * p.x + c * p.y); } // --- HSV to RGB --- vec3 hsv2rgb(vec3 c) { vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0); vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); } // --- Polygon winding number test --- float crossZ(vec2 a, vec2 b) { return a.x * b.y - a.y * b.x; } bool insidePolygon(vec2 p, int count) { int winding = 0; for (int i = 0; i < 4; i++) { if (i >= count) break; int j = i + 1; if (j >= count) j = 0; vec2 vi = u_vertices[i]; vec2 vj = u_vertices[j]; if (vi.y <= p.y) { if (vj.y > p.y) { if (crossZ(vj - vi, p - vi) > 0.0) winding++; } } else { if (vj.y <= p.y) { if (crossZ(vj - vi, p - vi) < 0.0) winding--; } } } return winding != 0; } // --- Map point inside polygon to unit disk --- vec2 mapToDisk(vec2 p, int count) { // Compute ctr vec2 ctr = vec2(0.0); for (int i = 0; i < 4; i++) { if (i >= count) break; ctr += u_vertices[i]; } ctr /= float(count); // Find max distance from ctr to any vertex float maxDist = 0.0; for (int i = 0; i < 4; i++) { if (i >= count) break; float d = length(u_vertices[i] - ctr); if (d > maxDist) maxDist = d; } // Normalize: ctr -> origin, scale so vertices sit near disk edge vec2 offset = (p - ctr) / max(maxDist, 0.001); // Clamp to disk float r = length(offset); if (r > 0.98) offset = normalize(offset) * 0.98; return offset; } void main() { // Convert fragment coords to normalized [-1,1] using resolution vec2 ndc = (gl_FragCoord.xy / u_resolution) * 2.0 - 1.0; // Polygon clip int count = u_vertexCount; if (!insidePolygon(ndc, count)) { discard; } // Map to unit disk vec2 diskPos = mapToDisk(ndc, count); float r = length(diskPos); if (r >= 1.0) { discard; } // Audio-reactive rotation float rotSpeed = 0.12 + 0.08 * u_amplitude; vec2 uv = rot(diskPos, u_time * rotSpeed); // Breathing scale float breathe = 1.0 + 0.1 * u_amplitude; uv *= breathe; if (length(uv) >= 1.0) { discard; } // --- {7,3} Poincare tessellation (from WaveformVisualizer) --- float n = 7.0; float angleStep = 6.283185 / n; float coshR = cos(3.14159265 / 3.0) / sin(3.14159265 / n); float sinhR = sqrt(coshR * coshR - 1.0); float tr = sinhR / (coshR + 1.0); vec2 z = uv; float iter = 0.0; for (int i = 0; i < 20; i++) { float ang = atan(z.y, z.x); float sector = floor(ang / angleStep + 0.5) * angleStep; z = rot(z, -sector); iter += abs(sector) > 0.01 ? 1.0 : 0.0; vec2 center = vec2(tr, 0.0); vec2 w = mobius(z, center); if (length(w) >= length(z) - 0.0001) break; z = w; iter += 1.0; } float d = hdist(z); // --- DMT-Chrome coloring --- // Pulsation: biological rhythm ~3 second cycle float pulsation = 0.5 + 0.5 * sin(u_time * 2.094); // 2*pi/3 ≈ 2.094 for ~3s cycle // Hue flows along geometry toward center float hueFlow = sin(iter * 0.3 + u_time * 0.1); // Map to: deep blue(0.6) -> purple(0.75) -> pink(0.85) -> green(0.3) float hue; if (hueFlow > 0.33) { hue = mix(0.6, 0.75, (hueFlow - 0.33) / 0.67); // blue -> purple } else if (hueFlow > -0.33) { hue = mix(0.75, 0.85, (0.33 - hueFlow) / 0.66); // purple -> pink } else { hue = mix(0.85, 1.3, (-0.33 - hueFlow) / 0.67); // pink -> green (via 1.0+) } hue = fract(hue); float sat = 0.9; float val = 0.7 + 0.3 * pulsation; vec3 color = hsv2rgb(vec3(hue, sat, val)); // Edge flare: internal tile edges glow brighter at pulsation peaks float edgeLine = 1.0 - smoothstep(0.0, 0.05, abs(fract(d * 1.5) - 0.5) - 0.42); color += vec3(0.3, 0.2, 0.4) * edgeLine * pulsation; // Sector pattern for extra geometry detail float ang = atan(z.y, z.x); float sectorPattern = smoothstep(0.02, 0.05, abs(sin(ang * n * 0.5))); color *= 0.75 + 0.25 * sectorPattern; // Audio boost color *= 0.9 + 0.3 * u_amplitude; // Disk edge fade float diskEdge = smoothstep(0.98, 0.85, r); color *= diskEdge; color = min(color, vec3(1.0)); gl_FragColor = vec4(color, 0.8); } `; export function createTessellationMaterial() { return new THREE.ShaderMaterial({ uniforms: { u_time: { value: 0 }, u_vertices: { value: [ new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2() ]}, u_vertexCount: { value: 3 }, u_amplitude: { value: 0 }, u_resolution: { value: new THREE.Vector2(1, 1) } }, vertexShader: tessVertexShader, fragmentShader: tessFragmentShader, transparent: true, depthWrite: false, depthTest: false, side: THREE.DoubleSide }); }