#๐ Pygame ray casting
15 messages ยท Page 1 of 1 (latest)
@mild locust
Remember to:
- Ask your Python question, not if you can ask or if there's an expert who can help.
- Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
- Explain what you expect to happen and what actually happens.
:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.
import pygame
import math
# Initialize Pygame
pygame.init()
width, height = 1000, 750
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
# Define walls as rectangles with top-left and bottom-right points
walls = [((100, 100), (200, 200)), ((300, 100), (400, 300)), ((350, 250), (650, 400)), ((250, 300), (300, 350)), ((100, 100), (200, 200)), ((100, 100), (200, 200))]
# Light range (distance the light can reach)
light_range = 200
def draw_walls():
for wall in walls:
pygame.draw.polygon(screen, (255, 255, 255),
[wall[0], (wall[1][0], wall[0][1]), wall[1], (wall[0][0], wall[1][1])], 1)
def get_rect_edges(wall):
"""Given a wall's top-left and bottom-right corners, return all four edges."""
tl, br = wall
tr = (br[0], tl[1]) # top-right corner
bl = (tl[0], br[1]) # bottom-left corner
return [(tl, tr), (tr, br), (br, bl), (bl, tl)]
def find_intersection(ray_origin, ray_direction, edge):
x1, y1 = edge[0]
x2, y2 = edge[1]
x3, y3 = ray_origin
x4, y4 = ray_origin[0] + ray_direction[0] * 1000, ray_origin[1] + ray_direction[1] * 1000
denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if denom == 0:
return None # Parallel lines
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom
if 0 <= t <= 1 and 0 <= u <= 1:
intersection_x = x1 + t * (x2 - x1)
intersection_y = y1 + t * (y2 - y1)
return (intersection_x, intersection_y)
else:
return None
def is_occluded(source, target, walls):
"""Check if the target point is occluded by any walls from the source point."""
direction = (target[0] - source[0], target[1] - source[1])
for wall in walls:
for edge in get_rect_edges(wall):
intersection = find_intersection(source, direction, edge)
if intersection and intersection != target:
if 0 < math.dist(source, intersection) < math.dist(source, target):
return True
return False
def point_inside_box(point, box):
"""Check if a point is inside a given box."""
(box_tl, box_br) = box
return box_tl[0] < point[0] < box_br[0] and box_tl[1] < point[1] < box_br[1]
def interpolate_points(start, end, num_points):
"""Generate points between start and end."""
return [(start[0] + i*(end[0]-start[0])/num_points, start[1] + i*(end[1]-start[1])/num_points) for i in range(num_points)]
def cast_rays_from_light(mouse_pos):
for wall in walls:
edges = get_rect_edges(wall)
for edge in edges:
for corner in edge[:1]: # Only check the first point of each edge to avoid duplicates
if not is_occluded(mouse_pos, corner, walls) and math.dist(mouse_pos, corner) <= light_range:
corner_ray_length = math.dist(mouse_pos, corner)
shadow_ray_max_length = light_range - corner_ray_length
direction = (corner[0] - mouse_pos[0], corner[1] - mouse_pos[1])
norm = math.sqrt(direction[0] ** 2 + direction[1] ** 2)
normalized_direction = (direction[0] / norm, direction[1] / norm)
pygame.draw.line(screen, (255, 0, 0), mouse_pos, corner, 1)
shadow_direction = normalized_direction
shadow_ray_end = (corner[0] + shadow_direction[0] * shadow_ray_max_length,
corner[1] + shadow_direction[1] * shadow_ray_max_length)
# Check multiple points along the shadow ray to see if it's inside any box
check_points = interpolate_points(corner, shadow_ray_end, 10) # 10 can be adjusted as needed
shadow_ray_inside_box = any(point_inside_box(point, box) for point in check_points for box in walls)
shadow_intersection_point = None
min_distance = float('inf')
for check_wall in walls:
for check_edge in get_rect_edges(check_wall):
intersection = find_intersection(corner, shadow_direction, check_edge)
if intersection:
intersection_distance = math.dist(corner, intersection)
if 0 < intersection_distance < shadow_ray_max_length:
shadow_intersection_point = intersection
shadow_ray_max_length = intersection_distance # Adjust the shadow ray's length
# Draw the shadow ray with the adjusted length
if shadow_intersection_point:
pygame.draw.line(screen, (0, 0, 255), corner, shadow_intersection_point, 1)
else:
pygame.draw.line(screen, (0, 0, 255), corner, shadow_ray_end, 1)
def main():
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((0, 0, 0))
mouse_pos = pygame.mouse.get_pos()
draw_walls()
cast_rays_from_light(mouse_pos)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
The problem is the shadow rays (blue lines) go inside the boxes. It should do something like that
hm. why does it do that?
why do the rays go through the corners?
do you have any explanation for that behaviour?
also do you have an idea how to specify in which cases the ray should not pass a corner based on the coordinates of the mouse and the two edges?
I'm trying to create an lighting system. The rays should be casted only when it is inside the light range and does see a corner of the box without begin blocked by any other walls (I call that corner ray). The blue rays are shadow rays. Shadow rays will be casted after the corner rays, they are cut when they hit another wall or the box itself. When it tries to go inside the box then just pass. And the ray length can not be over the light range so... light_range >= (corner_ray + shadow_ray).
Okay, but you don't want them to pass into the box.
So a corner that actually blocks the ray should be handled the same way as a solid edge. So it should be excluded from the ray casting.
This help channel has been closed and it's no longer possible to send messages here. If your question wasn't answered, feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.