Keep number of vertices low during ray tracing.
[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     def __init__(self, colour, position):
69         self.body = pymunk.Body(0, 0, pymunk.body.Body.STATIC)
70         self.colour = colour
71         self.position = position
72
73     def add(self, space):
74         if self.body.space is not None:
75             space.remove(self.body, *self.body.shapes)
76         shapes = self.shapes_for_ray_polys(
77             calculate_ray_polys(space, self.body, self.position))
78         for shape in shapes:
79             shape.filter = LIGHT_FILTER
80         space.add(self.body, *shapes)
81
82     def shapes_for_ray_polys(self, space):
83         raise NotImplementedError(
84             "Lights should implement .determine_ray_polys.")
85
86     @classmethod
87     def load(cls, config):
88         kw = config.copy()
89         light_type = kw.pop("type")
90         [light_class] = [
91             c for c in cls.__subclasses__()
92             if c.__name__.lower() == light_type]
93         return light_class(**kw)
94
95
96 class SpotLight(BaseLight):
97     def __init__(
98             self, colour="white", position=None, direction=90.0, spread=45.0):
99         super(SpotLight, self).__init__(colour, position)
100         self.direction = direction
101         self.spread = spread
102         self.i = 0
103
104     def shapes_for_ray_polys(self, ray_polys):
105         return ray_polys
106
107     def render(self, surface):
108         subsurface = surface.copy()
109         pygame.draw.circle(
110             surface, (255, 255, 0),
111             pymunk.pygame_util.to_pygame(self.position, surface), 5)
112         for shape in self.body.shapes:
113             pygame_poly = [
114                 pymunk.pygame_util.to_pygame(v, surface) for v in
115                 shape.get_vertices()]
116             pygame.draw.polygon(
117                 subsurface, (200, 200, 200), pygame_poly, 0)
118             pygame.draw.aalines(
119                 subsurface, (200, 200, 200), True, pygame_poly, 1)
120         subsurface.set_alpha(200)
121         surface.blit(subsurface, (0, 0), None)
122
123
124 class Lamp(BaseLight):
125     def __init__(self, colour="white", position=None, radius=100.0):
126         super(Lamp, self).__init__(colour, position)
127         self.radius = radius