@@ -281,6 +281,8 @@ pub struct VulkanEngine<'a> {
281281 last_history_cam_z : f64 ,
282282 smooth_time : f64 ,
283283 smooth_dt : f64 ,
284+ vu_needle_angles : Vec < f32 > ,
285+ vu_needle_velocities : Vec < f32 > ,
284286}
285287
286288pub ( crate ) fn generate_lamp_mesh ( ) -> ( Vec < Vertex > , Vec < u32 > ) {
@@ -671,6 +673,7 @@ impl<'a> VulkanEngine<'a> {
671673 15 => include_str ! ( "shaders/vis_3drain.wgsl" ) ,
672674 16 => include_str ! ( "shaders/vis_synthwaveracer.wgsl" ) ,
673675 17 => include_str ! ( "shaders/vis_cuboids.wgsl" ) ,
676+ 18 => include_str ! ( "shaders/vis_vumeters.wgsl" ) ,
674677 _ => include_str ! ( "shaders/vis_spectrum.wgsl" ) ,
675678 }
676679 } ;
@@ -1862,6 +1865,8 @@ impl<'a> VulkanEngine<'a> {
18621865 last_history_cam_z : 0.0f64 ,
18631866 smooth_time : 0.0f64 ,
18641867 smooth_dt : 1.0f64 / 60.0f64 ,
1868+ vu_needle_angles : vec ! [ 0.0 ; 32 ] ,
1869+ vu_needle_velocities : vec ! [ 0.0 ; 32 ] ,
18651870 }
18661871 }
18671872
@@ -1935,7 +1940,51 @@ impl<'a> VulkanEngine<'a> {
19351940
19361941 uniforms. spectrum . copy_from_slice ( & state. spectrum_data ) ;
19371942 uniforms. fire_heat . copy_from_slice ( & state. fire_heat ) ;
1938-
1943+
1944+ // Update needle physics for 3D retro VU meters
1945+ let ch_count = state. channel_vus . len ( ) . max ( 2 ) ;
1946+ if self . vu_needle_angles . len ( ) < ch_count {
1947+ self . vu_needle_angles . resize ( ch_count, 0.0 ) ;
1948+ self . vu_needle_velocities . resize ( ch_count, 0.0 ) ;
1949+ }
1950+
1951+ let rise_time_secs = 0.30 ;
1952+ let damping_ratio = 0.85 ;
1953+ let omega_n = 4.8 / rise_time_secs;
1954+ let spring = omega_n * omega_n;
1955+ let damping = 2.0 * damping_ratio * omega_n;
1956+
1957+ let dt_clamped = dt. min ( 0.05 ) ;
1958+ for i in 0 ..ch_count {
1959+ let target = if i < state. raw_channel_vus . len ( ) {
1960+ state. raw_channel_vus [ i]
1961+ } else if !state. file_loaded {
1962+ // Sway gently if no file is loaded
1963+ let t = self . smooth_time as f32 ;
1964+ let offset = if i == 0 { 0.0 } else { 1.5 } ;
1965+ ( 0.12 * ( t * 2.0 + offset) . sin ( ) + 0.15 * ( t * 3.7 - offset) . cos ( ) + 0.2 ) . clamp ( 0.0 , 1.0 )
1966+ } else {
1967+ 0.0
1968+ } ;
1969+ let angle = self . vu_needle_angles [ i] ;
1970+ let vel = self . vu_needle_velocities [ i] ;
1971+
1972+ let acceleration = spring * ( target - angle) - damping * vel;
1973+ let new_vel = vel + acceleration * dt_clamped;
1974+ let mut new_angle = angle + new_vel * dt_clamped;
1975+
1976+ if new_angle < -0.05 {
1977+ new_angle = -0.05 ;
1978+ self . vu_needle_velocities [ i] = 0.0 ;
1979+ } else if new_angle > 1.1 {
1980+ new_angle = 1.1 ;
1981+ self . vu_needle_velocities [ i] = -new_vel * 0.2 ; // pin bounce
1982+ } else {
1983+ self . vu_needle_velocities [ i] = new_vel;
1984+ }
1985+ self . vu_needle_angles [ i] = new_angle;
1986+ }
1987+
19391988 let ch_len = state. channel_vus . len ( ) . min ( 32 ) ;
19401989
19411990 // 1. Populate UI Display Channels (may be visually remapped)
@@ -1976,6 +2025,20 @@ impl<'a> VulkanEngine<'a> {
19762025 }
19772026 }
19782027
2028+ if state. visualizer_mode == 18 {
2029+ let actual_ch = state. channel_vus . len ( ) . max ( 2 ) ;
2030+ uniforms. num_channels = actual_ch as u32 ;
2031+ for i in 0 ..actual_ch {
2032+ if i < 32 {
2033+ uniforms. channels [ i] = if i < self . vu_needle_angles . len ( ) {
2034+ self . vu_needle_angles [ i]
2035+ } else {
2036+ 0.0
2037+ } ;
2038+ }
2039+ }
2040+ }
2041+
19792042 let vis_width = state. visual_width . max ( 128 ) . min ( 2048 ) as u32 ;
19802043 uniforms. waveform_resolution = vis_width;
19812044 uniforms. waveform_history_size = state. waveform_history . len ( ) . min ( 144 ) as u32 ;
0 commit comments