1 """ Light ray manipulation. Pew. Pew. Pew. Wommmm. """
6 import pymunk.autogeometry
7 import pymunk.pygame_util
9 from .constants import SCREEN_SIZE
10 from .utils import debug_timer
14 """ An iterable that returns ordered rays from pos to the edge of the
15 screen, starting with the edge point (0, 0) and continuing clockwise
16 in pymunk coordinates.
19 left, right, bottom, top = 0, w, 0, h
21 for y in range(0, h, step):
22 yield pymunk.Vec2d(left, y)
23 for x in range(0, w, step):
24 yield pymunk.Vec2d(x, top)
25 for y in range(top, -1, -step):
26 yield pymunk.Vec2d(right, y)
27 for x in range(right, -1, -step):
28 yield pymunk.Vec2d(x, bottom)
31 @debug_timer("rays.calculate_ray_polys")
32 def calculate_ray_polys(space, position, light_filter):
33 """ Calculate a set of convex RayPolys that cover all the areas that light
34 can reach from the given position, taking into account the obstacles
37 position = pymunk.Vec2d(position)
39 start, end = None, None
41 for ray in screen_rays(position):
42 info = space.segment_query_first(position, ray, 1, light_filter)
43 point = ray if info is None else info.point
44 vertices.append(point)
45 if len(vertices) == 2:
47 elif len(vertices) > 3:
48 trial_poly = pymunk.Poly(None, vertices)
49 trial_poly.update(pymunk.Transform.identity())
50 query_prev = trial_poly.point_query(end)
51 query_pos = trial_poly.point_query(position)
52 if query_prev.distance < -0.01 or query_pos.distance < -0.01:
53 ray_polys.append(RayPoly(
54 start - position, end - position, vertices[:-1]))
56 vertices = [position, start]
58 vertices = trial_poly.get_vertices()
61 ray_polys.append(RayPoly(start, end, vertices))
65 def to_pymunk_radians(deg):
66 """ Convert degrees in [0, 360] to radians in (-pi, pi].
68 Return None if degrees is None.
72 deg = deg * math.pi / 180.0
78 class RayPolyManager(object):
79 def __init__(self, body, ray_filter):
81 self._ray_filter = ray_filter
83 self._angle_limits = (None, None)
84 self._poly_cache = None
85 self._pygame_poly_cache = None
87 def generate_rays(self, space, position):
88 self._rays = calculate_ray_polys(space, position, self._ray_filter)
89 self._poly_cache = None
90 self._pygame_poly_cache = None
92 def set_angle_limits(self, angle_limits):
93 start, end = angle_limits
94 self._angle_limits = (
95 to_pymunk_radians(start), to_pymunk_radians(end))
96 self._poly_cache = None
97 self._pygame_poly_cache = None
100 if self._poly_cache is None:
102 rp.poly(self._body, self._ray_filter) for rp in self._rays
103 if rp.within_limits(*self._angle_limits)
105 return self._poly_cache
107 def pygame_polys(self, surface):
108 if self._pygame_poly_cache is None:
109 self._pygame_poly_cache = [
111 pymunk.pygame_util.to_pygame(v, surface)
112 for v in poly.get_vertices()
114 for poly in self.polys()
116 return self._pygame_poly_cache
119 class RayPoly(object):
120 def __init__(self, start_vec, end_vec, vertices):
121 self.start = start_vec # vector from position to first point
122 self.end = end_vec # vector from position to last point
123 self.vertices = vertices
125 def poly(self, body, filter):
126 shape = pymunk.Poly(body, self.vertices)
127 shape.filter = filter
130 def within_limits(self, start_limit, end_limit):
131 if start_limit is None or end_limit is None:
135 if start_limit < end_limit:
136 return start_limit <= n <= end_limit
137 return (start_limit <= n) or (n <= end_limit)
139 start_within = between(self.start.angle)
140 end_within = between(self.end.angle)
141 if start_within or end_within: