# 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.

Joined:
Oct 28, 2017
Posts:
180
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.
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?

GliderGuy likes this.
2. ### elcionap

Joined:
Jan 11, 2016
Posts:
133
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

Joined:
Jan 25, 2011
Posts:
2,180
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.         }

GliderGuy likes this.
4. ### DreamingImLatios

Joined:
Jun 3, 2017
Posts:
1,330
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

Joined:
Jun 3, 2017
Posts:
1,330
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

Joined:
Jan 25, 2011
Posts:
2,180
Oh good so I was doing it right. Nice to have a confirmation.

7. ### TRS6123

Joined:
May 16, 2015
Posts:
136
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)

GliderGuy likes this.

Joined:
Oct 28, 2017
Posts:
180
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?

GliderGuy likes this.
9. ### tertle

Joined:
Jan 25, 2011
Posts:
2,180
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));

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

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

12. ### tertle

Joined:
Jan 25, 2011
Posts:
2,180
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.         }

Joined:
Oct 28, 2017
Posts:
180
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(),
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.

Joined:
Oct 28, 2017
Posts:
180
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