Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_gte_core/
math.rs

1//! Fixed-point math types for GTE inputs/outputs.
2//!
3//! The PS1 GTE works in two flavours of fixed point:
4//!
5//! - **1.3.12** for matrix entries, normals, small vectors -- 16-bit
6//!   signed, divide by 4096 for real value. Range: roughly ±8.0 with
7//!   12 bits of fraction.
8//! - **19.12 / 31.0** for translations, colour bias, far colour --
9//!   32-bit signed stored raw.
10//!
11//! Types here are **POD** (`Copy`, `no_std`, no heap). The GTE register
12//! wire format is always a packed `u32`, so the types mainly exist for
13//! static correctness -- the actual register I/O happens through the
14//! `write_data!` / `write_control!` macros in `psx-gte`.
15
16/// 3-component vector of `i16` in 1.3.12 fixed point.
17/// Matches the GTE's V0/V1/V2 input slots and the IR accumulators.
18#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
19#[repr(C)]
20pub struct Vec3I16 {
21    /// X component.
22    pub x: i16,
23    /// Y component.
24    pub y: i16,
25    /// Z component.
26    pub z: i16,
27}
28
29impl Vec3I16 {
30    /// Zero vector.
31    pub const ZERO: Self = Self { x: 0, y: 0, z: 0 };
32
33    /// Construct from three components.
34    #[inline(always)]
35    pub const fn new(x: i16, y: i16, z: i16) -> Self {
36        Self { x, y, z }
37    }
38
39    /// Pack the `(x, y)` pair into the 32-bit form expected by MTC2
40    /// for V0/V1/V2 slots 0/2/4 -- low half = `x`, high half = `y`.
41    #[inline(always)]
42    pub const fn xy_packed(self) -> u32 {
43        ((self.y as u16 as u32) << 16) | (self.x as u16 as u32)
44    }
45
46    /// Sign-extended `z` for MTC2 of V0/V1/V2 slots 1/3/5.
47    #[inline(always)]
48    pub const fn z_packed(self) -> u32 {
49        self.z as i32 as u32
50    }
51}
52
53/// 3-component vector of `i32` -- used for translation / colour bias
54/// / far colour values (stored raw in 31.0 or 19.12 depending on which
55/// register, see the GTE spec).
56#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
57#[repr(C)]
58pub struct Vec3I32 {
59    /// X / R component.
60    pub x: i32,
61    /// Y / G component.
62    pub y: i32,
63    /// Z / B component.
64    pub z: i32,
65}
66
67impl Vec3I32 {
68    /// Zero vector.
69    pub const ZERO: Self = Self { x: 0, y: 0, z: 0 };
70
71    /// Construct from three components.
72    #[inline(always)]
73    pub const fn new(x: i32, y: i32, z: i32) -> Self {
74        Self { x, y, z }
75    }
76}
77
78/// 3×3 rotation / light matrix of `i16` in 1.3.12. Laid out row-major
79/// so `m[row][col]` matches PSX-SPX notation (`M_rc`).
80#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
81#[repr(C)]
82pub struct Mat3I16 {
83    /// Row-major 3×3 cells.
84    pub m: [[i16; 3]; 3],
85}
86
87impl Mat3I16 {
88    /// Zero matrix.
89    pub const ZERO: Self = Self { m: [[0; 3]; 3] };
90
91    /// Identity matrix scaled 1.0 in 1.3.12 (diagonal = 0x1000).
92    pub const IDENTITY: Self = Self {
93        m: [[0x1000, 0, 0], [0, 0x1000, 0], [0, 0, 0x1000]],
94    };
95
96    /// Access a row.
97    #[inline(always)]
98    pub const fn row(&self, i: usize) -> [i16; 3] {
99        self.m[i]
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn identity_diagonal_is_one_in_1_3_12() {
109        // 0x1000 = 4096 = 1.0 when divided by 4096.
110        assert_eq!(Mat3I16::IDENTITY.m[0][0], 0x1000);
111        assert_eq!(Mat3I16::IDENTITY.m[1][1], 0x1000);
112        assert_eq!(Mat3I16::IDENTITY.m[2][2], 0x1000);
113        // Off-diagonal zero.
114        assert_eq!(Mat3I16::IDENTITY.m[0][1], 0);
115        assert_eq!(Mat3I16::IDENTITY.m[1][2], 0);
116    }
117
118    #[test]
119    fn vec3_i16_xy_packs_little_endian() {
120        // x in low half, y in high half -- matches MTC2 expectation.
121        let v = Vec3I16::new(-1, 2, 3);
122        assert_eq!(v.xy_packed(), 0x0002_FFFF);
123    }
124
125    #[test]
126    fn vec3_i16_z_sign_extends() {
127        let v = Vec3I16::new(0, 0, -1);
128        assert_eq!(v.z_packed(), 0xFFFF_FFFF);
129    }
130
131    #[test]
132    fn vec3_i32_construct_and_fields() {
133        let t = Vec3I32::new(100, 200, 300);
134        assert_eq!(t.x, 100);
135        assert_eq!(t.y, 200);
136        assert_eq!(t.z, 300);
137    }
138
139    #[test]
140    fn zero_constants_are_zero() {
141        assert_eq!(Vec3I16::ZERO.x, 0);
142        assert_eq!(Vec3I16::ZERO.y, 0);
143        assert_eq!(Vec3I16::ZERO.z, 0);
144        assert_eq!(Vec3I32::ZERO.x, 0);
145        assert_eq!(Mat3I16::ZERO.m[0][0], 0);
146    }
147}