Skip to content

Commit 446f718

Browse files
Merge pull request #126 from luckystriike22/DancingBlob
feat: Add a the "Dancing blob" demo
2 parents 0a8230f + 18ac479 commit 446f718

File tree

4 files changed

+265
-0
lines changed

4 files changed

+265
-0
lines changed
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
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>

content/authors/luckystriike.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
name: luckystriike
3+
slug: luckystriike
4+
email: none
5+
twitter: none
6+
github: luckystriike22
7+
website: https://github.com/luckystriike22
8+
avatar: https://avatars.githubusercontent.com/u/43521233?v=4
9+
---
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
thumbnail: /dancing-blob.png
3+
title: Dancing blob
4+
slug: dancing-blob
5+
author: luckystriike
6+
status: published
7+
description: A sphere dancing to the sound of your microphone. This demo utilizes your microphone, so please ensure that you allow access.
8+
tags: ['basic', 'TresJs', 'audio', 'shader']
9+
---
10+
11+
<DancingBlob />

public/dancing-blob.png

1.32 MB
Loading

0 commit comments

Comments
 (0)