diff --git a/world_maker/City.py b/world_maker/City.py index c15df54..6993016 100644 --- a/world_maker/City.py +++ b/world_maker/City.py @@ -1,8 +1,8 @@ from District import District, Road from Position import Position from PIL import Image -import random -from data_analysis import handle_import_image +from random import randint +from data_analysis import handle_import_image, detect_mountain from typing import Union import numpy as np @@ -28,8 +28,8 @@ class City: """ Initialize the maps of the city. It reads the heightmap and watermap images and converts them into 2D lists. """ - heightmap = Image.open('./data/heightmap.png').convert('L') - watermap = Image.open('./data/watermap.png').convert('L') + heightmap = Image.open('./world_maker/data/heightmap.png').convert('L') + watermap = Image.open('./world_maker/data/watermap.png').convert('L') width, height = heightmap.size self.map_data = [[-1 if watermap.getpixel((x, y)) > 0 else 0 for x in range(width)] for y in range(height)] self.height_map = [[heightmap.getpixel((x, y)) for x in range(width)] for y in range(height)] @@ -105,7 +105,7 @@ class City: """ width, height = len(self.map_data[0]), len(self.map_data) img = Image.new('RGB', (width, height)) - colors = {id_district: (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + colors = {id_district: (randint(0, 255), randint(0, 255), randint(0, 255)) for id_district in range(1, len(self.districts) + 1)} for y in range(height): @@ -115,19 +115,18 @@ class City: else: img.putpixel((x, y), colors[self.map_data[y][x]]) - img.save('./data/district.png') + img.save('./world_maker/data/district.png') print("[City] District map created.") - def draw_roads(self, image: Union[str, Image], size: int = 1) -> Image: + def draw_roads(self, size_road: 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) + image = Image.new('RGB', Image.open('./world_maker/data/heightmap.png').size) for district in self.districts: - district.draw_roads(image, size) + district.draw_roads(image, size_road) return image def district_generate_road(self) -> list[Road]: @@ -138,8 +137,9 @@ class City: """ roads = [] for district in self.districts: - district.generate_roads(self.map_data) - roads.extend(district.roads) + if district.type != "mountain": + district.generate_roads(self.map_data) + roads.extend(district.roads) return roads def point_in_which_district(self, point: Union[Position, tuple[int, int]]) -> int: @@ -164,16 +164,44 @@ class City: 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') + image.save('./world_maker/data/mountain_map.png') return image + def generate_district(self): + image = handle_import_image('./world_maker/data/smooth_sobel_watermap.png').convert('L') + array = np.array(image) + mountain_coo = detect_mountain() + self.add_district(Position(mountain_coo[0], mountain_coo[1]), "mountain") + print("[City] District added.") + remove_circle_data(array, mountain_coo) + area = get_area_array(array) + sizeX, sizeY = len(array[0]), len(array) + while area > sizeX * sizeY * 0.1: + x, y = randint(0, sizeX - 1), randint(0, sizeY - 1) + if array[y][x]: + self.add_district(Position(x, y)) + remove_circle_data(array, (x, y)) + area = get_area_array(array) + print("[City] District added.") + + +def remove_circle_data(array, center, radius=100): + y_indices, x_indices = np.indices(array.shape) + dist_sq = (y_indices - center[1]) ** 2 + (x_indices - center[0]) ** 2 + mask = dist_sq <= radius ** 2 + array[mask] = False + + +def get_area_array(array) -> int: + return np.sum(array) + if __name__ == '__main__': city = City() for i in range(10): - city.add_district(Position(random.randint(0, 400), random.randint(0, 400))) + city.add_district(Position(randint(0, 400), randint(0, 400))) city.loop_expend_district() city.district_draw_map() city.district_generate_road() - image = city.draw_roads(Image.new('RGB', (401, 401)),4) - image.save('./data/roadmap.png') + image = city.draw_roads(Image.new('RGB', (401, 401)), 4) + image.save('./world_maker/data/roadmap.png') diff --git a/world_maker/Skeleton.py b/world_maker/Skeleton.py index 069a04e..63a92f9 100644 --- a/world_maker/Skeleton.py +++ b/world_maker/Skeleton.py @@ -158,7 +158,7 @@ class Skeleton: # xzDistance = (max(buildRect.end[0], buildRect.begin[0]) - min(buildRect.end[0], buildRect.begin[0]), # max(buildRect.end[1], buildRect.begin[1]) - min(buildRect.end[1], buildRect.begin[1])) - heightmap = Image.open("data/heightmap.png").convert('RGB') + heightmap = Image.open("./world_maker/data/heightmap.png").convert('RGB') # roadsArea = Image.new("L", xzDistance, 0) # width, height = heightmap.size @@ -216,7 +216,7 @@ class Skeleton: def road_area(self, name: str, radius: int = 10) -> Image: print("[Skeleton] Start mapping the road area...") - heightmap = Image.open("data/heightmap.png") + heightmap = Image.open("./world_maker/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) @@ -236,7 +236,7 @@ class Skeleton: 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) + road_area_map.save("./world_maker/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 9b08751..dcbcdab 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 3473607..f6df0f7 100644 Binary files a/world_maker/data/district.png and b/world_maker/data/district.png differ diff --git a/world_maker/data/mountain_map.png b/world_maker/data/mountain_map.png index 4f31616..47746e8 100644 Binary files a/world_maker/data/mountain_map.png and b/world_maker/data/mountain_map.png differ diff --git a/world_maker/data/roadmap.png b/world_maker/data/roadmap.png index fb9e0be..4375229 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 deleted file mode 100644 index be1121c..0000000 Binary files a/world_maker/data/roadmap2.png and /dev/null differ diff --git a/world_maker/data/skeleton_highway.png b/world_maker/data/skeleton_highway.png index d1d6e29..cee9fe4 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_mountain.png b/world_maker/data/skeleton_mountain.png index a00e99c..8c170dd 100644 Binary files a/world_maker/data/skeleton_mountain.png 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 index 02b5c47..3f54167 100644 Binary files a/world_maker/data/skeleton_mountain_area.png and b/world_maker/data/skeleton_mountain_area.png differ diff --git a/world_maker/data_analysis.py b/world_maker/data_analysis.py index 3be010b..717b335 100644 --- a/world_maker/data_analysis.py +++ b/world_maker/data_analysis.py @@ -4,15 +4,16 @@ import numpy as np from scipy import ndimage from Skeleton import Skeleton from typing import Union +from random import randint import cv2 def get_data(world: World): print("[Data Analysis] Generating data...") heightmap, watermap, treemap = world.getData() - heightmap.save('./data/heightmap.png') - watermap.save('./data/watermap.png') - treemap.save('./data/treemap.png') + heightmap.save('./world_maker/data/heightmap.png') + watermap.save('./world_maker/data/watermap.png') + treemap.save('./world_maker/data/treemap.png') print("[Data Analysis] Data generated.") return heightmap, watermap, treemap @@ -181,9 +182,9 @@ def filter_remove_details(image: Union[str, Image], n: int = 20) -> Image: def highway_map() -> Image: print("[Data Analysis] Generating highway map...") - smooth_sobel = filter_smooth("./data/sobelmap.png", 1) + smooth_sobel = filter_smooth("./world_maker/data/sobelmap.png", 1) negative_smooth_sobel = filter_negative(smooth_sobel) - negative_smooth_sobel_water = subtract_map(negative_smooth_sobel, './data/watermap.png') + negative_smooth_sobel_water = subtract_map(negative_smooth_sobel, './world_maker/data/watermap.png') array_sobel_water = np.array(negative_smooth_sobel_water) array_sobel_water = ndimage.binary_erosion(array_sobel_water, iterations=12) array_sobel_water = ndimage.binary_dilation(array_sobel_water, iterations=5) @@ -192,7 +193,7 @@ def highway_map() -> Image: array_sobel_water = filter_smooth_array(array_sobel_water, 6) image = Image.fromarray(array_sobel_water) image_no_details = filter_remove_details(image, 15) - image_no_details.save('./data/highwaymap.png') + image_no_details.save('./world_maker/data/highwaymap.png') print("[Data Analysis] Highway map generated.") return image_no_details @@ -210,45 +211,45 @@ def create_volume(surface: np.ndarray, heightmap: np.ndarray, make_it_flat: bool def convert_2D_to_3D(image: Union[str, Image], make_it_flat: bool = False) -> np.ndarray: image = handle_import_image(image) - heightmap = Image.open('./data/heightmap.png').convert('L') + heightmap = Image.open('./world_maker/data/heightmap.png').convert('L') heightmap = np.array(heightmap) surface = np.array(image) volume = create_volume(surface, heightmap, make_it_flat) return volume -def skeleton_highway_map(image: Union[str, Image] = './data/highwaymap.png') -> Skeleton: +def skeleton_highway_map(image: Union[str, Image] = './world_maker/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') + heightmap_skeleton.save('./world_maker/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: +def skeleton_mountain_map(image: Union[str, Image] = './world_maker/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') + heightmap_skeleton.save('./world_maker/data/skeleton_mountain.png') skeleton.road_area('skeleton_mountain_area.png',3) return skeleton def smooth_sobel_water() -> Image: - watermap = handle_import_image("./data/watermap.png") + watermap = handle_import_image("./world_maker/data/watermap.png") watermap = filter_negative(filter_remove_details(filter_negative(watermap), 5)) - sobel = handle_import_image("./data/sobelmap.png") + sobel = handle_import_image("./world_maker/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') + group.save('./world_maker/data/smooth_sobel_watermap.png') return group -def detect_mountain(image: Union[str, Image] = './data/sobelmap.png') -> Image: +def detect_mountain(image: Union[str, Image] = './world_maker/data/sobelmap.png') -> Image: image = handle_import_image(image) sobel = np.array(image) pixels = sobel.reshape((-1, 1)) @@ -272,3 +273,20 @@ def detect_mountain(image: Union[str, Image] = './data/sobelmap.png') -> Image: print(f"[Data Analysis] The center of the mountain is at ({cX}, {cY})") return (cX, cY) + + +def rectangle_2D_to_3D(rectangle: list[tuple[tuple[int, int],tuple[int, int]]], + height_min:int = 6, height_max:int = 10) \ + -> list[tuple[tuple[int, int, int], tuple[int, int, int]]]: + image = handle_import_image('./world_maker/data/heightmap.png') + new_rectangle = [] + for rect in rectangle: + start, end = rect + avg_height = 0 + for x in range(start[0], end[0]): + for y in range(start[1], end[1]): + avg_height += np.array(image.getpixel((x, y))) + avg_height = int(avg_height / ((end[0] - start[0]) * (end[1] - start[1]))) + new_rectangle.append(((start[0], avg_height, start[1]), (end[0], avg_height + randint(height_min, height_max), end[1]))) + return new_rectangle + diff --git a/world_maker/pack_rectangle.py b/world_maker/pack_rectangle.py index 76bbf0a..3611fc1 100644 --- a/world_maker/pack_rectangle.py +++ b/world_maker/pack_rectangle.py @@ -2,6 +2,8 @@ from PIL import Image import numpy as np from typing import Union from data_analysis import handle_import_image +from random import randint + class Rectangle: def __init__(self, width, height): @@ -27,7 +29,7 @@ class Bin: best_spot_empty_area = empty_area if best_spot is not None: - self.rectangles.append((best_spot, (best_spot[0]+rectangle.width, best_spot[1]+rectangle.height))) + self.rectangles.append((best_spot, (best_spot[0] + rectangle.width, best_spot[1] + rectangle.height))) self.update_grid(rectangle, *best_spot) return True @@ -72,19 +74,16 @@ def pack_rectangles(rectangles, grid): return True -import random - - -def generate_rectangle(max_width:int = 25): - width = random.randint(10, max_width) - height = random.randint(10, max_width) +def generate_rectangle(min_width: int = 10, max_width: int = 25): + width = randint(min_width, max_width) + height = randint(min_width, max_width) return Rectangle(width, height) -def pack_rectangles(grid): +def pack_rectangles(grid, min_width: int = 10, max_width: int = 25): bin = Bin(grid) while True: - rectangle = generate_rectangle() + rectangle = generate_rectangle(min_width, max_width) if not bin.place_rectangle(rectangle): break print(len(bin.rectangles)) @@ -101,12 +100,13 @@ def draw_rectangles(rectangles, grid): return image -def generate_building(image: Union[str, Image] = './data/roadmap2.png'): +def generate_building(image: Union[str, Image], min_width: int = 10, max_width: int = 25): image = handle_import_image(image).convert('L') grid = np.array(image) - rectangles = pack_rectangles(grid) - draw_rectangles(rectangles, grid).save('./data/building.png') + rectangles = pack_rectangles(grid, min_width, max_width) + draw_rectangles(rectangles, grid).save('./world_maker/data/building.png') return rectangles + if __name__ == '__main__': generate_building() diff --git a/world_maker/world_maker.py b/world_maker/world_maker.py index 462a89b..8ea3510 100644 --- a/world_maker/world_maker.py +++ b/world_maker/world_maker.py @@ -1,6 +1,6 @@ import World from PIL import Image -from data_analysis import get_data,filter_negative, skeleton_mountain_map, highway_map, filter_sobel, skeleton_highway_map, \ +from data_analysis import get_data,filter_negative, rectangle_2D_to_3D, 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 @@ -10,26 +10,24 @@ 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') + #filter_sobel("./world_maker/data/heightmap.png").save('./world_maker/data/sobelmap.png') smooth_sobel_water = smooth_sobel_water() skeleton_highway_map(highway_map()) city = City() - 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.generate_district() 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') + road = city.draw_roads(4) + road.save('./world_maker/data/roadmap.png') + subtract_map(smooth_sobel_water, road).save('./world_maker/data/city_map.png') + subtract_map('./world_maker/data/city_map.png', './world_maker/data/skeleton_highway_area.png').save('./world_maker/data/city_map.png') + subtract_map('./world_maker/data/city_map.png', './world_maker/data/mountain_map.png').save('./world_maker/data/city_map.png') + rectangle_building = generate_building('./world_maker/data/city_map.png') + rectangle_building = rectangle_2D_to_3D(rectangle_building) 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') + subtract_map('./world_maker/data/mountain_map.png', './world_maker/data/skeleton_mountain_area.png').save('./world_maker/data/mountain_map.png') + subtract_map(smooth_sobel_water, filter_negative('./world_maker/data/mountain_map.png')).save('./world_maker/data/mountain_map.png') + rectangle_mountain = generate_building('./world_maker/data/mountain_map.png')