X-Git-Url: https://git.ctpug.org.za/?a=blobdiff_plain;f=tabakrolletjie%2Frays.py;h=7d880d0aaa5ea417e38cb08fcfe167c5732c42f2;hb=f3d066d2bcc294701b80b245ac4f92d5bd05d32d;hp=e187f3d3c532633540f41b330f68be6f15b38867;hpb=7cd6ddff3bbbb40689dd88489b1ef63d8dffbf8f;p=tabakrolletjie.git diff --git a/tabakrolletjie/rays.py b/tabakrolletjie/rays.py index e187f3d..7d880d0 100644 --- a/tabakrolletjie/rays.py +++ b/tabakrolletjie/rays.py @@ -2,34 +2,36 @@ import math +import pygame.rect + import pymunk import pymunk.autogeometry import pymunk.pygame_util -from .constants import SCREEN_SIZE from .utils import debug_timer -def screen_rays(pos): +def screen_rays(pos, bounding_radius): """ An iterable that returns ordered rays from pos to the edge of the screen, starting with the edge point (0, 0) and continuing clockwise in pymunk coordinates. """ - w, h = SCREEN_SIZE - left, right, bottom, top = 0, w, 0, h + r = int(bounding_radius) + left, right = int(pos.x) - r, int(pos.x) + r + bottom, top = int(pos.y) + r, int(pos.y) - r step = 1 - for y in range(0, h, step): + for y in range(top, bottom + 1, step): yield pymunk.Vec2d(left, y) - for x in range(0, w, step): + for x in range(left, right + 1, step): yield pymunk.Vec2d(x, top) - for y in range(top, -1, -step): + for y in range(bottom, top - 1, -step): yield pymunk.Vec2d(right, y) - for x in range(right, -1, -step): + for x in range(right, left - 1, -step): yield pymunk.Vec2d(x, bottom) @debug_timer("rays.calculate_ray_polys") -def calculate_ray_polys(space, position, light_filter): +def calculate_ray_polys(space, position, bounding_radius, light_filter): """ Calculate a set of convex RayPolys that cover all the areas that light can reach from the given position, taking into account the obstacles present in the space. @@ -38,7 +40,7 @@ def calculate_ray_polys(space, position, light_filter): vertices = [position] start, end = None, None ray_polys = [] - for ray in screen_rays(position): + for ray in screen_rays(position, bounding_radius): info = space.segment_query_first(position, ray, 1, light_filter) point = ray if info is None else info.point vertices.append(point) @@ -50,15 +52,14 @@ def calculate_ray_polys(space, position, light_filter): query_prev = trial_poly.point_query(end) query_pos = trial_poly.point_query(position) if query_prev.distance < -0.01 or query_pos.distance < -0.01: - ray_polys.append(RayPoly( - start - position, end - position, vertices[:-1])) + ray_polys.append(RayPoly(position, vertices[:-1])) start = vertices[-1] vertices = [position, start] else: vertices = trial_poly.get_vertices() end = point if len(vertices) > 2: - ray_polys.append(RayPoly(start, end, vertices)) + ray_polys.append(RayPoly(position, vertices)) return ray_polys @@ -76,68 +77,192 @@ def to_pymunk_radians(deg): class RayPolyManager(object): - def __init__(self, body, ray_filter): - self._body = body - self._ray_filter = ray_filter - self._rays = [] - self._angle_limits = (None, None) + def __init__( + self, body, position, ray_filter, radius_limits, direction, + spread, bounding_radius): + self._body = body # light's body + self._position = pymunk.Vec2d(position) # light's position + self._ray_filter = ray_filter # light filter + self._rays = [] # list of RayPolys + self._direction = None # normal vector for direction + self._start = None # normal vector in direction of start angle limit + self._end = None # normal vector in direction of end angle limit + self._set_angle_limits(direction, spread) + self._bounding_radius = None # absolute maximum radius + if direction: + self.direction = direction # Update direction + self._max_radius = None # maximum radius in pixels + self._min_radius = None # minimum radius in pixels + self._set_radius_limits(radius_limits) + self._set_bounding_radius(bounding_radius) + self._old_poly_cache = None # last polys added to the space + self._poly_cache = None # list of pymunk.Polys for rays + self._space = None # space the rays form part of + + def set_space(self, space): + self._space = space + self._rays = calculate_ray_polys( + self._space, self._position, self._bounding_radius, + self._ray_filter) self._poly_cache = None - self._pygame_poly_cache = None - def generate_rays(self, space, position): - self._rays = calculate_ray_polys(space, position, self._ray_filter) + def update_shapes(self): + if self._old_poly_cache: + self._space.remove(*self._old_poly_cache) + new_polys = self._old_poly_cache = self.polys() + self._space.add(*new_polys) + + @property + def position(self): + return self._position + + @property + def max_radius(self): + return self._max_radius + + @max_radius.setter + def max_radius(self, value): + self._max_radius = value or 0.0 + + @property + def min_radius(self): + return self._min_radius + + @min_radius.setter + def min_radius(self, value): + self._min_radius = value or 0.0 + + def serialize(self): + """ Return the required information from the ray_manager """ + if self._direction is None: + direction = None + spread = None + else: + direction = self._direction.angle_degrees + spread = math.degrees(self.spread) + return { + "radius_limits": (self._min_radius, self._max_radius), + "direction": direction, + "spread": spread, + } + + def reaches(self, position): + distance = self.position.get_distance(position) + return (self._min_radius <= distance <= self._max_radius) + + def _set_radius_limits(self, radius_limits): + if radius_limits is None or not radius_limits[0]: + self._min_radius = 0 + else: + self._min_radius = radius_limits[0] + if radius_limits is None or not radius_limits[1]: + self._max_radius = 50.0 + else: + self._max_radius = radius_limits[1] + + def _set_bounding_radius(self, bounding_radius): + if bounding_radius is None: + bounding_radius = self._max_radius + self._bounding_radius = bounding_radius + + def rotatable(self): + return self._direction is not None + + @property + def direction(self): + if self._direction is None: + return 0 + return self._direction.angle_degrees + + @direction.setter + def direction(self, degrees): + spread = self._direction.get_angle_between(self._start) + self._direction.angle_degrees = degrees + self._start = self._direction.rotated(spread) + self._end = self._direction.rotated(-spread) self._poly_cache = None - self._pygame_poly_cache = None - def set_angle_limits(self, angle_limits): - start, end = angle_limits - self._angle_limits = ( - to_pymunk_radians(start), to_pymunk_radians(end)) + @property + def spread(self): + if not self._direction: + return 2 * math.pi + return math.fabs(self._start.get_angle_between(self._end)) + + def _set_angle_limits(self, direction, spread): + if direction is None or spread is None: + self._direction = None + self._start = None + self._end = None + else: + self._direction = pymunk.Vec2d(1, 0) + self._start = self._direction.rotated_degrees(-spread/2.) + self._end = self._direction.rotated_degrees(spread/2.) self._poly_cache = None - self._pygame_poly_cache = None def polys(self): if self._poly_cache is None: - self._poly_cache = [ - rp.poly(self._body, self._ray_filter) for rp in self._rays - if rp.within_limits(*self._angle_limits) - ] + self._poly_cache = poly_cache = [] + for rp in self._rays: + poly = rp.poly(self._start, self._end) + if poly: + poly.body = self._body + poly.filter = self._ray_filter + poly_cache.append(poly) return self._poly_cache + def pygame_position(self, surface): + return pymunk.pygame_util.to_pygame(self._position, surface) + + def pygame_rect(self, surface): + half_width = self.max_radius + rect_width = half_width * 2 + rect_x, rect_y = pymunk.pygame_util.to_pygame(self._position, surface) + dest_rect = pygame.rect.Rect(rect_x, rect_y, rect_width, rect_width) + dest_rect.move_ip(-half_width, -half_width) + return dest_rect + def pygame_polys(self, surface): - if self._pygame_poly_cache is None: - self._pygame_poly_cache = [ - [ - pymunk.pygame_util.to_pygame(v, surface) - for v in poly.get_vertices() - ] - for poly in self.polys() - ] - return self._pygame_poly_cache + return [ + [pymunk.pygame_util.to_pygame(v, surface) + for v in poly.get_vertices()] + for poly in self.polys() + ] class RayPoly(object): - def __init__(self, start_vec, end_vec, vertices): - self.start = start_vec # vector from position to first point - self.end = end_vec # vector from position to last point - self.vertices = vertices - - def poly(self, body, filter): - shape = pymunk.Poly(body, self.vertices) - shape.filter = filter - return shape - - def within_limits(self, start_limit, end_limit): - if start_limit is None or end_limit is None: - return True - - def between(n): - if start_limit < end_limit: - return start_limit <= n <= end_limit - return (start_limit <= n) or (n <= end_limit) - - start_within = between(self.start.angle) - end_within = between(self.end.angle) - if start_within or end_within: - return True - return False + def __init__(self, position, vertices): + self.position = position # pointy end of the conical polygon + self.vertices = vertices # all vertices in the polygon + + def _between(self, v, start, end): + if start < end: + return start <= v <= end + return (start <= v) or (v <= end) + + def poly(self, start, end): + trial = pymunk.Poly(None, self.vertices) + trial.update(pymunk.Transform.identity()) + + if start is None or end is None: + return trial # no limits + + start_info = trial.segment_query( + self.position + 1250 * start, self.position + 0.1 * start, 0) + end_info = trial.segment_query( + self.position + 1250 * end, self.position + 0.1 * end, 0) + + vertices = self.vertices[:] + vertices = [ + v for v in vertices + if self._between((v - self.position).angle, start.angle, end.angle) + ] + if start_info.shape is not None: + vertices.append(start_info.point) + if end_info.shape is not None: + vertices.append(end_info.point) + vertices.append(self.position) + + poly = pymunk.Poly(None, vertices) + if len(poly.get_vertices()) < 3: + return None + return poly