PSXDEV : Billboard system

Trying to devise a billboard system for this psx 3d engine I've been working on, I first used a trigonometric approach that used atan2, then found out that it was simpler and cheaper to use a inverse rotation matrix.

Trigonometry

So this was my first idea :

// Sprite system WIP
    // Find angle between billboard object and camera object using atan2
    objAngleToCam.vy = patan( posToCam.vx,posToCam.vz );
    objAngleToCam.vx = patan( posToCam.vx,posToCam.vy );
    // Apply rotation to billboard
    curLvl.meshPlan->rot.vy = -( (objAngleToCam.vy >> 4) + 1024 ) ;
    // Update billboard's relative position  to camera object
    posToCam.vx = -camera.pos.vx - curLvl.meshPlan->pos.vx ;
    posToCam.vz = -camera.pos.vz - curLvl.meshPlan->pos.vz ;
    posToCam.vy = -camera.pos.vy - curLvl.meshPlan->pos.vy ;

So here we're using patan(), which is a function that uses a pre-calculated atan2 table for speed.
As is, this solution works, but only for the Y axis (PSX uses a Y-down coord system).
Trying to align it on the X and Z axis result in gimbal lock, which is not good looking.

Inverse rotation matrix

Fast forward a few weeks, and I have this realization that somewhere in my brain is a knowledge I can use to solve this another way !

I learned from my good psxdev pal @Nicolas Noble that finding an inverse rotation matrix is quite trivial if you already have a rotation matrix handy :

Here is the inverse matrix formula :

In plain english, that's : The inverse matrix is the transpose of the matrix's co-factors divided by the determinant.

We know that the determinant of a rotation matrix is 1 :

Rotation matrices are square matrices, with real entries. More specifically, they can be characterized as orthogonal matrices with determinant 1;
https://en.wikipedia.org/wiki/Rotation_matrix

thus :

The inverse of a rotation matrix is the adjugate of this matrix.

Adjugate of a rotation matrix

The adjoint or adjugate of a matrix is the "transpose of the cofactor matrix".

Let's try with a rotation matrix. We know that the rotation matrix on the X axis is

Let's isolate the rotation part in a 2x2 matrix :

According to this document, finding the adjugate means swapping the entries without the signs diagonally, then applying the sign matrix :

In this case, on the diagonals, we switch with ... , and with ... .

Nothing changed so far.

But applying the sign matrix changes the signs of the and we end up with :

Transpose of a rotation matrix

The transpose of a matrix is a new matrix whose rows are the columns of the original. [https://www.quora.com/What-is-the-geometric-interpretation-of-the-transpose-of-a-matrix]

Remember the rotation part of our rotation matrix on X ?

Let's try to find the transpose of that.

The first column of that matrix is and the second column is .

Let's make our first row : .

Now let's make our second row : .

The result is ... ! Looks familiar ?

So we can see that if :

hence :

The inverse of a rotation matrix is the transpose of this matrix.

Code

So we know that the adjugate of a rotation matrix is the same as its transpose...

We do have a TransposeMatrix() function available in PsyQ, so let's use that !

    // Find inverse rotation matrix so that the billboard object always faces camera
    // working matrices
    MATRIX curRot, invRot;
    // Get current rotation matrix
    ReadRotMatrix(&curRot);
    // Get transpose of current rotation matrix and store it in invRot
    TransposeMatrix(&curRot, &invRot);
    // Multiply current rotation matrix by the inverse matrix
    SetMulRotMatrix(&invRot);

Sources

https://discord.com/channels/642647820683444236/663664210525290507/826109895874838551
https://en.wikipedia.org/wiki/Rotation_matrix
https://en.wikipedia.org/wiki/Adjugate_matrix
http://www.macs.hw.ac.uk/~markl/teaching/Inverses.pdf
https://www.quora.com/What-is-the-geometric-interpretation-of-the-transpose-of-a-matrix