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
132 def reaches(self, position):
133 distance = self.position.get_distance(position)
134 return (self._min_radius <= distance <= self._max_radius)
136 def _set_radius_limits(self, radius_limits):
137 if radius_limits is None or not radius_limits[0]:
140 self._min_radius = radius_limits[0]
141 if radius_limits is None or not radius_limits[1]:
142 self._max_radius = 50.0
144 self._max_radius = radius_limits[1]
147 return self._direction is not None
151 if self._direction is None:
153 return self._direction.angle_degrees
156 def direction(self, degrees):
157 spread = self._direction.get_angle_between(self._start)
158 self._direction.angle_degrees = degrees
159 self._start = self._direction.rotated(spread)
160 self._end = self._direction.rotated(-spread)
161 self._poly_cache = None
165 if not self._direction:
167 return math.fabs(self._start.get_angle_between(self._end))
169 def _set_angle_limits(self, direction, spread):
170 if direction is None or spread is None:
171 self._direction = None
175 self._direction = pymunk.Vec2d(1, 0)
176 self._start = self._direction.rotated_degrees(-spread/2.)
177 self._end = self._direction.rotated_degrees(spread/2.)
178 self._poly_cache = None
181 if self._poly_cache is None:
182 self._poly_cache = poly_cache = []
183 for rp in self._rays:
184 poly = rp.poly(self._start, self._end)
186 poly.body = self._body
187 poly.filter = self._ray_filter
188 poly_cache.append(poly)
189 return self._poly_cache
191 def pygame_position(self, surface):
192 return pymunk.pygame_util.to_pygame(self._position, surface)
194 def pygame_rect(self, surface):
195 half_width = self.max_radius
196 rect_width = half_width * 2
197 rect_x, rect_y = pymunk.pygame_util.to_pygame(self._position, surface)
198 dest_rect = pygame.rect.Rect(rect_x, rect_y, rect_width, rect_width)
199 dest_rect.move_ip(-half_width, -half_width)
202 def pygame_polys(self, surface):
204 [pymunk.pygame_util.to_pygame(v, surface)
205 for v in poly.get_vertices()]
206 for poly in self.polys()
210 class RayPoly(object):
211 def __init__(self, position, vertices):
212 self.position = position # pointy end of the conical polygon
213 self.vertices = vertices # all vertices in the polygon
215 def _between(self, v, start, end):
217 return start <= v <= end
218 return (start <= v) or (v <= end)
220 def poly(self, start, end):
221 trial = pymunk.Poly(None, self.vertices)
222 trial.update(pymunk.Transform.identity())
224 if start is None or end is None:
225 return trial # no limits
227 start_info = trial.segment_query(
228 self.position + 1250 * start, self.position + 0.1 * start, 0)
229 end_info = trial.segment_query(
230 self.position + 1250 * end, self.position + 0.1 * end, 0)
232 vertices = self.vertices[:]
235 if self._between((v - self.position).angle, start.angle, end.angle)
237 if start_info.shape is not None:
238 vertices.append(start_info.point)
239 if end_info.shape is not None:
240 vertices.append(end_info.point)
241 vertices.append(self.position)
243 poly = pymunk.Poly(None, vertices)
244 if len(poly.get_vertices()) < 3: