Cub3D Tutorial [Using angles]

Ahmed Fatir
10 min readDec 7, 2023

--

Hi, I am Ahmed FATIR, a computer science student at 1337 School part of the 42 The Network. In this article, we will explore the world of computer graphics using the ray casting technique.

INTRODUCTION

Step into the world of raycasting, a fascinating technique that brings 3D visuals to life. In the realm of computer graphics, raycasting stands out as a fundamental method for creating immersive environments. As I embarked on my journey to explore raycasting, I had the opportunity to work on a captivating project called cub3d during my time at the prestigious 1337 School. In this Medium article, I’ll dive into the intricacies of raycasting, discuss the implementation details of cub3d, and share the valuable insights I gained throughout the process. Join me on this exciting adventure as we unravel the magic behind raycasting and the captivating world it can create.

Explanation of the Four Parts of the Project

  1. Map Initialization and Window Creation
  2. Casting the rays
  3. Rendering the walls
  4. Player Movement

This project can be divided into four main parts: map initialization and window creation, raycasting, player movement, and rendering. Each part contributes to creating a basic 3D game-like experience. Here’s an overview of each part:

Part 1: Map Initialization and Window Creation:

In this part, the project begins by initializing the MLX library and creating a window for rendering. It then proceeds to parse a map file, extracting relevant information such as the map dimensions, player position, and the map layout itself. The map is validated to ensure its correctness, and memory is allocated to store the map information. This part sets the foundation for the rest of the project by providing the necessary data to render the game world.

Part 2: Casting the rays:

Raycasting is a rendering technique used to create a 3D perspective of the game world. In this part, raycasting is implemented to calculate the angles of rays emanating from the player’s viewpoint. These rays are then cast onto the walls of the map, allowing the determination of the distance from the player to each wall. Based on these calculations, the engine determines the color of each wall to be rendered on the screen. Raycasting creates the illusion of depth and adds a sense of realism to the game environment.

Part 3: Rendering the walls:

Part 3 of the project deals with rendering the game world, including walls, floors, and ceilings, onto the screen. By utilizing the map layout, the player’s position and direction, and the raycasting results, the engine determines the appropriate colors for each pixel on the screen. This information is then used to render the walls, creating a 3D perspective. Additionally, floors and ceilings are rendered to enhance the immersive experience. The rendering part gives life and visual representation to the game world.

Part 4: Player Movement:

Part 4 focuses on handling player movement and rotation within the game world. Keyboard input is used to control the player, allowing them to move forward, backward, and sideways. Additionally, the player can rotate left or right. The code monitors keyboard events and updates the player’s position and direction accordingly. This part enables the player to navigate and explore the game world.

Each part is crucial in creating the overall 3D game rendering engine. Understanding these parts in detail will help you comprehend the inner workings of the code and allow for further exploration and development of additional features. We’ll now dive into each part to delve deeper into their respective functionalities.

Part 1: Map Initialization and Window Creation:

a: include the libraries and set the structs:

