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(position, vertices[:-1]))
55 vertices = [position, start]
57 vertices = trial_poly.get_vertices()
60 ray_polys.append(RayPoly(position, vertices))
64 def to_pymunk_radians(deg):
65 """ Convert degrees in [0, 360] to radians in (-pi, pi].
67 Return None if degrees is None.
71 deg = deg * math.pi / 180.0
77 class RayPolyManager(object):
78 def __init__(self, body, ray_filter):
79 self._body = body # light's body
80 self._ray_filter = ray_filter # light filter
81 self._rays = [] # list of RayPolys
82 self._start = None # normal vector in direction of start angle limit
83 self._end = None # normal vector in direction of end angle limit
84 self._poly_cache = None # list of pymunk.Polys for rays
86 def generate_rays(self, space, position):
87 self._rays = calculate_ray_polys(space, position, self._ray_filter)
88 self._poly_cache = None
90 def set_angle_limits(self, angle_limits):
91 if angle_limits is None:
95 self._start = pymunk.Vec2d(1, 0).rotated(
96 to_pymunk_radians(angle_limits[0]))
97 self._end = pymunk.Vec2d(1, 0).rotated(
98 to_pymunk_radians(angle_limits[1]))
99 self._poly_cache = None
102 if self._poly_cache is None:
103 self._poly_cache = poly_cache = []
104 for rp in self._rays:
105 poly = rp.poly(self._start, self._end)
107 poly.body = self._body
108 poly.filter = self._ray_filter
109 poly_cache.append(poly)
110 return self._poly_cache
112 def pygame_polys(self, surface):
114 [pymunk.pygame_util.to_pygame(v, surface)
115 for v in poly.get_vertices()]
116 for poly in self.polys()
120 class RayPoly(object):
121 def __init__(self, position, vertices):
122 self.position = position # pointy end of the conical polygon
123 self.vertices = vertices # all vertices in the polygon
125 def _between(self, v, start, end):
127 return start <= v <= end
128 return (start <= v) or (v <= end)
130 def poly(self, start, end):
131 trial = pymunk.Poly(None, self.vertices)
132 trial.update(pymunk.Transform.identity())
134 if start is None or end is None:
135 return trial # no limits
137 start_info = trial.segment_query(
138 self.position + 1250 * start, self.position + 0.1 * start, 0)
139 end_info = trial.segment_query(
140 self.position + 1250 * end, self.position + 0.1 * end, 0)
142 vertices = self.vertices[:]
145 if self._between((v - self.position).angle, start.angle, end.angle)
147 if start_info.shape is not None:
148 vertices.append(start_info.point)
149 if end_info.shape is not None:
150 vertices.append(end_info.point)
151 vertices.append(self.position)
153 poly = pymunk.Poly(None, vertices)
154 if len(poly.get_vertices()) < 3: