Table of Contents

Here’s the same matrix as before, but expressed differently :

{{ 1, 2, 3, 0 } { 5, 6, 7, 0 } { 9, 10, 11, 0 } { 13, 14, 15, 1 }}

Woah, *big deal*. You’ve added some curly brackets.

...yeah. But here’s another thing :

TV_4DVECTOR m1 = new TV_4DVECTOR(1, 2, 3, 0); TV_4DVECTOR m2 = new TV_4DVECTOR(5, 6, 7, 0); TV_4DVECTOR m3 = new TV_4DVECTOR(9, 10, 11, 0); TV_4DVECTOR m4 = new TV_4DVECTOR(13, 14, 15, 1);

That’s the same matrix, just split in four.

In fact, in Direct3D, the cells of a matrix are accessed by a coordinate defined by `[row, column]`

, so that the cells of a TV6.5 `TV_3DMATRIX`

are accessed by those coordinates :

{{ m11, m12, m13, m14 } { m21, m22, m23, m24 } { m31, m32, m33, m34 } { m41, m42, m43, m44 }}

And by the way, they’re called `3DMATRIX`

even if they are in fact 4D. Go figure.

Now that it’s settled, we can define matrix multiplication. Then you’ll see why I split it in the first place.

When any 3D point is transformed :

- Scaled
- Moved
- Rotated

It invoves matrix multiplication in the background.

It’s a wierd process, but it all makes sense in context. Here goes :

| | | | | | | A nD VECTOR | * | A nD MATRIX | = | A nD VECTOR | | | | | | | [ X, Y, Z, 1 ] * | 1 2 3 0 | = [ X + 5Y + 9Z + 13, 2X + 6Y + 10Z + 14, 3X + 7Y + 11Z + 15, 1 ] | 5 6 7 0 | | 9 10 11 0 | | 13 14 15 1 |

That was probably confusing. But take it column-by-column :

[ A nD VECTOR ] * [ A COLUMN OF THE MATRIX AS A VERTICALLY TRANSPOSED nD VECTOR ] = A SINGLE VALUE [ X, Y, Z, 1 ] * | 1 | = 1X + 5Y + 9Z + 13 | 5 | | 9 | | 13 |

As messed up as it might be, two 4D vectors multiplied together end up in a single value. Then, the matrix’s columns are processed one by one to give the resulting vector’s `X, Y, Z`

components.

- It’s the position of the vertex, triangle or mesh we’re transforming,
*stored as a vector*.

- It’s 4D because everytime you supply a 3D vector to TV3D, in the background TV3D adds up that little
`1`

in the 4th component of the vector. Let’s call it`W`

. - Its existance is necessary for translation/movement to work! I’ll explain later on.

- Because it’s simply impossible to multiply two matrices unless the number of colums of the first one is equal to the number of rows in the second one! Since we are multiplying a 1×4 vector to a 4×4 matrix, the only possible order is Vector * Matrix.
- As a general rule, two matrices must verify the following property in order for multiplication to be valid: [a x n]*[n x b]

- All in good time.

OK, let’s start by the simpler stuff. How can translation, or movement, be encoded in a 4D matrix, or in a 4-length array of 4D vectors?

The `W`

of the “position vector” we’re processing will always be 1, since TV3D forces it in. That means that whatever the rest of the matrix is, the fourth row of the matrix will always be added up to the resulting vector.

*Note :* At this point, I’ll remove the fourth column as well because it doesn’t mean anything in this context.

We could then simplify the multiplication operation like so :

[ X, Y, Z ] * | 1 2 3 | = [ X + 5Y + 9Z, 2X + 6Y + 10Z, 3X + 7Y + 11Z ] + [ 13, 14, 15 ] | 5 6 7 | | 9 10 11 |

Already less numbers, woot.

So since `[ 13, 14, 15 ]`

is just added to the result,... it looks like a translation indeed!

There you have it. The fourth row of a mesh transformation matrix is the following : `[Translation.x, Translation.y, Translation.z, 1]`

.

That’s one down, two to go. Scaling is the next easy one.

If we clear all terms of the matrix except its **diagonal**, which means those :

[ X, Y, Z ] * | 1 0 0 | = [ X, 6Y, 11Z ] | 0 6 0 | | 0 0 11 |

We can see that :

- The
`X`

component of the 1st row in the matrix is multiplied with the`X`

component of the vector - The
`Y`

component of the 2nd row is multiplied with the`Y`

component of the vector - The
`Z`

component of the 3nd row is multiplied with the`Z`

component of the vector

Say, that looks a lot like scaling. We want to scale the mesh on the three axis, by defined values... we can fit them into a matrix!

Rotation, on the other hand, needs a topic on its own...

Now all the stuff about vectors above will come in useful.

Let’s not forget that the rows of the 3D matrix are vectors, with components too :

{{ m1.x, m1.y, m1.z }, { m2.x, m2.y, m2.z }, { m3.x, m3.y, m3.z }}

So in full added-up glory, this gives (I’ll **transpose** `(^t)`

vertically the resulting vector for clarity) :

[ X, Y, Z ] * {{ m1.x, m1.y, m1.z } = | m1.x * X + m2.x * Y + m3.x * Z |^t <- X { m2.x, m2.y, m2.z } | m1.y * X + m2.y * Y + m3.y * Z | <- Y { m3.x, m3.y, m3.z }} | m1.z * X + m2.z * Y + m3.z * Z | <- Z

Isn’t wierd how we take components from `X, Y`

and `Z`

to get `X`

? And same for `Y`

and `Z`

? You’ll see...

