diff --git a/world_maker/City.py b/world_maker/City.py index 8611be7..c15df54 100644 --- a/world_maker/City.py +++ b/world_maker/City.py @@ -4,6 +4,7 @@ from PIL import Image import random from data_analysis import handle_import_image from typing import Union +import numpy as np class City: @@ -141,6 +142,31 @@ class City: roads.extend(district.roads) return roads + def point_in_which_district(self, point: Union[Position, tuple[int, int]]) -> int: + """ + Get the index of the district in which the point is located. + + :param point: The point to check. + :return: The index of the district in which the point is located. + """ + if isinstance(point, Position): + point = (point.x, point.y) + return self.map_data[point[1]][point[0]] + + def get_district_mountain_map(self) -> Image: + """ + Get the map of a district. + + :param district_id: The id of the district. + :return: The map of the district. + """ + district_id = [district.tile_id for district in self.districts if district.type == "mountain"] + array = np.array([[True if self.map_data[y][x] in district_id else False for x in range(len(self.map_data[0]))] + for y in range(len(self.map_data))]) + image = Image.fromarray(array) + image.save('./data/mountain_map.png') + return image + if __name__ == '__main__': city = City() diff --git a/world_maker/District.py b/world_maker/District.py index 4a2f887..e0cd15a 100644 --- a/world_maker/District.py +++ b/world_maker/District.py @@ -3,6 +3,7 @@ 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 @@ -58,7 +59,7 @@ class District: 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 + (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: diff --git a/world_maker/Skeleton.py b/world_maker/Skeleton.py index 6b4d1ce..069a04e 100644 --- a/world_maker/Skeleton.py +++ b/world_maker/Skeleton.py @@ -3,7 +3,7 @@ import numpy as np from skimage.morphology import skeletonize from skan.csr import skeleton_to_csgraph from collections import Counter -from PIL import Image +from PIL import Image, ImageDraw import random @@ -213,3 +213,30 @@ class Skeleton: # ) print("[Skeleton] Mapping completed.") return heightmap # , roadsArea + + def road_area(self, name: str, radius: int = 10) -> Image: + print("[Skeleton] Start mapping the road area...") + heightmap = Image.open("data/heightmap.png") + width, height = heightmap.size + road_area_map = Image.new("L", (width, height), 0) + road_area_map_draw = ImageDraw.Draw(road_area_map) + + # Lines + for i in range(len(self.lines)): + for j in range(len(self.lines[i])): + z = self.coordinates[self.lines[i][j]][0] + x = self.coordinates[self.lines[i][j]][2] + circle_coords = (z - radius, x - radius, z + radius, x + radius) + road_area_map_draw.ellipse(circle_coords, fill=255) + + # Centers + for i in range(len(self.centers)): + z = self.coordinates[self.centers[i]][0] + x = self.coordinates[self.centers[i]][2] + circle_coords = (z - radius, x - radius, z + radius, x + radius) + road_area_map_draw.ellipse(circle_coords, fill=255) + + road_area_map.save("data/"+name) + + print("[Skeleton] Road area mapping completed.") + return road_area_map diff --git a/world_maker/data/building.png b/world_maker/data/building.png index 48e51dc..9b08751 100644 Binary files a/world_maker/data/building.png and b/world_maker/data/building.png differ diff --git a/world_maker/data/district.png b/world_maker/data/district.png index fc00abf..3473607 100644 Binary files a/world_maker/data/district.png and b/world_maker/data/district.png differ diff --git a/world_maker/data/heightmap.png b/world_maker/data/heightmap.png index 4ec6160..fc75ff3 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 0a5673e..7c4d178 100644 Binary files a/world_maker/data/highwaymap.png and b/world_maker/data/highwaymap.png differ diff --git a/world_maker/data/mountain_map.png b/world_maker/data/mountain_map.png new file mode 100644 index 0000000..4f31616 Binary files /dev/null and b/world_maker/data/mountain_map.png differ diff --git a/world_maker/data/roadmap.png b/world_maker/data/roadmap.png index 841ca64..fb9e0be 100644 Binary files a/world_maker/data/roadmap.png and b/world_maker/data/roadmap.png differ diff --git a/world_maker/data/roadmap2.png b/world_maker/data/roadmap2.png index c084d6e..be1121c 100644 Binary files a/world_maker/data/roadmap2.png 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 2ecb893..d1d6e29 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/skeleton_highway_area.png b/world_maker/data/skeleton_highway_area.png new file mode 100644 index 0000000..4bffcc6 Binary files /dev/null and b/world_maker/data/skeleton_highway_area.png differ diff --git a/world_maker/data/skeleton_mountain.png b/world_maker/data/skeleton_mountain.png new file mode 100644 index 0000000..a00e99c Binary files /dev/null and b/world_maker/data/skeleton_mountain.png differ diff --git a/world_maker/data/skeleton_mountain_area.png b/world_maker/data/skeleton_mountain_area.png new file mode 100644 index 0000000..02b5c47 Binary files /dev/null and b/world_maker/data/skeleton_mountain_area.png differ diff --git a/world_maker/data/smooth_sobel_watermap.png b/world_maker/data/smooth_sobel_watermap.png index 20ff75c..a2c350d 100644 Binary files a/world_maker/data/smooth_sobel_watermap.png 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 c608899..e587909 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 bf7f01f..83a187a 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 c900dbb..36cfd74 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 56e061f..3be010b 100644 --- a/world_maker/data_analysis.py +++ b/world_maker/data_analysis.py @@ -4,6 +4,7 @@ import numpy as np from scipy import ndimage from Skeleton import Skeleton from typing import Union +import cv2 def get_data(world: World): @@ -22,13 +23,14 @@ def handle_import_image(image: Union[str, Image]) -> Image: return image -def filter_negative(image: Image) -> Image: +def filter_negative(image: Union[str, Image]) -> Image: """ Invert the colors of an image. Args: image (image): image to filter """ + image = handle_import_image(image) return Image.fromarray(np.invert(np.array(image))) @@ -215,12 +217,24 @@ def convert_2D_to_3D(image: Union[str, Image], make_it_flat: bool = False) -> np return volume -def skeleton_highway_map(image: Union[str, Image] = './data/highwaymap.png'): +def skeleton_highway_map(image: Union[str, Image] = './data/highwaymap.png') -> Skeleton: image_array = convert_2D_to_3D(image, True) skeleton = Skeleton(image_array) skeleton.parse_graph(True) heightmap_skeleton = skeleton.map() heightmap_skeleton.save('./data/skeleton_highway.png') + skeleton.road_area('skeleton_highway_area.png', 10) + return skeleton + + +def skeleton_mountain_map(image: Union[str, Image] = './data/mountain_map.png') -> Skeleton: + image_array = convert_2D_to_3D(image, True) + skeleton = Skeleton(image_array) + skeleton.parse_graph() + heightmap_skeleton = skeleton.map() + heightmap_skeleton.save('./data/skeleton_mountain.png') + skeleton.road_area('skeleton_mountain_area.png',3) + return skeleton def smooth_sobel_water() -> Image: @@ -232,3 +246,29 @@ def smooth_sobel_water() -> Image: group = filter_negative(group) group.save('./data/smooth_sobel_watermap.png') return group + + +def detect_mountain(image: Union[str, Image] = './data/sobelmap.png') -> Image: + image = handle_import_image(image) + sobel = np.array(image) + pixels = sobel.reshape((-1, 1)) + pixels = np.float32(pixels) + + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2) + k = 3 + _, labels, centers = cv2.kmeans(pixels, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) + + centers = np.uint8(centers) + segmented_image = centers[labels.flatten()] + segmented_image = segmented_image.reshape(sobel.shape) + mountain = segmented_image == segmented_image.max() + + contours, _ = cv2.findContours(mountain.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + max_contour = max(contours, key=cv2.contourArea) + M = cv2.moments(max_contour) + cX = int(M["m10"] / M["m00"]) + cY = int(M["m01"] / M["m00"]) + + print(f"[Data Analysis] The center of the mountain is at ({cX}, {cY})") + return (cX, cY) diff --git a/world_maker/pack_rectangle.py b/world_maker/pack_rectangle.py index 879ae8c..76bbf0a 100644 --- a/world_maker/pack_rectangle.py +++ b/world_maker/pack_rectangle.py @@ -18,7 +18,7 @@ class Bin: best_spot = None best_spot_empty_area = float('inf') - for i in range(len(self.grid[0]) - rectangle.width + 1): # Swap usage of x and y + for i in range(len(self.grid[0]) - rectangle.width + 1): for j in range(len(self.grid) - rectangle.height + 1): if self.can_place(rectangle, i, j): empty_area = self.calculate_empty_area(rectangle, i, j) @@ -37,21 +37,21 @@ class Bin: empty_area = 0 for rect_x in range(x, x + rectangle.width): for rect_y in range(y, y + rectangle.height): - if self.grid[rect_y][rect_x]: # Swap usage of x and y + if self.grid[rect_y][rect_x]: empty_area += 1 return empty_area def can_place(self, rectangle, x, y): for rect_x in range(x, x + rectangle.width): for rect_y in range(y, y + rectangle.height): - if not self.grid[rect_y][rect_x]: # Swap usage of x and y + if not self.grid[rect_y][rect_x]: return False return True def update_grid(self, rectangle, x, y): for rect_x in range(x, x + rectangle.width): for rect_y in range(y, y + rectangle.height): - self.grid[rect_y][rect_x] = False # Swap usage of x and y + self.grid[rect_y][rect_x] = False def pack_rectangles(rectangles, grid): @@ -62,36 +62,33 @@ def pack_rectangles(rectangles, grid): for bin in bins: if bin.place_rectangle(rectangle): break - else: # No break, meaning rectangle couldn't be placed in any bin + else: new_bin = Bin(grid) if new_bin.place_rectangle(rectangle): bins.append(new_bin) else: - return False # If a rectangle can't be placed even in a new bin, return False + return False - return True # If all rectangles can be placed, return True + return True import random -def generate_rectangle(max_width, max_height): - width = random.randint(6, 20) - height = random.randint(6, 20) +def generate_rectangle(max_width:int = 25): + width = random.randint(10, max_width) + height = random.randint(10, max_width) return Rectangle(width, height) def pack_rectangles(grid): - max_width = len(grid[0]) - max_height = len(grid) bin = Bin(grid) - while True: - rectangle = generate_rectangle(max_width // 2, max_height // 2) + rectangle = generate_rectangle() if not bin.place_rectangle(rectangle): - break # Stop when a rectangle can't be placed + break print(len(bin.rectangles)) - return bin.rectangles # Return the list of rectangles that were placed + return bin.rectangles def draw_rectangles(rectangles, grid): diff --git a/world_maker/world_maker.py b/world_maker/world_maker.py index c86e021..462a89b 100644 --- a/world_maker/world_maker.py +++ b/world_maker/world_maker.py @@ -1,22 +1,35 @@ import World from PIL import Image -from data_analysis import get_data, highway_map, filter_sobel, skeleton_highway_map, smooth_sobel_water, subtract_map +from data_analysis import get_data,filter_negative, skeleton_mountain_map, highway_map, filter_sobel, skeleton_highway_map, \ + smooth_sobel_water, subtract_map, detect_mountain from City import City from Position import Position from random import randint +from pack_rectangle import generate_building if __name__ == '__main__': #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()) + skeleton_highway_map(highway_map()) city = City() - for i in range(10): - city.add_district(Position(randint(0, 400), randint(0, 400))) + mountain_coo = detect_mountain() + city.add_district(Position(mountain_coo[0], mountain_coo[1]), "mountain") + city.add_district(Position(200, 200), "zdz") + city.add_district(Position(300, 300), "cool") city.loop_expend_district() city.district_draw_map() city.district_generate_road() + image_mountain_map = city.get_district_mountain_map() road = city.draw_roads(Image.new('RGB', (401, 401)), 4) road.save('./data/roadmap.png') subtract_map(smooth_sobel_water, road).save('./data/roadmap2.png') + subtract_map('./data/roadmap2.png', './data/skeleton_highway_area.png').save('./data/roadmap2.png') + subtract_map('./data/roadmap2.png', './data/mountain_map.png').save('./data/roadmap2.png') + generate_building('./data/roadmap2.png') + + skeleton_mountain_map(image_mountain_map) + subtract_map('./data/mountain_map.png','./data/skeleton_mountain_area.png').save('./data/mountain_map.png') + subtract_map(smooth_sobel_water, filter_negative('./data/mountain_map.png')).save('./data/mountain_map.png') + generate_building('./data/mountain_map.png')