Skip to content

Commit 7703405

Browse files
committed
Bioluma vis update
1 parent a08fcf7 commit 7703405

5 files changed

Lines changed: 143 additions & 61 deletions

File tree

src/engine.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2840,6 +2840,9 @@ impl<'a> VulkanEngine<'a> {
28402840
shortcut(ui, "f", Some("Start"), "Toggle Fullscreen");
28412841
shortcut(ui, "g", Some("R1"), "Toggle GPU FFT");
28422842
shortcut(ui, "[ / ]", None, "Scale Panels");
2843+
if state.visualizer_mode == 19 {
2844+
shortcut(ui, "c", None, "Toggle Camera Mode");
2845+
}
28432846
});
28442847
});
28452848
}
@@ -4159,7 +4162,6 @@ impl<'a> VulkanEngine<'a> {
41594162
}
41604163

41614164
if vis_def.id == 19 {
4162-
println!("DEBUG: Bioluminescence Waves Sim Compute Dispatching!");
41634165
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
41644166
label: Some("Bioluminescence Waves Sim Compute"),
41654167
timestamp_writes: None,
@@ -4210,11 +4212,19 @@ impl<'a> VulkanEngine<'a> {
42104212
let aspect = vp_w / vp_h.max(1.0);
42114213

42124214
let proj = glam::Mat4::perspective_rh_gl(std::f32::consts::PI / 3.0, aspect, 0.1, 1000.0);
4213-
let view = glam::Mat4::look_at_rh(
4214-
glam::Vec3::new(0.0, 1.5, -2.0),
4215-
glam::Vec3::new(0.0, 1.5, 0.0),
4216-
glam::Vec3::new(0.0, 1.0, 0.0),
4217-
);
4215+
let view = if state.visualizer_mode == 19 && state.biolum_top_down {
4216+
glam::Mat4::look_at_rh(
4217+
glam::Vec3::new(0.0, 14.0, 0.0),
4218+
glam::Vec3::new(0.0, 0.0, 0.0),
4219+
glam::Vec3::new(0.0, 0.0, 1.0),
4220+
)
4221+
} else {
4222+
glam::Mat4::look_at_rh(
4223+
glam::Vec3::new(0.0, 1.5, -2.0),
4224+
glam::Vec3::new(0.0, 1.5, 0.0),
4225+
glam::Vec3::new(0.0, 1.0, 0.0),
4226+
)
4227+
};
42184228
let camera_uniforms = CameraUniforms {
42194229
view_matrix: view.to_cols_array_2d(),
42204230
proj_matrix: proj.to_cols_array_2d(),

src/main.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,14 @@ async fn run_gui(app_state: Arc<Mutex<AppState>>, mut active_stream: Option<audi
393393
let mut state = app_state.lock().unwrap();
394394
state.gpu_fft = !state.gpu_fft;
395395
},
396+
WinitKeyCode::KeyC => {
397+
let mut state = app_state.lock().unwrap();
398+
if state.visualizer_mode == 19 {
399+
state.biolum_top_down = !state.biolum_top_down;
400+
state.osd_text = Some(format!("Camera: {}", if state.biolum_top_down { "Top-down" } else { "Perspective" }));
401+
state.osd_timer = 2.0;
402+
}
403+
},
396404
WinitKeyCode::KeyS => {
397405
let mut state = app_state.lock().unwrap();
398406
state.show_stats = !state.show_stats;

src/shaders/biolum_compute.wgsl

Lines changed: 115 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
3460
fn 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>
5379
fn 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)
69130
fn 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
}

src/shaders/vis_bioluminescence.wgsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ fn fs_main(in: VertexOutput3D) -> @location(0) vec4<f32> {
122122

123123
// Brightness profile
124124
let intensity = (0.2 + in.energy * 2.5) * (1.0 - smoothstep(0.0, 0.5, dist));
125-
125+
126126
// Add distance fog to match the deep ocean depth feel
127127
let fog_factor = smoothstep(5.0, 35.0, in.depth);
128128
var final_color = mix(color * intensity, vec3<f32>(0.001, 0.003, 0.008), fog_factor);

src/state.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ pub struct AppState {
195195
pub osd_timer: f32,
196196
pub cumulative_scrub: f64,
197197
pub audio_device_lost: bool,
198+
pub biolum_top_down: bool,
198199
}
199200

200201
pub fn get_history_file_path() -> std::path::PathBuf {
@@ -325,6 +326,7 @@ impl AppState {
325326
osd_timer: 0.0,
326327
cumulative_scrub: 0.0,
327328
audio_device_lost: false,
329+
biolum_top_down: false,
328330
}
329331
}
330332

@@ -414,6 +416,7 @@ impl AppState {
414416
osd_timer: self.osd_timer,
415417
cumulative_scrub: self.cumulative_scrub,
416418
audio_device_lost: self.audio_device_lost,
419+
biolum_top_down: self.biolum_top_down,
417420
}
418421
}
419422
}

0 commit comments

Comments
 (0)