use gizmo_audio::{AudioManager, AudioSource};
use gizmo_core::World;
use gizmo_math::Vec3;
use gizmo_physics_core::Transform;
use gizmo_physics_rigid::components::Velocity;
fn should_autostart(source: &AudioSource) -> bool {
source.is_3d && !source.has_played && source._internal_sink_id.is_none()
}
pub fn audio_spatial_system(world: &mut World, _dt: f32) {
let audio_opt = world.get_resource_mut::<AudioManager>();
let mut audio = match audio_opt {
Some(m) => m,
None => return,
};
audio.update();
let transforms = world.borrow::<Transform>();
let mut listener_pos = Vec3::ZERO;
let mut listener_vel = Vec3::ZERO;
let mut listener_right = Vec3::new(1.0, 0.0, 0.0);
if let Some(mut query) = world.query::<(&gizmo_renderer::Camera, &Transform)>() {
for (e, (cam, t)) in query.iter_mut() {
if cam.primary {
listener_pos = t.position;
listener_right = t.rotation.mul_vec3(Vec3::new(1.0, 0.0, 0.0)).normalize();
if let Some(v) = world.borrow::<Velocity>().get(e) {
listener_vel = v.linear;
}
break;
}
}
}
let ear_distance = 0.2;
let left_ear = listener_pos - (listener_right * (ear_distance / 2.0));
let right_ear = listener_pos + (listener_right * (ear_distance / 2.0));
let left_ear_arr = [left_ear.x, left_ear.y, left_ear.z];
let right_ear_arr = [right_ear.x, right_ear.y, right_ear.z];
let source_ids: Vec<u32> = world.borrow::<AudioSource>().entities().collect();
let mut sources = unsafe { world.borrow_mut_unchecked::<AudioSource>() };
let velocities = world.borrow::<Velocity>();
for id in source_ids {
let mut source = if let Some(s) = sources.get_mut(id) {
s.clone()
} else {
continue;
};
let t = if let Some(t) = transforms.get(id) {
t
} else {
continue;
};
if should_autostart(&source) {
let sink_result = if source.loop_sound {
audio.play_3d_looped(
&source.sound_name,
[t.position.x, t.position.y, t.position.z],
left_ear_arr,
right_ear_arr,
)
} else {
audio.play_3d(
&source.sound_name,
[t.position.x, t.position.y, t.position.z],
left_ear_arr,
right_ear_arr,
)
};
let sink_id = match sink_result {
Ok(id) => Some(id),
Err(e) => {
tracing::warn!("3D ses çalınamadı '{}': {}", source.sound_name, e);
None
}
};
source._internal_sink_id = sink_id;
source.has_played = true;
if let Some(mut s) = sources.get_mut(id) {
s._internal_sink_id = sink_id;
s.has_played = true;
}
}
if let Some(sink_id) = source._internal_sink_id {
if !audio.is_playing(sink_id) {
if !source.loop_sound {
if let Some(mut s) = sources.get_mut(id) {
s._internal_sink_id = None;
}
}
continue;
}
audio.update_spatial_sink(
sink_id,
[t.position.x, t.position.y, t.position.z],
left_ear_arr,
right_ear_arr,
source.max_distance,
source.volume,
);
let emitter_vel = if let Some(v) = velocities.get(id) {
v.linear
} else {
Vec3::ZERO
};
let speed_of_sound = 343.0;
let diff = t.position - listener_pos;
let dist = diff.length();
if dist > 0.1 {
let dir = diff / dist;
let listener_speed_towards_emitter = listener_vel.dot(dir);
let emitter_speed_towards_listener = emitter_vel.dot(-dir);
let mut doppler_factor: f32 = (speed_of_sound + listener_speed_towards_emitter)
/ (speed_of_sound - emitter_speed_towards_listener).max(1.0);
doppler_factor = doppler_factor.clamp(0.5, 2.0);
let final_pitch = source.pitch * doppler_factor;
audio.set_pitch(sink_id, final_pitch);
} else {
audio.set_pitch(sink_id, source.pitch);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finished_one_shot_does_not_restart() {
let mut s = AudioSource::new("boom"); assert!(should_autostart(&s), "fresh 3D source should auto-start once");
s.has_played = true;
s._internal_sink_id = Some(42);
assert!(!should_autostart(&s), "must not restart while playing");
s._internal_sink_id = None;
assert!(
!should_autostart(&s),
"finished one-shot must stay stopped (was the infinite-repeat bug)"
);
}
#[test]
fn non_3d_source_never_autostarts() {
let mut s = AudioSource::new("ui_click");
s.is_3d = false;
assert!(!should_autostart(&s), "2D sources are not handled by the spatial system");
}
}