import pygame.gfxdraw import colors from drawable import * from matrix import * class Ray(drawable): """ Ray class as drawable object for visual debug. A Ray has a starting point and a direction vector. """ def __init__(self, start, dir, color=colors.BRIGHT_RED): """Initilise 'self' with standard color 'BRIGHT_RED'.""" drawable.__init__(self, start, dir, color=color) def __getattr__(self, attr): """ Return 'self[0]' and 'self[1]' as attribute 'start' and 'dir'. """ if attr == 'start': return self[0] if attr == 'dir': return self[1] def point_at(self, value): """ Return the point 'self.start + value * self.dir'. """ if type(value) in (int, float): return self.start + value * self.dir raise TypeError(f"Expected int or float, but {value.__class__.__name__} found") def value_at(self, point): """ Return the value with 'point_at(value) == point'. """ if type(point) is Vector: if self.dir.multiple_of(point - self.start): value = (point - self.start) / self.dir if value is not None: return value # hotfix h = (point - self.start).dot(self.dir) / self.dir.dot(self.dir) print(f"Warning: returned nearest value for 'value_at' of point {((point - self.start) - h * self.dir).length()} off the ray)") return h raise TypeError(f"Expected Vector, but {point.__class__.__name__} found") def offscreen(self, screen, offset): """ Return a point of the ray that is not on the rect 'screen'. This assumes all rays go away from the 'screen' center. """ if not self.dir: # == 0 return self.start x, y = float('inf'), float('inf') if self.dir.x > 0: x = (offset.x + screen.get_rect()[2] / 2 - self.start.x) / self.dir.x elif self.dir.x < 0: x = (offset.x - screen.get_rect()[2] / 2 - self.start.x) / self.dir.x if self.dir.y > 0: y = (offset.y + screen.get_rect()[3] / 2 - self.start.y) / self.dir.y elif self.dir.y < 0: y = (offset.y - screen.get_rect()[3] / 2 - self.start.y) / self.dir.y return self.point_at(max(0, min(x, y))) def intersect(self, other): """ Return list of intersections sorted by distance to 'self.start'. """ if self.dir.length() == 0: return [] # point if isinstance(other, Vector): if self.dir.multiple_of(other - self.start): # point on self if self.value_at(other) is not None: return [other] return [] if isinstance(other, drawable): if type(other) is Ray: if self.dir.multiple_of(other.dir): # linear depentant return self.intersect(other.start) if self.dir.x == 0: # switch vectors for gauss algorithm return other.intersect(self) matrix = [ [self.dir.x, -other.dir.x, other.start.x - self.start.x], [self.dir.y, -other.dir.y, other.start.y - self.start.y], ] # gauss algorithm matrix[0][1] /= matrix[0][0] matrix[0][2] /= matrix[0][0] matrix[1][1] -= matrix[1][0] * matrix[0][1] matrix[1][2] -= matrix[1][0] * matrix[0][2] matrix[1][2] /= matrix[1][1] matrix[0][2] -= matrix[0][1] * matrix[1][2] if matrix[0][2] >= 0 and matrix[1][2] >= 0: return [self.point_at(matrix[0][2])] return [] raise NotImplementedError # for other drawables raise TypeError(f"Expected drawable objects or vector, but '{other.__class__.__name__}' found") def draw(self, screen, offset): """ Draw 'self' as an infinite ray on 'screen'. """ ray = Ray(self.start, self.offscreen(screen, offset)) # calculate real position on screen rect = Vector(*screen.get_rect()[2:]) for i, _ in enumerate(ray): ray[i] = round(ray[i] - offset + rect / 2) if ray.dir: # != 0 pygame.draw.aaline(screen, self.color, ray[0], ray[1])