Separate rendering of lights and light fixtures.
[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.body = pymunk.Body(0, 0, pymunk.body.Body.STATIC)
78         self.colour = colour
79         self.position = position
80
81     def add(self, space):
82         if self.body.space is not None:
83             space.remove(self.body, *self.body.shapes)
84         shapes = self.shapes_for_ray_polys(
85             calculate_ray_polys(space, self.body, self.position))
86         for shape in shapes:
87             shape.filter = LIGHT_FILTER
88         space.add(self.body, *shapes)
89
90     def shapes_for_ray_polys(self, space):
91         raise NotImplementedError(
92             "Lights should implement .determine_ray_polys.")
93
94     @classmethod
95     def load(cls, config):
96         kw = config.copy()
97         light_type = kw.pop("type")
98         [light_class] = [
99             c for c in cls.__subclasses__()
100             if c.__name__.lower() == light_type]
101         return light_class(**kw)
102
103
104 class SpotLight(BaseLight):
105     def __init__(
106             self, colour="white", position=None, direction=90.0, spread=45.0):
107         super(SpotLight, self).__init__(colour, position)
108         self.direction = direction
109         self.spread = spread
110         self.i = 0
111
112     def shapes_for_ray_polys(self, ray_polys):
113         return ray_polys
114
115     def render(self, surface):
116         pygame.draw.circle(
117             surface, (255, 255, 0),
118             pymunk.pygame_util.to_pygame(self.position, surface), 5)
119
120     def render_light(self, surface):
121         subsurface = surface.copy()
122         light_colour = self.COLOURS[self.colour]
123         for shape in self.body.shapes:
124             pygame_poly = [
125                 pymunk.pygame_util.to_pygame(v, surface) for v in
126                 shape.get_vertices()]
127             pygame.draw.polygon(
128                 subsurface, light_colour, pygame_poly, 0)
129             pygame.draw.aalines(
130                 subsurface, light_colour, True, pygame_poly, 1)
131         subsurface.set_alpha(50)
132         surface.blit(subsurface, (0, 0), None)
133
134
135 class Lamp(BaseLight):
136     def __init__(self, colour="white", position=None, radius=100.0):
137         super(Lamp, self).__init__(colour, position)
138         self.radius = radius