def _contains_point(self, point): """Tests whether or not the current room cointains a specific point Return true if this room object contains the supplied object Uses the ray casting algorithm: http://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm For simplicity we assume the ray is casted horizontally to the left """ # We first compare against the bounding box. For most points this will be # enough min_point, max_point = self.bounding_box if ((point.x > max_point.x) or (point.x < min_point.x) or (point.y > max_point.y) or (point.y < min_point.y)): return False # Amount of intersections counted match_count = 0 # Handle some special cases counted_vertices = [] for (a, b) in circular_pairwise(self.points): # Is the y coordinate of the point valid? I.e.: is it between # the y coordinates of a and b? if (a.y <= point.y <= b.y or a.y >= point.y >= b.y): # The following function returns -1 if the point is to the left # of the segment, 0 if it is on the same line as the segment, # +1 if it is to the right comparison = Polygon._compare_line_and_point(a, b, point) # If the point is on the same line, we can conclude it belongs # to the shape if its x coordinates are between a's and b's if (comparison == 0 and max(a.x, b.x) >= point.x >= min(a.x, b.x) and max(a.y, b.y) >= point.y >= min(a.y, b.y)): return True # the point is over a border # If the point is not over the line, is it to the right? # Being to the right means that the ray being cast to the left # might actually intersect the segment. elif (comparison > 0): # Simple case, the ray does not match any vertice, so it # intersects inside the segment. if ((point.y != a.y) and (point.y != b.y)): match_count = match_count + 1 else: # Special cases, the ray intersects precisely one of the vertices if a.y == point.y and a.x <= point.x and a not in counted_vertices: counted_vertices.append(a) match_count = match_count + 1 if b.y == point.y and b.x <= point.x and ( b not in counted_vertices): counted_vertices.append(b) match_count = match_count + 1 # After analyzing all segments, if we found an even amount of intersections # it means the point is outside of the polygon return match_count % 2 != 0
def as_segment_list(self): return [ Segment(a.clone(), b.clone()) for a, b in circular_pairwise(self.points) ]
def test_polygon_contains_point(self): def polygon_contains(polygon, x, y): self.assertTrue(polygon._contains_point(Point(x, y)), "{} should contain {}, {}".format(polygon, x, y)) def polygon_not_contains(polygon, x, y): self.assertFalse( polygon._contains_point(Point(x, y)), "{} should not contain {}, {}".format(polygon, x, y)) # Generic points inside the square polygon for x, y in circular_pairwise(range(1, 10)): polygon_contains(self.polygon1, x, y) test_data = [ { "polygon": self.polygon1, "truths": [ # Inside, but near borders (0.1, 0.001), (9.99, 9.7), (5, 9.99), (0.1, 8), # Close to the border (0.001, 0.001), (9.999, 9.999), # Points precisely on the border (5, 0), (10, 5), (5, 10), (0, 5), (0, 0), (10, 0), (10, 10), (0, 10) ], "falses": [(5, -4), (5, 11), (10.001, 10.001), (-2, 5)] }, { "polygon": self.polygon2, "truths": [ # Close to the vertices (9.9, 0), (0, 9.9), (-9.9, 0), (0, -9.9), # Close to the center (0.001, 0.001), (3, 3), (-4.8, -4.8), (4.1, 3.8), # Points precisely over the border (10, 0), (0, 10), (-10, 0), (0, -10) ], "falses": [ # Points definetely outside (9.9, 9.9), (10.2, 0), (0, -10.1), (10.001, 10.001), (33, 12) ] }, { "polygon": self.polygon3, "truths": [ # Close to the vertices (1, 2), (0.1, 0.1), (9.9, 0.1), (0.1, 9.9), # Close to the center (2, 3), (4, 5), (8, 2), (1, 7), # Points precisely over the vertices (0, 0), (10, 0), (10, 5), (5, 5), (5, 10), (0, 10) ], "falses": [ # Points definetely outside (10.1, 0.3), (-0.1, 1), (2, 10.1), (-0.1, -0.1), (1, -0.1), (6, 6), (5.1, 5.1) ] }, { "polygon": self.polygon4, "truths": [(5, 5)], "falses": [ # Points definetely outside (11, 5) ] } ] for test_polygon in test_data: for (x, y) in test_polygon["truths"]: polygon_contains(test_polygon["polygon"], x, y) for (x, y) in test_polygon["falses"]: polygon_not_contains(test_polygon["polygon"], x, y)
def test_polygon_contains_point(self): def polygon_contains(polygon, x, y): self.assertTrue( polygon._contains_point(Point(x, y)), "{} should contain {}, {}".format(polygon, x, y) ) def polygon_not_contains(polygon, x, y): self.assertFalse( polygon._contains_point(Point(x, y)), "{} should not contain {}, {}".format(polygon, x, y) ) # Generic points inside the square polygon for x, y in circular_pairwise(range(1,10)): polygon_contains(self.polygon1, x, y) test_data = [ { "polygon" : self.polygon1, "truths" : [ # Inside, but near borders (0.1, 0.001), (9.99, 9.7), (5, 9.99), (0.1, 8), # Close to the border (0.001, 0.001), (9.999, 9.999), # Points precisely on the border (5, 0), (10, 5), (5, 10), (0, 5), (0, 0), (10, 0), (10, 10), (0, 10) ], "falses" : [ (5, -4), (5, 11), (10.001, 10.001), (-2, 5) ] }, { "polygon" : self.polygon2, "truths" : [ # Close to the vertices (9.9, 0), (0, 9.9), (-9.9, 0), (0, -9.9), # Close to the center (0.001, 0.001), (3, 3), (-4.8, -4.8), (4.1, 3.8), # Points precisely over the border (10, 0), (0, 10), (-10, 0), (0, -10) ], "falses" : [ # Points definetely outside (9.9, 9.9), (10.2, 0), (0, -10.1), (10.001, 10.001), (33, 12) ] }, { "polygon" : self.polygon3, "truths" : [ # Close to the vertices (1, 2), (0.1, 0.1), (9.9, 0.1), (0.1, 9.9), # Close to the center (2, 3), (4, 5), (8, 2), (1, 7), # Points precisely over the vertices (0,0),(10,0),(10,5),(5,5),(5,10),(0,10) ], "falses" : [ # Points definetely outside (10.1, 0.3), (-0.1, 1), (2, 10.1), (-0.1, -0.1), (1, -0.1), (6,6), (5.1, 5.1) ] }, { "polygon" : self.polygon4, "truths" : [ (5, 5) ], "falses" : [ # Points definetely outside (11, 5) ] } ] for test_polygon in test_data: for (x, y) in test_polygon["truths"]: polygon_contains(test_polygon["polygon"], x, y) for (x, y) in test_polygon["falses"]: polygon_not_contains(test_polygon["polygon"], x, y)
def _contains_point(self, point): """Tests whether or not the current room cointains a specific point Return true if this room object contains the supplied object Uses the ray casting algorithm: http://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm For simplicity we assume the ray is casted horizontally to the left """ # We first compare against the bounding box. For most points this will be # enough min_point, max_point = self.bounding_box if ( (point.x > max_point.x) or (point.x < min_point.x) or (point.y > max_point.y) or (point.y < min_point.y) ): return False # Amount of intersections counted match_count = 0 # Handle some special cases counted_vertices = [] for (a, b) in circular_pairwise(self.points): # Is the y coordinate of the point valid? I.e.: is it between # the y coordinates of a and b? if( a.y <= point.y <= b.y or a.y >= point.y >= b.y ): # The following function returns -1 if the point is to the left # of the segment, 0 if it is on the same line as the segment, # +1 if it is to the right comparison = Polygon._compare_line_and_point(a, b, point) # If the point is on the same line, we can conclude it belongs # to the shape if its x coordinates are between a's and b's if(comparison == 0 and max(a.x, b.x) >= point.x >= min(a.x, b.x) and max(a.y, b.y) >= point.y >= min(a.y, b.y)): return True # the point is over a border # If the point is not over the line, is it to the right? # Being to the right means that the ray being cast to the left # might actually intersect the segment. elif(comparison > 0): # Simple case, the ray does not match any vertice, so it # intersects inside the segment. if ((point.y != a.y) and (point.y != b.y)): match_count = match_count + 1 else: # Special cases, the ray intersects precisely one of the vertices if a.y == point.y and a.x <= point.x and a not in counted_vertices: counted_vertices.append(a) match_count = match_count + 1 if b.y == point.y and b.x <= point.x and (b not in counted_vertices): counted_vertices.append(b) match_count = match_count + 1 # After analyzing all segments, if we found an even amount of intersections # it means the point is outside of the polygon return match_count % 2 != 0