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