# My 2D translation matrix causes the box to be off screen when it shouldnt be

Now I know this isn't the way this is normally done so please bear with me. I am doing this this way so I know I have a solid conceptual understanding of everything that goes beyond example code.

I am trying to create a simple 2D game with sprites that and rotate, move, scale etc.

I thus constructed the following matrices

Transform

I want the sprites center to move from the origin by (x, y) units

$$T = \begin{bmatrix} 1 & 0 & x \\ 0 & 1 & y \\ 0 & 0 & 1 \end{bmatrix}$$

Rotation

I want the matrix to rotate by d radians so I create the following variables where s = sin(d) and c = cos(d)

$$R = \begin{bmatrix} c & -s & 0 \\ s & c & 0 \\ 0 & 0 & 1 \end{bmatrix}$$

Scale

I want the rectangular sprite to occupy a horizontal units and b vertical units so I generated the following matrix

$$S= \begin{bmatrix} a & 0 & 0 \\ 0 & b & 0 \\ 0 & 0 & 1 \end{bmatrix}$$

Center

And finally I want the sprite to do all of these transformations with its center of movement being at (u, v) therefore I will plan to have to use a matrix sort of like this (you'll see why later)

$$A = \begin{bmatrix} 1 & 0 & -u \\ 0 & 1 & -v \\ 0 & 0 & 1 \end{bmatrix}$$

Now the plan is to stream in the points (0, 0) (0, 1) (1, 1) and (1, 0) into my vertex shader and then use a single matrix to translate the points to where they need to go.

So I assumed that I could combine all these matrices as follows based on code examples etc.

$$A * S * R * T$$

So I pulled these matrixes into Symbolab and got the following result

Great! So time to pass it in. Now Metal uses SIMD but I need my engine to be completely cross platform so instead I read the SIMD alignment guides and tried to match them with this struct:

struct float3x3 {
float columns[3][4];

float3x3() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
rows[i][j] = 0;
}
}
}
} __attribute__ ((aligned(16))) ;


Which means inside of my CPU code I can create the matrix like this (based on the symbolab result)

float3x3 matrix;
matrix.columns[0][0] = a * c;
matrix.columns[1][0] = -a * s;
matrix.columns[2][0] = a * c * x - a * s * y + u;
matrix.columns[0][1] = s * b;
matrix.columns[1][1] = b * c;
matrix.columns[2][1] = b * s * x + b * c * y + v;
matrix.columns[2][2] = 1;


And pass it in like this

[encoder setVertexBytes:&matrix length:sizeof(simd_float3x3) atIndex:2];


And then finally I can pass it into my vertex shader which looks like this

vertex ColorInOut tex_plain_vert(uint vid [[vertex_id]],
constant matrix_float3x3* worldMatrix [[buffer(3)]],
constant matrix_float3x3* localMatrix [[buffer(2)]]) {
float2 coords[] = {float2(0, 0),
float2(1.0, 0),
float2(0, 1.0),
float2(1.0, 1.0)};

const float2 texc[] = {float2(0.0, 1.0),
float2(1.0, 1.0),
float2(0.0, 0.0),
float2(1.0, 0.0)};

const int lu[] = {0, 1, 2, 2, 1, 3};

ColorInOut out;
out.texCoord = texc[lu[vid]];
matrix_float3x3 w = *worldMatrix;
matrix_float3x3 l = *localMatrix;
float3 res = w * l * padded;
out.position = float4(res.x, res.y, 0.0 , 1.0);
return out;
}


However this doesnt work. Depending on how I have messed with things the square is either wildly off screen or doesn't translate to the (x, y) at all.

And yes my world matrix is VERY thoroughly tested and you will see in a bit that it is wrong even without that value.

So I pulled it into the vertex debugger and got the following information with the following variables

x = 0
y = 50
s = 0
c = 1
a = 120
b = 120
u = 0.5
v = 0.5


p2 is where you can really see the error with those settings 0,0 should have translated to -60,-10 and 1, 0 should have translated to 60, -60. None of these huge numbers and it is my inclination to think that it is mostly the 3rd column of the matrix at fault.

My inclination based on seeing these results is that the matrix I am passing in is wrong. But I dont know another way to do that math.

I have also tried tweaking things like switching rows and columns changing multiplication order to no avial. The closest I have gotten was something that rotated and scaled but did not move and one that moved but had sheering effects.

There are a lot of places in this process that I could have gone wrong (initial matrices, matrix multiplication\order, matrix struct, vertex shader, etc) and I would really appreciate your help finding it as I have spent awhile on this now.

• Giving a quick look I see a few problems. You are missing the negative signs ($-u,-v$) in the matrix $A$ during matrix composition. Also since you want to do transformations around the point $u,v$. You correctly move $u,v$ to origin by doing $-u,-v$ but you forgot to move back to the origin since $x,y$ are translating from origin to $x,y$ not from $u,v$ to $x,y$. So in your matrix composition before the last matrix $T$ you need to add another matrix which scales from $u,v$ to origin. – gallickgunner Feb 3 at 11:43
• @gallickgunner you are completely right. I am just now confused where the scale matrix should go or if maybe I need to make a new A matrix that considers the scale? Nonetheless with this implemented I still have the result of a rectangle that is either not moving or off screen depending on whether I pass in the transpose or not. – J.Doe Feb 3 at 19:41
• Ok I assume the vector you are multiplying with is a column vector since you do l * p i.e. the vector comes afterwards. Then shouldn't the above composition be backwards since thinking about it in components, you should have $TRSA * v$. I don't know if that's what you meant when you said you had tried changing the order of multiplications, though. Also try breaking your composite matrix in components, maybe first check each of your matrices separately to see if the individual operations are working perfectly, then start composing them 1 by 1. – gallickgunner Feb 3 at 21:36
• T R S A * v was just what I needed thanks! – J.Doe Feb 3 at 22:32
• Np. Adding this as an answer to make it clear the question was solved. – gallickgunner Feb 5 at 8:40

Since the OP was working with column vectors the correct order for transformations should have been $$TRSA∗v$$. The OP did the other way around, $$ASRT*v$$ thus the incorrect results.
Also there need to be another $$A^{\prime}$$ matrix added which negates the translation done by $$A$$. The first $$A$$ matrix should have $$-u, -v$$ to translate $$u,v$$ to origin. The $$A^{\prime}$$ matrix should have $$u, v$$ to translate back.
Hence the correct composition should be, $$TA^{\prime}RSA*v$$ or $$A^{\prime}TRSA*v$$ depending on whether the translation is intended around $$u,v$$ or the origin.