0

I am trying to write a raycaster with SDL. Unfortunately, a lot of the time the perspective turns out warped. I am calling SDL_RenderFillRect to fill each vertical rectangle, but sometimes that rectangle is not filled in. Additionally, when I go forward with the up arrow key, this wall shrinks instead of growing. I do not understand why this is happening. The controls are as follows: forwards with the up key, backward with the down key, turn left with the left key, and turn right with the right key. I am new to writing raycasters, so if possible please give me advice on how to improve it. Thank you.

I am compiling my code like this (on a Mac): gcc -Wall -Wformat -O3 `pkg-config --cflags --libs sdl2` raycaster.c

const int
    screen_width = 600, screen_height = 300,
    map_width = 10, map_height = 6;

const float width_ratio = (float) screen_width / map_width,
    height_ratio = (float) screen_height / map_height;

const unsigned char map[map_height][map_width] = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 1, 1, 1, 1, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};

typedef struct {
    float angle, fov;
} Camera;

typedef struct {
    float x, y;
    Camera camera;
} Player;

#define SET_COLOR(rend, r, g, b) SDL_SetRenderDrawColor(rend, r, g, b, SDL_ALPHA_OPAQUE)

#include <SDL2/SDL.h>
#include <math.h>

SDL_Window* window;
SDL_Renderer* renderer;

void render(Player player) {
    int rel_x = player.x * width_ratio, rel_y = player.y * height_ratio;
    Camera cam = player.camera;
    float half_fov = cam.fov / 2, width_fov_ratio = screen_width / cam.fov;
    
    for (float theta = cam.angle - half_fov, vline_x = 0;
        theta < cam.angle + half_fov;
        theta += 0.5, vline_x += width_fov_ratio) {

        float radian_theta = theta * (M_PI / 180);
        float cos_theta = cos(radian_theta), sin_theta = sin(radian_theta);

        float d = 0, new_x, new_y;
        while (d += 0.5) {
            new_y = sin_theta * d + rel_y, new_x = cos_theta * d + rel_x;

            int scaled_down_y = round(new_y / height_ratio),
                scaled_down_x = round(new_x / width_ratio);

            if (map[scaled_down_y][scaled_down_x]) {
                float dist_to_wall = sqrt(pow(new_x - rel_x, 2) + pow(new_y - rel_y, 2));

                SDL_Rect r;
                r.x = vline_x;
                r.y = dist_to_wall;
                r.w = width_fov_ratio * 2;
                r.h = screen_height - dist_to_wall * 2;

                SET_COLOR(renderer, 0, 191, 255);
                SDL_RenderFillRect(renderer, &r);
                SDL_RenderDrawRect(renderer, &r);
                break;
            }
        }
    }
}

int main() {
    SDL_CreateWindowAndRenderer(screen_width, screen_height, 0, &window, &renderer);
    SDL_SetWindowTitle(window, "Raycaster");
    Player player = {2, 2, {45, 90}};

    while (1) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_QUIT:
                    SDL_DestroyWindow(window), SDL_DestroyRenderer(renderer);
                    return 0;
                case SDL_KEYDOWN: {
                    float radian_angle = player.camera.angle * (M_PI / 180);
                    float move_x = cos(radian_angle) * 0.1, move_y = sin(radian_angle) * 0.1;
                    switch (event.key.keysym.sym) {
                        case SDLK_UP: player.x += move_x, player.y += move_y; break;
                        case SDLK_DOWN: player.x -= move_x, player.y -= move_y; break;
                        case SDLK_LEFT: player.camera.angle -= 2; break;
                        case SDLK_RIGHT: player.camera.angle += 2; break;
                    }
                    if (player.x < 0) player.x = 0;
                    else if (player.x > screen_width) player.x = screen_width;

                    if (player.y < 0) player.y = 0;
                    else if (player.y > screen_height) player.y = screen_height;

                    if (player.camera.angle > 360) player.camera.angle = 0;
                    else if (player.camera.angle < 0) player.camera.angle = 360;
                }
            }
        }
        SET_COLOR(renderer, 0, 0, 0);
        SDL_RenderClear(renderer);
        render(player);
        SDL_RenderPresent(renderer);
        SDL_UpdateWindowSurface(window);
    }
}
Caspian Ahlberg
  • 934
  • 10
  • 19
  • 1
    this code is C++, not C, global `map` has non true-const size – tstanisl Mar 01 '21 at 15:24
  • btw.. please add some shading, it's a nightmare to navigate between solid walls :) – tstanisl Mar 01 '21 at 15:27
  • @tstanisl This code is C; I don't know what you mean by "non true-const size". Also, I'd like to make this raycaster work correctly before adding shading. – Caspian Ahlberg Mar 01 '21 at 15:43
  • 1
    `const int n = 5; int A[n];` is not a valid C code if `A` is a global variable. Compilation with GCC produces error `error: variably modified ‘map’ at file scope`. Use `enum { n = 5 }; int A[n]`. – tstanisl Mar 01 '21 at 15:48
  • 1
    Are you [cosine-compensating the wall distances](https://permadi.com/1996/05/ray-casting-tutorial-8/)? – genpfault Mar 01 '21 at 15:51
  • @tstanisl Thank you. Also, I fixed the unfilled rectangles like this after `dist_to_wall`: `if (dist_to_wall * 2 >= screen_height) break;` – Caspian Ahlberg Mar 01 '21 at 15:54
  • @genpfault I am not. I'll check that out. – Caspian Ahlberg Mar 01 '21 at 15:56
  • maybe you should not draw walls that are *behind* you – tstanisl Mar 01 '21 at 16:09
  • @tstanisl What part of the code draws walls behind me? I think that it only draws walls within my field of view – Caspian Ahlberg Mar 01 '21 at 16:14
  • See also [How do I fix warped walls in my raycaster?](https://stackoverflow.com/questions/66644579/how-do-i-fix-warped-walls-in-my-raycaster) and [How do I fix the warped perspective in my raycaster?](https://stackoverflow.com/questions/66591163/how-do-i-fix-the-warped-perspective-in-my-raycaster) from the same poster. – ggorlen May 06 '21 at 20:19
  • Just a note for anyone stumbling upon this problem: there are two common sources of distortion in raycasters. To fix the first one, multiply the distance by `cos(beta)`, where beta is `player_angle - ray_theta` (that one causes most of the distortion). For the second one, don't use a constant step size for `ray_theta`. Find `ray_theta` like this: `const double theta = atan((screen_x - half_screen_width) / projection_distance) + player_angle;`. `projection_distance` is equal to `half_screen_width / tan(to_radians(fov / 2.0));`. – Caspian Ahlberg May 06 '21 at 20:35

0 Answers0