Problem with recognising where a ray in raycaster intersects a wall along the horizontal axis

Question:

I am making a ray caster in python with pygame.
https://youtu.be/gYRrGTC7GtA?t=407
The problem is the cast_rays function. I have commented out the previous method that I was using.I used the above video written in C and adapted it to Python.
I wanted to use the raycasting algorithm in the above video since it would be casted then checking pixel by pixel.I have tried checking only horizontal lines and checking to see if there is a wall there. But, it doesn’t work.

import pygame
import sys
import math

pygame.init()

screen_height = 480
screen_width = screen_height * 2
map_size = 8
tile_size = screen_height / 8
player_x = screen_width / 4
player_y = screen_width / 4
FOV = math.pi / 3
HALF_FOV = FOV / 2
player_angle = math.pi + math.pi / 2
casted_rays = 120
step_angle = FOV / casted_rays
scale = screen_height / casted_rays

MAP = (
    '########'
    '#   #  #'
    '#   #  #'
    '#  ##  #'
    '#      #'
    '###    #'
    '###    #'
    '########'
    )
def draw_map():
    for row in range(8):
        for col in range(8):
            # square index
            square = row * map_size + col
            pygame.draw.rect(win, (200,200,200) if MAP[square] == '#' else (100,100,100),(row * tile_size, col * tile_size, tile_size - 2, tile_size - 2))
    pygame.draw.circle(win, (255,0,0), (player_x, player_y), 8)
    #pygame.draw.line(win, (0,255,0), (player_x, player_y), (player_x + math.cos(player_angle) * 50,  player_y + math.sin(player_angle) * 50) ,3)
    #pygame.draw.line(win, (0,255,0), (player_x, player_y), (player_x + math.cos(player_angle - HALF_FOV) * 50,  player_y + math.sin(player_angle - HALF_FOV) * 50) ,3)
    #pygame.draw.line(win, (0,255,0), (player_x, player_y), (player_x + math.cos(player_angle + HALF_FOV) * 50,  player_y + math.sin(player_angle + HALF_FOV) * 50) ,3)
def cast_rays():
    '''
    start_angle = player_angle - HALF_FOV
    for ray in range(casted_rays):
        for depth in range(screen_height):
            target_x = player_x + math.cos(start_angle) * depth
            target_y = player_y + math.sin(start_angle) * depth
            pygame.draw.line(win, (255,255,0), (player_x, player_y), (target_x, target_y) ,3)
            row = int(target_x / tile_size)
            col = int(target_y / tile_size)
            
            square = int(row * map_size + col)

            if MAP[square] == "#":
                pygame.draw.rect(win, (0,255, 0),(row * tile_size, col * tile_size, tile_size - 2, tile_size - 2))
                wall_height = 21000 / (depth + 0.00001)
                pygame.draw.rect(win, (100,100,100), (screen_height + ray * scale, (screen_height - wall_height) / 2 ,scale,wall_height))
                break


        start_angle += step_angle
    '''
    #dof = 0
    r = 0
    ra = player_angle
    ry = 0
    rx = 0
    while r < 1:
        dof = 0
        aTan = -1/math.tan(ra);
        if ra > math.pi:
            ry = ((ry * tile_size) / tile_size) - 0.0001
            rx = (player_y - ry) * aTan + player_x
            yo = -64
            xo = -yo * aTan
        if ra < math.pi:
            ry = ((ry * tile_size) / tile_size) + 64
            rx = (player_y - ry) * aTan + player_x
            yo = 64
            xo = -yo * aTan
        if ra == 0 or ra == math.pi:
            dof = 8
            ra = 0
            rx = player_x
            ry = player_y
        while dof < 8:
            mx = rx * tile_size
            my = ry * tile_size
            mp = my * tile_size
            if mp < tile_size * 8 * tile_size * 8 and MAP[int(mp)] == '#':
                dof = 8
            else:
                rx += xo
                ry += yo
        pygame.draw.line(win, (255,255,0), (player_x, player_y), (rx, ry) ,3)
        r += 1
win = pygame.display.set_mode((screen_width, screen_height))

clock = pygame.time.Clock()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    pygame.draw.rect(win, (0,0,0), (0, 0, screen_width, screen_height))
    draw_map()
    cast_rays()
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player_angle -= 0.1
    if keys[pygame.K_RIGHT]:
        player_angle += 0.1
    if keys[pygame.K_UP]:
        player_x, player_y = player_x + math.cos(player_angle) * 3, player_y + math.sin(player_angle) * 3
    if keys[pygame.K_DOWN]:
        player_x, player_y = player_x - math.cos(player_angle) * 3, player_y - math.sin(player_angle) * 3
    pygame.display.flip()
    clock.tick(30)
