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

Viewed 69

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)
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 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():
    rx = math.cos(player_angle)
    ry = math.sin(player_angle)
    map_x = player_x // tile_size
    map_y = player_y // tile_size

    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

    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

    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

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

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)

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

Related