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, angle_limits):
82 self._body = body # light's body
83 self._position = pymunk.Vec2d(position) # light's position
84 self._ray_filter = ray_filter # light filter
85 self._rays = [] # list of RayPolys
86 self._start = None # normal vector in direction of start angle limit
87 self._end = None # normal vector in direction of end angle limit
88 self._set_angle_limits(angle_limits)
89 self._max_radius = None # maximum radius in pixels
90 self._min_radius = None # minimum radius in pixels
91 self._set_radius_limits(radius_limits)
92 self._old_poly_cache = None # last polys added to the space
93 self._poly_cache = None # list of pymunk.Polys for rays
94 self._space = None # space the rays form part of
96 def set_space(self, space):
98 self._rays = calculate_ray_polys(
99 self._space, self._position, self._ray_filter)
100 self._poly_cache = None
102 def update_shapes(self):
103 if self._old_poly_cache:
104 self._space.remove(*self._old_poly_cache)
105 new_polys = self._old_poly_cache = self.polys()
106 self._space.add(*new_polys)
110 return self._position
113 def max_radius(self):
114 return self._max_radius
117 def max_radius_setter(self, value):
118 self._max_radius = value or 0.0
121 def min_radius(self):
122 return self._min_radius
125 def min_radius_setter(self, value):
126 self._min_radius = value or 0.0
128 def _set_radius_limits(self, radius_limits):
129 if radius_limits is None or not radius_limits[0]:
132 self._min_radius = radius_limits[0]
133 if radius_limits is None or not radius_limits[1]:
134 self._max_radius = 50.0
136 self._max_radius = radius_limits[1]
138 def rotate_degrees(self, degrees):
139 self._start.rotate_degrees(degrees)
140 self._end.rotate_degrees(degrees)
141 self._poly_cache = None
143 def _set_angle_limits(self, angle_limits):
144 if angle_limits is None:
148 self._start = pymunk.Vec2d(1, 0).rotated(
149 to_pymunk_radians(angle_limits[0]))
150 self._end = pymunk.Vec2d(1, 0).rotated(
151 to_pymunk_radians(angle_limits[1]))
152 self._poly_cache = None
155 if self._poly_cache is None:
156 self._poly_cache = poly_cache = []
157 for rp in self._rays:
158 poly = rp.poly(self._start, self._end)
160 poly.body = self._body
161 poly.filter = self._ray_filter
162 poly_cache.append(poly)
163 return self._poly_cache
165 def pygame_position(self, surface):
166 return pymunk.pygame_util.to_pygame(self._position, surface)
168 def pygame_rect(self, surface):
169 half_width = self.max_radius
170 rect_width = half_width * 2
171 rect_x, rect_y = pymunk.pygame_util.to_pygame(self._position, surface)
172 dest_rect = pygame.rect.Rect(rect_x, rect_y, rect_width, rect_width)
173 dest_rect.move_ip(-half_width, -half_width)
176 def pygame_polys(self, surface):
178 [pymunk.pygame_util.to_pygame(v, surface)
179 for v in poly.get_vertices()]
180 for poly in self.polys()
184 class RayPoly(object):
185 def __init__(self, position, vertices):
186 self.position = position # pointy end of the conical polygon
187 self.vertices = vertices # all vertices in the polygon
189 def _between(self, v, start, end):
191 return start <= v <= end
192 return (start <= v) or (v <= end)
194 def poly(self, start, end):
195 trial = pymunk.Poly(None, self.vertices)
196 trial.update(pymunk.Transform.identity())
198 if start is None or end is None:
199 return trial # no limits
201 start_info = trial.segment_query(
202 self.position + 1250 * start, self.position + 0.1 * start, 0)
203 end_info = trial.segment_query(
204 self.position + 1250 * end, self.position + 0.1 * end, 0)
206 vertices = self.vertices[:]
209 if self._between((v - self.position).angle, start.angle, end.angle)
211 if start_info.shape is not None:
212 vertices.append(start_info.point)
213 if end_info.shape is not None:
214 vertices.append(end_info.point)
215 vertices.append(self.position)
217 poly = pymunk.Poly(None, vertices)
218 if len(poly.get_vertices()) < 3: