Merge branch 'master' of ctpug.org.za:tabakrolletjie
[tabakrolletjie.git] / tabakrolletjie / rays.py
1 """ Light ray manipulation. Pew. Pew. Pew. Wommmm. """
2
3 import math
4
5 import pymunk
6 import pymunk.autogeometry
7 import pymunk.pygame_util
8
9 from .constants import SCREEN_SIZE
10 from .utils import debug_timer
11
12
13 def screen_rays(pos):
14     """ An iterable that returns ordered rays from pos to the edge of the
15         screen, starting with the edge point (0, 0) and continuing clockwise
16         in pymunk coordinates.
17     """
18     w, h = SCREEN_SIZE
19     left, right, bottom, top = 0, w, 0, h
20     step = 1
21     for y in range(0, h, step):
22         yield pymunk.Vec2d(left, y)
23     for x in range(0, w, step):
24         yield pymunk.Vec2d(x, top)
25     for y in range(top, -1, -step):
26         yield pymunk.Vec2d(right, y)
27     for x in range(right, -1, -step):
28         yield pymunk.Vec2d(x, bottom)
29
30
31 @debug_timer("rays.calculate_ray_polys")
32 def calculate_ray_polys(space, position, light_filter):
33     """ Calculate a set of convex RayPolys that cover all the areas that light
34         can reach from the given position, taking into account the obstacles
35         present in the space.
36     """
37     position = pymunk.Vec2d(position)
38     vertices = [position]
39     start, end = None, None
40     ray_polys = []
41     for ray in screen_rays(position):
42         info = space.segment_query_first(position, ray, 1, light_filter)
43         point = ray if info is None else info.point
44         vertices.append(point)
45         if len(vertices) == 2:
46             start = vertices[1]
47         elif len(vertices) > 3:
48             trial_poly = pymunk.Poly(None, vertices)
49             trial_poly.update(pymunk.Transform.identity())
50             query_prev = trial_poly.point_query(end)
51             query_pos = trial_poly.point_query(position)
52             if query_prev.distance < -0.01 or query_pos.distance < -0.01:
53                 ray_polys.append(RayPoly(
54                     start - position, end - position, vertices[:-1]))
55                 start = vertices[-1]
56                 vertices = [position, start]
57             else:
58                 vertices = trial_poly.get_vertices()
59         end = point
60     if len(vertices) > 2:
61         ray_polys.append(RayPoly(start, end, vertices))
62     return ray_polys
63
64
65 def to_pymunk_radians(deg):
66     """ Convert degrees in [0, 360] to radians in (-pi, pi].
67
68         Return None if degrees is None.
69     """
70     if deg is None:
71         return None
72     deg = deg * math.pi / 180.0
73     if deg > math.pi:
74         deg -= 2 * math.pi
75     return deg
76
77
78 class RayPolyManager(object):
79     def __init__(self, body, ray_filter):
80         self._body = body
81         self._ray_filter = ray_filter
82         self._rays = []
83         self._angle_limits = (None, None)
84         self._poly_cache = None
85         self._pygame_poly_cache = None
86
87     def generate_rays(self, space, position):
88         self._rays = calculate_ray_polys(space, position, self._ray_filter)
89         self._poly_cache = None
90         self._pygame_poly_cache = None
91
92     def set_angle_limits(self, angle_limits):
93         start, end = angle_limits
94         self._angle_limits = (
95             to_pymunk_radians(start), to_pymunk_radians(end))
96         self._poly_cache = None
97         self._pygame_poly_cache = None
98
99     def polys(self):
100         if self._poly_cache is None:
101             print "===="
102             self._poly_cache = [
103                 rp.poly(self._body, self._ray_filter) for rp in self._rays
104                 if rp.within_limits(*self._angle_limits)
105             ]
106             print "===="
107
108         return self._poly_cache
109
110     def pygame_polys(self, surface):
111         if self._pygame_poly_cache is None:
112             print "REGEN ...", self._angle_limits
113             self._pygame_poly_cache = [
114                 [
115                     pymunk.pygame_util.to_pygame(v, surface)
116                     for v in poly.get_vertices()
117                 ]
118                 for poly in self.polys()
119             ]
120         return self._pygame_poly_cache
121
122
123 class RayPoly(object):
124     def __init__(self, start_vec, end_vec, vertices):
125         self.start = start_vec  # vector from position to first point
126         self.end = end_vec  # vector from position to last point
127         self.vertices = vertices
128
129     def poly(self, body, filter):
130         shape = pymunk.Poly(body, self.vertices)
131         shape.filter = filter
132         return shape
133
134     def within_limits(self, start_limit, end_limit):
135         if start_limit is None or end_limit is None:
136             return True
137         print "----"
138         print "LIM: ", start_limit, end_limit
139         print "ANG: ", self.start.angle, self.end.angle
140         n1 = self.start.normalized()
141         n2 = self.end.normalized()
142         s = pymunk.Vec2d(1, 0).rotated(start_limit)
143         e = pymunk.Vec2d(1, 0).rotated(end_limit)
144         n1bet = s.dot(e) < s.dot(n1) and s.dot(e) < s.dot(n1)
145         n2bet = s.dot(e) < e.dot(n2) and s.dot(e) < e.dot(n2)
146         print "DOTS n1: ", n1.dot(n2), n1.dot(s), n2.dot(s)
147         print "DOTS n2: ", n1.dot(n2), n1.dot(e), n2.dot(e)
148         print "BET: ", n1bet, n2bet
149         if n1bet:
150             print "TRUE\n----"
151             return True
152         return False