evtc/
position.rs

1//! Bindings & utilities for the game's 3d space.
2
3use crate::{extract::Extract, AgentId, Event, StateChange, TryExtract};
4use std::{
5    mem::transmute,
6    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
7};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// Positional information for an agent.
13#[derive(Debug, Clone)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15pub struct PositionEvent {
16    /// Time of registering the position.
17    pub time: u64,
18
19    /// Agent who changed position.
20    pub agent: AgentId,
21
22    /// The position.
23    pub position: Position,
24}
25
26impl Extract for PositionEvent {
27    #[inline]
28    unsafe fn extract(event: &Event) -> Self {
29        Self {
30            time: event.time,
31            agent: AgentId::from_src(event),
32            position: Position::extract(event),
33        }
34    }
35}
36
37impl TryExtract for PositionEvent {
38    #[inline]
39    fn can_extract(event: &Event) -> bool {
40        matches!(
41            event.get_statechange(),
42            StateChange::Position | StateChange::Velocity | StateChange::Facing
43        )
44    }
45}
46
47/// Positional information.
48///
49/// This can be from an [`Event`] with [`StateChange::Position`], [`StateChange::Velocity`] or [`StateChange::Facing`].
50/// It can also occur in effect or missile events as locations or orientations.
51///
52/// Ingame coordinates are interpreted as 1 unit = 1 inch.
53/// The z-axis represents vertical height and **points down**,
54/// meaning lower values are a higher location ingame.
55///
56/// Mumble coordinates are given in meters.
57/// The y-axis represents vertical height and **points up**.
58#[derive(Debug, Clone, PartialEq)]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
60pub struct Position {
61    pub x: f32,
62    pub y: f32,
63    pub z: f32,
64}
65
66impl Position {
67    /// Conversion from inch to meter.
68    pub const INCH_TO_METER: f32 = 0.0254;
69
70    /// Creates new positional information.
71    #[inline]
72    pub const fn new(x: f32, y: f32, z: f32) -> Self {
73        Self { x, y, z }
74    }
75
76    /// Creates a position from Mumble coordinates.
77    #[inline]
78    pub fn from_mumble(coords: [f32; 3]) -> Self {
79        let [x, y, z] = coords;
80        Self::new(
81            x / Self::INCH_TO_METER,
82            z / Self::INCH_TO_METER,
83            -y / Self::INCH_TO_METER,
84        )
85    }
86
87    /// Creates a position from [`i16`] values with a scaling factor.
88    #[inline]
89    pub fn from_scaled_i16s(x: i16, y: i16, z: i16, factor: f32) -> Self {
90        let x = x as f32 * factor;
91        let y = y as f32 * factor;
92        let z = z as f32 * factor;
93        Self::new(x, y, z)
94    }
95
96    /// Converts the position to an [`array`].
97    #[inline]
98    pub fn to_array(self) -> [f32; 3] {
99        [self.x, self.y, self.z]
100    }
101
102    /// Converts the position to a [`tuple`].
103    #[inline]
104    pub fn to_tuple(self) -> (f32, f32, f32) {
105        (self.x, self.y, self.z)
106    }
107
108    /// Converts the position to Mumble coordinates.
109    #[inline]
110    pub fn to_mumble(&self) -> [f32; 3] {
111        [
112            self.x * Self::INCH_TO_METER,
113            -self.z * Self::INCH_TO_METER,
114            self.y * Self::INCH_TO_METER,
115        ]
116    }
117
118    /// Returns the length of the position interpreted as vector.
119    #[inline]
120    pub fn len(&self) -> f32 {
121        (self.x.powi(2) + self.y.powi(2) + self.z.powi(2)).sqrt()
122    }
123
124    /// Interprets the position as vector and multiplies it with the given matrix.
125    #[inline]
126    pub fn mat_mul(&self, matrix: [[f32; 3]; 3]) -> Self {
127        let x = matrix[0][0] * self.x + matrix[0][1] * self.y + matrix[0][2] * self.z;
128        let y = matrix[1][0] * self.x + matrix[1][1] * self.y + matrix[1][2] * self.z;
129        let z = matrix[2][0] * self.x + matrix[2][1] * self.y + matrix[2][2] * self.z;
130        Self::new(x, y, z)
131    }
132
133    /// Interprets the position as rotation angles and converts it to a rotation matrix.
134    ///
135    /// `x`, `y` and `z` are interpreted as angles around each axis in radians.
136    #[inline]
137    pub fn as_rotation_matrix(&self) -> [[f32; 3]; 3] {
138        let Self {
139            x: alpha,
140            y: beta,
141            z: gamma,
142        } = self;
143        [
144            [
145                beta.cos() * gamma.cos(),
146                alpha.sin() * beta.sin() * gamma.cos() - alpha.cos() + gamma.sin(),
147                alpha.cos() * beta.sin() * gamma.cos() + alpha.sin() * gamma.sin(),
148            ],
149            [
150                beta.cos() * gamma.sin(),
151                alpha.sin() * beta.sin() * gamma.sin() + alpha.cos() + gamma.cos(),
152                alpha.cos() * beta.sin() * gamma.sin() - alpha.sin() * gamma.cos(),
153            ],
154            [
155                -beta.sin(),
156                alpha.sin() * beta.cos(),
157                alpha.cos() * beta.cos(),
158            ],
159        ]
160    }
161
162    /// Interprets the position as rotation angles and rotates the given vector.
163    #[inline]
164    pub fn rotate(&self, vector: Self) -> Self {
165        vector.mat_mul(self.as_rotation_matrix())
166    }
167
168    /// Performs a component-wise operation with another [`Position`].
169    #[inline]
170    fn component_wise_op(&self, other: &Position, op: impl Fn(f32, f32) -> f32) -> Self {
171        Self::new(
172            op(self.x, other.x),
173            op(self.y, other.y),
174            op(self.z, other.z),
175        )
176    }
177
178    /// Performs a scalar operation with a [`Position`] and a [`f32`].
179    #[inline]
180    fn scalar_op(&self, scalar: f32, op: impl Fn(f32, f32) -> f32) -> Self {
181        Self::new(op(self.x, scalar), op(self.y, scalar), op(self.z, scalar))
182    }
183}
184
185impl From<[f32; 3]> for Position {
186    #[inline]
187    fn from(value: [f32; 3]) -> Self {
188        let [x, y, z] = value;
189        Self { x, y, z }
190    }
191}
192
193impl From<Position> for [f32; 3] {
194    #[inline]
195    fn from(pos: Position) -> Self {
196        pos.to_array()
197    }
198}
199
200impl From<(f32, f32, f32)> for Position {
201    #[inline]
202    fn from(value: (f32, f32, f32)) -> Self {
203        let (x, y, z) = value;
204        Self { x, y, z }
205    }
206}
207
208impl From<Position> for (f32, f32, f32) {
209    #[inline]
210    fn from(pos: Position) -> Self {
211        pos.to_tuple()
212    }
213}
214
215impl Add for &Position {
216    type Output = Position;
217
218    #[inline]
219    fn add(self, rhs: &Position) -> Self::Output {
220        self.component_wise_op(rhs, Add::add)
221    }
222}
223
224impl AddAssign for Position {
225    #[inline]
226    fn add_assign(&mut self, rhs: Self) {
227        *self = &*self + &rhs
228    }
229}
230
231impl Sub for &Position {
232    type Output = Position;
233
234    #[inline]
235    fn sub(self, rhs: &Position) -> Self::Output {
236        self.component_wise_op(rhs, Sub::sub)
237    }
238}
239
240impl SubAssign for Position {
241    #[inline]
242    fn sub_assign(&mut self, rhs: Self) {
243        *self = &*self - &rhs
244    }
245}
246
247impl Mul<f32> for &Position {
248    type Output = Position;
249
250    #[inline]
251    fn mul(self, rhs: f32) -> Self::Output {
252        self.scalar_op(rhs, Mul::mul)
253    }
254}
255
256impl MulAssign<f32> for Position {
257    #[inline]
258    fn mul_assign(&mut self, rhs: f32) {
259        *self = &*self * rhs;
260    }
261}
262
263impl Mul<&Position> for f32 {
264    type Output = Position;
265
266    #[inline]
267    fn mul(self, rhs: &Position) -> Self::Output {
268        rhs.scalar_op(self, Mul::mul)
269    }
270}
271
272impl Div<f32> for &Position {
273    type Output = Position;
274
275    #[inline]
276    fn div(self, rhs: f32) -> Self::Output {
277        self.scalar_op(rhs, Div::div)
278    }
279}
280
281impl DivAssign<f32> for Position {
282    #[inline]
283    fn div_assign(&mut self, rhs: f32) {
284        *self = &*self / rhs;
285    }
286}
287
288impl Div<&Position> for f32 {
289    type Output = Position;
290
291    #[inline]
292    fn div(self, rhs: &Position) -> Self::Output {
293        rhs.scalar_op(self, Div::div)
294    }
295}
296
297impl Extract for Position {
298    #[inline]
299    unsafe fn extract(event: &Event) -> Self {
300        let [x, y]: [f32; 2] = transmute(event.dst_agent);
301        let z = f32::from_ne_bytes(event.value.to_ne_bytes());
302        Self::new(x, y, z)
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use approx::assert_relative_eq;
310    use std::f32::consts::{FRAC_1_SQRT_2, PI};
311
312    #[test]
313    fn mumble_conversion() {
314        let pos = Position::new(3993.409, 6225.539, -549.570);
315
316        let mumble = pos.to_mumble();
317        assert_relative_eq!(
318            *mumble.as_slice(),
319            [101.433, 13.959, 158.129],
320            max_relative = 1e-3
321        );
322
323        let back = Position::from_mumble(mumble);
324        assert_eq!(back, pos);
325    }
326
327    #[test]
328    fn rotation() {
329        let rotation = Position::new(0.0, 0.25 * PI, 0.5 * PI);
330        let vector = Position::new(1.0, 0.0, 0.0);
331
332        let result = rotation.rotate(vector).to_array();
333        assert_relative_eq!(
334            *result.as_slice(),
335            [0.0, FRAC_1_SQRT_2, -FRAC_1_SQRT_2],
336            max_relative = 1e-7
337        );
338    }
339}