1+ <!-- Github Luckystriike: https://github.com/luckystriike22/TresJsPlayground/ -->
2+ <script lang="ts" setup>
3+ import { Vector2 } from ' three'
4+
5+ // composables
6+ const { onLoop, resume } = useRenderLoop ()
7+
8+ // refs
9+ const blobRef = shallowRef <any >(null )
10+ const analyser = shallowRef ();
11+ const audioStream = shallowRef ();
12+ const dataArray = shallowRef ();
13+ const showInfoDialog = shallowRef (false );
14+
15+ // lifecycle
16+ onMounted (async () => {
17+ await nextTick ();
18+
19+ try {
20+ const access = await navigator .permissions .query ({ name: ' microphone' })
21+ showInfoDialog .value = access .state != " granted" ;
22+
23+ audioStream .value = await navigator .mediaDevices .getUserMedia ({ audio: true });
24+ showInfoDialog .value = false ;
25+ handleMicrophoneAccess ()
26+
27+ } catch (error ) {
28+ showInfoDialog .value = true ;
29+ alert (' Not able to accessing microphone' );
30+ }
31+ })
32+
33+ onLoop (({ elapsed }) => {
34+ if (blobRef .value ) {
35+ analyser .value ?.getByteFrequencyData (dataArray .value );
36+
37+ // calc average frequency
38+ let sum = 0 ;
39+ for (let i = 0 ; i < dataArray .value ?.length ; i ++ ) {
40+ sum += dataArray .value [i ];
41+ }
42+
43+ uniforms .value .u_frequency .value = sum > 0 ? sum / dataArray .value ?.length : 0 ;
44+ uniforms .value .u_time .value = elapsed
45+ blobRef .value .rotation .x += 0.01
46+ }
47+ })
48+ // call resume to fix a bug on prod with the onLoop function
49+ resume ();
50+
51+ // handle the microphone connection
52+ const handleMicrophoneAccess = () => {
53+ const audioContext = new (window .AudioContext )();
54+ const source = audioContext .createMediaStreamSource (audioStream .value );
55+
56+ analyser .value = audioContext .createAnalyser ();
57+ analyser .value .fftSize = 2048 ;
58+ source .connect (analyser .value );
59+
60+ const bufferLength = analyser .value .frequencyBinCount ;
61+ dataArray .value = new Uint8Array (bufferLength );
62+ };
63+
64+ // shader
65+ // set props to pass into the shader
66+ const uniforms = ref ({
67+ u_resolution: { type: ' V2' , value: new Vector2 (window .innerWidth , window .innerHeight ) },
68+ u_time: { type: ' f' , value: 0.0 },
69+ u_frequency: { type: ' f' , value: 0.0 }
70+ });
71+
72+ const vertexShader = ref (`
73+ uniform float u_time;
74+
75+ vec3 mod289(vec3 x)
76+ {
77+ return x - floor(x * (1.0 / 289.0)) * 289.0;
78+ }
79+
80+ vec4 mod289(vec4 x)
81+ {
82+ return x - floor(x * (1.0 / 289.0)) * 289.0;
83+ }
84+
85+ vec4 permute(vec4 x)
86+ {
87+ return mod289(((x*34.0)+10.0)*x);
88+ }
89+
90+ vec4 taylorInvSqrt(vec4 r)
91+ {
92+ return 1.79284291400159 - 0.85373472095314 * r;
93+ }
94+
95+ vec3 fade(vec3 t) {
96+ return t*t*t*(t*(t*6.0-15.0)+10.0);
97+ }
98+
99+ float pnoise(vec3 P, vec3 rep)
100+ {
101+ vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period
102+ vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period
103+ Pi0 = mod289(Pi0);
104+ Pi1 = mod289(Pi1);
105+ vec3 Pf0 = fract(P); // Fractional part for interpolation
106+ vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
107+ vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
108+ vec4 iy = vec4(Pi0.yy, Pi1.yy);
109+ vec4 iz0 = Pi0.zzzz;
110+ vec4 iz1 = Pi1.zzzz;
111+
112+ vec4 ixy = permute(permute(ix) + iy);
113+ vec4 ixy0 = permute(ixy + iz0);
114+ vec4 ixy1 = permute(ixy + iz1);
115+
116+ vec4 gx0 = ixy0 * (1.0 / 7.0);
117+ vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
118+ gx0 = fract(gx0);
119+ vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
120+ vec4 sz0 = step(gz0, vec4(0.0));
121+ gx0 -= sz0 * (step(0.0, gx0) - 0.5);
122+ gy0 -= sz0 * (step(0.0, gy0) - 0.5);
123+
124+ vec4 gx1 = ixy1 * (1.0 / 7.0);
125+ vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
126+ gx1 = fract(gx1);
127+ vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
128+ vec4 sz1 = step(gz1, vec4(0.0));
129+ gx1 -= sz1 * (step(0.0, gx1) - 0.5);
130+ gy1 -= sz1 * (step(0.0, gy1) - 0.5);
131+
132+ vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
133+ vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
134+ vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
135+ vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
136+ vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
137+ vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
138+ vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
139+ vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
140+
141+ vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
142+ g000 *= norm0.x;
143+ g010 *= norm0.y;
144+ g100 *= norm0.z;
145+ g110 *= norm0.w;
146+ vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
147+ g001 *= norm1.x;
148+ g011 *= norm1.y;
149+ g101 *= norm1.z;
150+ g111 *= norm1.w;
151+
152+ float n000 = dot(g000, Pf0);
153+ float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
154+ float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
155+ float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
156+ float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
157+ float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
158+ float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
159+ float n111 = dot(g111, Pf1);
160+
161+ vec3 fade_xyz = fade(Pf0);
162+ vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
163+ vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
164+ float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
165+ return 2.2 * n_xyz;
166+ }
167+
168+ uniform float u_frequency;
169+
170+ void main() {
171+ float noise = 5.0 * pnoise(position + u_time, vec3(10.0));
172+ float displacement = (u_frequency / 30.0) * (noise / 10.0);
173+ vec3 newPosition = position + normal * displacement;
174+ gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
175+ }
176+ ` );
177+
178+ const fragmentShader = ref (`
179+ uniform vec2 u_resolution;
180+
181+ void main() {
182+ vec2 st = gl_FragCoord.xy / u_resolution; // Normalized screen coordinates
183+
184+ float red = st.y + 0.3; // Red component increases horizontally
185+ float green = st.y - 0.3; // Green component increases vertically
186+ float blue = 0.0; // Blue component is kept at 0 for orange
187+
188+ vec3 orange = vec3(red, green, blue);
189+
190+ gl_FragColor = vec4(orange, 1.0);
191+ }
192+ ` );
193+
194+ </script >
195+
196+ <template >
197+ <button
198+ class =" gitBtn bg-gray-600 hover:bg-gray-700 opacity-40 transition-color shadow-lg hover:shadow-xl infline-flex w-12 h-12 justify-center items-center rounded-full absolute bottom-2 right-2" >
199+ <a href =" https://github.com/Tresjs/lab/tree/main/components/content/dancing-blob" target =" _blank" >Code</a >
200+ </button >
201+
202+ <TresCanvas :clear-color =" '#0c1a30'" v-show =" !showInfoDialog" >
203+ <TresPerspectiveCamera :position =" [13, 0, 0]" />
204+ <OrbitControls />
205+ <TresMesh ref =" blobRef" >
206+ <TresIcosahedronGeometry :args =" [4, 80]" ></TresIcosahedronGeometry >
207+ <TresShaderMaterial wireframe :uniforms =" uniforms" :fragment-shader =" fragmentShader"
208+ :vertex-shader =" vertexShader" />
209+ </TresMesh >
210+ <TresDirectionalLight :position =" [1, 1, 1]" />
211+ <TresAmbientLight :intensity =" 1" />
212+ </TresCanvas >
213+ <span class =" blobPermissionDialog justify-center items-center infline-flex absolute" v-if =" showInfoDialog" >
214+ <p >
215+ Hey! <br />
216+ This site requires microphone permissions. The
217+ microphone is only used to calculate the frequency necessary for the blob to dance. A browser pop-up will ask you
218+ for permission.
219+ </p >
220+ </span >
221+
222+ </template >
223+
224+ <style scoped>
225+ .gitBtn {
226+ margin-bottom : 10px ;
227+ margin-right : 10px ;
228+ z-index : 10 ;
229+ color : white ;
230+ }
231+
232+ .blobPermissionDialog {
233+ height : 100vh ;
234+ justify-content : center ;
235+ display : flex ;
236+ background-color : #0c1a30 ;
237+ width : 100vw ;
238+ color : white ;
239+ font-size : x-large ;
240+ }
241+
242+ .blobPermissionDialog p {
243+ width : 700px ;
244+ }
245+ </style >
0 commit comments