@@ -31,6 +31,32 @@ fn hash31(p: f32) -> vec3<f32> {
3131 return fract ((p3 . xxy + p3 . yzz ) * p3 . zyx );
3232}
3333
34+ fn noise2d (p : vec2 <f32 >) -> f32 {
35+ let ip = floor (p );
36+ let fp = fract (p );
37+
38+ let u = fp * fp * (3 .0 - 2 .0 * fp );
39+
40+ let a = hash11 (ip . x + ip . y * 57 .0 );
41+ let b = hash11 (ip . x + 1 .0 + ip . y * 57 .0 );
42+ let c = hash11 (ip . x + (ip . y + 1 .0 ) * 57 .0 );
43+ let d = hash11 (ip . x + 1 .0 + (ip . y + 1 .0 ) * 57 .0 );
44+
45+ return mix (mix (a , b , u . x ), mix (c , d , u . x ), u . y );
46+ }
47+
48+ fn fbm2d (p : vec2 <f32 >) -> f32 {
49+ var value = 0 .0 ;
50+ var amplitude = 0 .5 ;
51+ var pos = p ;
52+ for (var i = 0 ; i < 3 ; i = i + 1 ) {
53+ value += amplitude * noise2d (pos );
54+ pos = pos * 2 .0 ;
55+ amplitude = amplitude * 0 .5 ;
56+ }
57+ return value ;
58+ }
59+
3460fn get_gerstner_wave (wave : GerstnerWave , xz : vec2 <f32 >, time : f32 ) -> vec3 <f32 > {
3561 let k = 2 .0 * 3 .14159265 / wave . wavelength ;
3662 let c = wave . speed ;
@@ -53,18 +79,53 @@ fn get_gerstner_wave(wave: GerstnerWave, xz: vec2<f32>, time: f32) -> vec3<f32>
5379fn get_wave_displacement (xz : vec2 <f32 >, time : f32 ) -> vec3 <f32 > {
5480 var displacement = vec3 <f32 >(0 .0 );
5581
56- var waves : array <GerstnerWave , 3 >;
57- waves [0 ] = GerstnerWave (vec2 <f32 >(0 .0 , 1 .0 ), 0 .6 , 14 .0 , 2 .5 , 0 .5 );
58- waves [1 ] = GerstnerWave (vec2 <f32 >(0 .7 , 0 .7 ), 0 .35 , 7 .0 , 1 .8 , 0 .4 );
59- waves [2 ] = GerstnerWave (vec2 <f32 >(- 0 .7 , 0 .7 ), 0 .15 , 3 .5 , 1 .2 , 0 .3 );
82+ var waves : array <GerstnerWave , 5 >;
83+ waves [0 ] = GerstnerWave (vec2 <f32 >(0 .1 , 0 .99 ), 0 .55 , 16 .0 , 2 .2 , 0 .7 );
84+ waves [1 ] = GerstnerWave (vec2 <f32 >(0 .6 , 0 .8 ), 0 .35 , 8 .5 , 1 .6 , 0 .55 );
85+ waves [2 ] = GerstnerWave (vec2 <f32 >(- 0 .65 , 0 .75 ), 0 .25 , 5 .0 , 1 .3 , 0 .45 );
86+ waves [3 ] = GerstnerWave (vec2 <f32 >(0 .9 , 0 .4 ), 0 .1 , 2 .8 , 1 .0 , 0 .35 );
87+ waves [4 ] = GerstnerWave (vec2 <f32 >(- 0 .8 , 0 .5 ), 0 .06 , 1 .5 , 0 .8 , 0 .25 );
6088
61- for (var i = 0 ; i < 3 ; i = i + 1 ) {
89+ for (var i = 0 ; i < 5 ; i = i + 1 ) {
6290 displacement += get_gerstner_wave (waves [i ], xz , time );
6391 }
6492
6593 return displacement ;
6694}
6795
96+ fn get_wave_velocity (xz : vec2 <f32 >, time : f32 ) -> vec3 <f32 > {
97+ var velocity = vec3 <f32 >(0 .0 );
98+
99+ var waves : array <GerstnerWave , 5 >;
100+ waves [0 ] = GerstnerWave (vec2 <f32 >(0 .1 , 0 .99 ), 0 .55 , 16 .0 , 2 .2 , 0 .7 );
101+ waves [1 ] = GerstnerWave (vec2 <f32 >(0 .6 , 0 .8 ), 0 .35 , 8 .5 , 1 .6 , 0 .55 );
102+ waves [2 ] = GerstnerWave (vec2 <f32 >(- 0 .65 , 0 .75 ), 0 .25 , 5 .0 , 1 .3 , 0 .45 );
103+ waves [3 ] = GerstnerWave (vec2 <f32 >(0 .9 , 0 .4 ), 0 .1 , 2 .8 , 1 .0 , 0 .35 );
104+ waves [4 ] = GerstnerWave (vec2 <f32 >(- 0 .8 , 0 .5 ), 0 .06 , 1 .5 , 0 .8 , 0 .25 );
105+
106+ for (var i = 0 ; i < 5 ; i = i + 1 ) {
107+ let wave = waves [i ];
108+ let k = 2 .0 * 3 .14159265 / wave . wavelength ;
109+ let c = wave . speed ;
110+ let w = c * k ;
111+ let dir = normalize (wave . direction );
112+ let theta = k * dot (dir , xz ) - w * time ;
113+
114+ let sin_t = sin (theta );
115+ let cos_t = cos (theta );
116+
117+ let q = wave . steepness / (wave . amplitude * k + 0 .0001 );
118+
119+ let vx = q * wave . amplitude * dir . x * w * sin_t ;
120+ let vz = q * wave . amplitude * dir . y * w * sin_t ;
121+ let vy = - w * wave . amplitude * cos_t ;
122+
123+ velocity += vec3 <f32 >(vx , vy , vz );
124+ }
125+
126+ return velocity ;
127+ }
128+
68129@compute @workgroup_size (256 )
69130fn main (@builtin (global_invocation_id ) global_id : vec3 <u32 >) {
70131 let idx = global_id . x ;
@@ -85,70 +146,70 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
85146 let pos_z = (rnd . y - 0 .5 ) * 50 .0 ;
86147 let disp = get_wave_displacement (vec2 <f32 >(pos_x , pos_z ), time );
87148
88- p . pos = vec4 <f32 >(pos_x + disp . x , disp . y + rnd . z * 0 .4 , pos_z + disp . z , rnd . z * 4 .0 + 1 .0 ); // life: 1.0 to 5.0s
89- p . vel = vec4 <f32 >((rnd . x - 0 .5 ) * 0 .4 , 0 .0 , (rnd . y - 0 .5 ) * 0 .4 , rnd . x * 0 .2 ); // energy
149+ p . pos = vec4 <f32 >(pos_x + disp . x , disp . y , pos_z + disp . z , rnd . z * 4 .0 + 1 .0 ); // life: 1.0 to 5.0s
150+ p . vel = vec4 <f32 >((rnd . x - 0 .5 ) * 0 .4 , 0 .0 , (rnd . y - 0 .5 ) * 0 .4 , 0 .12 ); // energy starts at ground state
90151 } else {
91152 var pos = p . pos . xyz ;
92153 var vel = p . vel . xyz ;
93154 var energy = p . vel . w ;
94155
156+ // Decay energy glow slowly (takes ~2-3 seconds to fade to ground state of 0.12)
157+ energy = max (energy - dt * 0 .5 , 0 .12 );
158+
159+ // Local wave physics at particle coordinate
95160 let disp = get_wave_displacement (pos . xz , time );
96161 let wave_h = disp . y ;
97162
98- var force = vec3 <f32 >(0 .0 );
163+ // Smoothly interpolate towards the true orbital wave velocity + drift current
164+ let wave_vel = get_wave_velocity (pos . xz , time );
165+ let drift_vel = vec3 <f32 >(0 .15 , 0 .0 , 0 .0 ); // slow global current drift
166+ let target_vel = wave_vel + drift_vel ;
99167
100- // Fluid vs Air Physics
101- if (pos . y <= wave_h + 0 .1 ) {
102- // Under/on the water surface
103- let depth = wave_h - pos . y ;
104- // Buoyancy force
105- force . y += max (depth , 0 .0 ) * 35 .0 ;
106- // Align back to wave displacement slightly
107- force . x += (disp . x - (pos . x - (pos . x - disp . x ))) * 0 .5 ;
108- force . z += (disp . z - (pos . z - (pos . z - disp . z ))) * 0 .5 ;
109-
110- // Drag
111- vel *= 0 .93 ;
168+ // High drag keeps particles flowing smoothly with currents rather than zipping around
169+ vel = mix (vel , target_vel , 0 .15 );
170+
171+ // Audio reactivity - Bass agitation triggers glow directly
172+ let bass = max (audio . spectrum [0 ]. x , audio . spectrum [1 ]. x ) * 0 .02 ;
173+ if (bass > 0 .15 ) {
174+ energy = max (energy , clamp (bass * 1 .5 , 0 .0 , 2 .0 ));
112175
113- // Flow field (currents + curl noise simulation)
114- let wave_current = vec3 <f32 >(disp . x , 0 .0 , disp . z ) * 1 .5 ;
115- force += wave_current ;
176+ // Add a tiny localized horizontal puff (agitation), not a high-velocity launch
177+ let rnd_impulse = hash31 (idx_f + time );
178+ pos . x += (rnd_impulse . x - 0 .5 ) * bass * 0 .08 ;
179+ pos . z += (rnd_impulse . y - 0 .5 ) * bass * 0 .08 ;
180+ }
181+
182+ // Integrate horizontal position
183+ pos . x += vel . x * dt ;
184+ pos . z += vel . z * dt ;
185+
186+ // Snap vertical position to the surface wave height
187+ let new_disp = get_wave_displacement (pos . xz , time );
188+ pos . y = new_disp . y ;
189+ vel . y = 0 .0 ;
190+
191+ // Wave breaks and foam structure simulation (active at crests)
192+ let crest_factor = smoothstep (0 .35 , 1 .2 , wave_h );
193+ if (crest_factor > 0 .0 ) {
194+ // High frequency fBm noise creates organic foam lines and breaks
195+ let noise_coord = pos . xz * 2 .5 + vec2 <f32 >(time * 1 .5 , time * 1 .0 );
196+ let foam_noise = fbm2d (noise_coord );
116197
117- let swell = vec3 <f32 >(
118- sin (pos . z * 0 .4 + time ) * 1 .2 ,
119- cos (pos . x * 0 .4 - time ) * 0 .3 ,
120- cos (pos . y * 0 .4 + time ) * 1 .2
121- );
122- force += swell ;
198+ // horizontal micro-agitation/diffusion clusters particles into foam filaments
199+ let foam_disp = vec3 <f32 >(
200+ hash11 (idx_f + time ) - 0 .5 ,
201+ 0 .0 ,
202+ hash11 (idx_f + time * 1 .3 ) - 0 .5
203+ ) * 0 .22 * crest_factor * foam_noise ;
204+ pos += foam_disp ;
123205
124- // Audio reactivity - Bass disruption
125- // In WGPU, AudioUniforms.spectrum holds values from 0.0 to ~100.0
126- let bass = max (audio . spectrum [0 ]. x , audio . spectrum [1 ]. x ) * 0 .02 ;
127- if (bass > 0 .15 ) {
128- let rnd_impulse = hash31 (idx_f + time );
129- vel . y += bass * 4 .0 * rnd_impulse . z ;
130- vel . x += (rnd_impulse . x - 0 .5 ) * bass * 2 .0 ;
131- vel . z += (rnd_impulse . y - 0 .5 ) * bass * 2 .0 ;
132- energy = clamp (max (energy , bass * 2 .0 ), 0 .0 , 1 .5 );
206+ // Excite glow in the breaking foam zones
207+ let shear_agitation = foam_noise * 1 .3 * crest_factor ;
208+ if (shear_agitation > 0 .3 ) {
209+ energy = max (energy , 1 .6 );
133210 }
134-
135- // Glow when near wave crests due to high shear stress
136- let crest = smoothstep (0 .1 , 0 .8 , wave_h );
137- energy = max (energy , crest * 0 .95 );
138- } else {
139- // In the air (sea spray)
140- force . y -= 9 .8 ; // Gravity
141- force . x += 1 .5 ; // Wind blowing right
142- vel *= 0 .98 ; // Air resistance
143211 }
144212
145- // Decay energy glow
146- energy = max (energy - dt * 0 .25 , 0 .0 );
147-
148- // Integrate
149- vel += force * dt ;
150- pos += vel * dt ;
151-
152213 p . pos = vec4 <f32 >(pos , p . pos . w );
153214 p . vel = vec4 <f32 >(vel , energy );
154215 }
0 commit comments