Skip to content

Commit 3da09f0

Browse files
committed
interaction: pronounced mouse drag effect\n- Visual: feed mouse UV + velocity into waves uniforms (swirl)\n- Audio: modulate global reverb/delay wet and feedback based on drag energy\n- Perf: pre-allocate render vectors to reduce reallocs
1 parent 9df6f02 commit 3da09f0

File tree

1 file changed

+58
-19
lines changed

1 file changed

+58
-19
lines changed

crates/app-web/src/lib.rs

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -856,15 +856,23 @@ async fn init() -> anyhow::Result<()> {
856856
let engine_tick = engine.clone();
857857
let hover_tick = hover_index.clone();
858858
let canvas_for_tick = canvas_for_click_inner.clone();
859+
let mouse_tick = mouse_state.clone();
859860
let voice_gains_tick = voice_gains.clone();
860861
let delay_sends_tick = delay_sends.clone();
861862
let reverb_sends_tick = reverb_sends.clone();
863+
// Global effect controls accessible during tick
864+
let reverb_wet_tick = Rc::new(reverb_wet).clone();
865+
let delay_wet_tick = Rc::new(delay_wet).clone();
866+
let delay_feedback_tick = Rc::new(delay_feedback).clone();
862867
// Optional slow camera orbit
863868
let mut orbit_t: f32 = 0.0;
864869
let orbit_tick = orbit_enabled.clone();
865870
let tick: Rc<RefCell<Option<Closure<dyn FnMut()>>>> =
866871
Rc::new(RefCell::new(None));
867872
let tick_clone = tick.clone();
873+
// State for mouse-driven swirl energy
874+
let mut prev_uv: [f32; 2] = [0.5, 0.5];
875+
let mut swirl_energy: f32 = 0.0;
868876
*tick.borrow_mut() = Some(Closure::wrap(Box::new(move || {
869877
let now = Instant::now();
870878
let dt = now - last_instant;
@@ -890,6 +898,30 @@ async fn init() -> anyhow::Result<()> {
890898
// Exponential decay for smoother falloff
891899
*p *= (1.0 - (dt_sec * 1.8).min(0.9));
892900
}
901+
// Mouse-driven swirl effect intensity (visual + global audio whoosh)
902+
let w = canvas_for_tick.width().max(1) as f32;
903+
let h = canvas_for_tick.height().max(1) as f32;
904+
let ms = mouse_tick.borrow();
905+
let uv = [
906+
(ms.x / w).clamp(0.0, 1.0),
907+
(1.0 - (ms.y / h)).clamp(0.0, 1.0),
908+
];
909+
let du = uv[0] - prev_uv[0];
910+
let dv = uv[1] - prev_uv[1];
911+
let speed = ((du * du + dv * dv).sqrt() / (dt_sec + 1e-5)).min(10.0);
912+
let target = ((speed * 0.25) + if ms.down { 0.6 } else { 0.0 })
913+
.clamp(0.0, 1.0);
914+
swirl_energy = 0.85 * swirl_energy + 0.15 * target;
915+
prev_uv = uv;
916+
drop(ms);
917+
918+
// Apply global FX modulation based on swirl_energy
919+
let _ = reverb_wet_tick.gain().set_value(0.35 + 0.65 * swirl_energy);
920+
let _ = delay_wet_tick.gain().set_value(0.20 + 0.80 * swirl_energy);
921+
let _ = delay_feedback_tick
922+
.gain()
923+
.set_value(0.45 + 0.45 * swirl_energy);
924+
893925
for i in 0..voice_panners.len() {
894926
let pos = engine_tick.borrow().voices[i].position;
895927
voice_panners[i].set_position(
@@ -900,9 +932,14 @@ async fn init() -> anyhow::Result<()> {
900932
// Direct sound↔visual link: map position to per-voice mix and fx
901933
let dist = (pos.x * pos.x + pos.z * pos.z).sqrt();
902934
// Delay send increases with |x|, reverb with radial distance
903-
let d_amt = (0.15 + 0.85 * pos.x.abs().min(1.0)).clamp(0.0, 1.0);
904-
let r_amt =
905-
(0.25 + 0.75 * (dist / 2.5).clamp(0.0, 1.0)).clamp(0.0, 1.2);
935+
let mut d_amt = (0.15 + 0.85 * pos.x.abs().min(1.0))
936+
.clamp(0.0, 1.0);
937+
let mut r_amt = (0.25 + 0.75 * (dist / 2.5).clamp(0.0, 1.0))
938+
.clamp(0.0, 1.2);
939+
// Boost sends with swirl energy for pronounced movement effect
940+
let boost = 1.0 + 0.8 * swirl_energy;
941+
d_amt = (d_amt * boost).clamp(0.0, 1.2);
942+
r_amt = (r_amt * boost).clamp(0.0, 1.5);
906943
delay_sends_tick[i].gain().set_value(d_amt);
907944
reverb_sends_tick[i].gain().set_value(r_amt);
908945
// Subtle level change with proximity to center (prevents clipping)
@@ -944,16 +981,17 @@ async fn init() -> anyhow::Result<()> {
944981
let e_ref = engine_tick.borrow();
945982
let z_offset = z_offset_vec3();
946983
let spread = SPREAD;
947-
let mut positions: Vec<Vec3> = vec![
948-
e_ref.voices[0].position * spread + z_offset,
949-
e_ref.voices[1].position * spread + z_offset,
950-
e_ref.voices[2].position * spread + z_offset,
951-
];
952-
let mut colors: Vec<Vec4> = vec![
953-
Vec4::from((Vec3::from(e_ref.configs[0].color_rgb), 1.0)),
954-
Vec4::from((Vec3::from(e_ref.configs[1].color_rgb), 1.0)),
955-
Vec4::from((Vec3::from(e_ref.configs[2].color_rgb), 1.0)),
956-
];
984+
// Pre-allocate to avoid per-frame reallocations
985+
let ring_count = 48usize;
986+
let mut positions: Vec<Vec3> =
987+
Vec::with_capacity(3 + ring_count * 3 + 16);
988+
positions.push(e_ref.voices[0].position * spread + z_offset);
989+
positions.push(e_ref.voices[1].position * spread + z_offset);
990+
positions.push(e_ref.voices[2].position * spread + z_offset);
991+
let mut colors: Vec<Vec4> = Vec::with_capacity(3 + ring_count * 3 + 16);
992+
colors.push(Vec4::from((Vec3::from(e_ref.configs[0].color_rgb), 1.0)));
993+
colors.push(Vec4::from((Vec3::from(e_ref.configs[1].color_rgb), 1.0)));
994+
colors.push(Vec4::from((Vec3::from(e_ref.configs[2].color_rgb), 1.0)));
957995
let hovered = *hover_tick.borrow();
958996
for i in 0..3 {
959997
if e_ref.voices[i].muted {
@@ -968,15 +1006,14 @@ async fn init() -> anyhow::Result<()> {
9681006
colors[i].z = (colors[i].z * 1.4).min(1.0);
9691007
}
9701008
}
971-
let mut scales: Vec<f32> = vec![
972-
BASE_SCALE + ps[0] * SCALE_PULSE_MULTIPLIER,
973-
BASE_SCALE + ps[1] * SCALE_PULSE_MULTIPLIER,
974-
BASE_SCALE + ps[2] * SCALE_PULSE_MULTIPLIER,
975-
];
1009+
let mut scales: Vec<f32> =
1010+
Vec::with_capacity(3 + ring_count * 3 + 16);
1011+
scales.push(BASE_SCALE + ps[0] * SCALE_PULSE_MULTIPLIER);
1012+
scales.push(BASE_SCALE + ps[1] * SCALE_PULSE_MULTIPLIER);
1013+
scales.push(BASE_SCALE + ps[2] * SCALE_PULSE_MULTIPLIER);
9761014

9771015
// Orbiting ring particles around each voice center
9781016
let two_pi = std::f32::consts::PI * 2.0;
979-
let ring_count = 48usize;
9801017
for vi in 0..3 {
9811018
let center = positions[vi];
9821019
let base_col = Vec3::from(e_ref.configs[vi].color_rgb);
@@ -1049,6 +1086,8 @@ async fn init() -> anyhow::Result<()> {
10491086

10501087
if let Some(g) = &mut gpu {
10511088
g.set_camera(cam_eye, cam_target);
1089+
// Feed mouse-driven swirl into GPU uniforms
1090+
g.set_swirl(uv, 1.0, swirl_energy > 0.05);
10521091
// Keep WebGPU surface sized to canvas backing size
10531092
let w = canvas_for_tick.width();
10541093
let h = canvas_for_tick.height();

0 commit comments

Comments
 (0)