Add performance debugging for 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     if len(vertices) > 2:
54         ray_polys.append(pymunk.Poly(body, vertices))
55     end_time = time.time()
56     if DEBUG:
57         print(
58             "calculate_ray_polys: %d polys, %g seconds" %
59             (len(ray_polys), end_time - start_time))
60     return ray_polys
61
62
63 class BaseLight(object):
64     """ Common light functionality. """
65
66     def __init__(self, colour, position):
67         self.body = pymunk.Body(0, 0, pymunk.body.Body.STATIC)
68         self.colour = colour
69         self.position = position
70
71     def add(self, space):
72         if self.body.space is not None:
73             space.remove(self.body, *self.body.shapes)
74         shapes = self.shapes_for_ray_polys(
75             calculate_ray_polys(space, self.body, self.position))
76         for shape in shapes:
77             shape.filter = LIGHT_FILTER
78         space.add(self.body, *shapes)
79
80     def shapes_for_ray_polys(self, space):
81         raise NotImplementedError(
82             "Lights should implement .determine_ray_polys.")
83
84     @classmethod
85     def load(cls, config):
86         kw = config.copy()
87         light_type = kw.pop("type")
88         [light_class] = [
89             c for c in cls.__subclasses__()
90             if c.__name__.lower() == light_type]
91         return light_class(**kw)
92
93
94 class SpotLight(BaseLight):
95     def __init__(
96             self, colour="white", position=None, direction=90.0, spread=45.0):
97         super(SpotLight, self).__init__(colour, position)
98         self.direction = direction
99         self.spread = spread
100         self.i = 0
101
102     def shapes_for_ray_polys(self, ray_polys):
103         return ray_polys
104
105     def render(self, surface):
106         subsurface = surface.copy()
107         pygame.draw.circle(
108             surface, (255, 255, 0),
109             pymunk.pygame_util.to_pygame(self.position, surface), 5)
110         for shape in self.body.shapes:
111             pygame_poly = [
112                 pymunk.pygame_util.to_pygame(v, surface) for v in
113                 shape.get_vertices()]
114             pygame.draw.polygon(
115                 subsurface, (200, 200, 200), pygame_poly, 0)
116             pygame.draw.aalines(
117                 subsurface, (200, 200, 200), True, pygame_poly, 1)
118         subsurface.set_alpha(200)
119         surface.blit(subsurface, (0, 0), None)
120
121
122 class Lamp(BaseLight):
123     def __init__(self, colour="white", position=None, radius=100.0):
124         super(Lamp, self).__init__(colour, position)
125         self.radius = radius