# Table of Contents

Welcome to the sixth article of the **Sonic Battle (GBA) Renderer series**.

# Camera

## Background info

A general camera system is described in Joey de Vries’ *learnopengl.com* series:

## Specific

For reference, here’s **the coordinate system used in this series**.

Our camera has very little “primary” state. The state that the user can set is:

- Position in 3D space
- Heading (rotation around the Z axis)
- Pitch (rotation around the X axis)

It also contains “secondary” state which is derived once per frame from the primary state:

- Facing direction in 3D
- Facing direction in 2D (on the XY plane)
- Transformation matrix and its inverse (the inverse is the conventional view matrix)

## Transformation matrix

- Create translation matrix using position
- Apply heading rotation
- Apply pitch rotation

In conventional matrix notation this would be: `translationMatrix * headingMatrix * pitchMatrix`

.

The **GLM math library** takes care of the specifics. A few details:

- Base axis rotation (like rotation around the Z or X axis) is a special case which can be computed cheaply. GLM’s euler angle functions are used for that.
- The inverse can be computed from a mat3, then casted back to mat4 and multiplied with the opposite translation matrix (mat3 inverse is a ton faster than the mat4 inverse)

## Facing direction in 2D

`vec(cos(heading), sin(heading))`

(Using the trigonometric circle.)

## Facing direction in 3D

- The default direction is
`vec(0, 1, 0, 0)`

- The 3D direction is
`transformMatrix * defaultDirection`

Which simplifies to just taking the second column of the transformation matrix.

## Note - canvas space

I’ll ommit the subject of canvas/screen space (see **this note** for details). For the sake of simplicity the series will treat view/camera space as the final space for rendering.

# Tilemap

To render a tilemap we need to compute its affine matrix and its view-space position.

We’re dealing with two tilemaps (bottom and top). They can both use the same affine matrix because only their positions differ.

## View-space position

First, to get the view-space (or camera-space) position:
`camera.transformMatrixInverse * worldSpacePosition`

## Affine matrix

Our affine matrix is a 2D (2x2) matrix. To compute it we:

- Create a scale matrix from
`vec(1, cos(camera.pitch))`

- Apply rotation to it using the opposite of the camera heading

In conventional matrix notation this would be: `pitchScaleMatrix * oppositeHeadingRotationMatrix`

.

## Scale logic

The orthographic projection used by the renderer can be represented by vector projection. In the case of unit vectors, by a dot product more specifically.

The tilemap’s normal vector needs to be projected on the opposite of the camera’s direction.

The algebraic dot product formula for 3D vectors is `v1.x * v2.x + v1.y * v2.y + v1.z * v2.z`

.

We deal only with the YZ plane here, so let’s ignore `x`

: `v1.y * v2.y + v1.z * v2.z`

.

The tilemap normal is `vec(0, 0, 1)`

. Let’s plug it into our 2D formula (`v1`

would be the tilemap normal, and `v2`

the opposite of the camera direction): `0 * v2.y + 1 * v2.z`

which simplifies to `v2.z`

. This is `cos(camera.pitch - pi)`

(the minus `pi`

accounts for the opposite direction).

We’re not quite done though. Because we want to look towards +Z by default instead of -Z, we need to use the opposite direction again. So we get `cos(camera.pitch - pi - pi)`

which simplifies to `cos(camera.pitch)`

.

## Scale logic sketch

*Camera pitch = 0 degrees*

*Camera pitch = 45 degrees*

*Camera pitch = 90 degrees*

## Visualization

*Scale*

*Rotation*

*Scale and rotation*

The next article will deal with walls (basically “how to scale/slant the wall rectangles so that they look 3D”).