Last active
March 6, 2021 21:56
-
-
Save paulnbrd/d6ac6f948e58e001e4d423f035e664d8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame, sys | |
pygame.init() | |
import shadow | |
polygons = [ | |
( | |
(75, 150), | |
(60, 175), | |
(75, 350), | |
(300, 225), | |
(75, 150) | |
), | |
( | |
(88*2, 198*2), | |
(75*2, 210*2), | |
(100*2, 350*2), | |
(300*2, 225*2), | |
(100*2, 150*2) | |
), | |
( | |
(530, 250), | |
(708, 193), | |
(627, 300) | |
), | |
( | |
(530*2, 250*2), | |
(708*2, 193*2), | |
(627*2, 300*2) | |
), | |
] | |
root = pygame.display.set_mode((1920, 1080), pygame.DOUBLEBUF | pygame.HWACCEL | pygame.HWSURFACE) | |
done = False | |
shadow_engine = shadow.Engine(polygons, cast_number=100, optimized=True) | |
clock = pygame.time.Clock() | |
font = pygame.font.SysFont("Arial Black", 12) | |
debug_mode: bool = True | |
while not done: | |
_delta = clock.tick(0) | |
_t = str(round(clock.get_fps())) + " FPS (" + str(_delta) + "ms)" | |
# pygame.display.set_caption(_t) | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT or event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: | |
pygame.quit() | |
sys.exit(0) | |
elif event.type == pygame.KEYDOWN and event.key == pygame.K_e: | |
debug_mode = not debug_mode | |
root.fill((0, 0, 0)) | |
for points in polygons: | |
pygame.draw.polygon(root, (0, 255, 255), points, 0) | |
shadow_engine.set_pos(pygame.mouse.get_pos()) | |
shadow_engine.update() | |
if debug_mode: | |
shadow_engine.debug_draw(root) | |
else: | |
p = shadow_engine.get_shadow_polygon() | |
pygame.draw.polygon(root, (255, 255, 255), p) | |
t = font.render("Press e to switch between debug mode and render mode", True, (255, 0, 255)) | |
root.blit(t, (0, 0)) | |
t = font.render(str(len(shadow_engine.casts)) + " casts - "+_t, True, (255, 0, 255)) | |
root.blit(t, (0, t.get_height())) | |
pygame.display.flip() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygame | |
import math | |
from typing import List, Tuple | |
import logging | |
import math | |
import random | |
from operator import attrgetter | |
pygame.init() | |
logging.basicConfig(level="DEBUG") # Not used... | |
def line_intersect(Ax1, Ay1, Ax2, Ay2, Bx1, By1, Bx2, By2): | |
""" returns a (x, y) tuple or False if there is no intersection """ | |
d = (By2 - By1) * (Ax2 - Ax1) - (Bx2 - Bx1) * (Ay2 - Ay1) | |
if d: | |
uA = ((Bx2 - Bx1) * (Ay1 - By1) - (By2 - By1) * (Ax1 - Bx1)) / d | |
uB = ((Ax2 - Ax1) * (Ay1 - By1) - (Ay2 - Ay1) * (Ax1 - Bx1)) / d | |
else: | |
return False | |
if not (0 <= uA <= 1 and 0 <= uB <= 1): | |
return False | |
x = Ax1 + uA * (Ax2 - Ax1) | |
y = Ay1 + uA * (Ay2 - Ay1) | |
return x, y | |
def _line_intersection(line1, line2): | |
""" Simplification of line_intersect """ | |
return line_intersect(line1[0][0], line1[0][1], line1[1][0], line1[1][1], line2[0][0], line2[0][1], line2[1][0], | |
line2[1][1]) | |
#################### | |
class Cast: | |
def __init__( | |
self, | |
angle: float, | |
start_pos: Tuple[float, float], | |
polygons: List[Tuple], | |
length: float = 750 * 1.5, | |
screen_size: Tuple[int, int] = (750, 750) | |
): | |
""" | |
A cast | |
:param angle: The angle (in radians) of the cast | |
:param start_pos: The start pos | |
:param polygons: The polygons of the 'map' | |
:param length: The max length of the cast | |
:param screen_size: The surface/screen size | |
""" | |
self.angle = angle | |
self.polygons = polygons | |
self.cast_length = length | |
self.intersect_pos = (0, 0) | |
self._screen_size = screen_size | |
self.end_pos_max = None | |
self.start_pos = None | |
self.set_pos(start_pos) | |
self.has_intersected: bool = False | |
def set_position(self, *a, **k): | |
return self.set_pos(*a, **k) | |
def set_pos(self, newpos): | |
self.start_pos = newpos | |
self.end_pos_max = math.cos(self.angle) * self.cast_length + self.start_pos[0], math.sin( | |
self.angle) * self.cast_length + self.start_pos[1] | |
# if self.end_pos_max[0] > self._screen_size[0]: | |
# self.end_pos_max = self._screen_size[0], self.end_pos_max[1] | |
# elif self.end_pos_max[0] < 0: | |
# self.end_pos_max = 0, self.end_pos_max[1] | |
# if self.end_pos_max[1] > self._screen_size[1]: | |
# self.end_pos_max = self.end_pos_max[0], self._screen_size[1] | |
# elif self.end_pos_max[1] < 0: | |
# self.end_pos_max = self.end_pos_max[0], 0 | |
# Not used, but to optimize: the cast should stop on the screen/surface borders | |
def cast(self): | |
closest = False | |
intersect = None | |
for polygon in self.polygons: | |
for i in range(len(polygon)): # For each points of the polygons | |
# This loop compute the closest intersection point | |
if i == len(polygon) - 1: | |
tmp = _line_intersection((polygon[0], polygon[i]), | |
(self.start_pos, self.end_pos_max)) | |
else: | |
tmp = _line_intersection((polygon[i], polygon[i + 1]), | |
(self.start_pos, self.end_pos_max)) | |
if tmp: | |
dx = tmp[0] - self.start_pos[0] | |
dy = tmp[1] - self.start_pos[1] | |
distance = math.sqrt(dx ** 2 + dy ** 2) # Compute angle | |
if not closest: | |
closest = distance | |
intersect = tmp | |
elif distance < closest: | |
closest = distance | |
intersect = tmp | |
self.intersect_pos = intersect | |
if not self.intersect_pos: | |
self.has_intersected = False | |
self.intersect_pos = self.end_pos_max | |
else: | |
self.has_intersected = True | |
return self.intersect_pos # This code is right, I think | |
class Engine: | |
def __init__( | |
self, | |
polygons: List[tuple], | |
optimized: bool = True, | |
pos: Tuple[float, float] = pygame.mouse.get_pos(), | |
cast_number: int = 360 | |
): | |
""" | |
The shadow engine | |
:param polygons: The polygons, represented as tuple in a list of lists | |
:param optimized: Is the engine optimized (just for testing purposes, it should always be True) | |
:param pos: The engine pos, from where the shadow come from | |
:param cast_number: The number of cast if not optimized, else the number of additional casts | |
""" | |
self.polygons = polygons | |
self.optimized = optimized | |
self.pos = pos | |
self.casts = [] | |
self.additional_cast_number = cast_number | |
# if self.additional_cast_number < 25: | |
# self.additional_cast_number = 25 | |
# logging.debug("You need at least 50 additional casts for a good rendering") | |
if not self.optimized: | |
for i in range(self.additional_cast_number): | |
self.casts.append(Cast( | |
i / self.additional_cast_number * 360, | |
self.pos, | |
self.polygons | |
)) | |
def set_pos(self, newpos): | |
""" Change engine position, and all casts position too """ | |
self.pos = newpos | |
for cast in self.casts: | |
cast.set_pos(newpos) | |
def update(self): | |
if self.optimized: | |
self.casts.clear() | |
#### | |
# Additional casts | |
nb = self.additional_cast_number | |
for i in range(nb): | |
self.casts.append(Cast( | |
i / nb * 2 * math.pi, | |
self.pos, | |
self.polygons | |
)) | |
#### | |
for polygon in self.polygons: | |
for point in polygon: # For each points | |
dx = point[0] - self.pos[0] | |
dy = point[1] - self.pos[1] | |
angle = math.atan2(dy, dx) # Compute angle | |
# Add cast on the point, just before, and just after | |
self.casts.append(Cast( | |
angle - 0.001, | |
self.pos, | |
self.polygons | |
)) | |
self.casts.append(Cast( | |
angle, | |
self.pos, | |
self.polygons | |
)) | |
self.casts.append(Cast( | |
angle + 0.001, | |
self.pos, | |
self.polygons | |
)) | |
for cast in self.casts: | |
cast.cast() # Cast all casts | |
############# | |
## I THINK THIS IS THE PROBLEM (or something like that) ! ## | |
############# | |
self.casts = sorted(self.casts, key=lambda x: x.angle) # Sort by angle | |
def get_shadow_polygon(self): | |
""" Returns the polygon from each casts position """ | |
if self.optimized: | |
o = [] | |
c = self.casts.copy() | |
for i in range(len(c)): | |
m = min(c, key=attrgetter("angle")) | |
o.append(m) | |
c.remove(m) | |
else: | |
o = self.casts | |
returnarray = [] | |
for i in o: | |
returnarray.append(i.intersect_pos) | |
return returnarray | |
def debug_draw(self, surface: pygame.Surface): | |
""" Debug draw of casts """ | |
font = pygame.font.SysFont("Arial Black", 12) | |
for cast in self.casts: | |
pygame.draw.line(surface, (0, 0, 255) if cast.has_intersected else (255, 0, 0), cast.start_pos, | |
cast.intersect_pos) | |
# text = font.render(str(self.casts.index(cast)), True, (255, 255, 255)) | |
text = font.render(str(round(cast.angle, 2)), True, (255, 255, 255)) | |
surface.blit(text, cast.intersect_pos) | |
text = font.render(str(self.casts.index(cast)), True, (255, 255, 255)) | |
surface.blit(text, | |
(cast.intersect_pos[0], | |
cast.intersect_pos[1] + text.get_height() + 2) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment