Search Unity

What do the float4 values in LocalToWorld mean, and how can I use them?

Discussion in 'Data Oriented Technology Stack' started by MadboyJames, Aug 8, 2019.

  1. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    95
    I have a child object, attached to a rotating parent. On the child I have these values seen in the entity debugger during runtime. The parent is rotating along the Z axis at a rate of .1 per second. Capture.PNG
    The bottom float4 (c3 in script), containing (-4.2,2.5,-1.5,1), is the position. I do not know what the W is in c3. scale somehow?
    The first three float4s (c0, c1, c2 in script) are related to rotation, but I have no idea how it maths out, or how to use those values to get a world rotation for my child entity.
    Anyone have any ideas how c0, c1 and c2 can be used, and how they actually reflect the rotation?
     
  2. elcionap

    elcionap

    Joined:
    Jan 11, 2016
    Posts:
    87
    You can extract the rotation using math.quaternion(localToWorld.Value) and there others like math.transform to work with positions.
    LocalToWorld also provide Position and a Forward vector for example.

    About how that data is stored in the float4x4, I think you should look for "transformation matrix". You can find everything in the wikipedia. I don't like directing people to links this way but I think it will explain this better than I'll ever be able to.

    []'s
     
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,559
    Is it just me or does math.quaternion(localToWorld.Value) not work if the transformation matrix has scale?

    Code (CSharp):
    1. [TestCase(1, 1, 1)]
    2.         [TestCase(2, 3, 4)]
    3.         [TestCase(12, 33, 123)]
    4.         public void RotationTest(float scaleX, float scaleY, float scaleZ)
    5.         {
    6.             var scale = new float3(scaleX, scaleY, scaleZ);
    7.             var e = new float3(0.1f, 0.2f, 0.3f);
    8.             var position = new float3(0, 0, 0);
    9.             var rotation = quaternion.EulerXYZ(e);
    10.  
    11.             var localToWorld = float4x4.TRS(position, rotation, scale);
    12.  
    13.             var result = math.quaternion(localToWorld);
    14.  
    15.             Debug.Log($"With Scale {scale}");
    16.             Debug.Log($"Result: {result}");
    17.             Debug.Log($"Expected: {rotation}");
    18.  
    19.             AssertMath.AreApproximatelyEqual(rotation, result, 0.00001f);
    20.         }
    Maybe the resulting quaternion ends up in same position? I don't know the maths good enough and I haven't tested but there was a case where I want to get the original rotation back so I wrote my own Rotation extension to first remove scale from the matrix

    Code (CSharp):
    1.         public static quaternion Rotation(this LocalToWorld localToWorld)
    2.         {
    3.             var scale = localToWorld.Value.GetScale();
    4.             var inverted = scale.Invert();
    5.             var value = localToWorld.Value.ScaleBy(inverted); // remove the scale
    6.  
    7.             return new quaternion(value);
    8.         }
    And this seems to work perfect

    Code (CSharp):
    1.         [TestCase(1, 1, 1)]
    2.         [TestCase(2, 3, 4)]
    3.         [TestCase(12, 33, 123)]
    4.         public void Rotation(float scaleX, float scaleY, float scaleZ)
    5.         {
    6.             var scale = new float3(scaleX, scaleY, scaleZ);
    7.             var e = new float3(0.1f, 0.2f, 0.3f);
    8.  
    9.             var position = new float3(0, 0, 0);
    10.             var rotation = quaternion.EulerXYZ(e);
    11.  
    12.             var localToWorld = new LocalToWorld
    13.             {
    14.                 Value = float4x4.TRS(position, rotation, scale),
    15.             };
    16.  
    17.             var result = localToWorld.Rotation();
    18.  
    19.             Debug.Log($"With Scale {scale}");
    20.             Debug.Log($"Result: {result}");
    21.             Debug.Log($"Expected: {rotation}");
    22.  
    23.             AssertMath.AreApproximatelyEqual(rotation, result, 0.00001f);
    24.         }
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    371
    You need to divide out scale from a LocalToWorld matrix to get a matrix capable of extracting the rotation.
    Code (CSharp):
    1. ?/x    ?      ?      ?
    2. ?      ?/y    ?      ?
    3. ?      ?      ?/z    ?
    4. ?      ?      ?      ?
    The w in c3 is a 1 because that's part of the identity matrix.
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    371
    Also I should mention, if you need both translation and rotation from a float4x4, after dividing out the scale, use RigidTransform to extract both at once.
     
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,559
    Oh good so I was doing it right. Nice to have a confirmation.
     
  7. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    81
    for a float4x4 transformation matrix:
    c0.xyz represents the direction and scale of the local right vector (c0.w must be 0)
    c1.xyz represents the direction and scale of the local up vector (c1.w must be 0)
    c2.xyz represents the direction and scale of the local forward vector (c2.w must be 0)
    c3.xyz represents the position (c3.w must be 1)
     
  8. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    95
    Thank you all for your response! That clears up a lot for me. I do not have .GetScale() and .ScaleBy() though. What library is that in?
     
  9. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,559
    Sorry totally forgot to reply to you. They are just other extension methods I wrote. They are nothing special

    Code (CSharp):
    1.         public static float3 GetScale(this float4x4 matrix) => new float3(
    2.             math.length(matrix.c0.xyz),
    3.             math.length(matrix.c1.xyz),
    4.             math.length(matrix.c2.xyz));
    Code (CSharp):
    1. public static float4x4 ScaleBy(this float4x4 matrix, float3 scale) => math.mul(matrix, float4x4.Scale(scale));
     
  10. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    95
    Makes sense. Good to see the math behind that too. Thank you!
     
  11. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    95
    is .Invert() doing:
    new float3(scale.z,scale.y,scale.x);
    ?
     
  12. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,559
    Code (CSharp):
    1.       public static float3 Invert(this float3 f)
    2.         {
    3.             Assert.IsFalse(math.any(f == float3.zero));
    4.  
    5.             return new float3(1 / f.x, 1 / f.y, 1 / f.z);
    6.         }
     
  13. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    95
    Ah, okay that works a bit better. I was doing the wrong type of inverting. Although it doesn't update the rotational position. I think I'm doing something rather wrong. Here is my code to input information into a spawnshotdata struct that will be used later to instantiate a projectile.
    Code (CSharp):
    1. Entities.ForEach((ref SpawnShotComponent spawnShot, ref LocalToWorld localToWorld, ref NonUniformScale scale) =>
    2.              {
    3.                
    4.  
    5.                      float3 tempScale = new float3(scale.Value.x, scale.Value.y, scale.Value.z);
    6.                      float3 tempPos = new float3(localToWorld.Value.c3.x, localToWorld.Value.c3.y, localToWorld.Value.c3.z);
    7.                      float3 e = new float3(0.1f, 0.2f, 0.3f);
    8.                      quaternion tempRot = quaternion.EulerXYZ(e);
    9.  
    10.                      var tempLocalToWorld = new LocalToWorld
    11.                      {
    12.                        Value=  float4x4.TRS(tempPos, tempRot, tempScale)
    13.                      };
    14.  
    15.                      SpawnShotData data = new SpawnShotData
    16.                      {
    17.                          bulletPrefab = spawnShot.bulletPrefab,
    18.                          position = tempPos,
    19.                          rotation = tempLocalToWorld.Rotation(),
    20.                          heading = math.mul(tempLocalToWorld.Rotation(), spawnShot.forwardDirection),
    21.                          id = spawnShot.id
    22.                      };
    23.   //list management is below here. Code has been stripped to relevant data                
    24. }
    There must be something in the Rotation(scale x, scale y, scale z) method that I am missing (possibly the type of scale to enter). I deeply appreciate you helping me out. I did not know that custom extensions were a thing until today.
     
  14. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    95
    If I replace line 5 with
    float3 tempScale = localToWorld.Value.GetScale();

    it still does the same thing (spawns at roughly a 45 degree angle, regardless of spawner rotation)
    Additionally, the projectile entity has it's rotation set by
    [EntityManager.SetComponentData(newProjectiles[j], new Rotation { Value = data.rotation });

    for reference.
     
    Last edited: Aug 15, 2019
  15. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    95
    Oh. I just had to use the quaternion.Rotate function on the base localToWorld and it worked perfectly. I'm not sure what the other math was then with creating the new LocalToWorld, but thank you!