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}