First working version of light rays.
[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 import pygame.locals as pgl
8
9 from .constants import SCREEN_SIZE, LIGHT_CATEGORY
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     for y in range(h):
24         yield pymunk.Vec2d(left, y)
25     for x in range(w):
26         yield pymunk.Vec2d(x, top)
27     for y in range(top, -1, -1):
28         yield pymunk.Vec2d(right, y)
29     for x in range(right, -1, -1):
30         yield pymunk.Vec2d(x, bottom)
31
32
33 def calculate_ray_polys(space, body, position):
34     position = pymunk.Vec2d(position)
35     vertices = [position]
36     ray_polys = []
37     for ray in screen_rays(position):
38         info = space.segment_query_first(position, ray, 1, LIGHT_FILTER)
39         point = ray if info is None else info.point
40         vertices.append(point)
41         if len(vertices) > 3:
42             trial_poly = pymunk.Poly(None, vertices)
43             trial_poly.update(pymunk.Transform.identity())
44             query_prev = trial_poly.point_query(vertices[-2])
45             query_pos = trial_poly.point_query(position)
46             if query_prev.distance < -0.01 or query_pos.distance < -0.01:
47                 new_poly = pymunk.Poly(body, vertices[:-1])
48                 vertices = [position, vertices[-1]]
49                 ray_polys.append(new_poly)
50     if len(vertices) > 2:
51         ray_polys.append(pymunk.Poly(body, vertices))
52     print "NUM POLYS: ", len(ray_polys)
53     return ray_polys
54
55
56 class BaseLight(object):
57     """ Common light functionality. """
58
59     def __init__(self, colour, position):
60         self.body = pymunk.Body(0, 0, pymunk.body.Body.STATIC)
61         self.colour = colour
62         self.position = position
63
64     def add(self, space):
65         if self.body.space is not None:
66             space.remove(self.body, *self.body.shapes)
67         shapes = self.shapes_for_ray_polys(
68             calculate_ray_polys(space, self.body, self.position))
69         for shape in shapes:
70             shape.filter = LIGHT_FILTER
71         space.add(self.body, *shapes)
72
73     def shapes_for_ray_polys(self, space):
74         raise NotImplementedError(
75             "Lights should implement .determine_ray_polys.")
76
77     @classmethod
78     def load(cls, config):
79         kw = config.copy()
80         light_type = kw.pop("type")
81         [light_class] = [
82             c for c in cls.__subclasses__()
83             if c.__name__.lower() == light_type]
84         return light_class(**kw)
85
86
87 class SpotLight(BaseLight):
88     def __init__(
89             self, colour="white", position=None, direction=90.0, spread=45.0):
90         super(SpotLight, self).__init__(colour, position)
91         self.direction = direction
92         self.spread = spread
93         self.i = 0
94
95     def shapes_for_ray_polys(self, ray_polys):
96         x, y = self.position
97         return ray_polys #+ [
98         #    pymunk.Poly(self.body, [self.position, [x + 50, y], [x, y + 50]])]
99
100     def render(self, surface):
101         subsurface = surface.copy()
102         pygame.draw.circle(
103             surface, (255, 255, 0),
104             pymunk.pygame_util.to_pygame(self.position, surface), 5)
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, (200, 200, 200), pygame_poly, 0)
111             pygame.draw.aalines(
112                 subsurface, (200, 200, 200), True, pygame_poly, 1)
113         subsurface.set_alpha(200)
114         surface.blit(subsurface, (0, 0), None)
115
116
117 class Lamp(BaseLight):
118     def __init__(self, colour="white", position=None, radius=100.0):
119         super(Lamp, self).__init__(colour, position)
120         self.radius = radius