diff --git a/requirements.txt b/requirements.txt index 68aebf6..9942a85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ gdpc==7.1.0 numpy==1.26.4 +Pillow==10.3.0 pygame==2.5.2 -scipy==1.13.0 +scipy==1.13.1 +skan==0.11.1 +skimage==0.0 diff --git a/world_maker/City.py b/world_maker/City.py index 1e8574f..113e214 100644 --- a/world_maker/City.py +++ b/world_maker/City.py @@ -1,4 +1,4 @@ -from District import District, CustomDistrict, VoronoiDistrict +from District import District from Position import Position from PIL import Image import random @@ -33,13 +33,14 @@ class City: watermap.close() heightmap.close() - def add_district(self, center: Position): + def add_district(self, center: Position, district_type: str = ""): """ Add a new district to the city. + :param district_type: :param center: The center position of the new district. """ - self.districts.append(CustomDistrict(len(self.districts) + 1, center)) + self.districts.append(District(len(self.districts) + 1, center, district_type)) self.map_data[center.y][center.x] = len(self.districts) def is_expend_finished(self): @@ -62,15 +63,15 @@ class City: """ min_distance = point.distance_to(self.districts[index_district].center_expend) index_district_chosen = index_district - for i in range(index_district + 1, len(self.districts)): - if point in self.districts[i].area_expend: - distance = point.distance_to(self.districts[i].center_expend) + for index in range(index_district + 1, len(self.districts)): + if point in self.districts[index].area_expend: + distance = point.distance_to(self.districts[index].center_expend) if distance < min_distance: min_distance = distance self.districts[index_district_chosen].area_expend.remove(point) - index_district_chosen = i + index_district_chosen = index else: - self.districts[i].area_expend.remove(point) + 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) @@ -92,21 +93,19 @@ class City: """ Loop the expansion of all districts in the city until all districts are fully expanded. """ - loop_count = 0 + print("[City] Start expanding districts...") while not self.is_expend_finished(): self.update_expend_district() - loop_count += 1 - if loop_count % 100 == 0: - print("[City] Loop count: ", loop_count) + print("[City] Finished expanding districts.") - def custom_district_draw_map(self): + def district_draw_map(self): """ Draw the map of the city with different colors for each district. """ width, height = len(self.map_data[0]), len(self.map_data) img = Image.new('RGB', (width, height)) - colors = {i: (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) - for i in range(1, len(self.districts) + 1)} + colors = {id_district: (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + for id_district in range(1, len(self.districts) + 1)} for y in range(height): for x in range(width): @@ -115,12 +114,13 @@ class City: else: img.putpixel((x, y), colors[self.map_data[y][x]]) - img.save('./data/custom_district.png') + img.save('./data/district.png') + print("[City] District map created.") if __name__ == '__main__': city = City() for i in range(10): - city.add_district(Position(random.randint(0, 600), random.randint(0, 600))) + city.add_district(Position(random.randint(0, 800), random.randint(0, 800))) city.loop_expend_district() - city.custom_district_draw_map() + city.district_draw_map() diff --git a/world_maker/District.py b/world_maker/District.py index da218d1..e0e3f9a 100644 --- a/world_maker/District.py +++ b/world_maker/District.py @@ -1,45 +1,6 @@ from Position import Position - class District: - """ - The District class represents a district in the world. - A district can be characterized by its type and its unique id. - - Attributes: - tile_id (int): The unique id of the district. - type (str): The type of the district. Can be "Forest", "City", "Mountain" or "Villa". - """ - - def __init__(self, tile_id: int): - """ - The constructor for the District class. - - :param tile_id: Unique id of the district (Must be greater than 0) - """ - if tile_id <= 0: - raise ValueError("Tile id must be greater than 0") - self.tile_id = tile_id - self.type = "" #Forest, City, Montain, Villa - - -def verify_point(point: Position, point_new: Position, map_data: list[list[int]], height_map: list[list[int]]): - """ - Function to verify if a new point can be added to a district extend area list. - - :param point: The current point. - :param point_new: The new point to be verified. - :param map_data: The 2D list representing the map. - :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 - abs(height_map[point_new.y][point_new.x] - height_map[point.y][point.x]) < 2) - - -class CustomDistrict(District): """ The CustomDistrict class represents a district that can be expanded. @@ -49,19 +10,39 @@ class CustomDistrict(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): + def __init__(self, tile_id: int, center: Position, district_type: str = ""): """ - The constructor for the CustomDistrict class. + The constructor for the District class. :param tile_id: Unique id of the district (Must be greater than 0) :param center: The center position from which the district expands. + :param district_type: The type of the district (Forest, City, Mountain, Villa) """ - super().__init__(tile_id) + if tile_id <= 0: + raise ValueError("Tile id must be greater than 0") + 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 = [] + 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. + :param point_new: The new point to be verified. + :param map_data: The 2D list representing the map. + :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)) + def update_expend_points(self, point: Position, map_data: list[list[int]], height_map: list[list[int]]): """ Update the points to which the district can expand. @@ -71,20 +52,7 @@ class CustomDistrict(District): :param height_map: The 2D list representing the height map. """ for pos in [Position(1, 0), Position(-1, 0), Position(0, 1), Position(0, -1)]: - if verify_point(point, point + pos, map_data, height_map): + if self.verify_point(point, point + pos, map_data, height_map): if point + pos not in self.area_expend: self.area_expend.append(point + pos) self.area_expend_from_point.remove(point) - - -class Edge: #I'm Edging rn - def __init__(self, point1, point2): - self.point1 = point1 - self.point2 = point2 - - -class VoronoiDistrict(District): - def __init__(self, tile_id: int, center: Position): - super().__init__(tile_id) - self.center = center - self.edges = [] diff --git a/world_maker/Skeleton.py b/world_maker/Skeleton.py new file mode 100644 index 0000000..6b4d1ce --- /dev/null +++ b/world_maker/Skeleton.py @@ -0,0 +1,215 @@ +import numpy as np +#import skan +from skimage.morphology import skeletonize +from skan.csr import skeleton_to_csgraph +from collections import Counter +from PIL import Image +import random + + +class Skeleton: + def __init__(self, data: np.ndarray = None): + self.lines = [] + self.intersections = [] + self.centers = [] + self.coordinates = [] + self.graph = None + if data is not None: + self.set_skeleton(data) + + def set_skeleton(self, data: np.ndarray): + print("[Skeleton] Start skeletonization...") + binary_skeleton = skeletonize(data, method="lee") + + graph, coordinates = skeleton_to_csgraph(binary_skeleton) + self.graph = graph.tocoo() + + # List of lists. Inverted coordinates. + coordinates = list(coordinates) + # print(coordinates) + for i in range(len(coordinates)): + coordinates[i] = list(coordinates[i]) + # print(coordinates) + + for i in range(len(coordinates[0])): + # print((coordinates[0][i], coordinates[1][i], coordinates[2][i])) + self.coordinates.append((coordinates[0][i], coordinates[1][i], coordinates[2][i])) + print("[Skeleton] Skeletonization completed.") + + def find_next_elements(self, key: str) -> list: + """Find the very nearest elements""" + + line = [] + + values = np.array(self.graph.row) + indices = np.where(values == key)[0] + + for i in range(len(indices)): + if self.graph.row[indices[i]] == key: + line.append(self.graph.col[indices[i]]) + return line + + def find_line(self, key: str): + next_keys = self.find_next_elements(key) + + if len(next_keys) >= 3: # Intersections. + return next_keys + + if len(next_keys) == 2 or len(next_keys) == 1: # In line or endpoints. + line = [key] + line.insert(0, next_keys[0]) + if len(next_keys) == 2: + line.insert(len(line), next_keys[1]) + + next_keys = line[0], line[-1] + + while len(next_keys) == 2 or len(next_keys) == 1: + extremity = [] + for key in next_keys: + next_keys = self.find_next_elements(key) + + if len(next_keys) <= 2: + # Add the neighbors that is not already in the line. + for element in next_keys: + if element not in line: + extremity.append(element) + line.append(element) + + if len(next_keys) >= 3: + # Add the intersection only. + extremity.append(key) + + next_keys = [] + for key in extremity: + ends = self.find_next_elements(key) + if len(ends) == 2: + next_keys.append(key) + return line + + def parse_graph(self, parse_orphan: bool = False): + print("[Skeleton] Start parsing the graph", ("with orphans" if parse_orphan else "") + "...") + for key, value in sorted( + Counter(self.graph.row).items(), key=lambda kv: kv[1], reverse=True + ): + # Start from the biggest intersections. + if value != 2: # We don't want to be in the middle of a line. + line = self.find_line(key) + + # We have now all the connected points if it's an + # intersection. We need to find the line. + + if value != 1: + # It's not an endpoint. + self.centers.append(key) + self.intersections.append(line) + for i in line: + line = self.find_line(i) + + if i in line: + # The key is inside the result : it's a line. + already_inside = False + for l in self.lines: + # Verification if not already inside. + if Counter(l) == Counter(line): + already_inside = True + # print(line, "inside", lines) + + if not already_inside: + self.lines.append(line) + else: + # The key is not inside the result, it's an + # intersection directly connected to the key. + line = [key, i] + already_inside = False + for l in self.lines: + # Verification if not already inside. + if Counter(l) == Counter(line): + already_inside = True + # print(line, "inside", lines) + + if not already_inside: + self.lines.append(line) + elif value == 2 and parse_orphan: + line = self.find_line(key) + already_inside = False + for l in self.lines: + # Verification if not already inside. + if Counter(l) == Counter(line): + already_inside = True + + if not already_inside: + self.lines.append(line) + print("[Skeleton] Graph parsing completed.") + + def map(self) -> Image: + """ + + Generate an image to visualize 2D path of the skeleton. + + Returns: + image: 2D path of the skeleton on top of the heightmap. + """ + print("[Skeleton] Start mapping the skeleton...") + # editor = Editor() + + # buildArea = editor.getBuildArea() + # buildRect = buildArea.toRect() + # xzStart = buildRect.begin + # 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') + # roadsArea = Image.new("L", xzDistance, 0) + # width, height = heightmap.size + + # Lines + for i in range(len(self.lines)): + r, g, b = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + + for j in range(len(self.lines[i])): + z = self.coordinates[self.lines[i][j]][0] + # y = self.coordinates[self.lines[i][j]][1] + x = self.coordinates[self.lines[i][j]][2] + + heightmap.putpixel( + ( + int(z), + int(x), + ), + (r + j, g + j, b + j), + ) + + # roadsArea.putpixel( + # ( + # int(z), + # int(x), + # ), + # (255), + # ) + + # Centers + for i in range(len(self.centers)): + # print(self.coordinates[self.centers[i]]) + heightmap.putpixel( + (int(self.coordinates[self.centers[i]][0]), int(self.coordinates[self.centers[i]][2])), + (255, 255, 0), + ) + + # roadsArea.putpixel( + # (int(self.coordinates[self.centers[i]][0]), int(self.coordinates[self.centers[i]][2])), + # (255), + # ) + + # # Intersections + # for i in range(len(self.intersections)): + # intersection = [] + # for j in range(len(self.intersections[i])): + # intersection.append(self.coordinates[self.intersections[i][j]]) + + # for i in range(len(intersection)): + # heightmap.putpixel( + # (int(self.intersections[i][2]), int(self.intersections[i][0])), + # (255, 0, 255), + # ) + print("[Skeleton] Mapping completed.") + return heightmap # , roadsArea diff --git a/world_maker/World.py b/world_maker/World.py index f914df4..c771332 100644 --- a/world_maker/World.py +++ b/world_maker/World.py @@ -111,7 +111,7 @@ class World: buildRect = buildArea.toRect() xzStart = buildRect.begin - print(xzStart, "xzStart") + print("[World]", '('+str(xzStart[0])+', '+str(xzStart[1])+')', "xzStart") 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])) watermap = Image.new("L", xzDistance, 0) @@ -125,12 +125,9 @@ class World: for x in range(0, xzDistance[0]): for z in range(0, xzDistance[1]): - y = heightmapData[x][z] - 1 yTree = treesmapData[x][z] - 1 - print('getData', xzStart[0] + x, y, xzStart[1] + z) - biome = slice.getBiome((x, y, z)) block = slice.getBlock((x, y, z)) maybeATree = slice.getBlock((x, yTree, z)) @@ -145,11 +142,11 @@ class World: number = 0 for i in range(-1, 2): for j in range(-1, 2): - if (i != 0 or j != 0): + if i != 0 or j != 0: if (0 <= x + i < xzDistance[0]) and (0 <= z + j < xzDistance[1]): k = heightmapData[x + i][z + j] - 1 - print('getData for tree', xzStart[0] + x + i, k, xzStart[1] + z + j) + # print('getData for tree', xzStart[0] + x + i, k, xzStart[1] + z + j) blockNeighbor = slice.getBlock((x + i, k, z + j)) if blockNeighbor.id not in lookup.TREES: @@ -157,20 +154,19 @@ class World: number += 1 if number != 0: average = round(height / number) - print(average, "average") + # print(average, "average") heightmap.putpixel((x, z), (average, average, average)) - if ((biome in waterBiomes) or (block.id in waterBlocks)): - watermap.putpixel((x, z), (255)) + if (biome in waterBiomes) or (block.id in waterBlocks): + watermap.putpixel((x, z), 255) else: - watermap.putpixel((x, z), (0)) + watermap.putpixel((x, z), 0) self.addBlocks([Block((xzStart[0] + x, 100, xzStart[1] + z), block)]) # y set to 100 for 2D return heightmap, watermap, treesmap def propagate(self, coordinates, scanned=[]): - print('propagate', coordinates) i = 0 editor = Editor(buffering=True) if self.isInVolume(coordinates): @@ -190,7 +186,7 @@ class World: for y in range(self.length_y): binaryImage[x].append([]) for z in range(self.length_z): - if (self.volume[x][y][z] != None): + if self.volume[x][y][z] != None: binaryImage[x][y].append(True) else: binaryImage[x][y].append(False) @@ -222,7 +218,7 @@ class World: for x in range(0, xzDistance[0]): for z in range(0, xzDistance[1]): y = heightmapData[x][z] - 1 - if mask.getpixel((x, z)) == (255): + if mask.getpixel((x, z)) == 255: self.removeBlock((x, 100, z)) # y set to 100 for 2D def simplifyVolume(self): diff --git a/world_maker/data/.gitkeep b/world_maker/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/world_maker/data/custom_district.png b/world_maker/data/custom_district.png deleted file mode 100644 index b9b8b3c..0000000 Binary files a/world_maker/data/custom_district.png and /dev/null differ diff --git a/world_maker/data/heightmap.png b/world_maker/data/heightmap.png index e408aea..a743f15 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 new file mode 100644 index 0000000..887f225 Binary files /dev/null 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 new file mode 100644 index 0000000..893f381 Binary files /dev/null and b/world_maker/data/negative_sobel_water_map.png differ diff --git a/world_maker/data/skeleton_highway.png b/world_maker/data/skeleton_highway.png new file mode 100644 index 0000000..e6a99cc Binary files /dev/null and b/world_maker/data/skeleton_highway.png differ diff --git a/world_maker/data/sobelmap.png b/world_maker/data/sobelmap.png new file mode 100644 index 0000000..b61cb41 Binary files /dev/null and b/world_maker/data/sobelmap.png differ diff --git a/world_maker/data/treemap.png b/world_maker/data/treemap.png index a8afb9f..ab753da 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 05c37e8..0f1cdf9 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 new file mode 100644 index 0000000..fa685a2 --- /dev/null +++ b/world_maker/data_analysis.py @@ -0,0 +1,223 @@ +import World +from PIL import Image, ImageFilter +import numpy as np +from scipy import ndimage +from Skeleton import Skeleton +from typing import Union + + +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') + print("[Data Analysis] Data generated.") + return heightmap, watermap, treemap + + +def handle_import_image(image: Union[str, Image]) -> Image: + if isinstance(image, str): + return Image.open(image) + return image + + +def filter_negative(image: Image) -> Image: + """ + Invert the colors of an image. + + Args: + image (image): image to filter + """ + return Image.fromarray(np.invert(np.array(image))) + + +def filter_sobel(image: Union[str, Image]) -> Image: + """ + Edge detection algorithms from an image. + + Args: + image (image): image to filter + """ + + # Open the image + image = handle_import_image(image).convert('RGB') + + img = np.array(image).astype(np.uint8) + + # Apply gray scale + gray_img = np.round( + 0.299 * img[:, :, 0] + 0.587 * img[:, :, 1] + 0.114 * img[:, :, 2] + ).astype(np.uint8) + + # Sobel Operator + h, w = gray_img.shape + # define filters + horizontal = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) # s2 + vertical = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]]) # s1 + + # define images with 0s + newhorizontalImage = np.zeros((h, w)) + newverticalImage = np.zeros((h, w)) + newgradientImage = np.zeros((h, w)) + + # offset by 1 + for i in range(1, h - 1): + for j in range(1, w - 1): + horizontalGrad = ( + (horizontal[0, 0] * gray_img[i - 1, j - 1]) + + (horizontal[0, 1] * gray_img[i - 1, j]) + + (horizontal[0, 2] * gray_img[i - 1, j + 1]) + + (horizontal[1, 0] * gray_img[i, j - 1]) + + (horizontal[1, 1] * gray_img[i, j]) + + (horizontal[1, 2] * gray_img[i, j + 1]) + + (horizontal[2, 0] * gray_img[i + 1, j - 1]) + + (horizontal[2, 1] * gray_img[i + 1, j]) + + (horizontal[2, 2] * gray_img[i + 1, j + 1]) + ) + + newhorizontalImage[i - 1, j - 1] = abs(horizontalGrad) + + verticalGrad = ( + (vertical[0, 0] * gray_img[i - 1, j - 1]) + + (vertical[0, 1] * gray_img[i - 1, j]) + + (vertical[0, 2] * gray_img[i - 1, j + 1]) + + (vertical[1, 0] * gray_img[i, j - 1]) + + (vertical[1, 1] * gray_img[i, j]) + + (vertical[1, 2] * gray_img[i, j + 1]) + + (vertical[2, 0] * gray_img[i + 1, j - 1]) + + (vertical[2, 1] * gray_img[i + 1, j]) + + (vertical[2, 2] * gray_img[i + 1, j + 1]) + ) + + newverticalImage[i - 1, j - 1] = abs(verticalGrad) + + # Edge Magnitude + mag = np.sqrt(pow(horizontalGrad, 2.0) + pow(verticalGrad, 2.0)) + newgradientImage[i - 1, j - 1] = mag + + image = Image.fromarray(newgradientImage) + image = image.convert("L") + + return image + + +def filter_smooth(image: Union[str, Image], radius: int = 3): + """ + :param image: white and black image representing the derivative of the terrain (sobel), where black is flat and white is very steep. + :param radius: Radius of the Gaussian blur. + + Returns: + image: black or white image, with black as flat areas to be skeletonized + """ + + image = handle_import_image(image) + + # image = image.filter(ImageFilter.SMOOTH_MORE) + # image = image.filter(ImageFilter.SMOOTH_MORE) + # image = image.filter(ImageFilter.SMOOTH_MORE) + image = image.convert('L') + image = image.filter(ImageFilter.GaussianBlur(radius)) + array = np.array(image) + + bool_array = array > 7 + + # bool_array = ndimage.binary_opening(bool_array, structure=np.ones((3,3)), iterations=1) + # bool_array = ndimage.binary_closing(bool_array, structure=np.ones((3,3)), iterations=1) + # bool_array = ndimage.binary_opening(bool_array, structure=np.ones((5,5)), iterations=1) + # bool_array = ndimage.binary_closing(bool_array, structure=np.ones((5,5)), iterations=1) + # bool_array = ndimage.binary_opening(bool_array, structure=np.ones((7,7)), iterations=1) + # bool_array = ndimage.binary_closing(bool_array, structure=np.ones((7,7)), iterations=1) + + return Image.fromarray(bool_array) + + +def subtract_map(image: Union[str, Image], substractImage: Union[str, Image]) -> Image: + image = handle_import_image(image) + substractImage = handle_import_image(substractImage).convert('L') + + array_heightmap = np.array(image) + array_substractImage = np.array(substractImage) + + mask = array_substractImage == 255 + array_heightmap[mask] = 0 + + return Image.fromarray(array_heightmap) + + +def group_map(image1: Union[str, Image], image2: Union[str, Image]) -> Image: + image1 = handle_import_image(image1) + image2 = handle_import_image(image2) + + array1 = np.array(image1) + array2 = np.array(image2) + + mask = array1 == 255 + array2[mask] = 255 + + return Image.fromarray(array2) + + +def filter_smooth_array(array: np.ndarray, radius: int = 3) -> np.ndarray: + image = Image.fromarray(array) + smooth_image = filter_smooth(image, radius) + array = np.array(smooth_image) + return array + + +def filter_remove_details(image: Union[str, Image], n: int = 20) -> Image: + image = handle_import_image(image) + array = np.array(image) + for _ in range(n): + array = ndimage.binary_dilation(array, iterations=4) + array = ndimage.binary_erosion(array, iterations=5) + array = filter_smooth_array(array, 2) + array = ndimage.binary_erosion(array, iterations=3) + image = Image.fromarray(array) + return image + + +def highway_map() -> Image: + print("[Data Analysis] Generating highway map...") + smooth_sobel = filter_smooth("./data/sobelmap.png", 1) + negative_smooth_sobel = filter_negative(smooth_sobel) + negative_smooth_sobel_water = subtract_map(negative_smooth_sobel, './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) + array_sobel_water = filter_smooth_array(array_sobel_water, 5) + array_sobel_water = ndimage.binary_erosion(array_sobel_water, iterations=20) + 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') + print("[Data Analysis] Highway map generated.") + return image_no_details + + +def create_volume(surface: np.ndarray, heightmap: np.ndarray, make_it_flat: bool = False) -> np.ndarray: + volume = np.full((len(surface), 255, len(surface[0])), False) + for z in range(len(surface)): + for x in range(len(surface[0])): + if not make_it_flat: + volume[x][heightmap[z][x]][z] = surface[z][x] + else: + volume[x][0][z] = surface[z][x] + return volume + + +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 = 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'): + 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') diff --git a/world_maker/world_maker.py b/world_maker/world_maker.py index 1ff126f..c711b88 100644 --- a/world_maker/world_maker.py +++ b/world_maker/world_maker.py @@ -1,17 +1,9 @@ import World - - -def get_data(world: World): - heightmap, watermap, treemap = world.getData() - heightmap.save('./data/heightmap.png') - watermap.save('./data/watermap.png') - treemap.save('./data/treemap.png') - - -def main(): - world = World.World() - get_data(world) - +from PIL import Image +from data_analysis import get_data, highway_map, filter_sobel, skeleton_highway_map if __name__ == '__main__': - main() + world = World.World() + heightmap, watermap, treemap = get_data(world) + filter_sobel("./data/heightmap.png").save('./data/sobelmap.png') + skeleton_highway_map(highway_map())