def test_find_obstruction_when_transforming_line(self): """ test find_obstruction_when_transforming_line """ raw_map = raw_map.RawMap(10, 10, 100 * [0]) area_map = area_map.AreaMap(raw_map, lambda _: True) base = vector.PointF(2, 2) start = vector.PointF(8, 2) end = vector.PointF(2, 8) # print(area_map.find_tiles_in_triangle(base, start, end)) self.assertEqual( area_map.find_obstruction_when_transforming_line(base, start, end), (1.0, None)) area_map[vector.GridTile(4, 4)] = 0 # 4,4 is now unpassable self.assertEqual( area_map.find_obstruction_when_transforming_line(base, start, end), (0.4, vector.GridPoint(5, 4))) area_map[vector.GridTile(6, 3)] = 0 # 6,3 is now unpassable self.assertEqual( area_map.find_obstruction_when_transforming_line(base, start, end), (1. / 6., vector.GridTile(7, 3))) area_map[vector.GridTile(4, 2)] = 0 # 4,2 is now unpassable self.assertEqual( area_map.find_obstruction_when_transforming_line(base, start, end), (0., vector.GridPoint(5, 2))) area_map[vector.GridTile(2, 2)] = 0 # 2,2 is now unpassable self.assertEqual( area_map.find_obstruction_when_transforming_line(base, start, end), (0., vector.GridPoint(5, 2))) # test angle > 90 base = vector.PointF(6, 4) start = vector.PointF(9, 5) end = vector.PointF(3, 5) self.assertEqual( area_map.find_obstruction_when_transforming_line(base, start, end), (2. / 3., vector.GridPoint(5, 5))) base = vector.PointF(3, 3) start = vector.PointF(0, 5) end = vector.PointF(9, 7) self.assertEqual( area_map.find_obstruction_when_transforming_line(base, start, end), (0.5, vector.GridPoint(4, 5))) area_map[vector.GridTile(6, 5)] = 0 # 6,5 is now unpassable self.assertEqual( area_map.find_obstruction_when_transforming_line(base, start, end), (0.5, vector.GridPoint(4, 5))) area_map[vector.GridTile(4, 4)] = -1 # 4,4 is now passable again self.assertEqual( area_map.find_obstruction_when_transforming_line(base, start, end), (5. / 7., vector.GridPoint(6, 6)))
def _create_area_map(self): """ create it """ # create shortcuts width, height = self.raw_map.width, self.raw_map.height # initialise id first free id area_id = self.no_area_id + 1 # # special case the map boundary since we have to understand the boundary edges # in particular the boundary will have area_id 'self.no_area_id + 1' # for x in range(width): # y = 0 line t = vector.GridTile(x, 0) self.__process_tile(area_id, t) if self.pass_test(self.raw_map[t]): p = vector.GridPoint(x, 0) p_xpp = vector.GridPoint(x + 1, 0) # inner side is to the right self.edges[area_id].append(vector.GridEdge(p, p_xpp)) # y = max line t = vector.GridTile(x, height - 1) self.__process_tile(area_id, t) if self.pass_test(self.raw_map[t]): p = vector.GridPoint(x, height) p_xpp = vector.GridPoint(x + 1, height) # inner side is to the right self.edges[area_id].append(vector.GridEdge(p_xpp, p)) for y in range(height): # x = 0 line t = vector.GridTile(0, y) self.__process_tile(area_id, t) if self.pass_test(self.raw_map[t]): p = vector.GridPoint(0, y, ) p_ypp = vector.GridPoint(0, y + 1) # inner side is to the left self.edges[area_id].append(vector.GridEdge(p_ypp, p)) # x = max line t = vector.GridTile(width - 1, y) self.__process_tile(area_id, t) if self.pass_test(self.raw_map[t]): p = vector.GridPoint(width, y, ) p_ypp = vector.GridPoint(width, y + 1) # inner side is to the left self.edges[area_id].append(vector.GridEdge(p, p_ypp)) # increment area_id area_id += 1 # iterate over all data for t in self.raw_map.tiles_iterator(): if self.__process_tile(area_id, t): # if we found something, increment area_id area_id += 1
def find_gridpoints_in_triangle_iterator(a, b, c): """ find all grid points in the triangle defined by the three points """ assert (isinstance(a, vector.PointF)) assert (isinstance(b, vector.PointF)) assert (isinstance(c, vector.PointF)) # tolerance for the conversion to integer values eps = 1e-6 eps_floor = lambda x: math.floor(x + eps) eps_ceil = lambda x: math.ceil(x - eps) # sort the points by ascending y-coordinate p_y_min, p_mid, p_y_max = sorted([a, b, c], key=lambda p: p.y) # special case: empty triangle if p_y_min.y == p_y_max.y: return [] # iterate over all y between # (smallest integer >= p_y_min.y) # to # (largest integer <= p_y_max.y) # (we use eps here to counter rounding artifacts) i_y_min = eps_ceil(p_y_min.y) i_y_max = eps_floor(p_y_max.y) # initialise y y = i_y_min while y <= p_mid.y: # compute the x value of the intersection const y # and the line p_y_min->p_y_max # i.e. solve the equation # t (p_y_max - p_y_min) + p_y_min = s e_x + (0,y) # multiply the equation with e_y, then # t (<p_y_max,e_y> - <p_y_min,e_y>) + <p_y_min,e_y> = y t = (y - p_y_min.y) / (p_y_max.y - p_y_min.y) # then we can calculate x x1 = t * (p_y_max.x - p_y_min.x) + p_y_min.x # compute the other x, it is the intersection with p_y_min->p_mid if p_mid.y - p_y_min.y != 0.: # careful: the denominator here can be zero t = (y - p_y_min.y) / (p_mid.y - p_y_min.y) x2 = t * (p_mid.x - p_y_min.x) + p_y_min.x else: # otherwise, p_y_min->p_mid is parallel to the x-axis x2 = p_mid.x # rearrange if needed if x2 < x1: x1, x2 = x2, x1 # now find the largest integer interval in [x1,x2] # (we use eps here to counter rounding artifacts) i1 = eps_ceil(x1) i2 = eps_floor(x2) # communicate the vector for i in range(i1, i2 + 1): yield vector.GridPoint(i, y) # increase y y += 1 while y <= i_y_max: # compute the x value of the intersection const y # and the line p_y_min->p_y_max t = (y - p_y_min.y) / (p_y_max.y - p_y_min.y) x1 = t * (p_y_max.x - p_y_min.x) + p_y_min.x # compute the other x, it is now the intersection with p_mid->p_y_max if p_y_max.y - p_mid.y != 0.: # careful: the denominator here can be zero t = (y - p_mid.y) / (p_y_max.y - p_mid.y) x2 = t * (p_y_max.x - p_mid.x) + p_mid.x else: # otherwise, p_y_min->p_mid is parallel to the x-axis x2 = p_mid.x # rearrange if needed if x2 < x1: x1, x2 = x2, x1 # now find the largest integer interval in [x1,x2] # (we use eps here to counter rounding artifacts) i1 = eps_ceil(x1) i2 = eps_floor(x2) # communicate the vector for i in range(i1, i2 + 1): yield vector.GridPoint(i, y) # increase y y += 1
def find_tiles_in_triangle_iterator_slow(self, base, start, end): """ find all tiles in the triangle defined by the three points """ assert (isinstance(base, vector.PointF)) assert (isinstance(start, vector.PointF)) assert (isinstance(end, vector.PointF)) # # find orientation # # compute vectors originating from base v_start = start - base v_end = end - base v_far_edge = end - start # compute vector originating from base which goes to the left of v_start v_start_left = v_start.left() v_end_left = v_end.left() v_far_edge_left = v_far_edge.left() # find inner direction leftness = v_start_left * v_end if leftness > 0: # inner direction is to the left of v_start, i.e. to the right of v_end v_start_normal = v_start_left v_end_normal = -v_end_left v_far_edge_normal = v_far_edge_left elif leftness < 0: # inner direction is to the right of v_start v_start_normal = -v_start_left v_end_normal = v_end_left v_far_edge_normal = -v_far_edge_left else: # leftness == 0 # they are parallel: everything is fine because we assume that # the original lines are fine return [] # the triangle is the intersection of the three half planes triangle = [ halfplane.HalfPlane(base, v_start_normal), halfplane.HalfPlane(base, v_end_normal), halfplane.HalfPlane(start, v_far_edge_normal), ] def tile_intersects_triangle(tile): # tile polygon polygon = tile.polygon() # intersections for halfplane in triangle: polygon = halfplane.intersection(polygon) # we want a proper intersection, not just a line if polygon.node_count() <= 2: return False # if end up here, then the polygon has a non-trivial # intersection with the triangle return True # find all tiles intersecting the triangle open_tiles = [] all_tiles = [] # find first tile which intersects the interior of the area p = vector.GridPoint(int(base.x), int(base.y)) for tile in p.adjacent_tiles(): if tile_intersects_triangle(tile): # we found our first tile open_tiles.append(tile) all_tiles.append(tile) break else: raise ValueError() # find all tiles in the triangle while open_tiles: # propagate open tiles tile = open_tiles.pop(0) # propagate to all nearby points (including the diagonal) for next_tile in tile.adjacent_tiles(): # ignore points outside of the box if not self.contains(next_tile): continue # if it was already treated, skip it if next_tile in all_tiles: continue # does it intersect the triangle? if tile_intersects_triangle(next_tile): # if yes, add it to open list open_tiles.append(next_tile) all_tiles.append(next_tile) # return all found tiles return all_tiles
def __expand_loop(self, area_id, loop): """ expand the given loop and mark all new areas with the given area_id """ # compute an pseudo in-place update array update_loop = PseudoInPlaceUpdateArray(loop) # keep track if the loop was expanded loop_expanded = False # go through edges of loop and see if we can expand it # (use index based iterator as the list is going to change) while not update_loop.is_finished(): # get current edge and compute outer tile, i.e. the tile to the left edge = update_loop.get(0) tile_outer = edge.left_tile() # keep the original if the tile does not define a valid tile, # i.e. the tile does not belong to the grid if not self.contains(tile_outer): update_loop.confirm() continue # keep the original if the outer tile was already assigned to an area if self[tile_outer] != self.area_map.no_area_id: update_loop.confirm() continue # set expanded to true as we are going to expand the current edge loop_expanded = True # mark the new area self[tile_outer] = area_id # get left facing vector v_left = edge.direction().left() # create new points by translating the old ones to the left new_a, new_b = edge.a + v_left, edge.b + v_left new_a = vector.GridPoint(new_a.x, new_a.y) new_b = vector.GridPoint(new_b.x, new_b.y) # the configuration looks like that: # new_a----new_b # | | # | | # ----a----b----- # i-1 i i+1 # # compute the plan # # if the (i-1)-th edge is the inverse of a->new_a, # then the (i-1)-th gets annihilated edge_imm = update_loop.get(-1) # it always holds that edge_imm.b == edge.a assert (edge_imm.b == edge.a) imm_annihilated = (edge_imm.a == new_a) # if the (i-1)-th edge get annihilated and # (i-2)-th edge is the inverse of the edge new_a->new_b, # then the (i-2)-th gets annihilated edge_immmm = update_loop.get(-2) if imm_annihilated: assert (edge_immmm.b == new_a) immmm_annihilated = imm_annihilated and (edge_immmm.a == new_b) # if the (i+1)-th edge is the inverse of new_b->b, # then the (i+1)-th gets annihilated edge_ipp = update_loop.get(+1) # it always holds that edge.b == edge[ipp].a assert (edge.b == edge_ipp.a) ipp_annihilated = (new_b == edge_ipp.b) # if the (i+1)-th edge get annihilated and # (i+2)-th edge is the inverse of the edge new_a->new_b, # then the (i+2)-th gets annihilated edge_ipppp = update_loop.get(+2) if ipp_annihilated: assert (edge_ipppp.a == new_b) ipppp_annihilated = ipp_annihilated and (edge_ipppp.b == new_a) # # add and remove edges accordingly # if imm_annihilated: # delete the edge update_loop.delete(-1) # if we also have to delete the other edge if immmm_annihilated: update_loop.delete(-1) else: # we have to add the edge a->new_a new_edge = vector.GridEdge(edge.a, new_a) update_loop.insert(new_edge) # delete the current edge update_loop.delete(0) if immmm_annihilated or ipppp_annihilated: # we do not have to add the edge new_a->new_b pass else: # we have to add it new_edge = vector.GridEdge(new_a, new_b) update_loop.insert(new_edge) if ipp_annihilated: # delete the edge (-1 as we just deleted the current edge) update_loop.delete(+1 - 1) # if we also have to delete the other edge if ipppp_annihilated and not immmm_annihilated: update_loop.delete(+1 - 1) else: # we have to add the new_b->edge b new_edge = vector.GridEdge(new_b, edge.b) update_loop.insert(new_edge) new_loop = update_loop.get_array() # if the loop is empty, add the smallest possible loop # (this is only a hack, still the situation probably only arises # under synthetic conditions) if not new_loop: # create the smallest loop a->b and b->a edge = loop[0] inverse_edge = vector.GridEdge(edge.b, edge.a) new_loop = [edge, inverse_edge] return False, new_loop return loop_expanded, new_loop