def test_converting_from_relative_to_absolute_positions(self): relative_positions = [(-1, 0), (0, -1), (0, 0), (0, 1), (1, 1), (1, 2)] obj = Object(1, (1, 1), relative_positions) self.assertEqual([ (0, 1), (1, 0), (1, 1), (1, 2), (2, 2), (2, 3), ], obj.convert_to_absolute_positions())
def test_is_point_enclosed_in_rectangle(self): obj_a = Object(2, (0, 0), [(0, 0)]) relative_positions = [ (0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (3, 2), (2, 2), (1, 2), (0, 2), (0, 1), ] obj_b = Object(1, (2, 2), relative_positions) frame_model = ObjectRuntime.make_frame_model_from_objects( [obj_a, obj_b], 0) self.assertFalse( GeometryRuntime.is_point_fully_enclosed((0, 0), frame_model)) for pos in obj_b.convert_to_absolute_positions(): self.assertFalse( GeometryRuntime.is_point_fully_enclosed(pos, frame_model)) self.assertFalse( GeometryRuntime.is_point_fully_enclosed((1, 0), frame_model)) self.assertFalse( GeometryRuntime.is_point_fully_enclosed((1, 1), frame_model)) self.assertFalse( GeometryRuntime.is_point_fully_enclosed((0, 1), frame_model)) self.assertTrue( GeometryRuntime.is_point_fully_enclosed((3, 3), frame_model)) self.assertTrue( GeometryRuntime.is_point_fully_enclosed((4, 3), frame_model))
def points_contained_by_object( obj: Object.Object, frame_model: "src.FrameModel.FrameModel.FrameModel" ) -> List[AbsolutePosition]: obj_min_x = obj.top_left_offset[0] + min( list(map(lambda pos: pos[0], obj.relative_positions))) obj_max_x = obj.top_left_offset[0] + max( list(map(lambda pos: pos[0], obj.relative_positions))) obj_min_y = obj.top_left_offset[0] + min( list(map(lambda pos: pos[1], obj.relative_positions))) obj_max_y = obj.top_left_offset[0] + max( list(map(lambda pos: pos[1], obj.relative_positions))) grid = frame_model.to_grid() objs_positions = set(obj.convert_to_absolute_positions()) contained_points = [] seen_positions: Set[AbsolutePosition] = set() positions_that_have_neighbours_out_of_bounds: Set[AbsolutePosition] = set() """ a point p is contained by an object o if it's not possible to go outside of o's min or max x or y using neighbouring free squares or objects (this includes neighbours of neighbours, etc.) - the positions mustn't already be a part of object o """ for x in range(obj_min_x, obj_max_x + 1): for y in range(obj_min_y, obj_max_y + 1): if ( x, y ) in objs_positions: # a part of the object definitely isn't contained _in_ the object continue possible_positions_that_could_have_out_of_bounds_neighbours: Deque[ AbsolutePosition] = deque() possible_positions_that_could_have_out_of_bounds_neighbours.append( (x, y)) neighbours_that_are_out_of_bounds = set() while (len( possible_positions_that_could_have_out_of_bounds_neighbours )) > 0: possible_pos = possible_positions_that_could_have_out_of_bounds_neighbours.pop( ) seen_positions.add(possible_pos) possible_x = possible_pos[0] possible_y = possible_pos[1] neighbourhood = grid.get_neighbourhood(possible_x, possible_y) neighbourhood_not_this_obj = list( filter(lambda pos: (pos[0], pos[1]) not in objs_positions, neighbourhood)) for neighbour in neighbourhood_not_this_obj: neighbour_x = neighbour[0] neighbour_y = neighbour[1] neighbour_is_outside_of_object_bounds = ( (neighbour_x < obj_min_x or neighbour_x > obj_max_x) or (neighbour_y < obj_min_y or neighbour_y > obj_max_y)) neighbour_has_its_own_neighbours_out_of_bounds = neighbour in positions_that_have_neighbours_out_of_bounds if neighbour_is_outside_of_object_bounds or neighbour_has_its_own_neighbours_out_of_bounds: neighbours_that_are_out_of_bounds.add(neighbour) elif neighbour not in seen_positions: possible_positions_that_could_have_out_of_bounds_neighbours.append( neighbour) if len(neighbours_that_are_out_of_bounds) == 0: contained_points.append((x, y)) else: positions_that_have_neighbours_out_of_bounds.add((x, y)) return contained_points
def rotate_object_about_point( obj: Object.Object, angle: int, about_point: Tuple[int, int], is_relative_rotation: bool = True) -> Object.Object: def rotate(point: Tuple[int, int], radian_angle: float, origin: Tuple[int, int]): """ Rotate a point counterclockwise by a given angle around a given origin. The angle should be given in radians. """ ox, oy = origin px, py = point qx = ox + math.cos(radian_angle) * ( px - ox) - math.sin(radian_angle) * (py - oy) qy = oy + math.sin(radian_angle) * ( px - ox) + math.cos(radian_angle) * (py - oy) # we need to round them because we'd return stuff like (6.123233995736766e-17, 1.0) otherwise return round(qx), round(qy) angle_in_radians = (angle) * math.pi / 180 if is_relative_rotation: rotated_relative_positions = list( map(lambda pos: rotate(pos, angle_in_radians, about_point), obj.relative_positions)) # if any of the rotated_relative_positions contain a negative int, # we need to move the top left offset so that they're all >= 0, and recalculate all the relative positions min_x_offset = min( list(map(lambda pos: pos[0], rotated_relative_positions))) min_y_offset = min( list(map(lambda pos: pos[1], rotated_relative_positions))) top_left_offset_after_rotation = obj.top_left_offset if min_x_offset < 0: rotated_relative_positions = list( map(lambda pos: (pos[0] + abs(min_x_offset), pos[1]), rotated_relative_positions)) top_left_offset_after_rotation = ( top_left_offset_after_rotation[0] + min_x_offset, top_left_offset_after_rotation[1]) if min_y_offset < 0: rotated_relative_positions = list( map(lambda pos: (pos[0], pos[1] + abs(min_y_offset)), rotated_relative_positions)) top_left_offset_after_rotation = ( top_left_offset_after_rotation[0], top_left_offset_after_rotation[1] + min_y_offset) top_left_offset_after_rotation = (max( top_left_offset_after_rotation[0], 0), max(top_left_offset_after_rotation[1], 0)) # todo: move the tl offset if (it + max obj offset) is > grid size, too return Object.Object(obj.colour, top_left_offset_after_rotation, rotated_relative_positions, obj.depth) else: rotated_absolute_positions = list( map(lambda pos: rotate(pos, angle_in_radians, about_point), obj.convert_to_absolute_positions())) # if any of the rotated_absolute_positions contain a negative int, # we need to move them all so that they're all >= 0 min_x_offset = min( list(map(lambda pos: pos[0], rotated_absolute_positions))) min_y_offset = min( list(map(lambda pos: pos[1], rotated_absolute_positions))) if min_x_offset < 0: rotated_absolute_positions = list( map(lambda pos: (pos[0] + abs(min_x_offset), pos[1]), rotated_absolute_positions)) if min_y_offset < 0: rotated_absolute_positions = list( map(lambda pos: (pos[0], pos[1] + abs(min_y_offset)), rotated_absolute_positions)) return Object.Object.create_with_absolute_positions( obj.colour, rotated_absolute_positions, obj.depth)