Add toggle method to lights.
[tabakrolletjie.git] / tabakrolletjie / lights.py
1 """ May it be a light for you in dark places, when all other lights go out.
2 """
3
4 import time
5
6 import pymunk
7 import pymunk.pygame_util
8 import pygame.draw
9
10 from .constants import SCREEN_SIZE, LIGHT_CATEGORY, DEBUG
11
12 LIGHT_FILTER = pymunk.ShapeFilter(
13     mask=pymunk.ShapeFilter.ALL_MASKS ^ LIGHT_CATEGORY,
14     categories=LIGHT_CATEGORY)
15
16
17 def screen_rays(pos):
18     """ An iterable that returns ordered rays from pos to the edge of the
19         screen, starting with the edge point (0, 0) and continuing clockwise
20         in pymunk coordinates.
21     """
22     w, h = SCREEN_SIZE
23     left, right, bottom, top = 0, w, 0, h
24     step = 1
25     for y in range(0, h, step):
26         yield pymunk.Vec2d(left, y)
27     for x in range(0, w, step):
28         yield pymunk.Vec2d(x, top)
29     for y in range(top, -1, -step):
30         yield pymunk.Vec2d(right, y)
31     for x in range(right, -1, -step):
32         yield pymunk.Vec2d(x, bottom)
33
34
35 def calculate_ray_polys(space, body, position):
36     start_time = time.time()
37     position = pymunk.Vec2d(position)
38     vertices = [position]
39     ray_polys = []
40     for ray in screen_rays(position):
41         info = space.segment_query_first(position, ray, 1, LIGHT_FILTER)
42         point = ray if info is None else info.point
43         vertices.append(point)
44         if len(vertices) > 3:
45             trial_poly = pymunk.Poly(None, vertices)
46             trial_poly.update(pymunk.Transform.identity())
47             query_prev = trial_poly.point_query(vertices[-2])
48             query_pos = trial_poly.point_query(position)
49             if query_prev.distance < -0.01 or query_pos.distance < -0.01:
50                 new_poly = pymunk.Poly(body, vertices[:-1])
51                 vertices = [position, vertices[-1]]
52                 ray_polys.append(new_poly)
53             else:
54                 vertices = trial_poly.get_vertices() + [point]
55     if len(vertices) > 2:
56         ray_polys.append(pymunk.Poly(body, vertices))
57     end_time = time.time()
58     if DEBUG:
59         print(
60             "calculate_ray_polys: %d polys, %g seconds" %
61             (len(ray_polys), end_time - start_time))
62     return ray_polys
63
64
65 class BaseLight(object):
66     """ Common light functionality. """
67
68     COLOURS = {
69         "red": (255, 0, 0),
70         "green": (0, 255, 0),
71         "blue": (0, 255, 255),
72         "yellow": (255, 255, 0),
73         "white": (255, 255, 255),
74     }
75
76     def __init__(self, colour, position):
77         self.on = True
78         self.body = pymunk.Body(0, 0, pymunk.body.Body.STATIC)
79         self.colour = colour
80         self.position = position
81
82     @classmethod
83     def load(cls, config):
84         kw = config.copy()
85         light_type = kw.pop("type")
86         [light_class] = [
87             c for c in cls.__subclasses__()
88             if c.__name__.lower() == light_type]
89         return light_class(**kw)
90
91     def add(self, space):
92         if self.body.space is not None:
93             space.remove(self.body, *self.body.shapes)
94         shapes = self.shapes_for_ray_polys(
95             calculate_ray_polys(space, self.body, self.position))
96         for shape in shapes:
97             shape.filter = LIGHT_FILTER
98         space.add(self.body, *shapes)
99
100     def shapes_for_ray_polys(self, ray_polys):
101         return ray_polys
102
103     def toggle(self):
104         self.on = not self.on
105
106     def render_light(self, surface):
107         if not self.on:
108             return
109         subsurface = surface.copy()
110         light_colour = self.COLOURS[self.colour]
111         for shape in self.body.shapes:
112             pygame_poly = [
113                 pymunk.pygame_util.to_pygame(v, surface) for v in
114                 shape.get_vertices()]
115             pygame.draw.polygon(
116                 subsurface, light_colour, pygame_poly, 0)
117             pygame.draw.aalines(
118                 subsurface, light_colour, True, pygame_poly, 1)
119         subsurface.set_alpha(50)
120         surface.blit(subsurface, (0, 0), None)
121
122     def render_fittings(self, surface):
123         pygame.draw.circle(
124             surface, (255, 255, 0),
125             pymunk.pygame_util.to_pygame(self.position, surface), 5)
126
127
128 class SpotLight(BaseLight):
129     def __init__(
130             self, colour="white", position=None, direction=90.0, spread=45.0):
131         super(SpotLight, self).__init__(colour, position)
132         self.direction = direction
133         self.spread = spread
134         self.i = 0
135
136
137 class Lamp(BaseLight):
138     def __init__(self, colour="white", position=None, radius=100.0):
139         super(Lamp, self).__init__(colour, position)
140         self.radius = radius