diff --git a/world_maker/City.py b/world_maker/City.py index 113e214..7fe0b4e 100644 --- a/world_maker/City.py +++ b/world_maker/City.py @@ -1,7 +1,9 @@ -from District import District +from District import District, Road from Position import Position from PIL import Image import random +from data_analysis import handle_import_image +from typing import Union class City: @@ -72,7 +74,6 @@ class City: index_district_chosen = index else: self.districts[index].area_expend.remove(point) - self.districts[index_district_chosen].area.append(point) self.districts[index_district_chosen].area_expend_from_point.append(point) self.districts[index_district_chosen].area_expend.remove(point) self.map_data[point.y][point.x] = index_district_chosen + 1 @@ -81,7 +82,6 @@ class City: """ Update the expansion points of all districts in the city. """ - for district in self.districts: if len(district.area_expend_from_point) > 0: district.update_expend_points(district.area_expend_from_point[0], self.map_data, self.height_map) @@ -117,10 +117,37 @@ class City: img.save('./data/district.png') print("[City] District map created.") + def draw_roads(self, image: Union[str, Image], size: int = 1) -> Image: + """ + Draw the roads of the city on the image. + + :param size: + :param image: The image to draw the roads on. + """ + image = handle_import_image(image) + for district in self.districts: + district.draw_roads(image, size) + return image + + def district_generate_road(self) -> list[Road]: + """ + Generate the roads of the city for each district. + + :return: The list of roads of the city. + """ + roads = [] + for district in self.districts: + district.generate_roads(self.map_data) + roads.extend(district.roads) + return roads + if __name__ == '__main__': city = City() for i in range(10): - city.add_district(Position(random.randint(0, 800), random.randint(0, 800))) + city.add_district(Position(random.randint(0, 400), random.randint(0, 400))) city.loop_expend_district() city.district_draw_map() + city.district_generate_road() + image = city.draw_roads(Image.new('RGB', (401, 401)),3) + image.save('./data/roadmap.png') diff --git a/world_maker/District.py b/world_maker/District.py index e0e3f9a..4a2f887 100644 --- a/world_maker/District.py +++ b/world_maker/District.py @@ -1,8 +1,23 @@ from Position import Position +from typing import Union +from random import randint +from PIL import Image + +class Road: + def __init__(self, position: Position, id_height: int, id_width: int, border: bool = False): + self.position: Position = position + self.north: Union[Road, None] = None + self.south: Union[Road, None] = None + self.east: Union[Road, None] = None + self.west: Union[Road, None] = None + self.id_height = id_height + self.id_width = id_width + self.border = border + class District: """ - The CustomDistrict class represents a district that can be expanded. + The District class represents a district that can be expanded. Attributes: center_expend (Position): The center position from which the district expands. @@ -10,6 +25,7 @@ class District: area_expend_from_point (list): The list of positions from which the district can expand. area_expend (list): The list of positions to which the district will maybe expand. """ + def __init__(self, tile_id: int, center: Position, district_type: str = ""): """ The constructor for the District class. @@ -23,12 +39,14 @@ class District: self.tile_id = tile_id self.type = district_type self.center_expend = center - self.area = [center] self.area_expend_from_point = [center] self.area_expend = [] + self.roads: list[Road] = [] + self.roads_expend = [] - def verify_point(self, point: Position, point_new: Position, map_data: list[list[int]], height_map: list[list[int]]): - """ + def verify_point(self, point: Position, point_new: Position, map_data: list[list[int]], + height_map: list[list[int]]): + """ Verify if a new point can be added to a district extend area list. :param point: The current point. @@ -37,11 +55,34 @@ class District: :param height_map: The 2D list representing the height map. :return: True if the new point can be added, False otherwise. """ - return (0 <= point_new.x < len(map_data[0]) and - 0 <= point_new.y < len(map_data) and - map_data[point_new.y][point_new.x] == 0 and - (self.type == "Mountain" or - abs(height_map[point_new.y][point_new.x] - height_map[point.y][point.x]) < 2)) + return (0 <= point_new.x < len(map_data[0]) and + 0 <= point_new.y < len(map_data) and + map_data[point_new.y][point_new.x] == 0 and + (self.type == "Mountain" or + abs(height_map[point_new.y][point_new.x] - height_map[point.y][point.x]) < 2)) + + def is_point_inside(self, point: Position, map_data) -> bool: + """ + Check if a point is inside the district. + + :param point: The point to be checked. + :return: True if the point is inside the district, False otherwise. + """ + if not (0 <= point.x < len(map_data[0]) and 0 <= point.y < len(map_data)): + return False + return map_data[point.y][point.x] == self.tile_id + + def is_position_in_area_expend(self, position: Position) -> bool: + """ + Check if a position is inside the district. + + :param position: The position to be checked. + :return: True if the position is inside the district, False otherwise. + """ + for point in self.area_expend: + if point == position: + return True + return False def update_expend_points(self, point: Position, map_data: list[list[int]], height_map: list[list[int]]): """ @@ -53,6 +94,142 @@ class District: """ for pos in [Position(1, 0), Position(-1, 0), Position(0, 1), Position(0, -1)]: if self.verify_point(point, point + pos, map_data, height_map): - if point + pos not in self.area_expend: + if not self.is_position_in_area_expend(point + pos): self.area_expend.append(point + pos) self.area_expend_from_point.remove(point) + + def move_point_to_area(self, point: Position, vector: Position, map_data) -> Position: + while not self.is_point_inside(point + vector, map_data): + point += vector + return point + vector + + def get_road_from_point(self, point: Position) -> Union[Road, None]: + """ + Get the road that contains a specific point. + + :param point: The point to be checked. + :return: The road that contains the point. + """ + for road in self.roads: + if point == road.position: + return road + return None + + def get_road_expend_from_point(self, point: Position) -> Union[Road, None]: + """ + Get the road that contains a specific point. + + :param point: The point to be checked. + :return: The road that contains the point. + """ + for road in self.roads_expend: + if point == road.position: + return road + return None + + def generate_roads(self, map_data, random_range=(20, 40)): + width = {0: self.center_expend.x} + height = {0: self.center_expend.y} + self.roads_expend = [Road(self.center_expend, 0, 0)] + self.roads = [self.roads_expend[0]] + while len(self.roads_expend) > 0: + road = self.roads_expend.pop(0) + print(road.position) + for id_width in [-1, 1]: + if road.id_width + id_width not in width: + width[road.id_width + id_width] = width[road.id_width] + randint(random_range[0], + random_range[1]) * id_width + road_new = Road(Position(width[road.id_width + id_width], road.position.y), + road.id_height, road.id_width + id_width) + if self.is_point_inside(road_new.position, map_data): + road_search = self.get_road_from_point(road_new.position) + road_expend_search = self.get_road_expend_from_point(road_new.position) + if road_search is not None: + road_new = road_search + + if id_width == -1: + road.west = road_new + road_new.east = road + else: + road.east = road_new + road_new.west = road + + if road_search is None: + self.roads.append(road_new) + self.roads_expend.append(road_new) + else: + self.roads[self.roads.index(road_search)] = road_new + if road_expend_search is not None: + self.roads_expend[self.roads_expend.index(road_expend_search)] = road_new + else: + point_new = self.move_point_to_area(road_new.position, Position(-id_width, 0), map_data) + road_new = Road(point_new, road.id_height, road.id_width + id_width, True) + if id_width == -1: + road.west = road_new + road_new.east = road + else: + road.east = road_new + road_new.west = road + self.roads.append(road_new) + + for id_height in [-1, 1]: + if road.id_height + id_height not in height: + height[road.id_height + id_height] = height[road.id_height] + randint(random_range[0], + random_range[1]) * id_height + road_new = Road(Position(road.position.x, height[road.id_height + id_height]), + road.id_height + id_height, road.id_width) + if self.is_point_inside(road_new.position, map_data): + road_search = self.get_road_from_point(road_new.position) + road_expend_search = self.get_road_expend_from_point(road_new.position) + if road_search is not None: + road_new = road_search + + if id_height == -1: + road.north = road_new + road_new.south = road + else: + road.south = road_new + road_new.north = road + + if road_search is None: + self.roads.append(road_new) + self.roads_expend.append(road_new) + else: + self.roads[self.roads.index(road_search)] = road_new + if road_expend_search is not None: + self.roads_expend[self.roads_expend.index(road_expend_search)] = road_new + else: + pass + point_new = self.move_point_to_area(road_new.position, Position(0, -id_height), map_data) + road_new = Road(point_new, road.id_height + id_height, road.id_width, True) + if id_height == -1: + road.north = road_new + road_new.south = road + else: + road.south = road_new + road_new.north = road + self.roads.append(road_new) + + def draw_roads(self, image: Image, size: int = 1): + for road in self.roads: + image.putpixel((road.position.x, road.position.y), (255, 255, 255)) + if road.north is not None: + for y in range(road.position.y, road.north.position.y): + image = draw_square(image, Position(road.position.x, y), size) + if road.south is not None: + for y in range(road.position.y, road.south.position.y): + image = draw_square(image, Position(road.position.x, y), size) + if road.east is not None: + for x in range(road.position.x, road.east.position.x): + image = draw_square(image, Position(x, road.position.y), size) + if road.west is not None: + for x in range(road.position.x, road.west.position.x): + image = draw_square(image, Position(x, road.position.y), size) + + +def draw_square(image, center: Position, size: int) -> Image: + for x in range(center.x - size, center.x + size): + for y in range(center.y - size, center.y + size): + if 0 <= x < image.width and 0 <= y < image.height: + image.putpixel((x, y), (255, 255, 255)) + return image diff --git a/world_maker/data/district.png b/world_maker/data/district.png new file mode 100644 index 0000000..68fd1b2 Binary files /dev/null and b/world_maker/data/district.png differ diff --git a/world_maker/data/heightmap.png b/world_maker/data/heightmap.png index a743f15..4ec6160 100644 Binary files a/world_maker/data/heightmap.png and b/world_maker/data/heightmap.png differ diff --git a/world_maker/data/highwaymap.png b/world_maker/data/highwaymap.png index 887f225..0a5673e 100644 Binary files a/world_maker/data/highwaymap.png and b/world_maker/data/highwaymap.png differ diff --git a/world_maker/data/negative_sobel_water_map.png b/world_maker/data/negative_sobel_water_map.png deleted file mode 100644 index 893f381..0000000 Binary files a/world_maker/data/negative_sobel_water_map.png and /dev/null differ diff --git a/world_maker/data/roadmap.png b/world_maker/data/roadmap.png new file mode 100644 index 0000000..a80bf1c Binary files /dev/null and b/world_maker/data/roadmap.png differ diff --git a/world_maker/data/roadmap2.png b/world_maker/data/roadmap2.png new file mode 100644 index 0000000..7209b05 Binary files /dev/null and b/world_maker/data/roadmap2.png differ diff --git a/world_maker/data/skeleton_highway.png b/world_maker/data/skeleton_highway.png index e6a99cc..2ecb893 100644 Binary files a/world_maker/data/skeleton_highway.png and b/world_maker/data/skeleton_highway.png differ diff --git a/world_maker/data/smooth_sobel_watermap.png b/world_maker/data/smooth_sobel_watermap.png new file mode 100644 index 0000000..20ff75c Binary files /dev/null and b/world_maker/data/smooth_sobel_watermap.png differ diff --git a/world_maker/data/sobelmap.png b/world_maker/data/sobelmap.png index b61cb41..c608899 100644 Binary files a/world_maker/data/sobelmap.png and b/world_maker/data/sobelmap.png differ diff --git a/world_maker/data/treemap.png b/world_maker/data/treemap.png index ab753da..bf7f01f 100644 Binary files a/world_maker/data/treemap.png and b/world_maker/data/treemap.png differ diff --git a/world_maker/data/watermap.png b/world_maker/data/watermap.png index 0f1cdf9..c900dbb 100644 Binary files a/world_maker/data/watermap.png and b/world_maker/data/watermap.png differ diff --git a/world_maker/data_analysis.py b/world_maker/data_analysis.py index fa685a2..37d440b 100644 --- a/world_maker/data_analysis.py +++ b/world_maker/data_analysis.py @@ -146,8 +146,8 @@ def subtract_map(image: Union[str, Image], substractImage: Union[str, Image]) -> def group_map(image1: Union[str, Image], image2: Union[str, Image]) -> Image: - image1 = handle_import_image(image1) - image2 = handle_import_image(image2) + image1 = handle_import_image(image1).convert('L') + image2 = handle_import_image(image2).convert('L') array1 = np.array(image1) array2 = np.array(image2) @@ -221,3 +221,17 @@ def skeleton_highway_map(image: Union[str, Image] = './data/highwaymap.png'): skeleton.parse_graph(True) heightmap_skeleton = skeleton.map() heightmap_skeleton.save('./data/skeleton_highway.png') + + +def smooth_sobel_water() -> Image: + watermap = handle_import_image("./data/watermap.png") + watermap = filter_negative(filter_remove_details(filter_negative(watermap), 5)) + sobel = handle_import_image("./data/sobelmap.png") + sobel = filter_remove_details(filter_smooth(sobel, 1), 2) + group = group_map(watermap, sobel) + group = filter_negative(group) + group.save('./data/smooth_sobel_watermap.png') + return group + + + diff --git a/world_maker/world_maker.py b/world_maker/world_maker.py index c711b88..c52eb13 100644 --- a/world_maker/world_maker.py +++ b/world_maker/world_maker.py @@ -1,9 +1,22 @@ import World from PIL import Image -from data_analysis import get_data, highway_map, filter_sobel, skeleton_highway_map +from data_analysis import get_data, highway_map, filter_sobel, skeleton_highway_map, smooth_sobel_water, subtract_map +from City import City +from Position import Position +from random import randint if __name__ == '__main__': - world = World.World() - heightmap, watermap, treemap = get_data(world) - filter_sobel("./data/heightmap.png").save('./data/sobelmap.png') - skeleton_highway_map(highway_map()) + #world = World.World() + #heightmap, watermap, treemap = get_data(world) + #filter_sobel("./data/heightmap.png").save('./data/sobelmap.png') + smooth_sobel_water = smooth_sobel_water() + #skeleton_highway_map(highway_map()) + city = City() + for i in range(10): + city.add_district(Position(randint(0, 400), randint(0, 400))) + city.loop_expend_district() + city.district_draw_map() + city.district_generate_road() + road = city.draw_roads(Image.new('RGB', (401, 401)),3) + road.save('./data/roadmap.png') + subtract_map(smooth_sobel_water,road).save('./data/roadmap2.png')