Search Unity

Problems with quaternions and Rotation

Discussion in 'Entity Component System' started by Chris_Entropy, Sep 25, 2020.

  1. Chris_Entropy

    Chris_Entropy

    Joined:
    Apr 11, 2011
    Posts:
    202
    I am currently trying to create a simple first person character controller in ECS, more or less as a playfield to improve my ECS and Jobs programming. But I am stuck with the most simple of things. I want to rotate the player capsule according to the mouse movement, but the rotation makes weird jumps. For a certain range, the capsule rotates normally, but then it jumps about 180°.
    My code:
    Code (CSharp):
    1.    
    2. protected override JobHandle OnUpdate(JobHandle inputDeps)
    3.     {
    4.         Vector2 rawMovement = _controls.PlayerActions.Movement.ReadValue<Vector2>() * Time.DeltaTime;
    5.         Vector2 rawLook = _controls.PlayerActions.MouseLook.ReadValue<Vector2>();
    6.         float lookHorizontal = rawLook.x;
    7.         // float lookVertical = rawLook.y;
    8.        
    9.         var jobHandle =
    10.             Entities
    11.             .WithAll<Translation, Rotation, PlayerControllerData>()
    12.             .ForEach((ref Translation translation, ref Rotation rotation, in PlayerControllerData playerController) =>
    13.             {
    14.                 var movementDelta = rawMovement * playerController.MoveSpeed;
    15.                 translation.Value = translation.Value + new float3(movementDelta.x, 0f, movementDelta.y);
    16.                 Debug.Log("Rotating Horizontally: " + lookHorizontal);
    17.                 rotation.Value = math.mul(rotation.Value, quaternion.RotateY(lookHorizontal * Mathf.Deg2Rad));
    18.             })
    19.             .WithoutBurst()
    20.             .Schedule(inputDeps);
    21.  
    22.         return jobHandle;
    23.     }
    24.  
    I can't see anything weird happening with the input values, so I presume, that my line math.mul, where I multiply the current rotation with the move delta rotation (created with quaternion.RotateY) is the problem. Do you have any ideas, what I might be doing wrong?
     
  2. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    Try change:
    rotation.Value =  math.mul(rotation.Value, quaternion.RotateY(lookHorizontal * Mathf.Deg2Rad));

    To:
    rotation.Value = 
    math.normalize(math.mul(rotation.Value, quaternion.RotateY(math.radians(lookHorizontal ))));


    It works in my case. and as I look into the code.
    math did not normalize value in math.mul(quaternion,quaternion);
    but I think is intended. as you could mul() a long queue of quaternions and normalize only once. and normalize is expensive.
     
    Last edited: Sep 25, 2020
    bb8_1 and Chris_Entropy like this.
  3. Chris_Entropy

    Chris_Entropy

    Joined:
    Apr 11, 2011
    Posts:
    202
    Thank you for the answer. I tried it and it did not work. Then I started from the beginning. I am copying the Translate and Rotation values from an Entity to a GameObject, since there are (to my knowledge) no native ECS cameras, yet. So I thought that maybe something goes wrong when I copy the values. I added some mesh renderers to my transforms to be able to see their behaviour. Maybe the entities are moving correctly, but the values are not copied correctly to my camera? Lo and behold, the entities are moving and rotating correctly. But suddenly, the camera also behaved correctly. I removed the mesh renderers again, and it still worked correctly (with and without normalizing the quaternion).

    The solution was the following: in order to make it more visible, I made the transform for the vertical camera movement, which is a child of the main character controller object, a little bit longer. Before it had a scale of 0.1 / 0.1 / 0.1. Now it has a scale of 0.1 / 0.1 / 1. That's all it took. I am copying the values via the LocalToWorld component of the child transform from its Rotation property. It seems that I don't get a correct rotation value from that, when it has a uniform scale. Very weird.
     
  4. Chris_Entropy

    Chris_Entropy

    Joined:
    Apr 11, 2011
    Posts:
    202
    Ok, I am getting a clearer picture. When I tried to implement the Vertical camera movement, I had again strange behaviour. The camera always tilted a bit around the z-axis. But the entities are still looking normal. It seems like the Rotation returned by LocalToWorld.Rotation is affected by the scale value of the look rotation child object. So if any of its scale dimensions is NOT 1, I will get wrong values for rotation from the LocalToWorld component. Is this behaviour as intended or should I report this as a bug?
     
  5. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    That is a Shear transform. rotated child in None uniform scaled parent will have this side-effect.
    That's LinearAlgebra's bug. we can only report to math community. Not Unity ;)

    You can decompose shear/rotation from float3x3 but it's complicated.
    You should be able to avoid None uniform scale. In most of the case, It's just a mesh that is stopping you from doing so. You can try to put the mesh in a transform branch and scale itself. Or if you want to change camera distance. adjust position instead of scale. Don't set scale just because it's handy.

    Code (CSharp):
    1.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    2.         public static void DecomposeRotationScaleShear(in float3x3 rotScSh, out float3 scale, out float3x3 shear, out float3x3 rotation, MainAxisID mainAxis = MainAxisID.ZAxis)
    3.         {
    4.             var hasValidScale = DecomposeRotationScale(rotScSh, out scale, out rotation, out var shearRot, mainAxis);
    5.             shear = hasValidScale ? math.mul(shearRot, math.inverse(rotation)) : shearRot;
    6.         }
    7.  
    8.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
    9.         internal static bool DecomposeRotationScale(this float3x3 rotScSh, out float3 scale, out float3x3 rotation, out float3x3 shearRotation, MainAxisID mainAxis = MainAxisID.ZAxis)
    10.         {
    11.             float3 c0 = rotScSh.c0, c1 = rotScSh.c1, c2 = rotScSh.c2;
    12.             var lengthSq = math.float3(math.dot(c0, c0), math.dot(c1, c1), math.dot(c2, c2));
    13.             bool hasValidScale =
    14.                 math.cmin(lengthSq) > Epsilon &&
    15.                 math.cmax(lengthSq) < EpsilonRcp &&
    16.                 math.isfinite(lengthSq.x) &&
    17.                 math.isfinite(lengthSq.y) &&
    18.                 math.isfinite(lengthSq.z);
    19.             if (hasValidScale)
    20.             {
    21.                 var mainAxisID = (int)mainAxis;
    22.                 var secondAxisID = (mainAxisID + 1) % 3;
    23.                 var thirdAxisID = (secondAxisID + 1) % 3;
    24.                 scale = math.sqrt(lengthSq);
    25.                 scale[thirdAxisID] = (math.determinant(rotScSh) < 0f) ? -scale[thirdAxisID] : scale[thirdAxisID];
    26.                 var scaleRcp = math.rcp(scale);
    27.                 shearRotation = math.float3x3(c0 * scaleRcp.x, c1 * scaleRcp.y, c2 * scaleRcp.z);
    28.                 rotation = RotationFromAxisAssumeNormalizedValid(shearRotation[mainAxisID], shearRotation[secondAxisID], mainAxisID);
    29.             }
    30.             else
    31.             {
    32.                 scale = 1;
    33.                 rotation = float3x3.identity;
    34.                 shearRotation = rotScSh;
    35.             }
    36.             return hasValidScale;
    37.         }
     
    Last edited: Sep 25, 2020
    Chris_Entropy likes this.