Skip to main content

goud_engine/ecs/components/audiosource/
attenuation.rs

1//! Distance-based audio attenuation models.
2
3/// Audio attenuation model for distance-based volume falloff.
4///
5/// Controls how audio volume decreases with distance from the listener.
6/// Different models provide different falloff curves for realistic or
7/// stylized audio behavior.
8///
9/// # Models
10///
11/// - **Linear**: Linear falloff (volume = 1 - distance/max_distance)
12/// - **InverseDistance**: Realistic inverse distance falloff (volume = 1 / (1 + distance))
13/// - **Exponential**: Exponential falloff (volume = (1 - distance/max_distance)^rolloff)
14/// - **None**: No attenuation (constant volume regardless of distance)
15///
16/// # Examples
17///
18/// ```
19/// use goud_engine::ecs::components::AttenuationModel;
20///
21/// let linear = AttenuationModel::Linear;
22/// let inverse = AttenuationModel::InverseDistance;
23/// let exponential = AttenuationModel::Exponential { rolloff: 2.0 };
24/// let none = AttenuationModel::None;
25///
26/// assert_eq!(linear.name(), "Linear");
27/// assert_eq!(inverse.name(), "InverseDistance");
28/// ```
29#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
30pub enum AttenuationModel {
31    /// Linear falloff: volume = max(0, 1 - distance/max_distance)
32    Linear,
33    /// Inverse distance falloff: volume = 1 / (1 + distance)
34    InverseDistance,
35    /// Exponential falloff: volume = max(0, (1 - distance/max_distance)^rolloff)
36    Exponential {
37        /// The exponent for the falloff curve.
38        rolloff: f32,
39    },
40    /// No attenuation (constant volume)
41    None,
42}
43
44impl AttenuationModel {
45    /// Returns the model name for debugging.
46    pub fn name(&self) -> &str {
47        match self {
48            AttenuationModel::Linear => "Linear",
49            AttenuationModel::InverseDistance => "InverseDistance",
50            AttenuationModel::Exponential { .. } => "Exponential",
51            AttenuationModel::None => "None",
52        }
53    }
54
55    /// Computes the attenuation factor (0.0-1.0) based on distance.
56    ///
57    /// # Arguments
58    ///
59    /// - `distance`: Distance from listener (must be >= 0)
60    /// - `max_distance`: Maximum distance for attenuation (must be > 0)
61    ///
62    /// # Returns
63    ///
64    /// Volume multiplier in range [0.0, 1.0]
65    ///
66    /// # Examples
67    ///
68    /// ```
69    /// use goud_engine::ecs::components::AttenuationModel;
70    ///
71    /// let model = AttenuationModel::Linear;
72    /// assert_eq!(model.compute_attenuation(0.0, 100.0), 1.0);
73    /// assert_eq!(model.compute_attenuation(50.0, 100.0), 0.5);
74    /// assert_eq!(model.compute_attenuation(100.0, 100.0), 0.0);
75    /// assert_eq!(model.compute_attenuation(150.0, 100.0), 0.0); // Beyond max
76    /// ```
77    pub fn compute_attenuation(&self, distance: f32, max_distance: f32) -> f32 {
78        match self {
79            AttenuationModel::Linear => {
80                if distance >= max_distance {
81                    0.0
82                } else {
83                    (1.0 - distance / max_distance).max(0.0)
84                }
85            }
86            AttenuationModel::InverseDistance => 1.0 / (1.0 + distance),
87            AttenuationModel::Exponential { rolloff } => {
88                if distance >= max_distance {
89                    0.0
90                } else {
91                    ((1.0 - distance / max_distance).powf(*rolloff)).max(0.0)
92                }
93            }
94            AttenuationModel::None => 1.0,
95        }
96    }
97}
98
99impl Default for AttenuationModel {
100    /// Returns `AttenuationModel::InverseDistance` as the default (most realistic).
101    fn default() -> Self {
102        AttenuationModel::InverseDistance
103    }
104}
105
106impl std::fmt::Display for AttenuationModel {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        match self {
109            AttenuationModel::Exponential { rolloff } => {
110                write!(f, "Exponential(rolloff={})", rolloff)
111            }
112            other => write!(f, "{}", other.name()),
113        }
114    }
115}