# Table of Contents

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

# Coordinate system

Quick reminder (just in case): **here’s a link to the coordinate system**.

# Walls

## Directions

All walls are axis-aligned. (Except for the 45-degree walls but they will be discussed in a later article.)

It means that walls only face 4 directions which I call:

`Y_PLUS`

`X_MINUS`

`Y_MINUS`

`X_PLUS`

## Visibility

Our walls represent boxes so it’s safe to assume that if a given side is visible, then the opposite is invisible. (Ex: `X_MINUS`

and `X_PLUS`

visibility is exclusive)

This fact can be used for an optimization: once a side is determined to be visible, the processing of the opposite one can be skipped. Note that there’s a special case where two opposite sides can be both not visible, it happens when the dot product for both is 0.

The usual algorithm for determining if a wall is facing away from a view is to check if the dot product of its normal against (the opposite of) the camera’s direction is smaller than 0.

In short: `visible = dot(wallNormal, -cam.directionIn2D) > 0`

.

Our wall directions are the +/-X axis and +/-Y axis.

Then the dot product for the `X_PLUS`

direction is `dot(vec(0, 1), -cam.directionIn2D)`

. It simplifies to `-cam.directionIn2D.y`

. The dot product for `X_MINUS`

is `cam.directionIn2D.y`

.

Similarly, for `Y_PLUS`

it’s `-cam.directionIn2D.x`

and for `Y_MINUS`

it’s `cam.directionIn2D.x`

.

This translates to:

```
WallVisible[WallDirection::Y_PLUS] = camDirXY.x < 0.0f;
WallVisible[WallDirection::Y_MINUS] = camDirXY.x > 0.0f;
WallVisible[WallDirection::X_PLUS] = camDirXY.y > 0.0f;
WallVisible[WallDirection::X_MINUS] = camDirXY.y < 0.0f;
```

Another way to look at it is to visualize the camera angle from the top, where the relation between wall directions, camera direction, and XY axes is clear:

## Re-using affine matrices (optimization)

Once the visible wall directions are determined, the corresponding affine matrices are computed.

We re-use these affine matrices for every wall of the same facing direction because an individual wall’s position doesn’t affect its projection (only wall normal and camera direction matter - as will be described below).

So all visible `X_PLUS`

walls share the same `X_PLUS`

affine matrix for example.

This is a significant optimization: each frame we only compute 1 or 2 affine matrices instead of 1 per wall.

## Affine matrix

My initial approach was very awkward, I’m glad that I **replaced it** by a much simpler one recently.

In a nutshell we need to find the matrix that will transform the wall texture’s local coordinate space to the coordinate space of the texture as it would appear on screen.

Here’s an illustration of the full process:

We can pre-compute the world-space texture X axis (wall tangents). The texture Y axis points straight down (-Z in world space). So we create a matrix which uses those two as basis vectors.

Next we transform that matrix into camera space (`inCamSpace = mat3(camTransformInverse) * inWorldSpace`

).

Then we perform the orthographic projection (by simply discarding any depth-axis information). Finally we flip the vertical axis to account for screen/canvas coordinates.

I’m fairly confident that this method would support camera roll and arbitrary wall orientations. I’m tempted to revisit the approach I used earlier for tilemaps so that they could support these things too. Not that it matters for *Sonic Battle* of course, since it doesn’t use those bells and whistles.

Note: on the real hardware we wouldn’t be done just yet. There’s **a few gotchas with affine matrices**.

## View-space position

The view space positions of individual walls are computed the same as for tilemaps: `camera.transformMatrixInverse * worldSpacePosition`

.

## Note - Tilemap and wall base scale

All dimensions of the game world (save for the characters) have been doubled! Tilemaps and most wall sprites are exactly x2 larger than their source bitmaps. That really tripped me up until I figured it out with mGBA’s inspection tools.

*This wall is rendered ~64 pixels wide at native screen resolution*

*But its bitmap is only 32 pixels wide (the "Double Size" flag is unrelated by the way)*

*The wall behind it though has the same width as its bitmap (it's only scaled in height) - so there's some flexibility here*

This could have been done for performance reasons, to use ~4 times fewer hardware sprites to cover the same area (which reduces load on the entire renderer).

Or for artistic reasons, to reduce the visual noise.

## Note - Wall height scale

For gameplay reasons (to prevent the action from vertically overflowing the screen) all walls are additionally scaled in height to make them shorter. It also looks better that way.

*Just the basic x2 scale - that's really tall*

*x2 scale and additional height scale (I eyeballed the value, it's ~1.4)*

The next article will discuss the depth sorting of the walls.

You can subscribe to the **mailing list** to be notified when a new article appears.