Skip to main content

cxx_qt_lib/gui/
qgenericmatrix.rs

1// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Joshua Booth <joshua.n.booth@gmail.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use std::slice;
7
8use cxx::{type_id, ExternType};
9
10/// The QGenericMatrix class is a template class that represents a NxM
11/// transformation matrix with N columns and M rows.
12///
13/// Note: CXX-Qt currently only supports QGenericMatrix of f32, while the C++
14/// QGenericMatrix is generic over the contained type.
15///
16/// Qt Documentation: [QGenericMatrix](https://doc.qt.io/qt/qgenericmatrix.html#details)
17#[repr(C)]
18#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
19pub struct QGenericMatrix<const N: usize, const M: usize> {
20    data: [[f32; M]; N],
21}
22
23impl<const N: usize, const M: usize> QGenericMatrix<N, M> {
24    /// Constructs a matrix from floating-point `values` in row-major order.
25    pub const fn new(values: &[[f32; N]; M]) -> Self {
26        let mut data = [[0.0; M]; N];
27        let mut col = 0;
28        while col < N {
29            let mut row = 0;
30            while row < M {
31                data[col][row] = values[row][col];
32                row += 1;
33            }
34            col += 1;
35        }
36        Self { data }
37    }
38
39    /// Returns a reference to the raw data of this matrix.
40    pub const fn data(&self) -> &[f32] {
41        // TODO: Replace with `array::as_flattened` once MSRV is 1.80.0.
42        unsafe { slice::from_raw_parts(self.data.as_ptr().cast(), N * M) }
43    }
44
45    /// Returns a mutable reference to the raw data of this matrix.
46    pub fn data_mut(&mut self) -> &mut [f32] {
47        // TODO: Replace with `array::as_flattened_mut` once MSRV is 1.80.0.
48        unsafe { slice::from_raw_parts_mut(self.data.as_mut_ptr().cast(), N * M) }
49    }
50
51    /// Retrieves the N * M items in this matrix and copies them to `values` in row-major order.
52    pub fn copy_data_to(&self, values: &mut [f32]) {
53        for (col, data) in self.data.iter().enumerate() {
54            for (row, &value) in data.iter().enumerate() {
55                values[row * N + col] = value;
56            }
57        }
58    }
59
60    /// Fills all elements of this matrix with `value`.
61    pub fn fill(&mut self, value: f32) {
62        self.data_mut().fill(value);
63    }
64
65    /// Constructs a matrix with all values set to `value`.
66    pub const fn filled(value: f32) -> Self {
67        Self {
68            data: [[value; M]; N],
69        }
70    }
71
72    /// Constructs a NxM identity matrix.
73    pub const fn identity() -> Self {
74        let mut data = [[0.0; M]; N];
75        let mut i = 0;
76        let size = if M < N { M } else { N };
77        while i < size {
78            data[i][i] = 1.0;
79            i += 1;
80        }
81        Self { data }
82    }
83
84    /// Returns `true` if this matrix is the identity; `false` otherwise.
85    pub fn is_identity(&self) -> bool {
86        self == &Self::identity()
87    }
88
89    /// Constructs a two-dimensional array from the matrix in row-major order.
90    pub const fn rows(&self) -> [[f32; N]; M] {
91        self.transposed().data
92    }
93
94    /// Sets this matrix to the identity.
95    pub fn set_to_identity(&mut self) {
96        for (col, data) in self.data.iter_mut().enumerate() {
97            for (row, value) in data.iter_mut().enumerate() {
98                *value = if row == col { 1.0 } else { 0.0 };
99            }
100        }
101    }
102
103    /// Returns this matrix, transposed about its diagonal.
104    pub const fn transposed(&self) -> QGenericMatrix<M, N> {
105        let mut transposed = [[0.0; N]; M];
106        let mut col = 0;
107        while col < N {
108            let mut row = 0;
109            while row < M {
110                transposed[row][col] = self.data[col][row];
111                row += 1;
112            }
113            col += 1;
114        }
115        QGenericMatrix { data: transposed }
116    }
117}
118
119impl<const N: usize, const M: usize> Default for QGenericMatrix<N, M> {
120    /// Constructs a NxM identity matrix.
121    fn default() -> Self {
122        Self::identity()
123    }
124}
125
126impl<const N: usize, const M: usize> std::ops::Index<(usize, usize)> for QGenericMatrix<N, M> {
127    type Output = f32;
128
129    /// Returns a reference to the element at position (row, column) in this matrix.
130    fn index(&self, (row, column): (usize, usize)) -> &Self::Output {
131        &self.data[column][row]
132    }
133}
134
135impl<const N: usize, const M: usize> std::ops::IndexMut<(usize, usize)> for QGenericMatrix<N, M> {
136    /// Returns a mutable reference to the element at position (row, column) in this matrix.
137    fn index_mut(&mut self, (row, column): (usize, usize)) -> &mut Self::Output {
138        &mut self.data[column][row]
139    }
140}
141
142impl<const N: usize, const M: usize> std::ops::AddAssign for QGenericMatrix<N, M> {
143    fn add_assign(&mut self, rhs: Self) {
144        for (lhs, &rhs) in self.data_mut().iter_mut().zip(rhs.data()) {
145            *lhs += rhs;
146        }
147    }
148}
149
150impl<const N: usize, const M: usize> std::ops::Add for QGenericMatrix<N, M> {
151    type Output = Self;
152
153    fn add(mut self, rhs: Self) -> Self::Output {
154        self += rhs;
155        self
156    }
157}
158
159impl<const N: usize, const M: usize> std::ops::SubAssign for QGenericMatrix<N, M> {
160    fn sub_assign(&mut self, rhs: Self) {
161        for (lhs, &rhs) in self.data_mut().iter_mut().zip(rhs.data()) {
162            *lhs -= rhs;
163        }
164    }
165}
166
167impl<const N: usize, const M: usize> std::ops::Sub for QGenericMatrix<N, M> {
168    type Output = Self;
169
170    fn sub(mut self, rhs: Self) -> Self::Output {
171        self -= rhs;
172        self
173    }
174}
175
176impl<const N: usize, const M: usize> std::ops::MulAssign<f32> for QGenericMatrix<N, M> {
177    fn mul_assign(&mut self, rhs: f32) {
178        for value in self.data_mut() {
179            *value *= rhs;
180        }
181    }
182}
183
184impl<const N: usize, const M: usize> std::ops::Mul<f32> for QGenericMatrix<N, M> {
185    type Output = Self;
186
187    fn mul(mut self, rhs: f32) -> Self::Output {
188        self *= rhs;
189        self
190    }
191}
192
193impl<const N: usize, const M: usize> std::ops::DivAssign<f32> for QGenericMatrix<N, M> {
194    fn div_assign(&mut self, rhs: f32) {
195        for value in self.data_mut() {
196            *value /= rhs;
197        }
198    }
199}
200
201impl<const N: usize, const M: usize> std::ops::Div<f32> for QGenericMatrix<N, M> {
202    type Output = Self;
203
204    fn div(mut self, rhs: f32) -> Self::Output {
205        self /= rhs;
206        self
207    }
208}
209
210impl<const N: usize, const M: usize> std::ops::Neg for QGenericMatrix<N, M> {
211    type Output = Self;
212
213    fn neg(mut self) -> Self::Output {
214        for value in self.data_mut() {
215            *value = -*value;
216        }
217        self
218    }
219}
220
221impl<const N: usize, const M: usize> TryFrom<&[f32]> for QGenericMatrix<N, M> {
222    type Error = &'static str;
223
224    /// Constructs a matrix from the given N * M floating-point `values`. The contents of the array `values` is assumed to be in row-major order.
225    fn try_from(values: &[f32]) -> Result<Self, Self::Error> {
226        if values.len() != M * N {
227            return Err("invalid array length");
228        }
229        let mut matrix = [[0.0; M]; N];
230        for (col, data) in matrix.iter_mut().enumerate() {
231            for (row, value) in data.iter_mut().enumerate() {
232                *value = values[row * N + col];
233            }
234        }
235        Ok(Self { data: matrix })
236    }
237}
238
239impl<const N: usize, const M: usize> From<&[[f32; N]; M]> for QGenericMatrix<N, M> {
240    /// Constructs a matrix from the given N * M floating-point `values` in row-major order.
241    fn from(values: &[[f32; N]; M]) -> Self {
242        Self::new(values)
243    }
244}
245
246impl<const N: usize, const M: usize> From<&QGenericMatrix<N, M>> for [[f32; N]; M] {
247    /// Constructs a two-dimensional array from the matrix in row-major order.
248    fn from(value: &QGenericMatrix<N, M>) -> Self {
249        value.rows()
250    }
251}
252
253macro_rules! impl_matrix {
254    ($i:ident, $id:literal, $n:literal, $m:literal) => {
255        pub type $i = QGenericMatrix<$n, $m>;
256        // Safety:
257        //
258        // Static checks on the C++ side.
259        unsafe impl ExternType for $i {
260            type Id = type_id!($id);
261            type Kind = cxx::kind::Trivial;
262        }
263    };
264}
265
266impl_matrix!(QMatrix2x2, "QMatrix2x2", 2, 2);
267impl_matrix!(QMatrix2x3, "QMatrix2x3", 2, 3);
268impl_matrix!(QMatrix2x4, "QMatrix2x4", 2, 4);
269impl_matrix!(QMatrix3x2, "QMatrix3x2", 3, 2);
270impl_matrix!(QMatrix3x3, "QMatrix3x3", 3, 3);
271impl_matrix!(QMatrix3x4, "QMatrix3x4", 3, 4);
272impl_matrix!(QMatrix4x2, "QMatrix4x2", 4, 2);
273impl_matrix!(QMatrix4x3, "QMatrix4x3", 4, 3);
274
275#[cfg(test)]
276mod test {
277    use super::*;
278
279    #[rustfmt::skip]
280    const MATRIX: &QGenericMatrix<4, 2> = &QGenericMatrix::new(&[
281        [5.0, 4.0, 3.0, 2.0],
282        [6.0, 7.0, 8.0, 9.0],
283    ]);
284
285    #[test]
286    fn index() {
287        assert_eq!(MATRIX[(1, 2)], 8.0);
288    }
289
290    #[test]
291    fn data() {
292        assert_eq!(MATRIX.data(), [5.0, 6.0, 4.0, 7.0, 3.0, 8.0, 2.0, 9.0]);
293    }
294
295    #[test]
296    fn copy_data_to() {
297        let mut dest = [0.0; 8];
298        MATRIX.copy_data_to(&mut dest);
299        assert_eq!(dest, [5.0, 4.0, 3.0, 2.0, 6.0, 7.0, 8.0, 9.0]);
300    }
301
302    #[test]
303    fn fill() {
304        let mut filled = *MATRIX;
305        filled.fill(11.0);
306        assert_eq!(filled.data(), [11.0; 8]);
307    }
308
309    #[test]
310    fn filled() {
311        let filled = QGenericMatrix::<4, 2>::filled(11.0);
312        assert_eq!(filled.data(), [11.0; 8]);
313    }
314
315    #[test]
316    fn identity() {
317        let matrix = QGenericMatrix::<4, 2>::identity();
318        assert_eq!(matrix.rows(), [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]]);
319    }
320
321    #[test]
322    fn is_identity() {
323        let matrix = QGenericMatrix::new(&[
324            [1.0, 0.0, 0.0, 0.0],
325            [0.0, 1.0, 0.0, 0.0],
326            [0.0, 0.0, 1.0, 0.0],
327        ]);
328        assert!(matrix.is_identity());
329    }
330
331    #[test]
332    fn is_not_identity() {
333        let matrix = QGenericMatrix::new(&[
334            [1.0, 1.0, 0.0, 0.0],
335            [0.0, 1.0, 0.0, 0.0],
336            [0.0, 0.0, 1.0, 0.0],
337        ]);
338        assert!(!matrix.is_identity());
339    }
340
341    #[test]
342    fn rows() {
343        let rows = [[5.0, 4.0, 3.0, 2.0], [6.0, 7.0, 8.0, 9.0]];
344        let matrix = QGenericMatrix::new(&rows);
345        assert_eq!(matrix.rows(), rows);
346    }
347
348    #[test]
349    fn set_to_identity() {
350        let mut matrix = *MATRIX;
351        matrix.set_to_identity();
352        assert_eq!(matrix, QGenericMatrix::identity());
353    }
354
355    #[test]
356    fn transposed() {
357        let rows = MATRIX.transposed().rows();
358        assert_eq!(rows, [[5.0, 6.0], [4.0, 7.0], [3.0, 8.0], [2.0, 9.0]]);
359    }
360
361    #[test]
362    fn try_from_valid() {
363        let matrix =
364            QGenericMatrix::<4, 2>::try_from([5.0, 4.0, 3.0, 2.0, 6.0, 7.0, 8.0, 9.0].as_slice());
365        assert_eq!(matrix, Ok(*MATRIX));
366    }
367
368    #[test]
369    fn try_from_too_short() {
370        let matrix =
371            QGenericMatrix::<4, 2>::try_from([5.0, 4.0, 3.0, 2.0, 6.0, 7.0, 8.0].as_slice());
372        matrix.expect_err("Expected error, got");
373    }
374
375    #[test]
376    fn try_from_too_long() {
377        let matrix = QGenericMatrix::<4, 2>::try_from(
378            [5.0, 4.0, 3.0, 2.0, 6.0, 7.0, 8.0, 9.0, 1.0].as_slice(),
379        );
380        matrix.expect_err("Expected error, got");
381    }
382}