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]];
float3 padded = float3(coords[lu[vid]], 1.0);
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.
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. $\endgroup$