1 """ May it be a light for you in dark places, when all other lights go out.
5 import pymunk.pygame_util
8 import pygame.locals as pgl
10 from .constants import (
11 SCREEN_SIZE, LIGHT_CATEGORY, FITTINGS_CATEGORY)
12 from .utils import debug_timer
14 LIGHT_FILTER = pymunk.ShapeFilter(
15 mask=pymunk.ShapeFilter.ALL_MASKS ^ (
16 LIGHT_CATEGORY | FITTINGS_CATEGORY),
17 categories=LIGHT_CATEGORY)
19 FITTINGS_FILTER = pymunk.ShapeFilter(
20 mask=pymunk.ShapeFilter.ALL_MASKS ^ (
21 LIGHT_CATEGORY | FITTINGS_CATEGORY),
22 categories=FITTINGS_CATEGORY)
24 # Just match lights, nothing else
25 LIT_BY_FILTER = pymunk.ShapeFilter(mask=LIGHT_CATEGORY)
29 """ An iterable that returns ordered rays from pos to the edge of the
30 screen, starting with the edge point (0, 0) and continuing clockwise
31 in pymunk coordinates.
34 left, right, bottom, top = 0, w, 0, h
36 for y in range(0, h, step):
37 yield pymunk.Vec2d(left, y)
38 for x in range(0, w, step):
39 yield pymunk.Vec2d(x, top)
40 for y in range(top, -1, -step):
41 yield pymunk.Vec2d(right, y)
42 for x in range(right, -1, -step):
43 yield pymunk.Vec2d(x, bottom)
46 @debug_timer("lights.calculate_ray_polys")
47 def calculate_ray_polys(space, body, position):
48 position = pymunk.Vec2d(position)
51 for ray in screen_rays(position):
52 info = space.segment_query_first(position, ray, 1, LIGHT_FILTER)
53 point = ray if info is None else info.point
54 vertices.append(point)
56 trial_poly = pymunk.Poly(None, vertices)
57 trial_poly.update(pymunk.Transform.identity())
58 query_prev = trial_poly.point_query(vertices[-2])
59 query_pos = trial_poly.point_query(position)
60 if query_prev.distance < -0.01 or query_pos.distance < -0.01:
61 new_poly = pymunk.Poly(body, vertices[:-1])
62 vertices = [position, vertices[-1]]
63 ray_polys.append(new_poly)
65 vertices = trial_poly.get_vertices() + [point]
67 ray_polys.append(pymunk.Poly(body, vertices))
71 class LightManager(object):
72 """ Manages a set of lights. """
74 def __init__(self, space, gamestate):
77 BaseLight.load(cfg) for cfg in gamestate.station["lights"]]
78 for light in self._lights:
79 light.add(self._space)
81 def toggle_nearest(self, *args, **kw):
82 light = self.nearest(*args, **kw)
86 def nearest(self, pos, surfpos=False, max_distance=1.0):
88 surface = pygame.display.get_surface()
89 pos = pymunk.pygame_util.from_pygame(pos, surface)
90 point_info = self._space.point_query_nearest(
91 pos, max_distance, pymunk.ShapeFilter(mask=FITTINGS_CATEGORY))
92 if point_info is not None:
93 return point_info.shape.body.light
96 def lit_by(self, pos, surfpos=False, max_distance=0.0):
98 surface = pygame.display.get_surface()
99 pos = pymunk.pygame_util.from_pygame(pos, surface)
100 point_info_list = self._space.point_query(
101 pos, max_distance, pymunk.ShapeFilter(mask=LIGHT_CATEGORY))
102 lights = [p.shape.body.light for p in point_info_list]
103 return [light for light in lights if light.on]
105 def light_query(self, shape):
106 """Query the lights by shape"""
107 old_filter = shape.filter
108 # We need to restrict matches to only the lights
109 shape.filter = LIT_BY_FILTER
110 shape_info_list = self._space.shape_query(shape)
111 shape.filter = old_filter
112 lights = [p.shape.body.light for p in shape_info_list]
113 return [light for light in lights if light.on]
115 def render_light(self, surface):
116 for light in self._lights:
117 light.render_light(surface)
119 def render_fittings(self, surface):
120 for light in self._lights:
121 light.render_fitting(surface)
124 class BaseLight(object):
125 """ Common light functionality. """
129 "green": (0, 255, 0),
130 "blue": (0, 255, 255),
131 "yellow": (255, 255, 0),
132 "white": (255, 255, 255),
139 self, colour, position, intensity=1.0,
140 radius_limits=(None, None), angle_limits=(None, None)):
142 self.position = pymunk.Vec2d(position)
144 self.intensity = intensity
145 self.radius_limits = radius_limits
146 self.angle_limits = angle_limits
147 self.body = pymunk.Body(0, 0, pymunk.body.Body.STATIC)
148 self.fitting = pymunk.Circle(self.body, 10.0, self.position)
149 self.body.light = self
152 def load(cls, config):
154 light_type = kw.pop("type")
156 c for c in cls.__subclasses__()
157 if c.__name__.lower() == light_type]
158 return light_class(**kw)
160 def add(self, space):
161 if self.body.space is not None:
162 space.remove(self.body, *self.body.shapes)
163 shapes = self.shapes_for_ray_polys(
164 calculate_ray_polys(space, self.body, self.position))
166 shape.filter = LIGHT_FILTER
167 self.fitting.filter = FITTINGS_FILTER
168 space.add(self.body, self.fitting, *shapes)
170 def shapes_for_ray_polys(self, ray_polys):
174 self.on = not self.on
176 def _cached_surfaces(self, surface):
177 radius_mask = self._surface_cache.get('radius_mask')
178 if radius_mask is None:
179 radius_mask = self._surface_cache['radius_mask'] = (
180 pygame.surface.Surface(surface.get_size(), pgl.SWSURFACE))
182 ray_mask = self._surface_cache.get('ray_mask')
184 ray_mask = self._surface_cache['ray_mask'] = (
185 pygame.surface.Surface(surface.get_size(), pgl.SWSURFACE))
187 overlay_surf = self._surface_cache.get('overlay_surf')
188 if overlay_surf is None:
189 overlay_surf = self._surface_cache['overlay_surf'] = (
190 pygame.surface.Surface(
191 surface.get_size(), pgl.SWSURFACE | pgl.SRCALPHA))
193 return radius_mask, ray_mask, overlay_surf
195 def render_light(self, surface):
199 radius_mask, ray_mask, overlay_surf = self._cached_surfaces(surface)
200 white, black = (255, 255, 255), (0, 0, 0)
203 for shape in self.body.shapes:
204 if shape is self.fitting:
207 pymunk.pygame_util.to_pygame(v, surface) for v in
208 shape.get_vertices()]
209 pygame.draw.polygon(ray_mask, white, pygame_poly, 0)
210 pygame.draw.aalines(ray_mask, white, True, pygame_poly, 1)
212 radius_mask.fill(black)
213 centre = pymunk.pygame_util.to_pygame(self.position, surface)
214 max_radius = self.radius_limits[1] or 50.0
215 min_radius = self.radius_limits[0] or 0
216 width = max_radius - min_radius
218 radius_mask, white, centre, int(max_radius), int(width))
220 radius_mask, white, (centre[0] + 1, centre[1]),
221 int(max_radius), int(width))
223 ray_mask.blit(radius_mask, (0, 0), None, pgl.BLEND_RGB_MULT)
224 ray_mask.set_colorkey(black)
226 light_colour = self.COLOURS[self.colour]
227 intensity = int(255 * self.intensity)
228 light_colour = light_colour + (intensity,)
230 overlay_surf.blit(ray_mask, (0, 0), None)
231 overlay_surf.fill(light_colour, None, pgl.BLEND_RGBA_MULT)
233 surface.blit(overlay_surf, (0, 0), None)
235 def render_fitting(self, surface):
237 surface, (255, 255, 0),
238 pymunk.pygame_util.to_pygame(self.fitting.offset, surface),
239 int(self.fitting.radius))
242 class SpotLight(BaseLight):
243 def __init__(self, **kw):
244 kw.pop("direction", None)
245 kw.pop("spread", None)
246 super(SpotLight, self).__init__(**kw)