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

${\color{Red}M^{-1}&space;=&space;\frac{1}{detM}&space;*&space;adj(M)}$

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 :

${\color{Red}M^{-1}&space;=&space;adj(M)}$

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

${\color{Teal}&space;\begin{equation*}&space;A&space;=&space;\begin{bmatrix}&space;1&space;&&space;0&space;&&space;0&space;\\&space;0&space;&&space;cos\theta&space;&&space;-sin\theta&space;\\&space;0&space;&&space;sin\theta&space;&&space;cos\theta&space;\end{bmatrix}&space;\end{equation*}}$

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

${\color{Teal}&space;\begin{equation*}&space;B&space;=&space;\begin{bmatrix}&space;cos\theta&space;&&space;-sin\theta&space;\\&space;sin\theta&space;&&space;cos\theta&space;\end{bmatrix}&space;\end{equation*}}$

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

${\color{Teal}&space;\begin{equation*}&space;\begin{bmatrix}&space;+&space;&&space;-&space;\\&space;-&space;&&space;+&space;\\&space;\end{bmatrix}&space;\end{equation*}}$

In this case, on the diagonals, we switch ${\color{Red}sin\theta}$ with ... ${\color{Red}sin\theta}$, and ${\color{Red}cos\theta}$ with ... ${\color{Red}cos\theta}$.

Nothing changed so far.

But applying the sign matrix changes the signs of the $sin\theta$ and we end up with :

${\color{Teal}&space;\begin{equation*}&space;adj(B)&space;=&space;\begin{bmatrix}&space;cos\theta&space;&&space;sin\theta&space;\\&space;-sin\theta&space;&&space;cos\theta&space;\end{bmatrix}&space;\end{equation*}}$

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

${\color{Teal}&space;\begin{equation*}&space;B&space;=&space;\begin{bmatrix}&space;cos\theta&space;&&space;-sin\theta&space;\\&space;sin\theta&space;&&space;cos\theta&space;\end{bmatrix}&space;\end{equation*}}$

Let's try to find the transpose of that.

The first column of that matrix is ${\color{Teal}&space;\begin{equation*}&space;\begin{bmatrix}&space;cos\theta&space;\\&space;sin\theta&space;\end{bmatrix}&space;\end{equation*}}$ and the second column is ${\color{Teal}&space;\begin{equation*}&space;\begin{bmatrix}&space;-sin\theta&space;\\&space;cos\theta&space;\end{bmatrix}&space;\end{equation*}}$.

Let's make ${\color{Teal}&space;\begin{equation*}&space;\begin{bmatrix}&space;cos\theta&space;\\&space;sin\theta&space;\end{bmatrix}&space;\end{equation*}}$ our first row : ${\color{Teal}&space;\begin{equation*}&space;\begin{bmatrix}&space;cos\theta&space;&&space;sin\theta&space;\end{bmatrix}&space;\end{equation*}}$.

Now let's make ${\color{Teal}&space;\begin{equation*}&space;\begin{bmatrix}&space;-sin\theta&space;\\&space;cos\theta&space;\end{bmatrix}&space;\end{equation*}}$ our second row : ${\color{Teal}&space;\begin{equation*}&space;\begin{bmatrix}&space;-sin\theta&space;&&space;cos\theta&space;\end{bmatrix}&space;\end{equation*}}$.

The result is ... ${\color{Teal}&space;\begin{equation*}&space;B^T&space;=&space;\begin{bmatrix}&space;cos\theta&space;&&space;sin\theta&space;\\&space;-sin\theta&space;&&space;cos\theta&space;\\&space;\end{bmatrix}&space;\end{equation*}}$ ! Looks familiar ?

So we can see that if :

${\color{Teal}&space;\begin{equation*}&space;B&space;=&space;\begin{bmatrix}&space;cos\theta&space;&&space;-sin\theta&space;\\&space;sin\theta&space;&&space;cos\theta&space;\\&space;\end{bmatrix}&space;\\adj(B)&space;=&space;B^T&space;=&space;\begin{bmatrix}&space;cos\theta&space;&&space;sin\theta&space;\\&space;-sin\theta&space;&&space;cos\theta&space;\\&space;\end{bmatrix}&space;\end{equation*}}$

hence :

${\color{Red}M^{-1}&space;=&space;M^T}$

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
SetMulRotMatrix(&invRot);