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