Skip to content

fix(ltk_ritobin): transpose Mat4 for correct row-major text output#108

Merged
Crauzer merged 4 commits intoLeagueToolkit:mainfrom
budlibu500:fix/matrix44-transpose
Mar 3, 2026
Merged

fix(ltk_ritobin): transpose Mat4 for correct row-major text output#108
Crauzer merged 4 commits intoLeagueToolkit:mainfrom
budlibu500:fix/matrix44-transpose

Conversation

@budlibu500
Copy link
Copy Markdown
Contributor

@budlibu500 budlibu500 commented Mar 1, 2026

Changes

  • Add .transpose() in the ritobin text writer before to_cols_array() so matrices are output in row-major order
  • Add .transpose() in the ritobin text parser after from_cols_array() to convert row-major text input into glam's column-major Mat4

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.

@Crauzer Crauzer requested review from Crauzer and alanpq March 1, 2026 16:06
@Crauzer Crauzer added the bug Something isn't working label Mar 1, 2026
@Crauzer Crauzer moved this to In Progress in league-toolkit Mar 1, 2026
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>
@budlibu500 budlibu500 force-pushed the fix/matrix44-transpose branch from eac09f3 to fca9a80 Compare March 1, 2026 16:20
@budlibu500 budlibu500 requested a review from Crauzer March 1, 2026 16:20
@alanpq
Copy link
Copy Markdown
Contributor

alanpq commented Mar 1, 2026

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 🔥 🚀

alanpq
alanpq previously approved these changes Mar 1, 2026
@budlibu500 budlibu500 changed the title fix(ltk_meta): use [f32; 16] for Matrix44 to fix transposed text output fix(ltk_ritobin): transpose Mat4 for correct row-major text output Mar 1, 2026
@Crauzer
Copy link
Copy Markdown
Member

Crauzer commented Mar 1, 2026

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();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be in reverse ?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Mat4 before to_cols_array() when writing text to emit row-major ordering.
  • Transpose Mat4 after from_cols_array() when parsing text to convert row-major input into glam’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();
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.

Copilot uses AI. Check for mistakes.
Comment on lines +212 to 213
let arr = v.value.transpose().to_cols_array();
for (i, val) in arr.iter().enumerate() {
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
@@ -431,7 +431,7 @@ fn parse_mtx44(input: Span) -> ParseResult<Mat4> {

let (remaining, _) = preceded(ws, char('}'))(remaining)?;

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
Ok((remaining, Mat4::from_cols_array(&values)))
Ok((remaining, Mat4::from_cols_array(&values).transpose()))
}

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
#[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);
}
}

Copilot uses AI. Check for mistakes.
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>
budlibu500 and others added 2 commits March 2, 2026 14:46
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@budlibu500 budlibu500 requested a review from Crauzer March 2, 2026 13:49
@Crauzer Crauzer merged commit f679209 into LeagueToolkit:main Mar 3, 2026
1 check passed
@github-project-automation github-project-automation bot moved this from In Progress to Done in league-toolkit Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants