@@ -1,7 +1,9 @@
|
||||
from District import District
|
||||
from District import District, Road
|
||||
from Position import Position
|
||||
from PIL import Image
|
||||
import random
|
||||
from data_analysis import handle_import_image
|
||||
from typing import Union
|
||||
|
||||
|
||||
class City:
|
||||
@@ -72,7 +74,6 @@ class City:
|
||||
index_district_chosen = index
|
||||
else:
|
||||
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)
|
||||
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.
|
||||
"""
|
||||
|
||||
for district in self.districts:
|
||||
if len(district.area_expend_from_point) > 0:
|
||||
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')
|
||||
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__':
|
||||
city = City()
|
||||
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.district_draw_map()
|
||||
city.district_generate_road()
|
||||
image = city.draw_roads(Image.new('RGB', (401, 401)),4)
|
||||
image.save('./data/roadmap.png')
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
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:
|
||||
"""
|
||||
The CustomDistrict class represents a district that can be expanded.
|
||||
The District class represents a district that can be expanded.
|
||||
|
||||
Attributes:
|
||||
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 (list): The list of positions to which the district will maybe expand.
|
||||
"""
|
||||
|
||||
def __init__(self, tile_id: int, center: Position, district_type: str = ""):
|
||||
"""
|
||||
The constructor for the District class.
|
||||
@@ -23,12 +39,14 @@ class District:
|
||||
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 = []
|
||||
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.
|
||||
|
||||
:param point: The current point.
|
||||
@@ -37,11 +55,34 @@ class District:
|
||||
: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))
|
||||
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 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]]):
|
||||
"""
|
||||
@@ -53,6 +94,142 @@ class District:
|
||||
"""
|
||||
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 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_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
|
||||
|
||||
BIN
world_maker/data/building.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
world_maker/data/district.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 18 KiB |
BIN
world_maker/data/roadmap.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
world_maker/data/roadmap2.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 19 KiB |
BIN
world_maker/data/smooth_sobel_watermap.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -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:
|
||||
image1 = handle_import_image(image1)
|
||||
image2 = handle_import_image(image2)
|
||||
image1 = handle_import_image(image1).convert('L')
|
||||
image2 = handle_import_image(image2).convert('L')
|
||||
|
||||
array1 = np.array(image1)
|
||||
array2 = np.array(image2)
|
||||
@@ -221,3 +221,14 @@ def skeleton_highway_map(image: Union[str, Image] = './data/highwaymap.png'):
|
||||
skeleton.parse_graph(True)
|
||||
heightmap_skeleton = skeleton.map()
|
||||
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
|
||||
|
||||
115
world_maker/pack_rectangle.py
Normal 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()
|
||||
@@ -1,9 +1,22 @@
|
||||
import World
|
||||
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__':
|
||||
world = World.World()
|
||||
heightmap, watermap, treemap = get_data(world)
|
||||
filter_sobel("./data/heightmap.png").save('./data/sobelmap.png')
|
||||
skeleton_highway_map(highway_map())
|
||||
#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())
|
||||
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')
|
||||
|
||||