Skip to content

[raymath] Fix MatrixCompose() rotating vectors with already overwritten components - #6187

Merged
raysan5 merged 1 commit into
raysan5:masterfrom
ethanstoner:fix-matrix-compose
Sep 25, 2026
Merged

raysan5 merged 1 commit into
raysan5:masterfrom
ethanstoner:fix-matrix-compose

Conversation

@ethanstoner

Copy link
Copy Markdown
Contributor

#6133 inlined Vector3RotateByQuaternion() into MatrixCompose(), but the inlined version writes each component in place and the next line reads it back:

right.x = right.x*(...) + right.y*(...) + right.z*(...);
right.y = right.x*(...) + ...   // uses the new right.x

Vector3RotateByQuaternion() writes to a separate result, so it never had this problem. Most rotations now give a wrong matrix (a 180 degree turn about the X, Y or Z axis happens to come out right). With a 90 degree rotation about Z, the first column comes out all zeros.

This keeps a copy of each vector (temp) and reads from it, so the formulas are the same ones Vector3RotateByQuaternion() uses.

Test, comparing against scale, then rotation, then translation built from existing raymath functions:

Vector3 t = { 1.0f, 2.0f, 3.0f };
Vector3 s = { 2.0f, 3.0f, 4.0f };
Quaternion q = QuaternionFromAxisAngle((Vector3){ 0.0f, 0.0f, 1.0f }, PI/2.0f);

Matrix composed = MatrixCompose(t, q, s);
Matrix expected = MatrixMultiply(MatrixMultiply(MatrixScale(s.x, s.y, s.z), QuaternionToMatrix(q)), MatrixTranslate(t.x, t.y, t.z));

Vector3 a = Vector3Transform((Vector3){ 1.0f, 1.0f, 1.0f }, composed);
Vector3 b = Vector3Transform((Vector3){ 1.0f, 1.0f, 1.0f }, expected);

Before (master):

identity             max|diff|=0.0000  transform(1,1,1): compose=(3.000, 5.000, 7.000) expected=(3.000, 5.000, 7.000)
90 deg about Z       max|diff|=3.0000  transform(1,1,1): compose=(-2.000, -1.000, 7.000) expected=(-2.000, 4.000, 7.000)
45 deg about Y       max|diff|=2.0000  transform(1,1,1): compose=(5.243, 5.000, 2.828) expected=(5.243, 5.000, 4.414)
euler(0.3,0.5,0.7)   max|diff|=1.2227  transform(1,1,1): compose=(2.984, 5.864, 6.404) expected=(2.984, 5.873, 6.173)

After:

identity             max|diff|=0.0000  transform(1,1,1): compose=(3.000, 5.000, 7.000) expected=(3.000, 5.000, 7.000)
90 deg about Z       max|diff|=0.0000  transform(1,1,1): compose=(-2.000, 4.000, 7.000) expected=(-2.000, 4.000, 7.000)
45 deg about Y       max|diff|=0.0000  transform(1,1,1): compose=(5.243, 5.000, 4.414) expected=(5.243, 5.000, 4.414)
euler(0.3,0.5,0.7)   max|diff|=0.0000  transform(1,1,1): compose=(2.984, 5.873, 6.173) expected=(2.984, 5.873, 6.173)

The "after" numbers match what MatrixCompose() returned before #6133. raymath has no automated tests, so I checked this with the standalone program above (built as both C99 and C++), and I rebuilt the library and a few models examples on Linux with gcc. On Windows the program also builds with MinGW gcc and MSVC and gives the same numbers.

@raysan5

raysan5 commented Sep 25, 2026

Copy link
Copy Markdown
Owner

@ethanstoner oh! good catch! thanks for the review!

@raysan5
raysan5 merged commit fafa2ce into raysan5:master Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants