Components
Loading preview...
import React, { useEffect, useRef, useState } from 'react';
const VERTEX_SHADER = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const FRAGMENT_SHADER = `
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
#define ITERATIONS 6
#define SEA_HEIGHT 0.6
#define SEA_CHOPPY 4.0
#define SEA_SPEED 0.8
#define SEA_FREQ 0.16
#define SEA_BASE vec3(0.1, 0.19, 0.22)
#define SEA_WATER_COLOR vec3(0.8, 0.9, 0.6)
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);
}
float sea_octave(vec2 uv, float choppy) {
uv += noise(uv);
vec2 wv = 1.0 - abs(sin(uv));
vec2 swv = abs(cos(uv));
wv = mix(wv, swv, wv);
return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy);
}
float map(vec3 p, float time, vec2 resolution, vec2 mouse) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
vec2 mouseDir = (mouse / resolution.xy) - 0.5;
uv += mouseDir * 2.0;
float d, h = 0.0;
for(int i = 0; i < ITERATIONS; i++) {
d = sea_octave((uv + time * SEA_SPEED) * freq, choppy);
d += sea_octave((uv - time * SEA_SPEED) * freq, choppy);
h += d * amp;
uv *= mat2(1.6, 1.2, -1.2, 1.6);
freq *= 1.9; amp *= 0.22;
choppy = mix(choppy, 1.0, 0.2);
}
return p.y - h;
}
void main() {
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.y, u_resolution.x);
vec3 eye = vec3(0.0, 3.5, 5.0);
vec3 target = vec3(0.0, 0.0, 0.0);
vec3 forward = normalize(target - eye);
vec3 right = normalize(cross(forward, vec3(0.0, 1.0, 0.0)));
vec3 up = cross(right, forward);
vec3 rayDir = normalize(uv.x * right + uv.y * up + 1.5 * forward);
float t = 0.0;
for(int i = 0; i < 64; i++) {
vec3 p = eye + rayDir * t;
float d = map(p, u_time, u_resolution, u_mouse);
if(d < 0.01 || t > 20.0) break;
t += d;
}
vec3 p = eye + rayDir * t;
vec3 color;
if (t > 20.0) {
float skyFactor = (rayDir.y + 0.1) * 0.8;
color = mix(vec3(0.4, 0.7, 1.0), vec3(0.7, 0.9, 1.0), skyFactor);
} else {
vec3 n = normalize(vec3(
map(vec3(p.x + 0.01, p.y, p.z), u_time, u_resolution, u_mouse) - map(vec3(p.x - 0.01, p.y, p.z), u_time, u_resolution, u_mouse),
0.02,
map(vec3(p.x, p.y, p.z + 0.01), u_time, u_resolution, u_mouse) - map(vec3(p.x, p.y, p.z - 0.01), u_time, u_resolution, u_mouse)
));
float fresnel = pow(1.0 - max(0.0, dot(n, -rayDir)), 5.0);
vec3 waterColor = mix(vec3(0.0, 0.45, 0.55), vec3(0.0, 0.8, 0.8), p.y * 0.5 + 0.5);
vec3 skyReflect = vec3(0.8, 0.95, 1.0);
color = mix(waterColor, skyReflect, fresnel);
vec3 lightDir = normalize(vec3(0.5, 1.0, 0.5));
float spec = pow(max(0.0, dot(reflect(-lightDir, n), -rayDir)), 60.0);
color += vec3(1.0, 1.0, 0.9) * spec * 0.6;
color += (1.0 - fresnel) * 0.1 * vec3(0.5, 1.0, 0.9);
}
color = mix(color, vec3(0.6, 0.85, 0.9), smoothstep(10.0, 25.0, t));
gl_FragColor = vec4(color, 1.0);
}
`;
const GeneratedComponent: React.FC = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const fallbackCanvasRef = useRef<HTMLCanvasElement>(null);
const [useFallback, setUseFallback] = useState(false);
const mouseRef = useRef({ x: 0, y: 0 });
const requestRef = useRef<number>();
const startTimeRef = useRef<number>(Date.now());
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') as WebGLRenderingContext | null;
if (!gl) {
setUseFallback(true);
return;
}
const createShader = (gl: WebGLRenderingContext, type: number, source: string) => {
const shader = gl.createShader(type);
if (!shader) return null;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
};
const createProgram = (gl: WebGLRenderingContext, vs: WebGLShader, fs: WebGLShader) => {
const program = gl.createProgram();
if (!program) return null;
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
return null;
}
return program;
};
const vShader = createShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
const fShader = createShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
if (!vShader || !fShader) {
setUseFallback(true);
return;
}
const program = createProgram(gl, vShader, fShader);
if (!program) {
setUseFallback(true);
return;
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const resize = () => {
canvas.width = window.innerWidth * window.devicePixelRatio;
canvas.height = window.innerHeight * window.devicePixelRatio;
gl.viewport(0, 0, canvas.width, canvas.height);
};
const handleMouseMove = (e: MouseEvent) => {
mouseRef.current = { x: e.clientX, y: e.clientY };
};
const render = () => {
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
const positionLoc = gl.getAttribLocation(program, "position");
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
const timeLoc = gl.getUniformLocation(program, "u_time");
gl.uniform1f(timeLoc, (Date.now() - startTimeRef.current) / 1000.0);
const resLoc = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resLoc, canvas.width, canvas.height);
const mouseLoc = gl.getUniformLocation(program, "u_mouse");
gl.uniform2f(mouseLoc, mouseRef.current.x * window.devicePixelRatio, (window.innerHeight - mouseRef.current.y) * window.devicePixelRatio);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestRef.current = requestAnimationFrame(render);
};
window.addEventListener('resize', resize);
window.addEventListener('mousemove', handleMouseMove);
resize();
requestRef.current = requestAnimationFrame(render);
return () => {
window.removeEventListener('resize', resize);
window.removeEventListener('mousemove', handleMouseMove);
if (requestRef.current) cancelAnimationFrame(requestRef.current);
};
}, []);
useEffect(() => {
if (!useFallback) return;
const canvas = fallbackCanvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let fallbackReq: number;
const resizeFallback = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
const drawFallback = () => {
const time = Date.now() * 0.001;
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, '#4facfe');
grad.addColorStop(1, '#00f2fe');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
for (let i = 0; i < 50; i++) {
const x = (Math.sin(time + i) * 0.5 + 0.5) * canvas.width;
const y = (Math.cos(time * 0.5 + i) * 0.5 + 0.5) * canvas.height;
ctx.beginPath();
ctx.arc(x, y, 2 + Math.sin(time + i) * 2, 0, Math.PI * 2);
ctx.fill();
}
fallbackReq = requestAnimationFrame(drawFallback);
};
window.addEventListener('resize', resizeFallback);
resizeFallback();
fallbackReq = requestAnimationFrame(drawFallback);
return () => {
window.removeEventListener('resize', resizeFallback);
cancelAnimationFrame(fallbackReq);
};
}, [useFallback]);
return (
<div className="relative w-full h-screen overflow-hidden bg-zinc-950">
{!useFallback ? (
<canvas
ref={canvasRef}
className="block w-full h-full cursor-crosshair"
/>
) : (
<>
<canvas
ref={fallbackCanvasRef}
className="fixed top-0 left-0 w-full h-full -z-10"
/>
<div className="flex flex-col items-center justify-center w-full h-full text-white font-sans bg-transparent">
<h1 className="text-4xl font-bold mb-4">Tropical Ocean</h1>
<p>WebGL is not supported in your browser. Using fallback animation.</p>
</div>
</>
)}
</div>
);
};
export default GeneratedComponent;