Merge pull request #6 from NichiHachi/main

Road and Building
This commit is contained in:
Xeon0X
2024-06-15 14:07:52 +02:00
committed by GitHub
17 changed files with 364 additions and 21 deletions

View File

@@ -1,7 +1,9 @@
from District import District from District import District, Road
from Position import Position from Position import Position
from PIL import Image from PIL import Image
import random import random
from data_analysis import handle_import_image
from typing import Union
class City: class City:
@@ -72,7 +74,6 @@ class City:
index_district_chosen = index index_district_chosen = index
else: else:
self.districts[index].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_from_point.append(point)
self.districts[index_district_chosen].area_expend.remove(point) self.districts[index_district_chosen].area_expend.remove(point)
self.map_data[point.y][point.x] = index_district_chosen + 1 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. Update the expansion points of all districts in the city.
""" """
for district in self.districts: for district in self.districts:
if len(district.area_expend_from_point) > 0: if len(district.area_expend_from_point) > 0:
district.update_expend_points(district.area_expend_from_point[0], self.map_data, self.height_map) 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') img.save('./data/district.png')
print("[City] District map created.") 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__': if __name__ == '__main__':
city = City() city = City()
for i in range(10): 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.loop_expend_district()
city.district_draw_map() city.district_draw_map()
city.district_generate_road()
image = city.draw_roads(Image.new('RGB', (401, 401)),4)
image.save('./data/roadmap.png')

View File

@@ -1,8 +1,23 @@
from Position import Position 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: class District:
""" """
The CustomDistrict class represents a district that can be expanded. The District class represents a district that can be expanded.
Attributes: Attributes:
center_expend (Position): The center position from which the district expands. 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_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. 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 = ""): def __init__(self, tile_id: int, center: Position, district_type: str = ""):
""" """
The constructor for the District class. The constructor for the District class.
@@ -23,12 +39,14 @@ class District:
self.tile_id = tile_id self.tile_id = tile_id
self.type = district_type self.type = district_type
self.center_expend = center self.center_expend = center
self.area = [center]
self.area_expend_from_point = [center] self.area_expend_from_point = [center]
self.area_expend = [] 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. Verify if a new point can be added to a district extend area list.
:param point: The current point. :param point: The current point.
@@ -37,11 +55,34 @@ class District:
:param height_map: The 2D list representing the height map. :param height_map: The 2D list representing the height map.
:return: True if the new point can be added, False otherwise. :return: True if the new point can be added, False otherwise.
""" """
return (0 <= point_new.x < len(map_data[0]) and return (0 <= point_new.x < len(map_data[0]) and
0 <= point_new.y < len(map_data) and 0 <= point_new.y < len(map_data) and
map_data[point_new.y][point_new.x] == 0 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)) 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]]): 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)]: 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 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.append(point + pos)
self.area_expend_from_point.remove(point) 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -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: def group_map(image1: Union[str, Image], image2: Union[str, Image]) -> Image:
image1 = handle_import_image(image1) image1 = handle_import_image(image1).convert('L')
image2 = handle_import_image(image2) image2 = handle_import_image(image2).convert('L')
array1 = np.array(image1) array1 = np.array(image1)
array2 = np.array(image2) array2 = np.array(image2)
@@ -221,3 +221,14 @@ def skeleton_highway_map(image: Union[str, Image] = './data/highwaymap.png'):
skeleton.parse_graph(True) skeleton.parse_graph(True)
heightmap_skeleton = skeleton.map() heightmap_skeleton = skeleton.map()
heightmap_skeleton.save('./data/skeleton_highway.png') 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

View File

@@ -0,0 +1,115 @@
from PIL import Image
import numpy as np
from typing import Union
from data_analysis import handle_import_image
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
class Bin:
def __init__(self, grid):
self.grid = grid
self.rectangles = []
def place_rectangle(self, rectangle):
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 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)
if empty_area < best_spot_empty_area:
best_spot = (i, j)
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.update_grid(rectangle, *best_spot)
return True
return False
def calculate_empty_area(self, rectangle, x, y):
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
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
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
def pack_rectangles(rectangles, grid):
rectangles = sorted(rectangles, key=lambda r: r.width * r.height, reverse=True)
bins = [Bin(grid)]
for rectangle in rectangles:
for bin in bins:
if bin.place_rectangle(rectangle):
break
else: # No break, meaning rectangle couldn't be placed in any bin
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 True # If all rectangles can be placed, return True
import random
def generate_rectangle(max_width, max_height):
width = random.randint(6, 20)
height = random.randint(6, 20)
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)
if not bin.place_rectangle(rectangle):
break # Stop when a rectangle can't be placed
print(len(bin.rectangles))
return bin.rectangles # Return the list of rectangles that were placed
def draw_rectangles(rectangles, grid):
image = Image.new('RGB', (len(grid[0]), len(grid)), (0, 0, 0))
for rectangle in rectangles:
start, end = rectangle
for x in range(start[0], end[0]):
for y in range(start[1], end[1]):
image.putpixel((x, y), (144, 255, 144))
return image
def generate_building(image: Union[str, Image] = './data/roadmap2.png'):
image = handle_import_image(image).convert('L')
grid = np.array(image)
rectangles = pack_rectangles(grid)
draw_rectangles(rectangles, grid).save('./data/building.png')
return rectangles
if __name__ == '__main__':
generate_building()

View File

@@ -1,9 +1,22 @@
import World import World
from PIL import Image 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__': if __name__ == '__main__':
world = World.World() #world = World.World()
heightmap, watermap, treemap = get_data(world) #heightmap, watermap, treemap = get_data(world)
filter_sobel("./data/heightmap.png").save('./data/sobelmap.png') #filter_sobel("./data/heightmap.png").save('./data/sobelmap.png')
skeleton_highway_map(highway_map()) 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)), 4)
road.save('./data/roadmap.png')
subtract_map(smooth_sobel_water, road).save('./data/roadmap2.png')