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)
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(self.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 return self._direction.angle_degrees
154 def direction(self, degrees):
155 spread = self._direction.get_angle_between(self._start)
156 self._direction.angle_degrees = degrees
157 self._start = self._direction.rotated(spread)
158 self._end = self._direction.rotated(-spread)
159 self._poly_cache = None
163 if not self._direction:
165 return math.fabs(self._start.get_angle_between(self._end))
167 def _set_angle_limits(self, direction, spread):
168 if direction is None or spread is None:
169 self._direction = None
173 self._direction = pymunk.Vec2d(1, 0)
174 self._start = self._direction.rotated_degrees(-spread/2.)
175 self._end = self._direction.rotated_degrees(spread/2.)
176 self._poly_cache = None
179 if self._poly_cache is None:
180 self._poly_cache = poly_cache = []
181 for rp in self._rays:
182 poly = rp.poly(self._start, self._end)
184 poly.body = self._body
185 poly.filter = self._ray_filter
186 poly_cache.append(poly)
187 return self._poly_cache
189 def pygame_position(self, surface):
190 return pymunk.pygame_util.to_pygame(self._position, surface)
192 def pygame_rect(self, surface):
193 half_width = self.max_radius
194 rect_width = half_width * 2
195 rect_x, rect_y = pymunk.pygame_util.to_pygame(self._position, surface)
196 dest_rect = pygame.rect.Rect(rect_x, rect_y, rect_width, rect_width)
197 dest_rect.move_ip(-half_width, -half_width)
200 def pygame_polys(self, surface):
202 [pymunk.pygame_util.to_pygame(v, surface)
203 for v in poly.get_vertices()]
204 for poly in self.polys()
208 class RayPoly(object):
209 def __init__(self, position, vertices):
210 self.position = position # pointy end of the conical polygon
211 self.vertices = vertices # all vertices in the polygon
213 def _between(self, v, start, end):
215 return start <= v <= end
216 return (start <= v) or (v <= end)
218 def poly(self, start, end):
219 trial = pymunk.Poly(None, self.vertices)
220 trial.update(pymunk.Transform.identity())
222 if start is None or end is None:
223 return trial # no limits
225 start_info = trial.segment_query(
226 self.position + 1250 * start, self.position + 0.1 * start, 0)
227 end_info = trial.segment_query(
228 self.position + 1250 * end, self.position + 0.1 * end, 0)
230 vertices = self.vertices[:]
233 if self._between((v - self.position).angle, start.angle, end.angle)
235 if start_info.shape is not None:
236 vertices.append(start_info.point)
237 if end_info.shape is not None:
238 vertices.append(end_info.point)
239 vertices.append(self.position)
241 poly = pymunk.Poly(None, vertices)
242 if len(poly.get_vertices()) < 3: