Why does my rotator from matrix always faces positive world axes

I can’t say whether it’s a bug, feature or if I just don’t get something, but this puzzles me alot. When I create a matrix from a normal vector with FRotationMatrix::MakeFromZ I will get proper matrix where Z points into specified direction. After I create a rotator from it by using Rotator() method, I get a rotator that always faces positive x, y and z axes. This causing my particle effects to look weird.

Examples, normal vector (matrix Z axis) → rotator Z axis

1, 0, 0 → 1, 0, 0

-1, 0, 0 → 1, 0, 0

0, -1, 0 → 0, 1, 0

etc. This happens for all axes as well as for rotated angles so that the effect always points into positive world quadrad. See below the code I’m using.

    FMatrix matrix = FRotationMatrix::MakeFromZ(normal);
    FRotator rotation = matrix.Rotator();
    UGameplayStatics::SpawnEmitterAtLocation(this, pSystem, position, rotation, true);

Any ideas what is wrong?

Edit

I did additional testing and found out that its indeed something to do with rotator (or how I use it). I made following test and ran it twice for vector opposite to each other.

Code I used:

FMatrix matrix = FRotationMatrix::MakeFromZ(normal);
FRotator rotation = matrix.Rotator();

FQuat fromMatrix = FQuat(matrix);
FQuat fromRotator = FQuat(rotation);
LOG4(_T("Matrix %f %f %f %f"), fromMatrix.X, fromMatrix.Y, fromMatrix.Z, fromMatrix.W);
LOG4(_T("Rotator %f %f %f %f"), fromRotator.X, fromRotator.Y, fromRotator.Z, fromRotator.W);

I got following results. The latter one gives different result from same input.

Using vector V

Matrix 0.707107 0.000001 0.000001 0.707107

Rotator 0.707107 0.000001 0.000001 0.707107

Using vector -V

Matrix -0.000001 0.707107 0.707107 -0.000001

Rotator 0.000001 -0.707107 -0.707107 0.000001

What I’m trying to do is to create a particle effect where bullet hits and this effect needs to be normal to the surface. It works for other side of a wall, but not for another side. I can see this behavior for floors and in walls of any direction.

The normal comes from line trace. What the MakeFromZ does (checked the source and the documentation as well) is that it tries to use Z-axis for another axis and if can’t then uses X-axis. Then it calculates normals with cross product. This is a valid method and I have done it before myself (in DX world). I used the result matrix and plotted each axes by using DrawDebugLine and it actually works as expected. So I find it very unlikely that it would not work properly.

Even with rounding errors, I think the result of same operation with same input should always be (about) the same. Now with FRotator and FQuat I get same results with one vector and completely different result with another vector. This doesn’t make sense to me… So if the difference would be small it would make sense, now it flips X-axis to -X or so.

You are trying to create an entire rotation matrix from a single vector, and I think that’s what is tripping you up. Where are you getting your normal that you are using as the argument for MakeFromZ()? You mention particles, so I assume that it might be particles you are spawning when a ray cast hits an object. If that is the case, you should be able to get the tangent or binormal vector of the point of impact and the proceed to use MakeFromZX() (or whichever fits your use case) to reliably calculate a rotation matrix.

Basically you need at least two vectors to accurately compute rotations, otherwise you’re giving the code a lot of freedom by saying “here is one vector, do whatever it takes to find me two other vectors and make a rotation matrix out of them.” Internally the math there looks good, yes, but you’re still relying on an approximation where you should be able to get a perfect result.

For clarification, what you are experiencing by having the “axis flipped” on the quaternion is to be expected. There you are going from a matrix to a quaternion, which is an accurate conversion, and testing the result against an euler angle (FRotator) to a quaternion, which is a lossy conversion.

It shouldn’t be the same, and in some cases it’s impossible for it to be the same. That’s just the way the math works out. Going from quaternions to eulers you do in fact lose “accuracy” like that. Quaternions and rotation matrices will always have valid states of orientation, but there’s no guarantee that the orientation will still be valid when converted to euler angle. That angle might be in a state of gimbal lock.

What direction do the particles spray with zero rotation applied?

It’s actually getting really confusing. I did a following test with my particle system (it spawns few particles to +Z direction).

    FRotation rotator = FRotator(90.f, 0.f, 0.f);
    UGameplayStatics::SpawnEmitterAtLocation(this, pSystem, position, rotator, true);
    rotator = FRotator(-90.f, 0.f, 0.f);
    UGameplayStatics::SpawnEmitterAtLocation(this, pSystem, position, rotator, true);

It spawns 2 particle emitters which BOTH spray to world X-axis.

    FRotation rotator = FRotator(0.f, 0.f, 90.f);
    UGameplayStatics::SpawnEmitterAtLocation(this, pSystem, position, rotator, true);
    rotator = FRotator(0.f, 0.f, -90.f);
    UGameplayStatics::SpawnEmitterAtLocation(this, pSystem, position, rotator, true);

It spawns 2 particle emitters which BOTH spray to world Y-axis.

Strange indeed…

What do you mean “unstable”?

It actually seems so that the angles are very unstable in angles near PI/2. Thats really annoying. Any idea what can be done to it?

I think the math might be breaking down near a singularity. You’re just placing them in the editor? As in, it’s definitely not your code that determines the rotations of these particles? Once again converting rotations is gross, and it’s never ideal to convert anything to euler angles except for making them user-friendly to edit.

Up, +Z-axis.

If I edit a placed emitter in the editor alone and set angle near to +/-90 deg and just keep pressing enter, it swaps its direction back and forth to left and right (90 degrees). Feature of rotations?

I created a new effect and assigned it to my code. It now works flawlesly. I can’t say what is wrong with the particle emitter I have implemented originally and why the new one behaves differently. I will need to continue investigating what is happening.

Thanks for you guys for giving me motivation and pointers.

Found out that the problem is Velocity/Life module. Unless the “In World Space” is checked it behaves badly. That enabled it works as expected.