gilrs/ff/
effect_source.rs

1// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use std::error::Error;
9use std::ops::{AddAssign, Mul};
10use std::{fmt, mem, u16};
11
12use crate::{Event, EventType, GamepadId};
13
14use super::base_effect::{BaseEffect, BaseEffectType};
15use super::time::{Repeat, Ticks};
16
17use vec_map::VecMap;
18
19/// Specifies how distance between effect source and listener attenuates effect.
20///
21/// They are based on
22/// [OpenAL Specification](http://openal.org/documentation/openal-1.1-specification.pdf) (chapter
23/// 3.4), but the best way to see how they differ is to run `ff_pos` example.
24///
25/// Make sure that all parameters are ≥ 0. Additionally `Linear` and `LinearClamped` models don't
26/// like if `ref_distance == max_distance` while others would prefer `ref_distance > 0`.
27#[derive(Clone, Copy, Debug, Default, PartialEq)]
28pub enum DistanceModel {
29    /// Effect is not attenuated by distance.
30    #[default]
31    None,
32    /// Linear distance model.
33    Linear {
34        ref_distance: f32,
35        rolloff_factor: f32,
36        max_distance: f32,
37    },
38    /// Linear distance clamped model.
39    LinearClamped {
40        ref_distance: f32,
41        rolloff_factor: f32,
42        max_distance: f32,
43    },
44    /// Inverse distance model.
45    Inverse {
46        ref_distance: f32,
47        rolloff_factor: f32,
48    },
49    /// Inverse distance clamped model.
50    InverseClamped {
51        ref_distance: f32,
52        rolloff_factor: f32,
53        max_distance: f32,
54    },
55    /// Exponential distance model.
56    Exponential {
57        ref_distance: f32,
58        rolloff_factor: f32,
59    },
60    /// Exponential distance clamped model.
61    ExponentialClamped {
62        ref_distance: f32,
63        rolloff_factor: f32,
64        max_distance: f32,
65    },
66}
67
68impl DistanceModel {
69    fn attenuation(self, mut distance: f32) -> f32 {
70        // For now we will follow OpenAL[1] specification for distance models. See chapter 3.4 for
71        // more details.
72        //
73        // [1]: http://openal.org/documentation/openal-1.1-specification.pdf
74        match self {
75            DistanceModel::Linear {
76                ref_distance,
77                max_distance,
78                rolloff_factor,
79            } => {
80                distance = distance.min(max_distance);
81
82                1.0 - rolloff_factor * (distance - ref_distance) / (max_distance - ref_distance)
83            }
84            DistanceModel::LinearClamped {
85                ref_distance,
86                max_distance,
87                rolloff_factor,
88            } => {
89                distance = distance.max(ref_distance);
90                distance = distance.min(max_distance);
91
92                1.0 - rolloff_factor * (distance - ref_distance) / (max_distance - ref_distance)
93            }
94            DistanceModel::Inverse {
95                ref_distance,
96                rolloff_factor,
97            } => ref_distance / (ref_distance + rolloff_factor * (distance - ref_distance)),
98            DistanceModel::InverseClamped {
99                ref_distance,
100                max_distance,
101                rolloff_factor,
102            } => {
103                distance = distance.max(ref_distance);
104                distance = distance.min(max_distance);
105
106                ref_distance / (ref_distance + rolloff_factor * (distance - ref_distance))
107            }
108            DistanceModel::Exponential {
109                ref_distance,
110                rolloff_factor,
111            } => (distance / ref_distance).powf(-rolloff_factor),
112            DistanceModel::ExponentialClamped {
113                ref_distance,
114                max_distance,
115                rolloff_factor,
116            } => {
117                distance = distance.max(ref_distance);
118                distance = distance.min(max_distance);
119
120                (distance / ref_distance).powf(-rolloff_factor)
121            }
122            DistanceModel::None => 1.0,
123        }
124    }
125
126    pub(crate) fn validate(self) -> Result<(), DistanceModelError> {
127        let (ref_distance, rolloff_factor, max_distance) = match self {
128            DistanceModel::Inverse {
129                ref_distance,
130                rolloff_factor,
131            } => {
132                if ref_distance <= 0.0 {
133                    return Err(DistanceModelError::InvalidModelParameter);
134                }
135
136                (ref_distance, rolloff_factor, 0.0)
137            }
138            DistanceModel::InverseClamped {
139                ref_distance,
140                max_distance,
141                rolloff_factor,
142            } => {
143                if ref_distance <= 0.0 {
144                    return Err(DistanceModelError::InvalidModelParameter);
145                }
146
147                (ref_distance, rolloff_factor, max_distance)
148            }
149            DistanceModel::Linear {
150                ref_distance,
151                max_distance,
152                rolloff_factor,
153            } => {
154                if ref_distance == max_distance {
155                    return Err(DistanceModelError::InvalidModelParameter);
156                }
157
158                (ref_distance, rolloff_factor, max_distance)
159            }
160            DistanceModel::LinearClamped {
161                ref_distance,
162                max_distance,
163                rolloff_factor,
164            } => {
165                if ref_distance == max_distance {
166                    return Err(DistanceModelError::InvalidModelParameter);
167                }
168
169                (ref_distance, rolloff_factor, max_distance)
170            }
171            DistanceModel::Exponential {
172                ref_distance,
173                rolloff_factor,
174            } => {
175                if ref_distance <= 0.0 {
176                    return Err(DistanceModelError::InvalidModelParameter);
177                }
178
179                (ref_distance, rolloff_factor, 0.0)
180            }
181            DistanceModel::ExponentialClamped {
182                ref_distance,
183                max_distance,
184                rolloff_factor,
185            } => {
186                if ref_distance <= 0.0 {
187                    return Err(DistanceModelError::InvalidModelParameter);
188                }
189
190                (ref_distance, rolloff_factor, max_distance)
191            }
192            DistanceModel::None => (0.0, 0.0, 0.0),
193        };
194
195        if ref_distance < 0.0 {
196            Err(DistanceModelError::InvalidReferenceDistance)
197        } else if rolloff_factor < 0.0 {
198            Err(DistanceModelError::InvalidRolloffFactor)
199        } else if max_distance < 0.0 {
200            Err(DistanceModelError::InvalidMaxDistance)
201        } else {
202            Ok(())
203        }
204    }
205}
206
207/// Error that can be returned when passing [`DistanceModel`](struct.DistanceModel.html) with
208/// invalid value.
209#[derive(Copy, Clone, Debug, PartialEq, Eq)]
210#[non_exhaustive]
211pub enum DistanceModelError {
212    /// Reference distance is < 0.
213    InvalidReferenceDistance,
214    /// Rolloff factor is < 0.
215    InvalidRolloffFactor,
216    /// Max distance is < 0.
217    InvalidMaxDistance,
218    /// Possible divide by zero
219    InvalidModelParameter,
220}
221
222impl Error for DistanceModelError {}
223
224impl fmt::Display for DistanceModelError {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        let s = match self {
227            DistanceModelError::InvalidReferenceDistance => "reference distance is < 0",
228            DistanceModelError::InvalidRolloffFactor => "rolloff factor is < 0",
229            DistanceModelError::InvalidMaxDistance => "max distance is < 0",
230            DistanceModelError::InvalidModelParameter => "possible divide by zero",
231        };
232
233        f.write_str(s)
234    }
235}
236
237#[derive(Copy, Clone, PartialEq, Eq, Debug)]
238pub(super) enum EffectState {
239    Playing { since: Ticks },
240    Stopped,
241}
242
243#[derive(Clone, PartialEq, Debug)]
244pub(crate) struct EffectSource {
245    base_effects: Vec<BaseEffect>,
246    // TODO: Use bitset
247    pub(super) devices: VecMap<()>,
248    pub(super) repeat: Repeat,
249    pub(super) distance_model: DistanceModel,
250    pub(super) position: [f32; 3],
251    pub(super) gain: f32,
252    pub(super) state: EffectState,
253    pub(super) completion_events: Vec<Event>,
254}
255
256impl EffectSource {
257    pub(super) fn new(
258        base_effects: Vec<BaseEffect>,
259        devices: VecMap<()>,
260        repeat: Repeat,
261        dist_model: DistanceModel,
262        position: [f32; 3],
263        gain: f32,
264    ) -> Self {
265        EffectSource {
266            base_effects,
267            devices,
268            repeat,
269            distance_model: dist_model,
270            position,
271            gain,
272            state: EffectState::Stopped,
273            completion_events: vec![],
274        }
275    }
276
277    pub(super) fn combine_base_effects(&mut self, ticks: Ticks, actor_pos: [f32; 3]) -> Magnitude {
278        let ticks = match self.state {
279            EffectState::Playing { since } => {
280                debug_assert!(ticks >= since);
281                ticks - since
282            }
283            EffectState::Stopped => return Magnitude::zero(),
284        };
285
286        match self.repeat {
287            Repeat::For(max_dur) if ticks > max_dur => {
288                self.state = EffectState::Stopped;
289                self.devices.keys().for_each(|id| {
290                    let event = Event::new(GamepadId(id), EventType::ForceFeedbackEffectCompleted);
291                    self.completion_events.push(event);
292                });
293            }
294            _ => (),
295        }
296
297        let attenuation = self
298            .distance_model
299            .attenuation(self.position.distance(actor_pos))
300            * self.gain;
301        if attenuation < 0.05 {
302            return Magnitude::zero();
303        }
304
305        let mut final_magnitude = Magnitude::zero();
306        for effect in &self.base_effects {
307            match effect.magnitude_at(ticks) {
308                BaseEffectType::Strong { magnitude } => {
309                    final_magnitude.strong = final_magnitude.strong.saturating_add(magnitude)
310                }
311                BaseEffectType::Weak { magnitude } => {
312                    final_magnitude.weak = final_magnitude.weak.saturating_add(magnitude)
313                }
314            };
315        }
316        final_magnitude * attenuation
317    }
318
319    pub(super) fn flush_completion_events(&mut self) -> Vec<Event> {
320        mem::take(&mut self.completion_events)
321    }
322}
323
324/// (strong, weak) pair.
325#[derive(Copy, Clone, Debug)]
326pub(super) struct Magnitude {
327    pub strong: u16,
328    pub weak: u16,
329}
330
331impl Magnitude {
332    pub fn zero() -> Self {
333        Magnitude { strong: 0, weak: 0 }
334    }
335}
336
337impl Mul<f32> for Magnitude {
338    type Output = Magnitude;
339
340    fn mul(self, rhs: f32) -> Self::Output {
341        debug_assert!(rhs >= 0.0);
342        let strong = self.strong as f32 * rhs;
343        let strong = if strong > u16::MAX as f32 {
344            u16::MAX
345        } else {
346            strong as u16
347        };
348        let weak = self.weak as f32 * rhs;
349        let weak = if weak > u16::MAX as f32 {
350            u16::MAX
351        } else {
352            weak as u16
353        };
354        Magnitude { strong, weak }
355    }
356}
357
358impl AddAssign for Magnitude {
359    fn add_assign(&mut self, rhs: Magnitude) {
360        self.strong = self.strong.saturating_add(rhs.strong);
361        self.weak = self.weak.saturating_add(rhs.weak);
362    }
363}
364
365trait SliceVecExt {
366    type Base;
367
368    fn distance(self, from: Self) -> Self::Base;
369}
370
371impl SliceVecExt for [f32; 3] {
372    type Base = f32;
373
374    fn distance(self, from: Self) -> f32 {
375        ((from[0] - self[0]).powi(2) + (from[1] - self[1]).powi(2) + (from[2] - self[2]).powi(2))
376            .sqrt()
377    }
378}