Skip to main content

boon/
position.rs

1//! World-coordinate helpers for Source 2's split position storage.
2//!
3//! Networked entities in Source 2 do not transmit a full world position
4//! every tick. Each position is split across two networked fields:
5//!
6//! - an integer **cell index** (`m_cellX`, `m_cellY`, `m_cellZ`) identifying
7//!   which fixed-size cell of the world the entity is currently in, and
8//! - a quantized **offset** (`m_vecOrigin.m_vecX`, etc.) describing where
9//!   inside that cell the entity sits, bounded to `[0, CELL_SIZE)`.
10//!
11//! The true world position (in Hammer units, the same coordinate space used
12//! by Valve's level editor and `.vmap` data) is reconstructed via
13//! [`cell_to_world`]. Reading the offset alone gives a sawtooth signal that
14//! resets every time the entity crosses a cell boundary, not a usable
15//! coordinate. See [`Entity::world_position`](crate::Entity::world_position)
16//! for the typical entity-side combine.
17
18/// Number of bits used by Source 2 to address a position within a cell.
19///
20/// The on-the-wire offset is quantized into a `2^CELL_BITS` window, so cells
21/// are `CELL_SIZE` Hammer units wide on each axis.
22pub const CELL_BITS: u32 = 9;
23
24/// Edge length of a single cell in Hammer units (`2^CELL_BITS`).
25pub const CELL_SIZE: f32 = (1u32 << CELL_BITS) as f32;
26
27/// Half the addressable world extent in Hammer units.
28///
29/// Source 2's cell grid is centred on the world origin, so cell 0 starts at
30/// `-WORLD_HALF` and cell indices run upward from there. This constant is the
31/// shift applied in [`cell_to_world`] to translate cell-relative addresses
32/// back to centred world coordinates.
33pub const WORLD_HALF: f32 = 16384.0;
34
35/// Combine a cell index and an in-cell offset into a world coordinate.
36///
37/// Applies the standard Source 2 transform `cell * CELL_SIZE - WORLD_HALF +
38/// offset` along a single axis. Operate on each axis independently to
39/// recover a full `[x, y, z]` world position; see
40/// [`Entity::world_position`](crate::Entity::world_position) for the typical
41/// entity-side combine that does this for all three axes at once.
42pub fn cell_to_world(cell: i32, offset: f32) -> f32 {
43    (cell as f32) * CELL_SIZE - WORLD_HALF + offset
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn constants_match_source2_layout() {
52        assert_eq!(CELL_BITS, 9);
53        assert_eq!(CELL_SIZE, 512.0);
54        assert_eq!(WORLD_HALF, 16384.0);
55    }
56
57    #[test]
58    fn world_origin_maps_to_centre_of_cell_32() {
59        // Cell 32 is the cell that contains the world origin (0): it starts
60        // at `32 * 512 - 16384 = 0` and ends just before `+512`.
61        assert_eq!(cell_to_world(32, 0.0), 0.0);
62        assert_eq!(cell_to_world(32, 256.0), 256.0);
63    }
64
65    #[test]
66    fn cell_zero_is_negative_world_half() {
67        assert_eq!(cell_to_world(0, 0.0), -WORLD_HALF);
68        assert_eq!(cell_to_world(0, 1.0), -WORLD_HALF + 1.0);
69    }
70
71    #[test]
72    fn adjacent_cells_differ_by_cell_size() {
73        let a = cell_to_world(32, 511.0);
74        let b = cell_to_world(33, 0.0);
75        // The sawtooth: a tiny offset step across a cell boundary jumps from
76        // (cell, ~CELL_SIZE) to (cell+1, ~0), but the *world* delta is 1.0.
77        assert!((b - a - 1.0).abs() < 1e-3);
78    }
79}