Asked By: dis_quake3_1

||

Answers:

Calculate the ray vextore:

rx = math.cos(player_angle)
ry = math.sin(player_angle)

Calculate the row and column of the players position in the map:

map_x = player_x // tile_size
map_y = player_y // tile_size

Calculation of the initial position relative to the tile and the direction:

t_max_x = player_x/tile_size - map_x
if rx > 0:
    t_max_x = 1 - t_max_x
t_max_y = player_y/tile_size - map_y
if ry > 0:
    t_max_y = 1 - t_max_y

Step forward until you leave the map or hit a block in a loop. This is the performance critical part, namely the actual raycasting loop:

while True:
    if ry == 0 or t_max_x < t_max_y * abs(rx / ry):
        side = 'x'
        map_x += 1 if rx > 0 else -1
        t_max_x += 1
        if map_x < 0 or map_x >= map_size:
            break
    else:
        side = 'y'
        map_y += 1 if ry > 0 else -1
        t_max_y += 1
        if map_x < 0 or map_y >= map_size:
            break
    if MAP[int(map_x * map_size + map_y)] == "#":
        break

Calculate the position at the edge of the hit block:

if side == 'x':
    x = (map_x + (1 if rx < 0 else 0)) * tile_size
    y = player_y + (x - player_x) * ry / rx
else:
    y = (map_y + (1 if ry < 0 else 0)) * tile_size
    x = player_x + (y - player_y) * rx / ry

Draw the ray:

pygame.draw.line(win, (0,255, 0), (player_x, player_y), (x, y))

Complete example:

import pygame
import math

pygame.init()

tile_size, map_size = 50, 8
board = [
    '########',
    '#   #  #',
    '#   # ##',
    '#  ##  #',
    '#      #',
    '###  ###',
    '#      #',
    '########']

def cast_rays(sx, sy, angle):
    rx = math.cos(angle)
    ry = math.sin(angle)
    map_x = sx // tile_size
    map_y = sy // tile_size

    t_max_x = sx/tile_size - map_x
    if rx > 0:
        t_max_x = 1 - t_max_x
    t_max_y = sy/tile_size - map_y
    if ry > 0:
        t_max_y = 1 - t_max_y

    while True:
        if ry == 0 or t_max_x < t_max_y * abs(rx / ry):
            side = 'x'
            map_x += 1 if rx > 0 else -1
            t_max_x += 1
            if map_x < 0 or map_x >= map_size:
                break
        else:
            side = 'y'
            map_y += 1 if ry > 0 else -1
            t_max_y += 1
            if map_x < 0 or map_y >= map_size:
                break
        if board[int(map_y)][int(map_x)] == "#":
            break

    if side == 'x':
        x = (map_x + (1 if rx < 0 else 0)) * tile_size
        y = player_y + (x - player_x) * ry / rx
    else:
        y = (map_y + (1 if ry < 0 else 0)) * tile_size
        x = player_x + (y - player_y) * rx / ry
    return x, y    


window = pygame.display.set_mode((tile_size*map_size, tile_size*map_size))
clock = pygame.time.Clock()

board_surf = pygame.Surface((tile_size*map_size, tile_size*map_size))
for row in range(8):
    for col in range(8):
        color = (192, 192, 192) if board[row][col] == '#' else (96, 96, 96)
        pygame.draw.rect(board_surf, color, (col * tile_size, row * tile_size, tile_size - 2, tile_size - 2))

player_x, player_y = round(tile_size * 4.5) + 0.5, round(tile_size * 4.5) + 0.5
player_angle = 0

run = True
while run:
    clock.tick(30)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False    
    
    keys = pygame.key.get_pressed()
    player_angle += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 0.1
    speed = (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * 3
    player_x -= math.cos(player_angle) * speed
    player_y -= math.sin(player_angle) * speed
    hit_pos = cast_rays(player_x, player_y, player_angle)

    window.blit(board_surf, (0, 0))
    pygame.draw.line(window, (0,255, 0), (player_x, player_y), hit_pos)
    pygame.draw.circle(window, (255,0,0), (player_x, player_y), 8)
    pygame.display.flip()

pygame.quit()
exit()

I actually used the same algorithm (just the 3D version) in a simple voxel raytracer:
Voxel Ray Tracing

Answered By: Rabbid76
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.