Ваш подход работает нормально, если компоненты x и y оси луча указывают в положительном направлении, но не работает, если он указывает в отрицательном направлении. Как вы отметили, это вызвано тем, как работает pygame.mask.Mask.overlap :
Начиная с верхнего левого угла, он проверяет биты от 0 до W - 1 из первая строка (от (0, 0) до (W - 1, 0)) затем продолжается до следующей строки (от (0, 1) до (W - 1, 1)). После проверки всего этого блока столбца он переходит к следующему (от W до 2 * W - 1).
Чтобы алгоритм работал, вы должны убедиться, что лучи всегда указывают на положительное направление. Следовательно, если луч указывает в отрицательном направлении x, то переверните маску и луч по вертикали, а если луч указывает в отрицательном направлении y, чем переверните луч по горизонтали.
Используйте pygame.transform.flip()
Сверху создать 4 маски. Не перевернуто, перевернуто по горизонтали, перевернуто по вертикали и перевернуто по вертикали и горизонтали:
mask = pg.mask.from_surface(mask_surface)
mask_fx = pg.mask.from_surface(pg.transform.flip(mask_surface, True, False))
mask_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, False, True))
mask_fx_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, True, True))
flipped_masks = [[mask, mask_fy], [mask_fx, mask_fx_fy]]
Определить, если направление луча:
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
Получить маску перевернутая в зависимости от направления луча:
flip_x = c < 0
flip_y = s < 0
filpped_mask = flipped_masks[flip_x][flip_y]
Вычислить перевернутую целевую точку:
x_dest = 250 + 500 * abs(c)
y_dest = 250 + 500 * abs(s)
Вычислить перевернутый смещение:
offset_x = 250 - pos[0] if flip_x else pos[0] - 250
offset_y = 250 - pos[1] if flip_y else pos[1] - 250
Получить ближайшую точку пересечения перевернутого луча и маски и отменить точку пересечения:
hit = filpped_mask.overlap(beam_mask, (offset_x, offset_y))
if hit is not None and (hit[0] != pos[0] or hit[1] != pos[1]):
hx = 500 - hit[0] if flip_x else hit[0]
hy = 500 - hit[1] if flip_y else hit[1]
hit_pos = (hx, hy)
pg.draw.line(surface, BLUE, mouse_pos, hit_pos)
pg.draw.circle(surface, GREEN, hit_pos, 3)
См. Пример:
import math
import sys
import pygame as pg
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
pg.init()
beam_surface = pg.Surface((500, 500), pg.SRCALPHA)
def draw_beam(surface, angle, pos):
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
flip_x = c < 0
flip_y = s < 0
filpped_mask = flipped_masks[flip_x][flip_y]
# compute beam final point
x_dest = 250 + 500 * abs(c)
y_dest = 250 + 500 * abs(s)
beam_surface.fill((0, 0, 0, 0))
# draw a single beam to the beam surface based on computed final point
pg.draw.line(beam_surface, BLUE, (250, 250), (x_dest, y_dest))
beam_mask = pg.mask.from_surface(beam_surface)
# find overlap between "global mask" and current beam mask
offset_x = 250 - pos[0] if flip_x else pos[0] - 250
offset_y = 250 - pos[1] if flip_y else pos[1] - 250
hit = filpped_mask.overlap(beam_mask, (offset_x, offset_y))
if hit is not None and (hit[0] != pos[0] or hit[1] != pos[1]):
hx = 499 - hit[0] if flip_x else hit[0]
hy = 499 - hit[1] if flip_y else hit[1]
hit_pos = (hx, hy)
pg.draw.line(surface, BLUE, pos, hit_pos)
pg.draw.circle(surface, GREEN, hit_pos, 3)
#pg.draw.circle(surface, (255, 255, 0), mouse_pos, 3)
surface = pg.display.set_mode((500, 500))
#mask_surface = pg.image.load("../assets/mask.png")
mask_surface = pg.Surface((500, 500), pg.SRCALPHA)
mask_surface.fill((255, 0, 0))
pg.draw.circle(mask_surface, (0, 0, 0, 0), (250, 250), 100)
pg.draw.rect(mask_surface, (0, 0, 0, 0), (170, 170, 160, 160))
mask = pg.mask.from_surface(mask_surface)
mask_fx = pg.mask.from_surface(pg.transform.flip(mask_surface, True, False))
mask_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, False, True))
mask_fx_fy = pg.mask.from_surface(pg.transform.flip(mask_surface, True, True))
flipped_masks = [[mask, mask_fy], [mask_fx, mask_fx_fy]]
clock = pg.time.Clock()
while True:
for e in pg.event.get():
if e.type == pg.QUIT:
pg.quit()
sys.exit()
mouse_pos = pg.mouse.get_pos()
surface.fill((0, 0, 0))
surface.blit(mask_surface, mask_surface.get_rect())
for angle in range(0, 359, 30):
draw_beam(surface, angle, mouse_pos)
pg.display.update()
clock.tick(30)
Нет, алгоритм можно улучшить. Луч всегда рисуется в правом нижнем квадранте beam_surface
. Следовательно, остальные 3 квадранта больше не нужны, и размер beam_surface
можно уменьшить до 250x250. Начало луча находится в (0, 0), а не в (250, 250), и вычисление смещений должно быть немного адаптировано:
beam_surface = pg.Surface((250, 250), pg.SRCALPHA)
def draw_beam(surface, angle, pos):
c = math.cos(math.radians(angle))
s = math.sin(math.radians(angle))
flip_x = c < 0
flip_y = s < 0
filpped_mask = flipped_masks[flip_x][flip_y]
# compute beam final point
x_dest = 500 * abs(c)
y_dest = 500 * abs(s)
beam_surface.fill((0, 0, 0, 0))
# draw a single beam to the beam surface based on computed final point
pg.draw.line(beam_surface, BLUE, (0, 0), (x_dest, y_dest))
beam_mask = pg.mask.from_surface(beam_surface)
# find overlap between "global mask" and current beam mask
offset_x = 499-pos[0] if flip_x else pos[0]
offset_y = 499-pos[1] if flip_y else pos[1]
hit = filpped_mask.overlap(beam_mask, (offset_x, offset_y))
if hit is not None and (hit[0] != pos[0] or hit[1] != pos[1]):
hx = 499 - hit[0] if flip_x else hit[0]
hy = 499 - hit[1] if flip_y else hit[1]
hit_pos = (hx, hy)
pg.draw.line(surface, BLUE, pos, hit_pos)
pg.draw.circle(surface, GREEN, hit_pos, 3)