OK, let’s take it simply. What if there is no rotation applied to the mesh. We want it to look straight ahead, no transformation done on the initial mesh. Then the only way of getting that would be with `1`

’s on the diagonal :

[ X, Y, Z ] * {{ 1, 0, 0 } = | X |^t <- X { 0, 1, 0 } | Y | <- Y { 0, 0, 1 }} | Z | <- Z

Well that was just dumb. Why make the multiplication anyway, eh. But let’s analyze the vectors separately!

- We can see that its
`X`

component equals`1`

, but the rest is 0. Then, we can simply say that it’s facing**right**, and its length is`1`

.

- We can see that its
`Y`

component equals`1`

, but the rest is 0. Then, we can simply say that it’s facing**up**, and its length is`1`

.

- More of the same : its
`Z`

component equals`1`

, rest is 0. It’s facing**forward**, and its length is`1`

as well.

Interesting... That would mean that the matrix is formed like this :

{ Normalized Right-Facing Vector, Normalized Up-Facing Vector, Normalized Forward-Facing Vector }

That’s a bit fast to draw such a big conclusion... but you can take it for granted; it’s the format of a rotation matrix.

Alright, let’s take another fairly simple case : you’re **looking** UP, and **above you** it’s parallel to the ground BEHIND YOU. That would mean that at your **right**, it’s still the same RIGHT as normal.

In other words, you’re laying on your back and looking into the sky. You haven’t turned around so your right is still the world’s right, and above the top of your head is the ground that was behind you.

Then, logically :

- The relative
**Right-Facing**vector will point in*positive X* - The relative
**Up-Facing**vector will point in*negative Z* - The relative
**Forward-Facing**vector will point in*positive Y*

And here’s the rotation matrix for that situation :

[ X, Y, Z ] * {{ 1, 0, 0 } = | X |^t <- X { 0, 0, -1 } | Z | <- Y { 0, 1, 0 }} | -Y | <- Z

But you ain’t naïve, and you want proof of my hastily-explained vector algebra. There you go then :

If you have a point that was in front of you; let’s say your nose. It’s right in front of your eye, at `(0, 0, 1)`

in the positive Z axis. What happens if we transform our body with that rotation matrix, including our nose? Where would it stand?

[ 0, 0, 1 ] * {{ 1, 0, 0 } = | 0 |^t <- X { 0, 0, -1 } | 1 | <- Y { 0, 1, 0 }} | 0 | <- Z

And it works! It stands just on top of us.

I hate to say this, but do it in degrees, or with quaternions. And you’ll have to learn it somewhere else.

As far as I know, rotation matrices aren’t practical when you want to arbitrarily rotate in the three axis, since you’d have to compute the sin/cos operations that TV3D does in the background for you when you use `RotateX/Y/Z`

anyway.

But that is not to say that rotation matrices aren’t useful for TV3D programmers. Just keep reading.

Now it would probably be a good idea to construct the actual transformation matrix, the composition of the three operations. And there are associated wierdness which we need to cover.

In fact, they all conflict together. Now that we’ve learned about matrix multiplication, let’s see how much it’s really used.

The main utility of matrix multiplications is that they preserve transformations : you can multiply a series of matrices together so that you have a resulting matrix that does everything that the initial matrices did, but in one shot.

To get the transformation matrix, here are the steps :

[ Rotation Matrix ] * [ Scaling Matrix ] * [ Translation Matrix ] = [ Transformation Matrix ]

Do note that matrix multiplication is not a commutative operation; which means that `M1 * M2 != M2 * M1`

. So that order is inflexible.

We’ve only defined the rotation matrix... and we’ve defined the 3D one. So here’s the full operation, in 4D!

| Right.x Right.y Right.z 0 | | Scale.x 0 0 0 | | 1 0 0 0 | | Up.x Up.y Up.z 0 | * | 0 Scale.y 0 0 | * | 0 1 0 0 | | Forward.x Forward.y Forward.z 0 | | 0 0 Scale.z 0 | | 0 0 1 0 | | 0 0 0 1 | | 0 0 0 1 | | Move.x Move.y Move.z 1 | = | Right.x*Scale.x Right.y*Scale.y Right.z*Scale.z 0 | | Up.x*Scale.x Up.y*Scale.y Up.z*Scale.z 0 | <= Full transformation matrix | Forward.x*Scale.x Forward.y*Scale.y Forward.z*Scale.z 0 | | Move.x Move.y Move.z 1 |

So you can see why I dropped the 4th column earlier... It just doesn’t apply on mesh transformations. It’s used for projection... but that’s out of this article’s scope.

Notice how 1’s were added on the diagonal of some matrices? Those were put for the same reason as why scaling components are placed on the diagonal; the 1’s act like global mulipliers for the whole column, which preserve what was there before. In fact, the “unit” matrix which does not do anything on whatever it is multiplied against is called the **identity** matrix, and has 1’s filled on its diagonal and 0’s everywhere else.

And remember when I said that the three vectors forming the rotation matrix need to be normalized? Well if they’re not, scaling is performed, because the matrix interprets non-unit-length (non-normalized) vectors as scaling!

*Note :* Thankfully, you never have to extract the rotation matrix or any other out of a composed transformation matrix in TV3D. There is usually a `GetRotationMatrix`

method which returns only the 4D rotation matrix and even better, a `GetBasisVectors`

method which returns the three normalized vectors that form the rotation matrix.

Right. It doesn’t help you for custom rotation, it’s rather useless for setting scale and position since it’s so easy to go via `SetScale`

and `SetPosition`

, what *does* it help you do? Read more in part 3!