use bevy::prelude::*;
use firewheel::nodes::spatial_basic::SpatialBasicNode;
use crate::pool::sample_effects::EffectOf;
#[derive(Component, Debug, Clone, Copy)]
pub struct SpatialScale(pub Vec3);
impl Default for SpatialScale {
fn default() -> Self {
Self(Vec3::ONE)
}
}
#[derive(Resource, Debug, Default, Clone)]
pub struct DefaultSpatialScale(SpatialScale);
impl core::ops::Deref for DefaultSpatialScale {
type Target = SpatialScale;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::ops::DerefMut for DefaultSpatialScale {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Debug, Default, Component)]
#[require(Transform)]
pub struct SpatialListener2D;
#[derive(Debug, Default, Component)]
#[require(Transform)]
pub struct SpatialListener3D;
pub(crate) fn update_2d_emitters(
listeners: Query<&GlobalTransform, With<SpatialListener2D>>,
mut emitters: Query<(
&mut SpatialBasicNode,
Option<&SpatialScale>,
&GlobalTransform,
)>,
default_scale: Res<DefaultSpatialScale>,
) {
for (mut spatial, scale, transform) in emitters.iter_mut() {
let emitter_pos = transform.translation();
let closest_listener = find_closest_listener(
emitter_pos,
listeners.iter().map(GlobalTransform::translation),
);
let Some(listener_pos) = closest_listener else {
continue;
};
let scale = scale.map(|s| s.0).unwrap_or(default_scale.0.0);
let x_diff = (emitter_pos.x - listener_pos.x) * scale.x;
let y_diff = (emitter_pos.y - listener_pos.y) * scale.y;
spatial.offset.x = x_diff;
spatial.offset.z = y_diff;
}
}
pub(crate) fn update_2d_emitters_effects(
listeners: Query<&GlobalTransform, With<SpatialListener2D>>,
mut emitters: Query<(&mut SpatialBasicNode, Option<&SpatialScale>, &EffectOf)>,
effect_parents: Query<&GlobalTransform>,
default_scale: Res<DefaultSpatialScale>,
) {
for (mut spatial, scale, effect_of) in emitters.iter_mut() {
let Ok(transform) = effect_parents.get(effect_of.0) else {
continue;
};
let emitter_pos = transform.translation();
let closest_listener = find_closest_listener(
emitter_pos,
listeners.iter().map(GlobalTransform::translation),
);
let Some(listener_pos) = closest_listener else {
continue;
};
let scale = scale.map(|s| s.0).unwrap_or(default_scale.0.0);
let x_diff = (emitter_pos.x - listener_pos.x) * scale.x;
let y_diff = (emitter_pos.y - listener_pos.y) * scale.y;
spatial.offset.x = x_diff;
spatial.offset.z = y_diff;
}
}
pub(crate) fn update_3d_emitters(
listeners: Query<&GlobalTransform, With<SpatialListener3D>>,
mut emitters: Query<(
&mut SpatialBasicNode,
Option<&SpatialScale>,
&GlobalTransform,
)>,
default_scale: Res<DefaultSpatialScale>,
) {
for (mut spatial, scale, transform) in emitters.iter_mut() {
let emitter_pos = transform.translation();
let closest_listener = find_closest_listener(
emitter_pos,
listeners.iter().map(GlobalTransform::translation),
);
let Some(listener_pos) = closest_listener else {
continue;
};
let scale = scale.map(|s| s.0).unwrap_or(default_scale.0.0);
spatial.offset = (emitter_pos - listener_pos) * scale;
}
}
pub(crate) fn update_3d_emitters_effects(
listeners: Query<&GlobalTransform, With<SpatialListener3D>>,
mut emitters: Query<(&mut SpatialBasicNode, Option<&SpatialScale>, &EffectOf)>,
effect_parents: Query<&GlobalTransform>,
default_scale: Res<DefaultSpatialScale>,
) {
for (mut spatial, scale, effect_of) in emitters.iter_mut() {
let Ok(transform) = effect_parents.get(effect_of.0) else {
continue;
};
let emitter_pos = transform.translation();
let closest_listener = find_closest_listener(
emitter_pos,
listeners.iter().map(GlobalTransform::translation),
);
let Some(listener_pos) = closest_listener else {
continue;
};
let scale = scale.map(|s| s.0).unwrap_or(default_scale.0.0);
spatial.offset = (emitter_pos - listener_pos) * scale;
}
}
fn find_closest_listener(emitter_pos: Vec3, listeners: impl Iterator<Item = Vec3>) -> Option<Vec3> {
let mut closest_listener: Option<(f32, Vec3)> = None;
for listener_pos in listeners {
let distance = emitter_pos.distance_squared(listener_pos);
match &mut closest_listener {
None => closest_listener = Some((distance, listener_pos)),
Some((old_distance, old_pos)) => {
if distance < *old_distance {
*old_distance = distance;
*old_pos = listener_pos;
}
}
}
}
closest_listener.map(|l| l.1)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_closest() {
let positions = [Vec3::splat(5.0), Vec3::splat(4.0), Vec3::splat(6.0)];
let emitter = Vec3::splat(0.0);
let closest = find_closest_listener(emitter, positions.iter().copied()).unwrap();
assert_eq!(closest, positions[1]);
}
#[test]
fn test_empty() {
let positions = [];
let emitter = Vec3::splat(0.0);
let closest = find_closest_listener(emitter, positions.iter().copied());
assert!(closest.is_none());
}
}