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
159 def _set_angle_limits(self, direction, spread):
160 if direction is None or spread is None:
161 self._direction = None
165 self._direction = pymunk.Vec2d(1, 0)
166 self._start = self._direction.rotated_degrees(-spread/2.)
167 self._end = self._direction.rotated_degrees(spread/2.)
168 self._poly_cache = None
171 if self._poly_cache is None:
172 self._poly_cache = poly_cache = []
173 for rp in self._rays:
174 poly = rp.poly(self._start, self._end)
176 poly.body = self._body
177 poly.filter = self._ray_filter
178 poly_cache.append(poly)
179 return self._poly_cache
181 def pygame_position(self, surface):
182 return pymunk.pygame_util.to_pygame(self._position, surface)
184 def pygame_rect(self, surface):
185 half_width = self.max_radius
186 rect_width = half_width * 2
187 rect_x, rect_y = pymunk.pygame_util.to_pygame(self._position, surface)
188 dest_rect = pygame.rect.Rect(rect_x, rect_y, rect_width, rect_width)
189 dest_rect.move_ip(-half_width, -half_width)
192 def pygame_polys(self, surface):
194 [pymunk.pygame_util.to_pygame(v, surface)
195 for v in poly.get_vertices()]
196 for poly in self.polys()
200 class RayPoly(object):
201 def __init__(self, position, vertices):
202 self.position = position # pointy end of the conical polygon
203 self.vertices = vertices # all vertices in the polygon
205 def _between(self, v, start, end):
207 return start <= v <= end
208 return (start <= v) or (v <= end)
210 def poly(self, start, end):
211 trial = pymunk.Poly(None, self.vertices)
212 trial.update(pymunk.Transform.identity())
214 if start is None or end is None:
215 return trial # no limits
217 start_info = trial.segment_query(
218 self.position + 1250 * start, self.position + 0.1 * start, 0)
219 end_info = trial.segment_query(
220 self.position + 1250 * end, self.position + 0.1 * end, 0)
222 vertices = self.vertices[:]
225 if self._between((v - self.position).angle, start.angle, end.angle)
227 if start_info.shape is not None:
228 vertices.append(start_info.point)
229 if end_info.shape is not None:
230 vertices.append(end_info.point)
231 vertices.append(self.position)
233 poly = pymunk.Poly(None, vertices)
234 if len(poly.get_vertices()) < 3: