AcademySoftwareFoundation / AcademySoftwareFoundation/MaterialX
Invert direction of `rotate3d` in MaterialX v1.40
- Dominant language
- C++
- Stars
- 2.3k
- Forks
- 451
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 8
Description
it seem to assume that the matrix constructor is expecting to receive components of basis vectors as:
`X.x`, `Y.x`, `Z.x`, `W.x`, `X.y`, `Y.y`, `Z.y`, `W.y`, `X.z`, `Y.z`, `Z.z`, `W.z`, `X.w`, `Y.w`, `Z.w`, `W.w`
This is derived by the negative sign of the sine in the 2nd argument to the constructor, and positive sign in the 5th argument (see screenshots).
As far as I know, the correct expected ordering, in both OSL and GLSL, should actually be:
`X.x`, `X.y`, `X.z`, `X.w`, `Y.x`, `Y.y`, `Y.z`, `Y.w`, `Z.x`, `Z.y`, `Z.x`, `Z.w`, `W.x`, `W.y`, `W.x`, `W.w`
That is according to the OpenGL specification stating that the matrix representation holds the translation in the 13th, 14th and 15th components (out of 16).
And translation in a 4x4 matrix is a shear in the 4th dimension so is represented by the `W` basis vector.
And according to the [OSL specification the matrix constructor](https://open-shading-language.readthedocs.io/en/main/stdlib.html#matrix-functions) is expecting components in **row-major** order (which I interpret to mean, providing contiguous basis vectors one after the other)
This is further corroborated by the OSL overload `transform(matrix, vector4)` function bundled with the MaterialX library from [the official OSL repository](https://github.com/AcademySoftwareFoundation/OpenShadingLanguage/blob/main/src/shaders/vector4.h)
Where, the manual matrix/vector multiplication implementation contained, shows that the matrix indexing assumes contiguous basis vectors of `X` in `M[0]`, `Y` in `M[1]` , `Z` in `M[2]` and `W` in `M[3]` (see screenshot attached)
Using the MaterialX Graph Editor provided with the library, applying a -30 degrees rotation using the `rotate3d` node, actually shows a positive rotation.
This may in first glance seem correct as the pattern seem to turn clockwise as negative rotation would suggest.
Until one realizes that it is actually reversed, because the object itself is not rotating, it is the object-space position coordinates that are being rotated.
So, with a positive +30 degrees rotation, the object should appear as though the space around it is rotating clockwise, as it's rotated position grab values from the rotated coordinate frame.
This is illustrated in the attached image I made, and can be tested with the files attached.
So it would appear as though both OSL and GLSL implementation of `rotate3d` are incorrect (transposed in the matrix construction).
Included is the same behavior in Blender, using the OSL code from MaterialX.
From discussion on chat, it has been suggested that the actual reason is a deliberate choice to rotate from the other side of the axis vector. To that I've responded with the following:
Imagine an actual physical 3D model of an orthonormal basis, basically 3 cylindrical arrows, colored red, green and blue, and connected at their base as the origin.
When someone says to you, rotate this object by 30 degrees around the X axis, what do you do?
I think everyone would agree that you grab the red arrow, looking at it where it's arrow-tip is pointing at you, and rotating it counterclockwise to form a positive angle.
That is not a controversial convention, and is how 3D rotations work in any 3D math library that I know of.
OSL has a built-in `rotate()` function that does exactly that - it takes an arbitrary axis vector and an angle.
The only differences between it and the `rotate3d` node in MaterialX are:
1. It assumes the axis vector is normalized so does not normalize it
2. It works with radians, not degrees
3. It operates on a `point` and returns a `point` instead of a `vector`
MaterialX does not have a `point` type and I'm not suggesting that it would, and working with degrees is totally fine.
Normalizing in the MaterialX is also fine, it is safer that way.
But they definitely should both be rotating in the same direction.
In fact, I would go further and say that the OSL back-end implementation of MaterialX's `rotate3d_vector3` node should just be:
`vector result = (vector)rotate((point)in, axis, radians(amount))`
Currently, to make that work, the `amount` would have to be negated.
Video:
https://github.com/user-attachments/assets/b119d4a1-92c0-4e3a-a276-fd36eb1f7cc7
Additional test content:
[rotation3d_vector3.zip](https://github.com/user-attachments/files/17483537/rotation3d_vector3.zip)
Contributor guide
Research direction
Start with the rotate3d implementations for the OSL and GLSL backends and compare their matrix ordering and rotation convention with the attached rotation3d_vector3.zip examples. Confirm the intended behavior with the existing OSL rotate function and reproduce the reported direction mismatch. Done means the convention is resolved and both backends produce consistent rotation behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100