fix(ltk_ritobin): transpose Mat4 for correct row-major text output#108
fix(ltk_ritobin): transpose Mat4 for correct row-major text output#108Crauzer merged 4 commits intoLeagueToolkit:mainfrom
Conversation
The text writer output matrices in column-major order because it called to_cols_array() without transposing first. The text parser also lacked a transpose when constructing Mat4 from row-major text input. Add .transpose() in both the writer (Mat4 -> row-major text) and the parser (row-major text -> Mat4) to match the binary reader/writer which already handle this correctly via read/write_mat4_row_major. Co-Authored-By: FrogCslol <lustigleandro@gmail.com>
eac09f3 to
fca9a80
Compare
|
Agreed — keeping Mat4 and just transposing/reordering is cleaner than switching to [f32; 16]. The PR needs to be reworded/renamed to match the new changes — but other than that it LGTM — Let's keep cooking 🔥 🚀 |
|
Can you also write tests to make sure that the ordering is correct when doing a parsing round-trip ? |
| self.write_raw("{\n"); | ||
| self.indent(); | ||
| let arr = v.value.to_cols_array(); | ||
| let arr = v.value.transpose().to_cols_array(); |
There was a problem hiding this comment.
I think this might be in reverse ?
There was a problem hiding this comment.
Pull request overview
Fixes mtx44 handling in the ritobin text path so glam::Mat4 values are written and read in row-major order (matching the text format expectations), by transposing at the serialization/deserialization boundaries.
Changes:
- Transpose
Mat4beforeto_cols_array()when writing text to emit row-major ordering. - Transpose
Mat4afterfrom_cols_array()when parsing text to convert row-major input intoglam’s column-major representation.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
crates/ltk_ritobin/src/writer.rs |
Adjusts mtx44 text output to row-major order via transpose-before-write. |
crates/ltk_ritobin/src/parser.rs |
Adjusts mtx44 text parsing to interpret row-major input via transpose-after-read. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -209,7 +209,7 @@ impl<'a, H: HashProvider> TextWriter<'a, H> { | |||
| PropertyValueEnum::Matrix44(v) => { | |||
| self.write_raw("{\n"); | |||
| self.indent(); | |||
There was a problem hiding this comment.
The matrix text serialization order is subtle (ritobin text appears to be row-major while glam::Mat4 is column-major). Please add an inline comment here explaining why .transpose() is required before to_cols_array() so future refactors don’t accidentally remove/double-apply it.
| self.indent(); | |
| self.indent(); | |
| // ritobin text stores matrices in row-major order, but glam::Mat4 is column-major. | |
| // Transpose before to_cols_array() so the serialized values are written row by row. |
| let arr = v.value.transpose().to_cols_array(); | ||
| for (i, val) in arr.iter().enumerate() { |
There was a problem hiding this comment.
This change fixes a correctness issue in matrix formatting, but there are no tests covering mtx44 write behavior. Please add a unit/integration test that writes a known Mat4 (e.g., a translation matrix) and asserts the emitted 16 floats are in the expected row-major order (and/or that parse->write roundtrips preserve the matrix).
| @@ -431,7 +431,7 @@ fn parse_mtx44(input: Span) -> ParseResult<Mat4> { | |||
|
|
|||
| let (remaining, _) = preceded(ws, char('}'))(remaining)?; | |||
|
|
|||
There was a problem hiding this comment.
Similar to the writer side, please document why this .transpose() is necessary (row-major text input vs glam::Mat4 column-major storage). Without a note, it’s easy to remove this during cleanup and reintroduce the bug.
| // The text format stores the 16 matrix elements in row-major order, but | |
| // `Mat4::from_cols_array` interprets the slice as column-major. Transpose | |
| // here to convert from the row-major input layout to glam's column-major | |
| // internal storage. Without this, matrices will be interpreted incorrectly. |
| Ok((remaining, Mat4::from_cols_array(&values))) | ||
| Ok((remaining, Mat4::from_cols_array(&values).transpose())) | ||
| } | ||
|
|
There was a problem hiding this comment.
There are parser tests for several value kinds, but none for mtx44. Please add a test that parses a row-major 4x4 (ideally one with non-symmetric values, e.g. translation + scaling) and asserts the resulting Mat4 matches the expected transform; this will lock in the corrected transpose behavior.
| #[cfg(test)] | |
| mod mtx44_tests { | |
| use super::{parse_mtx44, Span}; | |
| use glam::{Mat4, Quat, Vec3}; | |
| #[test] | |
| fn parse_mtx44_row_major_translation_and_scaling() { | |
| // Construct an expected transform using glam's column-major API: | |
| // scaling (2, 3, 4) and translation (10, 20, 30), with no rotation. | |
| let scale = Vec3::new(2.0, 3.0, 4.0); | |
| let translation = Vec3::new(10.0, 20.0, 30.0); | |
| let expected: Mat4 = | |
| Mat4::from_scale_rotation_translation(scale, Quat::IDENTITY, translation); | |
| // Derive a row-major representation of `expected`. | |
| // `to_cols_array` returns column-major elements of its receiver, | |
| // so using `expected.transpose()` yields the rows of `expected`. | |
| let row_major = expected.transpose().to_cols_array(); | |
| // Format the row-major values into the text format expected by `parse_mtx44`. | |
| let input_str = format!( | |
| "{{ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} }}", | |
| row_major[0], | |
| row_major[1], | |
| row_major[2], | |
| row_major[3], | |
| row_major[4], | |
| row_major[5], | |
| row_major[6], | |
| row_major[7], | |
| row_major[8], | |
| row_major[9], | |
| row_major[10], | |
| row_major[11], | |
| row_major[12], | |
| row_major[13], | |
| row_major[14], | |
| row_major[15], | |
| ); | |
| let span = Span::new(&input_str); | |
| let (remaining, parsed) = parse_mtx44(span).expect("parse_mtx44 should succeed"); | |
| // Ensure we've consumed all significant input. | |
| assert!(remaining.fragment().trim().is_empty()); | |
| // The parsed matrix should match the original column-major transform. | |
| assert_eq!(parsed, expected); | |
| } | |
| } |
Add test_matrix44_roundtrip_ordering to verify that Mat4 values survive a text round-trip with correct row/column ordering. Also add inline comments on both transpose calls explaining why they're needed (row-major text vs column-major glam). Co-Authored-By: FrogCslol <lustigleandro@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changes
Root Cause
The text writer called to_cols_array() without transposing, outputting column-major order instead of row-major. Translation values appeared in the wrong row.
The binary reader/writer already handle this correctly — only the text path was missing it.