1 """ Light ray manipulation. Pew. Pew. Pew. Wommmm. """
8 import pymunk.autogeometry
9 import pymunk.pygame_util
11 from .constants import SCREEN_SIZE
12 from .utils import debug_timer
16 """ An iterable that returns ordered rays from pos to the edge of the
17 screen, starting with the edge point (0, 0) and continuing clockwise
18 in pymunk coordinates.
21 left, right, bottom, top = 0, w, 0, h
23 for y in range(0, h, step):
24 yield pymunk.Vec2d(left, y)
25 for x in range(0, w, step):
26 yield pymunk.Vec2d(x, top)
27 for y in range(top, -1, -step):
28 yield pymunk.Vec2d(right, y)
29 for x in range(right, -1, -step):
30 yield pymunk.Vec2d(x, bottom)
33 @debug_timer("rays.calculate_ray_polys")
34 def calculate_ray_polys(space, position, light_filter):
35 """ Calculate a set of convex RayPolys that cover all the areas that light
36 can reach from the given position, taking into account the obstacles
39 position = pymunk.Vec2d(position)
41 start, end = None, None
43 for ray in screen_rays(position):
44 info = space.segment_query_first(position, ray, 1, light_filter)
45 point = ray if info is None else info.point
46 vertices.append(point)
47 if len(vertices) == 2:
49 elif len(vertices) > 3:
50 trial_poly = pymunk.Poly(None, vertices)
51 trial_poly.update(pymunk.Transform.identity())
52 query_prev = trial_poly.point_query(end)
53 query_pos = trial_poly.point_query(position)
54 if query_prev.distance < -0.01 or query_pos.distance < -0.01:
55 ray_polys.append(RayPoly(position, vertices[:-1]))
57 vertices = [position, start]
59 vertices = trial_poly.get_vertices()
62 ray_polys.append(RayPoly(position, vertices))
66 def to_pymunk_radians(deg):
67 """ Convert degrees in [0, 360] to radians in (-pi, pi].
69 Return None if degrees is None.
73 deg = deg * math.pi / 180.0
79 class RayPolyManager(object):
81 self, body, position, ray_filter, radius_limits, angle_limits):
82 self._body = body # light's body
83 self._position = pymunk.Vec2d(position) # light's position
84 self._ray_filter = ray_filter # light filter
85 self._rays = [] # list of RayPolys
86 self._start = None # normal vector in direction of start angle limit
87 self._end = None # normal vector in direction of end angle limit
88 self._set_angle_limits(angle_limits)
89 self._max_radius = None # maximum radius in pixels
90 self._min_radius = None # minimum radius in pixels
91 self._set_radius_limits(radius_limits)
92 self._old_poly_cache = None # last polys added to the space
93 self._poly_cache = None # list of pymunk.Polys for rays
94 self._space = None # space the rays form part of
96 def set_space(self, space):
98 self._rays = calculate_ray_polys(
99 self._space, self._position, self._ray_filter)
100 self._poly_cache = None
102 def update_shapes(self):
103 if self._old_poly_cache:
104 self._space.remove(*self._old_poly_cache)
105 new_polys = self._old_poly_cache = self.polys()
106 self._space.add(*new_polys)
110 return self._position
113 def max_radius(self):
114 return self._max_radius
117 def max_radius(self, value):
118 self._max_radius = value or 0.0
121 def min_radius(self):
122 return self._min_radius
125 def min_radius(self, value):
126 self._min_radius = value or 0.0
128 def reaches(self, position):
129 distance = self.position.get_distance(self.position)
130 return (self._min_radius <= distance <= self._max_radius)
132 def _set_radius_limits(self, radius_limits):
133 if radius_limits is None or not radius_limits[0]:
136 self._min_radius = radius_limits[0]
137 if radius_limits is None or not radius_limits[1]:
138 self._max_radius = 50.0
140 self._max_radius = radius_limits[1]
142 def rotate_degrees(self, degrees):
143 self._start.rotate_degrees(degrees)
144 self._end.rotate_degrees(degrees)
145 self._poly_cache = None
147 def _set_angle_limits(self, angle_limits):
148 if angle_limits is None:
152 self._start = pymunk.Vec2d(1, 0).rotated(
153 to_pymunk_radians(angle_limits[0]))
154 self._end = pymunk.Vec2d(1, 0).rotated(
155 to_pymunk_radians(angle_limits[1]))
156 self._poly_cache = None
159 if self._poly_cache is None:
160 self._poly_cache = poly_cache = []
161 for rp in self._rays:
162 poly = rp.poly(self._start, self._end)
164 poly.body = self._body
165 poly.filter = self._ray_filter
166 poly_cache.append(poly)
167 return self._poly_cache
169 def pygame_position(self, surface):
170 return pymunk.pygame_util.to_pygame(self._position, surface)
172 def pygame_rect(self, surface):
173 half_width = self.max_radius
174 rect_width = half_width * 2
175 rect_x, rect_y = pymunk.pygame_util.to_pygame(self._position, surface)
176 dest_rect = pygame.rect.Rect(rect_x, rect_y, rect_width, rect_width)
177 dest_rect.move_ip(-half_width, -half_width)
180 def pygame_polys(self, surface):
182 [pymunk.pygame_util.to_pygame(v, surface)
183 for v in poly.get_vertices()]
184 for poly in self.polys()
188 class RayPoly(object):
189 def __init__(self, position, vertices):
190 self.position = position # pointy end of the conical polygon
191 self.vertices = vertices # all vertices in the polygon
193 def _between(self, v, start, end):
195 return start <= v <= end
196 return (start <= v) or (v <= end)
198 def poly(self, start, end):
199 trial = pymunk.Poly(None, self.vertices)
200 trial.update(pymunk.Transform.identity())
202 if start is None or end is None:
203 return trial # no limits
205 start_info = trial.segment_query(
206 self.position + 1250 * start, self.position + 0.1 * start, 0)
207 end_info = trial.segment_query(
208 self.position + 1250 * end, self.position + 0.1 * end, 0)
210 vertices = self.vertices[:]
213 if self._between((v - self.position).angle, start.angle, end.angle)
215 if start_info.shape is not None:
216 vertices.append(start_info.point)
217 if end_info.shape is not None:
218 vertices.append(end_info.point)
219 vertices.append(self.position)
221 poly = pymunk.Poly(None, vertices)
222 if len(poly.get_vertices()) < 3: