Benutzer-Werkzeuge

Webseiten-Werkzeuge


ss20:neg_drawables

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen gezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen Revision Vorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
ss20:neg_drawables [2020/08/26 21:04]
srather [drawable]
ss20:neg_drawables [2020/09/11 14:28] (aktuell)
srather [Portal]
Zeile 4: Zeile 4:
 ====== Sourcecode ====== ====== Sourcecode ======
  
-Download ​sourcecode from Git repository:+Download ​the release version here:\\ 
 +{{:​ss20:​neg-world-engine.zip}}
  
-[[https://​gitlab.tubit.tu-berlin.de/​srather/​NEG-World-Engine.git|https://​gitlab.tubit.tu-berlin.de/​srather/​NEG-World-Engine.git]]+Or download the newest version from the Git repository:​\\ 
 +[[https://​gitlab.tubit.tu-berlin.de/​srather/​NEG-World-Engine.git]]
  
 ==== Folder Structure: ==== ==== Folder Structure: ====
Zeile 26: Zeile 28:
 ==== drawable ==== ==== drawable ====
 <file python drawable.py>​ <file python drawable.py>​
 +import colors
 from vector import * from vector import *
  
Zeile 31: Zeile 34:
 class drawable(list):​ class drawable(list):​
     """​     """​
-    Abstract superclass ​as a list of points ​for objects drawable on canvas.+    Abstract superclass for all objects drawable on canvas
 +    A drawable is a list of points ('​Vector'​) with a '​color'​ attribute.
     """​     """​
  
  
-    ​# constructor:​ create self as list and populate it +    def __init__(self,​ *points, color=colors.BLACK): 
-    ​def __init__(self,​ *points, color=(0,0,0)):+        """​ 
 +        Overrides '​__init__'​ method of '​list'​ class. 
 +        Will only accept point-like objects and adds '​color'​ attribute. 
 +        """​ 
         list.__init__(self)         list.__init__(self)
         for point in points:         for point in points:
Zeile 43: Zeile 51:
  
  
-    # override append() inherited from list to only accept points 
     def append(self,​ item):     def append(self,​ item):
 +        """​
 +        Overrides '​append'​ method of '​list'​ class.
 +        Will only accept point-like objects.
 +        """​
 +
         try:         try:
             list.append(self,​ Vector(*item))             list.append(self,​ Vector(*item))
Zeile 51: Zeile 63:
  
  
-    ​# override ​__str__() ​inherited from list to display ​class and instance variables +    ​def __str__(self)
-    def __str__(self): # sorry this is a mess+        """​ 
 +        Override '​__str__'​ method of 'list' ​class. 
 +        ​Return string with classname, points in 'self' and additional attributes. 
 +        """​ 
         return f"​{self.__class__.__name__}({list.__repr__(self)[1:​-1]}) with {self.__dict__.__repr__()[1:​-1].replace(':',​ ' ='​).replace('​ ', ''​)}"​         return f"​{self.__class__.__name__}({list.__repr__(self)[1:​-1]}) with {self.__dict__.__repr__()[1:​-1].replace(':',​ ' ='​).replace('​ ', ''​)}"​
  
  
-    ​# override ​__repr__() ​inherited from list to display ​class and instance variables +    ​def __repr__(self)
-    def __repr__(self): # sorry this is a mess+        """​ 
 +        Override '​__repr__'​ method of 'list' ​class. 
 +        ​Return string with classname, points in 'self' and additional attributes. 
 +        """​ 
         return f"​{self.__class__.__name__}({list.__repr__(self)[1:​-1]},​ {self.__dict__.__repr__()[1:​-1].replace(':',​ ' ='​).replace('​ ', ''​)})"​         return f"​{self.__class__.__name__}({list.__repr__(self)[1:​-1]},​ {self.__dict__.__repr__()[1:​-1].replace(':',​ ' ='​).replace('​ ', ''​)})"​
  
  
-    # these have to be implemented by a subclass 
     def draw(self, screen, offset):     def draw(self, screen, offset):
 +        """​Abstract method to draw '​self'​ on '​screen'​."""​
         raise NotImplementedError         raise NotImplementedError
  
  
-    def draw_shadow(self,​ screen, source):+    def draw_shadow(self,​ screen, offset, source, color=(31,​31,​31)): 
 +        """​Abstract method to draw a shadow of '​self'​ on '​screen'​."""​
         raise NotImplementedError         raise NotImplementedError
  
  
-    def dist_to(self, ​other): # used for colision detection+    def dist_to(self, ​source): 
 +        """​Abstract method to calculate the distance to the player ('​source'​)."""​
         raise NotImplementedError         raise NotImplementedError
  
  
-    def intersect(self, ​other): # returns list of points +    def cut(self, ​source, portal): 
-        ​raise NotImplementedError +        ​"""​Abstract method to cut 'self' to fit in the portal view."""​
- +
- +
-    def cut(self, ray): # returns part of object+
         raise NotImplementedError         raise NotImplementedError
  
Zeile 91: Zeile 110:
 <file python ray.py> <file python ray.py>
 import pygame.gfxdraw import pygame.gfxdraw
-import ​numpy+import ​colors
 from drawable import * from drawable import *
 from matrix import * from matrix import *
Zeile 98: Zeile 117:
 class Ray(drawable):​ class Ray(drawable):​
     """​     """​
-    Ray class as drawable object for visual debug. A Ray is a vector with +    Ray class as drawable object for visual debug. 
-    ​a starting point, visualised as line with a startpoint, but no end. +    ​A Ray has a starting point and direction vector.
- +
-    Create a new '​Ray'​ object using 'ray = Ray((x1, y1), (x2, y2))'​ +
-    or if the points are in a list like '[(x1, y1), (x2, y2)]'​ +
-    use 'ray = Ray(*[(x1, y1), (x2, y2)])' to unpack it. +
-    Note that '​Ray(*ray)'​ creates a copy of '​ray'​.+
     """​     """​
  
  
-    def __init__(self,​ start, ​direction, color=(255,0,0)): +    def __init__(self,​ start, ​dir, color=colors.BRIGHT_RED): 
-        """​Call the init function of parent class 'drawable'​."""​ +        """​Initilise ​'self' with standard color '​BRIGHT_RED'​."""​ 
-        drawable.__init__(self,​ start, ​direction, color=color)+        drawable.__init__(self,​ start, ​dir, color=color)
  
  
-    # Overrides attribute access 
     def __getattr__(self,​ attr):     def __getattr__(self,​ attr):
         """​         """​
Zeile 123: Zeile 136:
         if attr == '​dir':​         if attr == '​dir':​
             return self[1]             return self[1]
-        if attr == '​color':​ 
-            return self.__dict__['​color'​] 
- 
-        raise AttributeError(f"​{self.__class__.__name__} has no Attribute {attr}."​) 
  
  
     def point_at(self,​ value):     def point_at(self,​ value):
         """​         """​
-        Return the point '​value'​ times 'self.dir' from 'self.start'.+        Return the point 'self.start + value * self.dir'.
         """​         """​
  
Zeile 142: Zeile 151:
     def value_at(self,​ point):     def value_at(self,​ point):
         """​         """​
-        Return the how many '​self.dir' ​'point' is from '​self.start'.+        Return the value with 'point_at(value) == point'​.
         """​         """​
  
Zeile 151: Zeile 160:
                     return value                     return value
  
 +            # hotfix
             h = (point - self.start).dot(self.dir) / self.dir.dot(self.dir)             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             return h
- 
-            print(f"​Warning:​ returned '​None'​ for value_at of point {((point - self.start) - h * self.dir).length()} off the ray)") 
-            return None 
  
         raise TypeError(f"​Expected Vector, but {point.__class__.__name__} found"​)         raise TypeError(f"​Expected Vector, but {point.__class__.__name__} found"​)
Zeile 162: Zeile 170:
     def offscreen(self,​ screen, offset):     def offscreen(self,​ screen, offset):
         """​         """​
-        Return a point of the ray that is definetly ​not on '​screen'​.+        Return a point of the ray that is not on the rect '​screen'​
 +        This assumes all rays go away from the '​screen'​ center.
         """​         """​
  
Zeile 183: Zeile 192:
  
  
-    # Implements intersect() from abstract drawable class 
     def intersect(self,​ other):     def intersect(self,​ other):
         """​         """​
Zeile 192: Zeile 200:
             return []             return []
  
 +        # point
         if isinstance(other,​ Vector):         if isinstance(other,​ Vector):
             if self.dir.multiple_of(other - self.start):​ # point on self             if self.dir.multiple_of(other - self.start):​ # point on self
                 if self.value_at(other) is not None:                 if self.value_at(other) is not None:
                     return [other]                     return [other]
- 
             return []             return []
  
Zeile 204: Zeile 212:
                     return self.intersect(other.start)                     return self.intersect(other.start)
  
-                if self.dir.x == 0: # switch vectors for gauss algorithm+                if self.dir.x == 0: 
 +                    ​# switch vectors for gauss algorithm
                     return other.intersect(self)                     return other.intersect(self)
  
Zeile 223: Zeile 232:
                     return [self.point_at(matrix[0][2])]                     return [self.point_at(matrix[0][2])]
                 return []                 return []
- 
- 
-            if type(other) is Polygon: 
-                if len(other) == 1: 
-                    return self.intersect(*other) 
- 
-                if len(other) >= 2: 
-                    lines = [] 
-                    for i, point in other[:-1]: 
-                        lines.append(Ray(other[i],​ other[i+1] - other[i])) 
-                    if len(other) >= 3: 
-                        lines.append(Ray(other[-1],​ other[0] - other[-1])) 
- 
-                    points = [] 
-                    for line in lines: 
-                        point = self.intersect(line) 
-                        if len(point) == 1 and point[0] / line.dir <= 1: 
-                            points += point 
- 
-                    values = [] 
-                    for point in points: 
-                        values.append(self.value_at(point)) 
-                    values.sort() 
- 
-                    intersections = [] 
-                    for value in values: 
-                        intersections.append(self.point_at(value)) 
- 
-                    return intersections 
- 
-                return [] # for empty polygons 
  
             raise NotImplementedError # for other drawables             raise NotImplementedError # for other drawables
  
-        raise TypeError(f"​Expected drawable objects, but '​{other.__class__.__name__}'​ found"​)+        raise TypeError(f"​Expected drawable objects ​or vector, but '​{other.__class__.__name__}'​ found"​)
  
  
-    # Implements draw() method of abstract drawable class 
     def draw(self, screen, offset):     def draw(self, screen, offset):
         """​         """​
-        ​Visualizes ​'​self'​ as a line with a startpoint, but no end.+        ​Draw '​self'​ as an infinite ray on '​screen'​.
         """​         """​
  
         ray = Ray(self.start,​ self.offscreen(screen,​ offset))         ray = Ray(self.start,​ self.offscreen(screen,​ offset))
  
-        # move to real position on screen+        # calculate ​real position on screen
         rect = Vector(*screen.get_rect()[2:​])         rect = Vector(*screen.get_rect()[2:​])
         for i, _ in enumerate(ray):​         for i, _ in enumerate(ray):​
Zeile 278: Zeile 255:
 </​file>​ </​file>​
  
-[[neg_drawables#sourcecode|↑ Back to top]]+[[#top|↑ Back to top]]
  
 ---- ----
Zeile 285: Zeile 262:
 ==== Polygon ==== ==== Polygon ====
 <file python polygon.py>​ <file python polygon.py>​
 +import colors
 from drawable import * from drawable import *
 from ray import * from ray import *
Zeile 291: Zeile 269:
 class Polygon(drawable):​ class Polygon(drawable):​
     """​     """​
-    Polygon class as drawable ​object. Implements functionality of abstract drawable class.+    Polygon class as 'drawable' objects. 
 +    ​Implements functionality of abstract drawable class.
     """​     """​
  
  
-    ​# constructor:​ create self as drawable object +    def __init__(self,​ *points, color=colors.BLACK): 
-    ​def __init__(self,​ *points, color=(0,0,0)):+        """​Initilise '​self'​ with standard color '​BLACK'​."""​
         drawable.__init__(self,​ *points, color=color)         drawable.__init__(self,​ *points, color=color)
  
  
-    # Implements draw() method of abstract drawable class 
     def draw(self, screen, offset):     def draw(self, screen, offset):
 +        """​
 +        Draw '​self'​ as a polygon on '​screen'​.
 +        """​
 +
         if not self: # empty         if not self: # empty
             return             return
Zeile 307: Zeile 289:
         polygon = self.copy()         polygon = self.copy()
  
-        # move to real position on screen+        # calculate ​real position on screen
         rect = Vector(*screen.get_rect()[2:​])         rect = Vector(*screen.get_rect()[2:​])
         for i, _ in enumerate(polygon):​         for i, _ in enumerate(polygon):​
             polygon[i] = round(polygon[i] - offset + rect / 2)             polygon[i] = round(polygon[i] - offset + rect / 2)
-            if polygon[i].length2() > 30000:+            if polygon[i].length2() > 30000: ​# hotfix
                 return                 return
  
 +        # point
         if len(polygon) == 1:         if len(polygon) == 1:
             pygame.gfxdraw.pixel(screen,​ *polygon[0],​ self.color)             pygame.gfxdraw.pixel(screen,​ *polygon[0],​ self.color)
 +        # line
         elif len(polygon) == 2:         elif len(polygon) == 2:
             pygame.draw.aaline(screen,​ self.color, polygon[0], polygon[1])             pygame.draw.aaline(screen,​ self.color, polygon[0], polygon[1])
 +        # polygon
         else:         else:
             pygame.gfxdraw.aapolygon(screen,​ polygon, self.color)             pygame.gfxdraw.aapolygon(screen,​ polygon, self.color)
Zeile 323: Zeile 308:
  
  
-    ​# Implements draw_shadow() from abstract drawable class +    def draw_shadow(self,​ screen, offset, source, color=colors.DARK_GRAY): 
-    ​def draw_shadow(self,​ screen, offset, source, color=(31,31,31)): +        """​ 
-        if len(self) == 0:+        Draw draw a shadow of '​self'​ on '​screen'​ respecting the light-'​source'​. 
 +        """​ 
 + 
 +        if not self: # empty
             return             return
  
 +        # point
         elif len(self) == 1:         elif len(self) == 1:
             Ray(self[0],​ self[0] - source, color=color).draw(screen,​ offset)             Ray(self[0],​ self[0] - source, color=color).draw(screen,​ offset)
  
 +        # line
         elif len(self) == 2:         elif len(self) == 2:
-            ​if not self[0].onscreen(screen,​ offset) and not self[1].onscreen(screen,​ offset): +            ​# edgecasewould lead to DivisionByZeroError
-                return +
             if self[0] == source or self[1] == source:             if self[0] == source or self[1] == source:
                 for point in self:                 for point in self:
                     Ray(point, point - source, color=color).draw(screen,​ offset)                     Ray(point, point - source, color=color).draw(screen,​ offset)
-            else: +                return
-                rect = screen.get_rect() +
-                corner = Vector(*rect[2:​]) / 2 +
-                corners = [ +
-                    corner * Vector( 1, 1), +
-                    corner * Vector(-1, 1), +
-                    corner * Vector(-1,​-1),​ +
-                    corner * Vector( 1,-1), +
-                    corner * Vector( 1, 1), +
-                    corner * Vector( 1,-1), +
-                ]+
  
-                ​ray1 = Ray(self[0],​ self[0] - source) +            # corners of screen area 
-                ray2 = Ray(self[1],​ self[1] - source) +            rect = screen.get_rect() 
-                if ray1.dir.angle_to(ray2.dir) > 180: +            corner = Vector(*rect[2:​]) / 2 
-                    ray1, ray2 = ray2, ray1+            corners = [ 
 +                corner * Vector( 1, 1), 
 +                corner * Vector(-1, 1), 
 +                corner * Vector(-1,​-1),​ 
 +                corner * Vector( 1,-1), 
 +                corner * Vector( 1, 1), 
 +                corner * Vector( 1,-1), 
 +            ] 
 +            # rays from source to both ends 
 +            ​ray1 = Ray(self[0],​ self[0] - source) 
 +            ray2 = Ray(self[1],​ self[1] - source) 
 +            # garantees anticlockwise order (important for calculations) 
 +            ​if ray1.dir.angle_to(ray2.dir) > 180: 
 +                ray1, ray2 = ray2, ray1
  
-                ​shadow = Polygon(color=color) +            # creates minimal polygon 
-                shadow.append(ray2.offscreen(screen,​ offset)) +            ​shadow = Polygon(color=color) 
-                shadow.append(ray2.start) +            shadow.append(ray2.offscreen(screen,​ offset)) 
-                shadow.append(ray1.start) +            shadow.append(ray2.start) 
-                shadow.append(ray1.offscreen(screen,​ offset))+            shadow.append(ray1.start) 
 +            shadow.append(ray1.offscreen(screen,​ offset))
  
-                ​for i, _ in enumerate(corners[1:​-1]):​ +            # adds corners of screen to polygon if needed 
-                    if corners[i].is_between(ray1.dir,​ ray2.dir):​ +            ​for i, _ in enumerate(corners[1:​-1]):​ 
-                        if corners[i-1].is_between(ray1.dir,​ ray2.dir):​ +                if corners[i].is_between(ray1.dir,​ ray2.dir):​ 
-                            shadow.append(offset + corners[i-1]) +                    if corners[i-1].is_between(ray1.dir,​ ray2.dir):​ 
-                        shadow.append(offset + corners[i]) +                        shadow.append(offset + corners[i-1]) 
-                        if corners[i+1].is_between(ray1.dir,​ ray2.dir):​ +                    shadow.append(offset + corners[i]) 
-                            shadow.append(offset + corners[i+1]) +                    if corners[i+1].is_between(ray1.dir,​ ray2.dir):​ 
-                        break+                        shadow.append(offset + corners[i+1]) 
 +                    break
  
-                ​shadow.draw(screen,​ offset)+            # finally draws calculated shadow 
 +            ​shadow.draw(screen,​ offset)
  
-        else:+        ​# polygon 
 +        ​else: # draws shadow for every line in polygon
             for i, _ in enumerate(self[:​-1]):​             for i, _ in enumerate(self[:​-1]):​
                 Polygon(self[i],​ self[i+1]).draw_shadow(screen,​ offset, source)                 Polygon(self[i],​ self[i+1]).draw_shadow(screen,​ offset, source)
Zeile 378: Zeile 373:
  
  
-    # Implements dist_to() method of abstract drawable class 
     def dist_to(self,​ source):     def dist_to(self,​ source):
 +        """​
 +        Return euclidean distance to the player ('​source'​).
 +        """​
 +
         def sdf_line(a, b, p):         def sdf_line(a, b, p):
-            ​b_a = b - a +            ​"""​ 
-            ​p_a = a+            ​Return distance from point to line a-b. 
 +            (source: https://​youtu.be/​PMltMdi1Wzg) 
 +            """​ 
             if b == a:             if b == a:
                 h = 0                 h = 0
             else:             else:
-                h = min(1, max(0, ​p_a.dot(b_a) / b_a.dot(b_a))) +                h = min(1, max(0, ​(p-a).dot(b-a) / (b-a).dot(b-a))) 
-            return (p_a - h * b_a).length()+            return ((p-a) - h * (b-a)).length()
  
-        if len(self) == 0:+        if not self: # empty
             return float('​inf'​)             return float('​inf'​)
  
-        ​if len(self) ​== 1+        ​# minimum distance to a side 
-            ​return ​(source ​- self[0]).length()+        m = float('​inf'​) 
 +        for i, _ in enumerate(self): 
 +            ​m = min(m, sdf_line(self[i-1], self[i], source)) 
 +        return m
  
-        else: 
-            m = float('​inf'​) 
-            for i, _ in enumerate(self):​ 
-                m = min(m, sdf_line(self[i-1],​ self[i], source)) 
-            return m 
  
- 
-    # Implements cut() from abstract drawable class 
     def cut(self, source, portal):     def cut(self, source, portal):
 +        """​
 +        Return '​self'​ but cut to fit in the view from '​source'​ though '​portal'​.
 +        This algorithm isn't perfect, but the best I got at the moment.
 +        """​
 +
         def position(point):​         def position(point):​
 +            """​
 +            Return the area '​point'​ is in:
 +            -1 -- in the triangle between source and portal line
 +             0 -- to the right of visible area
 +             1 -- in the visible portal area
 +             2 -- to the left of visible area
 +            """​
 +
             vec = point - source             vec = point - source
             if vec.is_between(ray1.dir,​ ray2.dir):             if vec.is_between(ray1.dir,​ ray2.dir):
Zeile 411: Zeile 421:
                 else:                 else:
                     return -1                     return -1
- 
             if vec.is_between(null,​ ray1.dir):             if vec.is_between(null,​ ray1.dir):
                 return 0                 return 0
Zeile 417: Zeile 426:
                 return 2                 return 2
  
-        ray1 = Ray(portal[0], portal[0] - source) +        ​# rays that are borders of different areas 
-        ray2 = Ray(portal[1], portal[1] - source) +        ​ray1 = Ray(portal.start, portal.start ​- source) ​# area 0 <--> 1 
-        border = Ray(ray1.start,​ ray2.start - ray1.start)+        ray2 = Ray(portal.end, portal.end - source) ​# area 1 <--> 2 
 +        border = Ray(ray1.start,​ ray2.start - ray1.start) # area -1 <--> 1 
 +        null = -(ray1.dir + ray2.dir) # area 0 <--> 2 
 +        # garantees anticlockwise order (important for calculations)
         if ray1.dir.angle_to(ray2.dir) > 180:         if ray1.dir.angle_to(ray2.dir) > 180:
             ray1, ray2 = ray2, ray1             ray1, ray2 = ray2, ray1
 +
         rest = Polygon()         rest = Polygon()
         rest.__dict__ = self.__dict__         rest.__dict__ = self.__dict__
-        null = -(ray1.dir + ray2.dir) 
  
-        if len(self) == 0:+        if not self: # empty
             return rest             return rest
  
 +        # edgecase for when a portal is in its own portal world in the same place
         if len(self) == 2:         if len(self) == 2:
             if self[0] in portal and self[1] in portal:             if self[0] in portal and self[1] in portal:
                 return rest                 return rest
  
 +        # algorithm for cutting polygon
         nxt_pos = position(self[-1])         nxt_pos = position(self[-1])
         for i, _ in enumerate(self):​         for i, _ in enumerate(self):​
Zeile 438: Zeile 452:
             nxt_pos = position(self[i])             nxt_pos = position(self[i])
  
-            if cur_pos == 1:+            if cur_pos == 1: # visible
                 rest.append(self[i-1])                 rest.append(self[i-1])
  
 +            # same position, nothing changes
             if nxt_pos == cur_pos:             if nxt_pos == cur_pos:
                 continue                 continue
  
 +            # different position, intersections with visible area needed
             inter = []             inter = []
  
 +            # through the left or right boundary of visible area -> intersection
             if 1 in (cur_pos, nxt_pos):             if 1 in (cur_pos, nxt_pos):
                 if 0 in (cur_pos, nxt_pos):                 if 0 in (cur_pos, nxt_pos):
Zeile 454: Zeile 471:
                     inter += border.intersect(Ray(self[i-1],​ self[i] - self[i-1]))                     inter += border.intersect(Ray(self[i-1],​ self[i] - self[i-1]))
  
 +            # line goes outside the visible area around the portal -> edges of portal
             elif -1 in (cur_pos, nxt_pos):             elif -1 in (cur_pos, nxt_pos):
                 if 0 in (cur_pos, nxt_pos):                 if 0 in (cur_pos, nxt_pos):
Zeile 460: Zeile 478:
                     inter += [ray2.start]                     inter += [ray2.start]
  
 +            # line goes from the left to the right -> may intersect visible area
             else: # 0 and 2             else: # 0 and 2
                 if cur_pos == 0:                 if cur_pos == 0:
Zeile 468: Zeile 487:
                     inter += ray1.intersect(Ray(self[i-1],​ self[i] - self[i-1]))                     inter += ray1.intersect(Ray(self[i-1],​ self[i] - self[i-1]))
  
 +            # add intersections
             for pt in inter:             for pt in inter:
-                if pt.length2() > 10000:+                if pt.length2() > 10000: ​# hotfix
                     print(f"​CalculationError while cutting {self} at {ray1, ray2}:​\n ​   {pt} is very big")                     print(f"​CalculationError while cutting {self} at {ray1, ray2}:​\n ​   {pt} is very big")
                 else:                 else:
                     rest.append(pt)                     rest.append(pt)
- 
-        if nxt_pos == 1: 
-            rest.append(self[-1]) 
  
         return rest         return rest
Zeile 481: Zeile 498:
 </​file>​ </​file>​
  
-[[neg_drawables#sourcecode|↑ Back to top]]+[[#top|↑ Back to top]]
  
 ---- ----
Zeile 490: Zeile 507:
 import colors import colors
 from polygon import * from polygon import *
 +
  
 class Floor(Polygon):​ class Floor(Polygon):​
     """​     """​
-    ​Class for polygons ​just drawn on the floor as drawable object. Implements functionality of abstract drawable class.+    ​Subclass ​for polygons as floor tiles wich don't have collisions.
     """​     """​
  
  
-    # constructor:​ create self as drawable object 
     def __init__(self,​ *points, color=colors.DARK_RED):​     def __init__(self,​ *points, color=colors.DARK_RED):​
 +        """​Initilise '​self'​ with standard color '​DARK_RED'​."""​
         Polygon.__init__(self,​ *points, color=color)         Polygon.__init__(self,​ *points, color=color)
  
  
-    # Implements draw_shadow() from abstract drawable class 
     def draw_shadow(self,​ screen, offset, source, color=None):​     def draw_shadow(self,​ screen, offset, source, color=None):​
 +        """​Overide '​draw_shadow'​ to not draw a shadow."""​
         pass         pass
  
  
-    # Implements cut() from abstract drawable class 
     def cut(self, source, portal):     def cut(self, source, portal):
 +        """​
 +        Return '​self'​ but cut to fit in the view from '​source'​ though '​portal'​.
 +        Makes use of '​Polygon.cut'​.
 +        """​
 +
         rest = Polygon.cut(self,​ source, portal)         rest = Polygon.cut(self,​ source, portal)
         if len(rest) > 2:         if len(rest) > 2:
             return Floor(*rest,​ color=self.color)             return Floor(*rest,​ color=self.color)
- +        else: 
-        return Polygon()+            return Polygon()
  
  
     def dist_to(self,​ source):     def dist_to(self,​ source):
 +        """​Overide '​dist_to'​ to never cause collisions."""​
         return float('​inf'​)         return float('​inf'​)
  
 </​file>​ </​file>​
  
-[[neg_drawables#sourcecode|↑ Back to top]]+[[#top|↑ Back to top]]
  
 ---- ----
Zeile 528: Zeile 551:
 ==== Portal ==== ==== Portal ====
 <file python portal.py>​ <file python portal.py>​
 +import colors
 from polygon import * from polygon import *
 from matrix import * from matrix import *
-import colors 
  
  
 class Portal(Polygon):​ class Portal(Polygon):​
     """​     """​
-    Portal class as drawable ​object. ​Implements functionality ​of abstract drawable class.+    Portal class as a 2-element '​Polygon' ​object. 
 +    In for both sides of the portal there is 
 +    an offset vector and a distortion matrix.
     """​     """​
  
  
     # constructor:​ create self as Polygon object     # constructor:​ create self as Polygon object
-    def __init__(self,​ start, end, M=((1,​0),​(0,​1)),​ M2=None, offset=None,​ offset2=None,​ color=(0,0,255)):+    def __init__(self,​ start, end, M=((1,​0),​(0,​1)),​ M2=None, offset=None,​ offset2=None,​ color=colors.BRIGHT_BLUE):​ 
 +        """​ 
 +        Initilises '​self'​ with standard color '​BRIGHT_BLUE'​. 
 +        offset ​ -- for side 1 (std: None == portal will remain in same place) 
 +        offset2 -- for side 2 (std: None == portal will remain in same place) 
 +        M       -- distortion matrix for side 1 (stdunit matrix) 
 +        M2      -- distortion matrix for side 2 (std: None == inverse of M) 
 +        """​ 
         Polygon.__init__(self,​ start, end, color=color)         Polygon.__init__(self,​ start, end, color=color)
 +
         self.M = Matrix(*M)         self.M = Matrix(*M)
         if M2 is None:         if M2 is None:
Zeile 557: Zeile 591:
  
  
-    # Overrides attribute access 
     def __getattr__(self,​ attr):     def __getattr__(self,​ attr):
         """​         """​
Zeile 567: Zeile 600:
         if attr == '​end':​         if attr == '​end':​
             return self[1]             return self[1]
-        if attr == '​M':​ 
-            return self.__dict__['​M'​] 
-        if attr == '​offset':​ 
-            return self.__dict__['​offset'​] 
-        if attr == '​color':​ 
-            return self.__dict__['​color'​] 
- 
-        raise AttributeError(f"​{self.__class__.__name__} has no Attribute {attr}."​) 
  
  
     def go_through(self,​ dir, world):     def go_through(self,​ dir, world):
 +        """​
 +        Return the portal world of '​world'​ for the side in direction '​dir'​.
 +        """​
 +
         portal_world = []         portal_world = []
  
Zeile 584: Zeile 613:
             port.__dict__ = object.__dict__             port.__dict__ = object.__dict__
  
 +            # gone through side 1
             if dir.is_between(self.end - self.start, self.start - self.end):             if dir.is_between(self.end - self.start, self.start - self.end):
                 for i, _ in enumerate(object):​                 for i, _ in enumerate(object):​
                     if self.offset is None:                     if self.offset is None:
 +                        # middle of portal in portal world
                         offset = self.M.prod((self.start + self.end) / 2) - (self.start + self.end) / 2                         offset = self.M.prod((self.start + self.end) / 2) - (self.start + self.end) / 2
                     else:                     else:
                         offset = self.offset                         offset = self.offset
  
 +                    # apply distortion and offset
                     port[i] = self.M.prod(object[i]) - offset                     port[i] = self.M.prod(object[i]) - offset
  
 +                # if world gets mirrored the side-sensitive portals have to be reverted
                 if type(object) is Portal:                 if type(object) is Portal:
                     if self.M.i.angle_to(self.M.j) > 180:                     if self.M.i.angle_to(self.M.j) > 180:
                         port[0], port[1] = port[1], port[0]                         port[0], port[1] = port[1], port[0]
 +
 +            # gone through side 2
             else:             else:
                 for i, _ in enumerate(object):​                 for i, _ in enumerate(object):​
                     if self.offset2 is None:                     if self.offset2 is None:
 +                        # middle of portal in portal world
                         offset2 = self.M2.prod((self.start + self.end) / 2) - (self.start + self.end) / 2                         offset2 = self.M2.prod((self.start + self.end) / 2) - (self.start + self.end) / 2
                     else:                     else:
                         offset2 = self.offset2                         offset2 = self.offset2
  
 +                    # apply distortion and offset
                     port[i] = self.M2.prod(object[i]) - offset2                     port[i] = self.M2.prod(object[i]) - offset2
  
 +                # if world gets mirrored the side-sensitive portals have to be reverted
                 if type(object) is Portal:                 if type(object) is Portal:
                     if self.M2.i.angle_to(self.M2.j) > 180:                     if self.M2.i.angle_to(self.M2.j) > 180:
                         port[0], port[1] = port[1], port[0]                         port[0], port[1] = port[1], port[0]
  
 +            # add distorted object to portal world
             portal_world.append(port)             portal_world.append(port)
  
Zeile 621: Zeile 660:
             port.__dict__ = object.__dict__             port.__dict__ = object.__dict__
  
 +            # gone through side 1
             if (self.start - source).is_between(self.end - self.start, self.start - self.end):             if (self.start - source).is_between(self.end - self.start, self.start - self.end):
                 for i, _ in enumerate(object):​                 for i, _ in enumerate(object):​
                     if self.offset is None:                     if self.offset is None:
 +                        # middle of portal in portal world
                         offset = self.M.prod((self.start + self.end) / 2) - (self.start + self.end) / 2                         offset = self.M.prod((self.start + self.end) / 2) - (self.start + self.end) / 2
                     else:                     else:
                         offset = self.offset                         offset = self.offset
  
 +                    # apply distortion and offset
                     port[i] = self.M.prod(object[i]) - offset                     port[i] = self.M.prod(object[i]) - offset
  
 +                # if world gets mirrored the side-sensitive portals have to be reverted
                 if type(object) is Portal:                 if type(object) is Portal:
                     if self.M.i.angle_to(self.M.j) > 180:                     if self.M.i.angle_to(self.M.j) > 180:
                         port[0], port[1] = port[1], port[0]                         port[0], port[1] = port[1], port[0]
 +
 +            # gone through side 2
             else:             else:
                 for i, _ in enumerate(object):​                 for i, _ in enumerate(object):​
                     if self.offset2 is None:                     if self.offset2 is None:
 +                        # middle of portal in portal world
                         offset2 = self.M2.prod((self.start + self.end) / 2) - (self.start + self.end) / 2                         offset2 = self.M2.prod((self.start + self.end) / 2) - (self.start + self.end) / 2
                     else:                     else:
                         offset2 = self.offset2                         offset2 = self.offset2
  
 +                    # apply distortion and offset
                     port[i] = self.M2.prod(object[i]) - offset2                     port[i] = self.M2.prod(object[i]) - offset2
  
 +                # if world gets mirrored the side-sensitive portals have to be reverted
                 if type(object) is Portal:                 if type(object) is Portal:
                     if self.M2.i.angle_to(self.M2.j) > 180:                     if self.M2.i.angle_to(self.M2.j) > 180:
                         port[0], port[1] = port[1], port[0]                         port[0], port[1] = port[1], port[0]
  
 +            # add distorted and truncated object to portal world
             cut = port.cut(source,​ self)             cut = port.cut(source,​ self)
             if cut: # not empty             if cut: # not empty
Zeile 654: Zeile 703:
  
     def draw(self, screen, offset):     def draw(self, screen, offset):
-        ​pass+        ​"""​ 
 +        Draw the endpoints of '​self'​ on '​screen'​. 
 +        """​ 
 + 
 +        for point in self: 
 +            Polygon(point).draw(screen,​ offset) 
  
-    # overrides draw_shadow() inherited from Polygon class 
     def draw_shadow(self,​ screen, offset, source):     def draw_shadow(self,​ screen, offset, source):
         """​         """​
-        ​Draws shadow of portal +        ​Draw draw a shadow of 'self' on 'screen' ​respecting the light-'​source'​.
-        Called by 'self.draw_shadow(screen, offset)'+
         """​         """​
  
 +        # draw the shadow in the same color as the background to wipe the canvas
         Polygon.draw_shadow(self,​ screen, offset, source, color=colors.GRAY)         Polygon.draw_shadow(self,​ screen, offset, source, color=colors.GRAY)
-        # Polygon.draw_shadow(self,​ screen, offset, source, color=(150,​150,​150)) 
  
 +        # draw shadows for the end points of portal
         for point in self:         for point in self:
-            ​Ray(point, point - source, color=(0,​0,​0)).draw(screen, offset)+            ​Polygon(point).draw_shadow(screen, offset, source)
  
  
-    # Implements cut() from abstract drawable class 
     def cut(self, source, portal):     def cut(self, source, portal):
 +        """​
 +        Return '​self'​ but cut to fit in the view from '​source'​ though '​portal'​.
 +        Makes use of '​Polygon.cut'​.
 +        """​
 +
         rest = Polygon.cut(self,​ source, portal)         rest = Polygon.cut(self,​ source, portal)
-        if len(rest) ​>= 2:+        if len(rest) ​== 2:
             return Portal(*reversed(rest[:​2]),​ M=self.M, color=self.color)             return Portal(*reversed(rest[:​2]),​ M=self.M, color=self.color)
  
Zeile 680: Zeile 738:
 </​file>​ </​file>​
  
-[[neg_drawables#sourcecode|↑ Back to top]]+[[#top|↑ Back to top]]
  
  
ss20/neg_drawables.1598468674.txt.gz · Zuletzt geändert: 2020/08/26 21:04 von srather