(don't worry you gonna find the whole code at the end of the article and the link to my GitHub repo):

b: the initial code for the first part:

I think comments in the code will be enough to understand what each function does.

Part 2: Raycasting:

Now let me explain the concept by breaking it down into steps and examining each one.:

0_understand all that you need to complete this part

1_find the angle for the first ray

2_Loop for each ray.

3_Get the horizontal intersection

4_Get the vertical interception

5_Calculate the distance

6_Take the closest distance

0_understand all that you need to complete this part:

you need to understand some of the trigonometry functions: sin(), cos(), and tan() and one thing else is the used unit circle in the computer graphics:

the unit circle in computer graphics

Positive x and negative x represent the x-axis, while positive y and negative y represent the y-axis.

so the screen will be like this :

the screen coordinates in computer graphics

The variable S_W refers to the screen width, while the variable S_H refers to the screen height.

1_find the angle for the first ray:

the ray angle depends on the player’s angle and the FOV

so if the player’s angle is “PI / 2 ” and the FOV is 60 the ray angle should be “(PI / 2) - FOV/2”

we going to begin with the most left angle and go ray by ray to cast all of them, “p_x” and “p_y” refer to the player's position.

2_Loop for each ray.

The player's field of view is 60 degrees. We will expand it to fill the entire screen. that’s why we gonna cast (S_W ray).

Our increment will be equal to the sum of the current angle and the field of view divided by the S_W.

3_Get the horizontal intersection

So, we have a map made up of simple squares. we plan to draw a line from the player’s position in a known direction. Then, we will check if there is a wall at each step along the line. Whenever we come across a wall, that will be our intersection point.

this is the map :

The squares with the value 1 refer to a wall, and the empty slots are the floor where the player can move. Usually, the floor is represented by 0, but for more visibility, let's keep them empty.

the map and the player’s angle.

now we gonna check the intersection just for the horizontal lines:

Finding the intersections on the horizontal lines.

First, we need to calculate the x and y positions for the first intersection, which is represented by the little blue triangle. After that, we will calculate the values of "x_step" and "y_step". These values will be used to increment our intersection with them until we find a wall.

Our intersection points will be at (x_inter, y_inter) with the help of x_step and y_step to move to the next points.

Let's zoom in on the small blue triangle:

Finding the first intersection points

To determine the y position, we can use the fact that our map contains both horizontal and vertical lines, so y will always be on a horizontal line, making it easy to locate. As for the x position, once we have the y point and a triangle, we can use the tangent of the angle to find the remaining value.

Let’s determine the y_step value using the title size and the same tan formula used to find the x_step value.

finding the x_step and y_step

Now we need to consider how the player is facing, whether it's down and right, down and left, or up and left, as this will create a problem.

adjust x_step and y_step

I already fixed the problem in the code. You will understand it better now. I fixed the code. Note that 0 is at the top and numbers increase downward.

Let's discuss the wall hit. The y-position will always be on the top of a horizontal line. When we check for the wall hit, we will convert the pixel coordinates into the map coordinates, which will result in a loss of float precision. To ensure that the intersection is inside a map grid, we will add a pixel every time we check for a wall hit. and that is the use of the pixel variable.

and then we just keep incrementing the intersection position by the steps and check if it hits the wall.

if we hit a wall then we should calculate the distance from the player’s position to the intersection position, Mr. Pythagoras’s theorem. a² + b² = c²

Pythagoras’s theorem

We need to find the value of c, where c is the distance between two points A(a,0) and B(0,b) on a 2D plane. The formula to find c is c = sqrt(a^2 + b^2). Finding the distance is as easy as that.

Now you should understand the process for vertical intersections. The steps are the same, but the calculations are different.

Let's start rendering some walls now.

Part 3: Rendering:

so now we have a distance to the wall, what we gonna do now is calculate the height of the wall and draw it, as simple as that.

so we gonna use Thalès’s theorem

Thalès’s theorem

AB/AD = AE/AC = DE/BC

You will be able to locate the values within the code, and they will become more visible.

If we find ourselves in a situation where a wall fills our field of view, we may encounter a problem. The edges of the wall will appear slightly curved due to the fisheye effect. This happens because the distance between the player's position and the wall in the first ray is not the same as the distance in the middle ray. However, we can easily solve this problem by applying a simple equation. If you comment on that line, you can observe the effect for yourself.

Calculating the wall height allows us to determine the top and bottom pixels for drawing the floor and ceiling.

And then, we simply fill in the pixels with the desired colors, and the task is complete.

If you read the code, you will find that I have added a variable called 'flag'. It is used to determine which angle I am facing to get the correct wall color. I hope this clarifies everything and we can now move on to discussing the player's movement.

note that the `nor_angle()` function is just used to keep the angle between 0 and 2 PI

Part 4: Player Movement:

If you are a 42 student, you have likely worked with player movement before in the so_long project. In this part, I will give a brief recap.

The function mlx_key_hook() is responsible for handling user input via keyboard keys. When triggered, it executes the operation defined in the function passed as its argument. it’s simple, right?

If you take a look at the `mlx_key()` function, it will become clearer. For instance, if I press the “A” key, the variable `l_r` will have a value of -1. This indicates that the player should move to the left. As you know, the `hook()` function in the `game_loop()` updates the player’s position based on the movement variables. When I release the key, the value of `l_r` goes back to 0.

If you have reached this point, I hope you are now more familiar with the project and capable of implementing it.

To ensure that the game is properly completed, we need to include an exit function. If you review the `mlx_key()` function at the beginning, you'll notice that the `ft_exit()` function is called when the player presses the ESC key. This function frees up memory allocation, terminates the mlx image and window, and exits the program. Additionally, if you hit the red cross, it also calls the same function since it's placed at the end of the `start_the_game()` function.

As promised this is the whole code for cub3d:

If you execute this code, you will obtain a result similar to the one shown.

the expected results.

I didn’t add the texture to the code because my primary focus was on explaining the raycasting technique. However, you can find the implementation of textures in my GitHub repository. Thank you for reading this article, and I wish you all the best. Here’s the link to my GitHub repository: cub3D, and my LinkedIn account.

These are some pictures of the project with added texture to the walls. You can use any texture you like, but it should be in PNG format.

cub3D

--

--