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():
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, direction,
83 self._body = body # light's body
84 self._position = pymunk.Vec2d(position) # light's position
85 self._ray_filter = ray_filter # light filter
86 self._rays = [] # list of RayPolys
87 self._direction = None # normal vector for direction
88 self._start = None # normal vector in direction of start angle limit
89 self._end = None # normal vector in direction of end angle limit
90 self._set_angle_limits(direction, spread)
92 self.direction = direction # Update direction
93 self._max_radius = None # maximum radius in pixels
94 self._min_radius = None # minimum radius in pixels
95 self._set_radius_limits(radius_limits)
96 self._old_poly_cache = None # last polys added to the space
97 self._poly_cache = None # list of pymunk.Polys for rays
98 self._space = None # space the rays form part of
100 def set_space(self, space):
102 self._rays = calculate_ray_polys(
103 self._space, self._position, self._ray_filter)
104 self._poly_cache = None
106 def update_shapes(self):
107 if self._old_poly_cache:
108 self._space.remove(*self._old_poly_cache)
109 new_polys = self._old_poly_cache = self.polys()
110 self._space.add(*new_polys)
114 return self._position
117 def max_radius(self):
118 return self._max_radius
121 def max_radius(self, value):
122 self._max_radius = value or 0.0
125 def min_radius(self):
126 return self._min_radius
129 def min_radius(self, value):
130 self._min_radius = value or 0.0
133 """ Return the required information from the ray_manager """
134 if self._direction is None:
138 direction = self._direction.angle_degrees
139 spread = math.degrees(self.spread)
141 "radius_limits": (self._min_radius, self._max_radius),
142 "direction": direction,
146 def reaches(self, position):
147 distance = self.position.get_distance(position)
148 return (self._min_radius <= distance <= self._max_radius)
150 def _set_radius_limits(self, radius_limits):
151 if radius_limits is None or not radius_limits[0]:
154 self._min_radius = radius_limits[0]
155 if radius_limits is None or not radius_limits[1]:
156 self._max_radius = 50.0
158 self._max_radius = radius_limits[1]
161 return self._direction is not None
165 if self._direction is None:
167 return self._direction.angle_degrees
170 def direction(self, degrees):
171 spread = self._direction.get_angle_between(self._start)
172 self._direction.angle_degrees = degrees
173 self._start = self._direction.rotated(spread)
174 self._end = self._direction.rotated(-spread)
175 self._poly_cache = None
179 if not self._direction:
181 return math.fabs(self._start.get_angle_between(self._end))
183 def _set_angle_limits(self, direction, spread):
184 if direction is None or spread is None:
185 self._direction = None
189 self._direction = pymunk.Vec2d(1, 0)
190 self._start = self._direction.rotated_degrees(-spread/2.)
191 self._end = self._direction.rotated_degrees(spread/2.)
192 self._poly_cache = None
195 if self._poly_cache is None:
196 self._poly_cache = poly_cache = []
197 for rp in self._rays:
198 poly = rp.poly(self._start, self._end)
200 poly.body = self._body
201 poly.filter = self._ray_filter
202 poly_cache.append(poly)
203 return self._poly_cache
205 def pygame_position(self, surface):
206 return pymunk.pygame_util.to_pygame(self._position, surface)
208 def pygame_rect(self, surface):
209 half_width = self.max_radius
210 rect_width = half_width * 2
211 rect_x, rect_y = pymunk.pygame_util.to_pygame(self._position, surface)
212 dest_rect = pygame.rect.Rect(rect_x, rect_y, rect_width, rect_width)
213 dest_rect.move_ip(-half_width, -half_width)
216 def pygame_polys(self, surface):
218 [pymunk.pygame_util.to_pygame(v, surface)
219 for v in poly.get_vertices()]
220 for poly in self.polys()
224 class RayPoly(object):
225 def __init__(self, position, vertices):
226 self.position = position # pointy end of the conical polygon
227 self.vertices = vertices # all vertices in the polygon
229 def _between(self, v, start, end):
231 return start <= v <= end
232 return (start <= v) or (v <= end)
234 def poly(self, start, end):
235 trial = pymunk.Poly(None, self.vertices)
236 trial.update(pymunk.Transform.identity())
238 if start is None or end is None:
239 return trial # no limits
241 start_info = trial.segment_query(
242 self.position + 1250 * start, self.position + 0.1 * start, 0)
243 end_info = trial.segment_query(
244 self.position + 1250 * end, self.position + 0.1 * end, 0)
246 vertices = self.vertices[:]
249 if self._between((v - self.position).angle, start.angle, end.angle)
251 if start_info.shape is not None:
252 vertices.append(start_info.point)
253 if end_info.shape is not None:
254 vertices.append(end_info.point)
255 vertices.append(self.position)
257 poly = pymunk.Poly(None, vertices)
258 if len(poly.get_vertices()) < 3: