diff --git a/main.py b/main.py index 1ec3300..7639db0 100644 --- a/main.py +++ b/main.py @@ -3,17 +3,23 @@ import random import gdpc.exceptions from world_maker.world_maker import * +from world_maker.Skeleton import Skeleton, transpose_form_heightmap, simplify_coordinates +from networks.geometry.Point3D import Point3D +from networks.roads_2.Road import Road +from world_maker.District import Road as Road_grid from House import * def main(): - rectangle_house_mountain, rectangle_building, skeleton_highway, skeleton_mountain = world_maker() + rectangle_house_mountain, rectangle_building, skeleton_highway, skeleton_mountain, road_grid = world_maker() editor = Editor(buffering=True) buildArea = editor.getBuildArea() - print(skeleton_mountain.lines) + set_roads(skeleton_mountain) + set_roads(skeleton_highway) + set_roads_grids(road_grid) blocks = { "wall": "blackstone", @@ -43,6 +49,46 @@ def main(): house.build() +def set_roads_grids(road_grid: Road_grid): + for i in range(len(road_grid)): + if road_grid[i].border: + for j in range(len(road_grid)): + # Same line + if (road_grid[i].position.x == road_grid[j].position.x and road_grid[i].position.y != road_grid[j].position.y) or (road_grid[i].position.x != road_grid[j].position.x and road_grid[i].position.y == road_grid[j].position.y): + point_1 = transpose_form_heightmap( + './world_maker/data/heightmap.png', (road_grid[i].position.x, road_grid[i].position.y)) + point_2 = transpose_form_heightmap( + './world_maker/data/heightmap.png', (road_grid[j].position.x, road_grid[j].position.y)) + Road( + [Point3D(point_1[0], point_1[1], point_1[2]), Point3D(point_2[0], point_2[1], point_2[2])], 9) + + +def set_roads(skeleton: Skeleton): + # Parsing + print("[Roads] Start parsing...") + for i in range(len(skeleton.lines)): + print(f"[Roads] Parsing skeleton {i+1}/{len(skeleton.lines)}.") + for j in range(len(skeleton.lines[i])): + xyz = transpose_form_heightmap('./world_maker/data/heightmap.png', + skeleton.coordinates[skeleton.lines[i][j]]) + skeleton.lines[i][j] = xyz + + print("[Roads] Start simplification...") + # Simplification + for i in range(len(skeleton.lines)): + print(f"[Roads] Simplify skelton {i+1}/{len(skeleton.lines)}") + skeleton.lines[i] = simplify_coordinates(skeleton.lines[i], 10) + + print("[Roads] Start generation...") + for i in range(len(skeleton.lines)): + print(f"[Roads] Generating roads {i+1}/{len(skeleton.lines)}.") + if len(skeleton.lines[i]) >= 4: + Road(Point3D.from_arrays(skeleton.lines[i]), 25) + else: + print( + f"[Roads] Ignore roads {i+1} with {len(skeleton.lines[i])} coordinates between {skeleton.lines[i][1]} and {skeleton.lines[i][-1]}.") + + if __name__ == '__main__': main() diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index d7cbac5..2808be0 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -28,6 +28,9 @@ class Polyline: self.length_polyline = len(self.points_array) if self.length_polyline < 4: + print(self.length_polyline) + print(self.points_array) + print(self.output_points) raise ValueError("The list must contain at least 4 elements.") self.vectors = [None] * self.length_polyline # v diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py index e5dac0f..558e218 100644 --- a/networks/geometry/Segment3D.py +++ b/networks/geometry/Segment3D.py @@ -22,6 +22,8 @@ class Segment3D: >>> Segment3D(Point3D(0, 0, 0), Point3D(10, 10, 15)) """ + start = self.start.copy() + end = self.end.copy() self.output_points.append(start.copy()) dx = abs(self.end.x - self.start.x) dy = abs(self.end.y - self.start.y) diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Road.py similarity index 56% rename from networks/roads_2/Roads.py rename to networks/roads_2/Road.py index 99b9b0d..bffd365 100644 --- a/networks/roads_2/Roads.py +++ b/networks/roads_2/Road.py @@ -4,6 +4,8 @@ from networks.geometry.Polyline import Polyline from networks.geometry.Point3D import Point3D from networks.geometry.Point2D import Point2D +from networks.geometry.Segment2D import Segment2D +from networks.geometry.Segment3D import Segment3D from networks.geometry.Circle import Circle from Enums import LINE_THICKNESS_MODE from gdpc import Block, Editor @@ -11,21 +13,45 @@ from gdpc import Block, Editor class Road: def __init__(self, coordinates: List[Point3D], width: int): - self.coordinates = coordinates + self.coordinates = self._remove_collinear_points(coordinates) self.output_block = [] # with open(road_configuration) as f: # self.road_configuration = json.load(f) # self.width = self.road_configuration["width"] self.width = width self.polyline_height = None - - self.polyline = Polyline(Point3D.to_2d(coordinates, 'y')) - self.polyline_total_line_output = [ - [] for _ in range(len(self.polyline.total_line_output))] + self.polyline_total_line_output = None + self.segment_total_line_output = None self.index_factor = 0 - self._projection() - self._surface() + if len(self._remove_collinear_points(self.coordinates)) >= 4: + self.polyline = Polyline(Point3D.to_2d(coordinates, 'y')) + self.polyline_total_line_output = [ + [] for _ in range(len(self.polyline.total_line_output))] + + self._projection_polyline() + + if len(self.coordinates) == 2: + self.segment_total_line_output = Segment2D( + Point3D.to_2d([self.coordinates[0]], 'y')[0], Point3D.to_2d([self.coordinates[1]], 'y')[0]).segment_thick(self.width, LINE_THICKNESS_MODE.MIDDLE) + self._projection_segment() + self.place() + + @staticmethod + def _remove_collinear_points(points): + output_points = [points[0]] + + for i in range(1, len(points) - 1): + if isinstance(points[0], Point3D): + if not Point2D.collinear( + Point3D.to_2d([points[i-1]], 'y')[0], Point3D.to_2d([points[i]], 'y')[0], Point3D.to_2d([points[i+1]], 'y')[0]): + output_points.append(points[i]) + else: + if not Point2D.collinear(points[i-1], points[i], points[i+1]): + output_points.append(points[i]) + + output_points.append(points[-1]) + return output_points def _surface(self): # Segments @@ -60,7 +86,7 @@ class Road: (Point3D.insert_3d([circle.points_thick[j]], 'y', [ self.polyline_total_line_output[nearest[0]].y])[0].coordinates, Block("white_concrete"))) - def _projection(self): + def _projection_polyline(self): nearest_points_to_reference = [] for i in range(len(self.coordinates)): # nearest_points_to_reference.append(Point3D.insert_3d([Point3D.to_2d([self.coordinates[i]], 'y')[0].nearest( @@ -70,17 +96,30 @@ class Road: nearest_points_to_reference.append( Point2D(index, self.coordinates[i].y)) - self.polyline_height = Polyline(nearest_points_to_reference) + if len(self._remove_collinear_points(nearest_points_to_reference)) >= 4: + self.polyline_height = Polyline(nearest_points_to_reference) - self.index_factor = len( - self.polyline_height.total_line_output)/len(self.polyline.total_line_output) + self.index_factor = len( + self.polyline_height.total_line_output)/len(self.polyline.total_line_output) - for i in range(len(self.polyline.total_line_output)): - self.polyline_total_line_output[i] = Point3D( - self.polyline.total_line_output[i].x, self.polyline_height.total_line_output[round(i*self.index_factor)].y, self.polyline.total_line_output[i].y) + for i in range(len(self.polyline.total_line_output)): + self.polyline_total_line_output[i] = Point3D( + self.polyline.total_line_output[i].x, self.polyline_height.total_line_output[round(i*self.index_factor)].y, self.polyline.total_line_output[i].y) - self.polyline_total_line_output = self.polyline_total_line_output[0].optimized_path( - self.polyline_total_line_output) + self._surface() + self.place() + # self.polyline_total_line_output = self.polyline_total_line_output[0].optimized_path( + # self.polyline_total_line_output) + + def _projection_segment(self): + s = Segment3D( + self.coordinates[0], self.coordinates[1]) + + reference = s.segment() + + for i in range(len(self.segment_total_line_output)): + self.output_block.append((( + self.segment_total_line_output[i].x, reference[self.segment_total_line_output[i].nearest(Point3D.to_2d(reference, 'y'), True)[0]].y, self.segment_total_line_output[i].y), Block("black_concrete"))) def place(self): editor = Editor(buffering=True) diff --git a/world_maker/Skeleton.py b/world_maker/Skeleton.py index c62e846..12f010f 100644 --- a/world_maker/Skeleton.py +++ b/world_maker/Skeleton.py @@ -3,11 +3,11 @@ from collections import Counter from typing import List, Union import numpy as np +from gdpc import Editor from PIL import Image, ImageDraw from skan.csr import skeleton_to_csgraph from skimage.morphology import skeletonize - -from gdpc import Editor +from networks.geometry.Point3D import Point3D def handle_import_image(image: Union[str, Image]) -> Image: @@ -16,6 +16,51 @@ def handle_import_image(image: Union[str, Image]) -> Image: return image +def transpose_form_heightmap(heightmap: Union[str, Image], coordinates): + heightmap = handle_import_image(heightmap).convert('L') + + editor = Editor() + xMin = (editor.getBuildArea().begin).x + zMin = (editor.getBuildArea().begin).z + + return (coordinates[0] + xMin, heightmap.getpixel( + (coordinates[0], coordinates[-1])), coordinates[-1] + zMin) + + +def simplify_coordinates(coordinates, epsilon): + if len(coordinates) < 3: + return coordinates + + # Find the point with the maximum distance + max_distance = 0 + max_index = 0 + end_index = len(coordinates) - 1 + + for i in range(1, end_index): + distance = Point3D(coordinates[i][0], coordinates[i][1], coordinates[i][2]).distance( + Point3D(coordinates[0][0], coordinates[0][1], coordinates[0][2])) + if distance > max_distance: + max_distance = distance + max_index = i + + simplified_coordinates = [] + + # If the maximum distance is greater than epsilon, recursively simplify + if max_distance > epsilon: + rec_results1 = simplify_coordinates(coordinates[:max_index+1], epsilon) + rec_results2 = simplify_coordinates(coordinates[max_index:], epsilon) + + # Combine the simplified sub-results + simplified_coordinates.extend(rec_results1[:-1]) + simplified_coordinates.extend(rec_results2) + else: + # The maximum distance is less than epsilon, retain the endpoints + simplified_coordinates.append(coordinates[0]) + simplified_coordinates.append(coordinates[end_index]) + + return simplified_coordinates + + class Skeleton: def __init__(self, data: np.ndarray = None): self.lines = [] @@ -26,19 +71,6 @@ class Skeleton: if data is not None: self.set_skeleton(data) - def transpose_form_heightmap(heightmap: Union[str, Image], coordinates): - - heightmap = handle_import_image(heightmap).convert('L') - - editor = Editor() - xMin = (editor.getBuildArea().begin).x - zMin = (editor.getBuildArea().begin).z - - coordinates_final = [] - - return coordinates_final(coordinates[0] + xMin, heightmap.getpixel( - (coordinates[0], coordinates[2]))[0], coordinates[2] + zMin) - def set_skeleton(self, data: np.ndarray): print("[Skeleton] Start skeletonization...") binary_skeleton = skeletonize(data, method="lee") diff --git a/world_maker/data/building.png b/world_maker/data/building.png index 66f43e7..c3ad1eb 100644 Binary files a/world_maker/data/building.png and b/world_maker/data/building.png differ diff --git a/world_maker/data/city_map.png b/world_maker/data/city_map.png index 05c4c92..d83d3c6 100644 Binary files a/world_maker/data/city_map.png and b/world_maker/data/city_map.png differ diff --git a/world_maker/data/district.png b/world_maker/data/district.png index d7657bd..e21d711 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 8753fbb..b707a30 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 521ae0a..5d713b2 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 index c394dd5..69a0654 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 c2b1605..b332976 100644 Binary files a/world_maker/data/roadmap.png and b/world_maker/data/roadmap.png differ diff --git a/world_maker/data/skeleton_highway.png b/world_maker/data/skeleton_highway.png index 60090f5..e2d8875 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 index 377df9b..7345149 100644 Binary files a/world_maker/data/skeleton_highway_area.png 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 index 9cbd799..6ea1561 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 8a25533..86d2c2e 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/smooth_sobel_watermap.png b/world_maker/data/smooth_sobel_watermap.png index e312ae2..e60ab19 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 c77ae37..d755e40 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 05daf73..d2d6aa5 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 f86194d..cfa137a 100644 Binary files a/world_maker/data/watermap.png and b/world_maker/data/watermap.png differ diff --git a/world_maker/world_maker.py b/world_maker/world_maker.py index 1402244..a007e62 100644 --- a/world_maker/world_maker.py +++ b/world_maker/world_maker.py @@ -1,35 +1,43 @@ from world_maker.World import World from PIL import Image -from world_maker.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 world_maker.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 world_maker.City import City from world_maker.Position import Position from random import randint from world_maker.pack_rectangle import generate_building + def world_maker(): world = World() heightmap, watermap, treemap = get_data(world) - filter_sobel("./world_maker/data/heightmap.png").save('./world_maker/data/sobelmap.png') + filter_sobel( + "./world_maker/data/heightmap.png").save('./world_maker/data/sobelmap.png') smooth_sobel_water_map = smooth_sobel_water() skeleton_highway = skeleton_highway_map(highway_map()) city = City() city.generate_district() city.loop_expend_district() city.district_draw_map() - city.district_generate_road() + road_grid = city.district_generate_road() image_mountain_map = city.get_district_mountain_map() road = city.draw_roads(4) road.save('./world_maker/data/roadmap.png') - subtract_map(smooth_sobel_water_map, 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') + subtract_map(smooth_sobel_water_map, 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 = skeleton_mountain_map(image_mountain_map) - 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_map, 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') + 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_map, 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') rectangle_mountain = rectangle_2D_to_3D(rectangle_mountain) - return rectangle_mountain, rectangle_building, skeleton_highway, skeleton_mountain + return rectangle_mountain, rectangle_building, skeleton_highway, skeleton_mountain, road_grid