1+ "use client" ;
2+ import { useEffect , useRef } from "react" ;
3+ import {
4+ Camera ,
5+ Mesh ,
6+ Plane ,
7+ Program ,
8+ Renderer ,
9+ RenderTarget ,
10+ Transform ,
11+ Box ,
12+ Vec3 ,
13+ } from "ogl" ;
14+
15+ export const HeroASCII = ( ) => {
16+ const containerRef = useRef < HTMLDivElement > ( null ) ;
17+ useEffect ( ( ) => {
18+ const container = containerRef . current ;
19+ const vertex = `#version 300 es
20+ in vec3 position;
21+ in vec3 normal;
22+ in vec2 uv;
23+ uniform mat4 modelViewMatrix;
24+ uniform mat4 projectionMatrix;
25+ uniform mat3 normalMatrix;
26+ out vec2 vUv;
27+ out vec3 vNormal;
28+ void main() {
29+ vUv = uv;
30+ vNormal = normalize(normalMatrix * normal);
31+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
32+ }` ;
33+ const fragment = `#version 300 es
34+ precision mediump float;
35+ uniform float uTime;
36+ in vec2 vUv;
37+ in vec3 vNormal;
38+ out vec4 fragColor;
39+ void main() {
40+ // Simple lighting from the top-left
41+ vec3 lightDir = normalize(vec3(-1.0, 1.0, 1.0));
42+ float light = max(0.1, dot(vNormal, lightDir)) * 0.9 + 0.2;
43+ // Basic white color with light intensity
44+ fragColor = vec4(light, light, light, 1.0);
45+ }` ;
46+ const asciiVertex = `#version 300 es
47+ in vec2 uv;
48+ in vec2 position;
49+ out vec2 vUv;
50+ void main() {
51+ vUv = uv;
52+ gl_Position = vec4(position, 0., 1.);
53+ }` ;
54+ const asciiFragment = `#version 300 es
55+ precision highp float;
56+ uniform vec2 uResolution;
57+ uniform sampler2D uTexture;
58+ out vec4 fragColor;
59+ float character(int n, vec2 p) {
60+ p = floor(p * vec2(-4.0, 4.0) + 2.5);
61+ if(clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) {
62+ int a = int(round(p.x) + 5.0 * round(p.y));
63+ if(((n >> a) & 1) == 1) return 0.8;
64+ }
65+ return 0.0;
66+ }
67+ void main() {
68+ vec2 pix = gl_FragCoord.xy;
69+ vec3 col = texture(uTexture, floor(pix / 16.0) * 16.0 / uResolution.xy).rgb;
70+ float gray = 0.3 * col.r + 0.59 * col.g + 0.11 * col.b;
71+ int n = 4096;
72+ if(gray > 0.2) n = 65600;
73+ if(gray > 0.3) n = 163153;
74+ if(gray > 0.4) n = 15255086;
75+ if(gray > 0.5) n = 13121101;
76+ if(gray > 0.6) n = 15252014;
77+ if(gray > 0.7) n = 13195790;
78+ if(gray > 0.8) n = 11512810;
79+ vec2 p = mod(pix / 8.0, 2.0) - vec2(1.0);
80+ col = vec3(character(n, p));
81+ if (gray < 0.2) {
82+ col *= 0.4;
83+ }
84+ fragColor = vec4(col, 1.0);
85+ }` ;
86+ const renderer = new Renderer ( { antialias : true } ) ;
87+ const gl = renderer . gl ;
88+ containerRef . current ?. appendChild ( gl . canvas ) ;
89+ const camera = new Camera ( gl , { near : 0.1 , far : 100 } ) ;
90+ camera . position . set ( 2 , 2 , 2 ) ;
91+ camera . lookAt ( new Vec3 ( 0 , 0 , 0 ) ) ;
92+ renderer . setSize ( window . innerWidth , window . innerHeight ) ;
93+ const resize = ( ) => {
94+ camera . perspective ( { aspect : gl . canvas . width / gl . canvas . height } ) ;
95+ } ;
96+ window . addEventListener ( "resize" , resize ) ;
97+ resize ( ) ;
98+ const boxProgram = new Program ( gl , {
99+ vertex : vertex ,
100+ fragment : fragment ,
101+ uniforms : {
102+ uTime : { value : 0 } ,
103+ } ,
104+ cullFace : false ,
105+ } ) ;
106+ const boxMesh = new Mesh ( gl , {
107+ geometry : new Box ( gl , { width : 1 , height : 2 , depth : 1 } ) ,
108+ program : boxProgram ,
109+ } ) ;
110+ const renderTarget = new RenderTarget ( gl ) ;
111+ const asciiProgram = new Program ( gl , {
112+ vertex : asciiVertex ,
113+ fragment : asciiFragment ,
114+ uniforms : {
115+ uResolution : { value : [ gl . canvas . width , gl . canvas . height ] } ,
116+ uTexture : { value : renderTarget . texture } ,
117+ } ,
118+ } ) ;
119+ const asciiMesh = new Mesh ( gl , {
120+ geometry : new Plane ( gl , { width : 2 , height : 2 } ) ,
121+ program : asciiProgram ,
122+ } ) ;
123+ const boxScene = new Transform ( ) ;
124+ boxMesh . setParent ( boxScene ) ;
125+ const asciiScene = new Transform ( ) ;
126+ asciiMesh . setParent ( asciiScene ) ;
127+
128+ const MAX_TILT = 0.2 ;
129+ const LERP_FACTOR = 0.05 ;
130+
131+ // Add hover animation
132+ let targetRotationX = 0 ;
133+ let targetRotationY = 0 ;
134+ let targetRotationZ = 0 ;
135+ let currentRotationX = 0 ;
136+ let currentRotationY = 0 ;
137+ let currentRotationZ = 0 ;
138+
139+ function onMouseMove ( e : MouseEvent ) {
140+ const x = ( e . clientX / window . innerWidth ) * 2 - 1 ;
141+ const y = ( e . clientY / window . innerHeight ) * 2 - 1 ;
142+
143+ targetRotationX = y * MAX_TILT ;
144+ targetRotationY = x * MAX_TILT ;
145+ targetRotationZ = - y * MAX_TILT ;
146+ }
147+
148+ window . addEventListener ( "mousemove" , onMouseMove ) ;
149+
150+ function update ( time : number ) {
151+ const elapsedTime = time * 0.001 ;
152+ boxProgram . uniforms . uTime . value = elapsedTime ;
153+
154+ // Smoothly interpolate current rotation towards target rotation
155+ currentRotationX += ( targetRotationX - currentRotationX ) * LERP_FACTOR ;
156+ currentRotationY += ( targetRotationY - currentRotationY ) * LERP_FACTOR ;
157+ currentRotationZ += ( targetRotationZ - currentRotationZ ) * LERP_FACTOR ;
158+
159+ // Apply rotation to the box
160+ boxMesh . rotation . x = currentRotationX ;
161+ boxMesh . rotation . y = currentRotationY ;
162+ boxMesh . rotation . z = currentRotationZ ;
163+
164+ renderer . render ( { scene : boxScene , camera, target : renderTarget } ) ;
165+ asciiProgram . uniforms . uResolution . value = [
166+ gl . canvas . width ,
167+ gl . canvas . height ,
168+ ] ;
169+ renderer . render ( { scene : asciiScene , camera } ) ;
170+ }
171+
172+ let animationId : number ;
173+ function animate ( time : number ) {
174+ update ( time ) ;
175+ animationId = requestAnimationFrame ( animate ) ;
176+ }
177+ animationId = requestAnimationFrame ( animate ) ;
178+ return ( ) => {
179+ window . removeEventListener ( "resize" , resize ) ;
180+ window . removeEventListener ( "mousemove" , onMouseMove ) ;
181+ cancelAnimationFrame ( animationId ) ;
182+ renderer . gl . getExtension ( "WEBGL_lose_context" ) ?. loseContext ( ) ;
183+ if ( container ?. contains ( gl . canvas ) ) {
184+ container . removeChild ( gl . canvas ) ;
185+ }
186+ } ;
187+ } , [ ] ) ;
188+
189+ return (
190+ < div ref = { containerRef } className = "w-full h-full absolute inset-0" > </ div >
191+ ) ;
192+ } ;
0 commit comments