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, 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)
91 self._max_radius = None # maximum radius in pixels
92 self._min_radius = None # minimum radius in pixels
93 self._set_radius_limits(radius_limits)
94 self._old_poly_cache = None # last polys added to the space
95 self._poly_cache = None # list of pymunk.Polys for rays
96 self._space = None # space the rays form part of
98 def set_space(self, space):
100 self._rays = calculate_ray_polys(
101 self._space, self._position, self._ray_filter)
102 self._poly_cache = None
104 def update_shapes(self):
105 if self._old_poly_cache:
106 self._space.remove(*self._old_poly_cache)
107 new_polys = self._old_poly_cache = self.polys()
108 self._space.add(*new_polys)
112 return self._position
115 def max_radius(self):
116 return self._max_radius
119 def max_radius(self, value):
120 self._max_radius = value or 0.0
123 def min_radius(self):
124 return self._min_radius
127 def min_radius(self, value):
128 self._min_radius = value or 0.0
130 def reaches(self, position):
131 distance = self.position.get_distance(self.position)
132 return (self._min_radius <= distance <= self._max_radius)
134 def _set_radius_limits(self, radius_limits):
135 if radius_limits is None or not radius_limits[0]:
138 self._min_radius = radius_limits[0]
139 if radius_limits is None or not radius_limits[1]:
140 self._max_radius = 50.0
142 self._max_radius = radius_limits[1]
145 return self._direction is not None
149 return self._direction.angle_degrees
152 def direction(self, degrees):
153 spread = self._direction.get_angle_between(self._start)
154 self._direction.angle_degrees = degrees
155 self._start = self._direction.rotated(spread)
156 self._end = self._direction.rotated(-spread)
157 self._poly_cache = None
161 if not self._direction:
163 return math.fabs(self._start.get_angle_between(self._end))
165 def _set_angle_limits(self, direction, spread):
166 if direction is None or spread is None:
167 self._direction = None
171 self._direction = pymunk.Vec2d(1, 0)
172 self._start = self._direction.rotated_degrees(-spread/2.)
173 self._end = self._direction.rotated_degrees(spread/2.)
174 self._poly_cache = None
177 if self._poly_cache is None:
178 self._poly_cache = poly_cache = []
179 for rp in self._rays:
180 poly = rp.poly(self._start, self._end)
182 poly.body = self._body
183 poly.filter = self._ray_filter
184 poly_cache.append(poly)
185 return self._poly_cache
187 def pygame_position(self, surface):
188 return pymunk.pygame_util.to_pygame(self._position, surface)
190 def pygame_rect(self, surface):
191 half_width = self.max_radius
192 rect_width = half_width * 2
193 rect_x, rect_y = pymunk.pygame_util.to_pygame(self._position, surface)
194 dest_rect = pygame.rect.Rect(rect_x, rect_y, rect_width, rect_width)
195 dest_rect.move_ip(-half_width, -half_width)
198 def pygame_polys(self, surface):
200 [pymunk.pygame_util.to_pygame(v, surface)
201 for v in poly.get_vertices()]
202 for poly in self.polys()
206 class RayPoly(object):
207 def __init__(self, position, vertices):
208 self.position = position # pointy end of the conical polygon
209 self.vertices = vertices # all vertices in the polygon
211 def _between(self, v, start, end):
213 return start <= v <= end
214 return (start <= v) or (v <= end)
216 def poly(self, start, end):
217 trial = pymunk.Poly(None, self.vertices)
218 trial.update(pymunk.Transform.identity())
220 if start is None or end is None:
221 return trial # no limits
223 start_info = trial.segment_query(
224 self.position + 1250 * start, self.position + 0.1 * start, 0)
225 end_info = trial.segment_query(
226 self.position + 1250 * end, self.position + 0.1 * end, 0)
228 vertices = self.vertices[:]
231 if self._between((v - self.position).angle, start.angle, end.angle)
233 if start_info.shape is not None:
234 vertices.append(start_info.point)
235 if end_info.shape is not None:
236 vertices.append(end_info.point)
237 vertices.append(self.position)
239 poly = pymunk.Poly(None, vertices)
240 if len(poly.get_vertices()) < 3: