Raycasting Adventures
Tags: gamedev, writing, architecture
Published: 2022-11-05 19:49:11

> Read the (better) _PDF_ version of this blog [here](https://korigamik.ml/assets/raycasting.pdf)!

So, this is going to be more of a summary and some implementation details of
my first ever experience with computer graphics and making games.

You can find the [itch-io page](https://korigamik.itch.io/untitled-psp)
with all the instructions and links to download and play the game (maybe
even give me a follow)

Two weeks ago, not knowing anything about computer graphics and game
development, I embarked on a journey down the rabbit hole of creating
one of the pioneering games of video game history, such as the OG _Doom_
and _Wolfenstein 3D_.

I decided to create a raycasting engine in _C++_ using the _Simple
DirectMedia Layer_ graphics library. And for a beginner like me, it was
quite the challenge.

The whole idea of making this in a very bare-bones library and language
like _SDL2_ and _C++_ was because I wanted to create something that
would be truly cross-platform from the start. And by cross-platform I
mean **cross-platform**. The game would run on all desktop operating
systems (Linux, Windows, Apple) and all mobile operating systems
(Android, iOS, Raspberry Pi, Chrome)! And of course the
_PSP_. I could talk a lot about why the PSP, but I'll save
that for another time.

## Introduction

Now you must be asking - \"Bruh, what even is a Raycaster\".

The simplest way to explain it is that it's a rendering technique to
create a 3D perspective from a 2D map, what we like to call as 2.5D.

Although this is about the making of a raycasting game engine, I had to
give a lot of thought on how game engines are structured in the first
place.

I wanted my project to follow the _Object-Oriented_ design pattern as
well so that it would be easy to extend and plug in components without
touching a lot of code, enabling a deeper level of abstraction. I taught
me a lot about inheritance and composition in C++.

## The Game Engine

I'll talk about the game architecture in this section.

The engine was written in C++ and then compiled using the _CMake_ build
system, which enables me to go completely cross-platform.

The high level architecture of the engine looks something like:

![Design](https://korigamik.deno.dev/img/game_design.png)

I would've gone more into the design but TikZ is such a huge pain to
learn. Though, I might come back to this in the future.

## Raycasting

What actually distinguishes my project from any other implementation
that you may find on the web is the fact that instead of dividing the
whole world into square grids, it defines all the objects and texture
rendering using their actual coordinates.

Looking back, using the 2D grid approach while being a lot easier to
implement, it's also quite a bit more efficient and accurate.

Having said that, it also restricts a lot of what can be actually
created in the game.

The biggest leap of faith I took for this engine was to create the
raycasting logic. So, here's how raycasting actually works.

We first need to define these terms:

- **Player direction**  <img style="" src="https://korigamik.deno.dev/svg/edJH4OEEPn.svg"> : The direction that the player is currently facing.

- **Maximum view distance**  <img style="" src="https://korigamik.deno.dev/svg/iB5cma2PPp.svg"> : The position of the player in the world.

- **Horizontal field of view**  <img style="" src="https://korigamik.deno.dev/svg/RZWtFQKtpj.svg"> : The visible field of view
    the player sees in the horizontal direction. I assumed this to be,
    100°.

- **Vertical field of view** : This gives an idea of how high or low
    can the person see. I assume this to be, 60°.

- **Number of rays** : This is the number of rays that we cast from
    the player's position. You can set this number a lot of ways, and
    how high you set this will determine how accurate the raycasting
    will be.

Now we are ready to tackle the actual casting of rays. What our aim is
to cast a ray from the player's position in the direction of the
player's direction  <img style="" src="https://korigamik.deno.dev/svg/Mi0j5nQvvr.svg"> and then check if it intersects with any of
the walls in the map. If it does, then we need to calculate the distance
between the player and the wall and then draw a slice of the player's
view corresponding to that ray.

So, consider this diagram of the player's situation:

![Raycasting](https://korigamik.deno.dev/img/raycasting.png)

This circle corresponds to the player's maximum view distance ( <img style="" src="https://korigamik.deno.dev/svg/A6d68g1dlj.svg">).

It should be noted that half of the cast rays would be on the left side
of the player's field of view ( <img style="transform:translateY(0.5rem)" src="https://korigamik.deno.dev/svg/0mROBlb6PV.svg">) and the other half
would be on the right side.

Now, we can define the angle between each ray as:


<div align="center"><img style="" src="https://korigamik.deno.dev/svg/LqPyuCwYSx.svg"></div>


We are going to construct the view of the player by drawing a slice of
the view for each ray. The rays would be equally spaced (suspending the
angle  <img style="" src="https://korigamik.deno.dev/svg/XojCZ1vov8.svg"> at the player) starting from point  <img style="" src="https://korigamik.deno.dev/svg/KzUjUjCLNR.svg"> till  <img style="" src="https://korigamik.deno.dev/svg/vRSi1UTzJu.svg">

We'll calculate  <img style="" src="https://korigamik.deno.dev/svg/vRSi1UTzJu.svg"> (distance taken from  <img style="" src="https://korigamik.deno.dev/svg/KzUjUjCLNR.svg">) by using the
following formula:

 

<div align="center"><img style="" src="https://korigamik.deno.dev/svg/vdsxWw29HS.svg"></div>

So, the distance  <img style="" src="https://korigamik.deno.dev/svg/Aibb1JUkgP.svg"> for any ray at angle  <img style="" src="https://korigamik.deno.dev/svg/qdxm14ZrTJ.svg"> would be:

 

<div align="center"><img style="" src="https://korigamik.deno.dev/svg/RFtxHz5oQj.svg"></div>

Now, we can calculate the width of the slice ( <img style="" src="https://korigamik.deno.dev/svg/brSYRl5LQL.svg">) for each ray as:

 

<div align="center"><img style="" src="https://korigamik.deno.dev/svg/gQwnpxYOLw.svg"></div>

But, we cannot calculate the vertical height of the slice by using the
distance of the ray's end point from the player  <img style="transform:translateY(0.5rem);" src="https://korigamik.deno.dev/svg/PdrG3CIhcj.svg">
directly.

This is the exact cause of the fish eye effect that you might have seen
from some cameras that looks very alien to your eyes. It looks something
like

<div align="center"><img width="500px" src="https://korigamik.deno.dev/img/fish-eye.png" alt="Fish-eye effect"></div>

We can correct for the fish eye effect by taking the length of the ray
from the plane of the camera.

 

<div align="center"><img style="" src="https://korigamik.deno.dev/svg/2yz5VIH8B2.svg"></div>

Thus, we calculate the height of the slice based on this:

 

<div align="center"><img style="" src="https://korigamik.deno.dev/svg/lVYegxFEYf.svg"></div>

Nice! The fish-eye effect is no more.

<div align="center"><img src="https://korigamik.deno.dev/img/no-fish-eye.png" alt="Fish-eye effect corrected" width="500px"></div>

Phew! That was a lot of math. But, we are almost done.  

Another thing that we can calculate is the brightness of an individual
slice:

 

<div align="center"><img style="" src="https://korigamik.deno.dev/svg/vHyM7UXP89.svg"></div>

This means that farther objects will appear darker than when they're
closer. You could probably play around here and find a way to do fog or
mist, but I'll leave that up to you.

The last thing left for us to do (at least on this blog) is to figure
out a way to map the image textures onto the actual walls.

- The method is pretty simple,

  + The width and height of the textures remain the same.

  + The start of the texture can be calculated by taking into account
      the distance of the ray along the line.

 

![Texture X](https://korigamik.deno.dev/svg/JeQRHO5l1h.svg)

The remainder operator will repeat the texture when the length of the
wall is greater than the texture itself.

## Conclusion

That's it, Congrats, you made it to the end. The raycasting is working,
and you're now a game developer.

 

<div align="center"><img style="" src="https://korigamik.deno.dev/svg/gAx0rCca5B.svg"></div>

I hope you enjoyed the journey with me as much as I did making and
writing this. Maybe you'll even feel inclined to contribute and fix the
inevitably many bugs I have [in my code](https://github.com//korigamik/untitledpsp).

Thank you Kueeing! (king + queen let's [make this go mainstream](https://www.urbandictionary.com/define.php?term=kueeing))