diff --git a/Enums.py b/Enums.py new file mode 100644 index 0000000..33c7487 --- /dev/null +++ b/Enums.py @@ -0,0 +1,12 @@ +from enum import Enum + +class DIRECTION(Enum): + WEST = 0 + EAST = 1 + NORTH = 2 + SOUTH = 3 + +class COLLUMN_STYLE(Enum): + INNER = 1 + OUTER = 2 + BOTH = 3 \ No newline at end of file diff --git a/buildings/Building.py b/buildings/Building.py new file mode 100644 index 0000000..befeb87 --- /dev/null +++ b/buildings/Building.py @@ -0,0 +1,31 @@ +import random as rd +from Enums import COLLUMN_STYLE +from buildings.Foundations import Foundations +from buildings.Facade import Facade + +class Building: + def __init__(self, position : tuple[int,int], size : tuple[int, int], matrice : list[list[int]]): + self.position = position + self.length, self.width = size + self.matrice = matrice + + # Generate every random components here + is_collumn_full_tile = bool(rd.getrandbits(1)) + is_inner_or_outer = rd.choice(list(COLLUMN_STYLE)) + tile_size = self.gen_tile_size() + floor_height = rd.randint(4, 7) + + is_inner_or_outer = COLLUMN_STYLE.BOTH + + self.foundations = Foundations(position, size, matrice, tile_size, is_collumn_full_tile, is_inner_or_outer) + self.facade = Facade(self.foundations.vertices, floor_height, is_inner_or_outer) + + def gen_tile_size(self) -> int: + # Tiles are constant square units different for each buildings + smaller_side = min(self.length, self.width) + + # area is too small, will work but not very well + if smaller_side <= 15 : return smaller_side // 5 + + return rd.randint(3, smaller_side // len(self.matrice)) + \ No newline at end of file diff --git a/buildings/Facade.py b/buildings/Facade.py new file mode 100644 index 0000000..25b07ad --- /dev/null +++ b/buildings/Facade.py @@ -0,0 +1,29 @@ +from Enums import COLLUMN_STYLE +from buildings.geometry.Vertice import Vertice +from buildings.geometry.Rectangle import Rectangle +from buildings.elements.Window import Window + +class Facade: + def __init__(self, vertices : list[Vertice], height : int, is_inner_or_outer : COLLUMN_STYLE): + self.vertices = vertices + self.is_inner_or_outer = is_inner_or_outer + self.height = height + self.window_size = self.get_window_size() + self.window = self.get_window() + self.has_balcony = self.has_balcony() + self.has_inter_floor = self.has_inter_floor() + + def build_facade(self): + pass + + def get_window_size(self) -> tuple[int,int]: + pass + + def has_balcony(self) -> bool: + pass + + def has_inter_floor(self) -> bool: + pass + + def get_window(self) -> Window: + pass \ No newline at end of file diff --git a/buildings/Foundations.py b/buildings/Foundations.py new file mode 100644 index 0000000..b36c610 --- /dev/null +++ b/buildings/Foundations.py @@ -0,0 +1,144 @@ +import random as rd +import numpy as np +import math +from Enums import COLLUMN_STYLE +from buildings.geometry.Tile import Tile +from buildings.geometry.Polygon import Polygon +from buildings.geometry.Point import Point +from buildings.geometry.Rectangle import Rectangle +from buildings.elements.Collumn import Collumn + +class Foundations: + # TODO : gérer les collones sur les tiles trop petites et les colones 1tile/2 + fulltile + + def __init__(self, + position : tuple[int,int], + size : tuple[int, int], + matrice : list[list[int]], + tile_size : int, + is_collumn_full_tile : bool, + is_inner_or_outer : COLLUMN_STYLE): + # Foundations are the base of the building, they are made of tiles and based on a matrice + + # Random components + self.tile_size = tile_size + self.is_collumn_full_tile = is_collumn_full_tile + self.is_inner_or_outer = is_inner_or_outer + + x,z = position + self.position = Point(x = x, z = z) + self.size = size + self.length = size[0] + self.width = size[1] + self.matrice = matrice + self.tiles = [] + self.vertices = [] + self.length_in_tiles = self.length // self.tile_size + self.width_in_tiles = self.width // self.tile_size + self.x_distribution = [] + self.z_distribution = [] + self.polygon = self.get_polygon() + self.collumns = self.get_columns() + + def add_tile(self, tile : Tile): + self.tiles.append(tile) + + def get_polygon(self) -> Polygon: + ## The polygon is a shape of tiles representing the foundation shape + polygon = Polygon(self.position, self.size) + avaliable_space = (self.length_in_tiles, self.width_in_tiles) + + # we save the distribution, usefull for the next steps + self.x_distribution = self.get_distribution(len(self.matrice), avaliable_space[0]) + self.z_distribution = self.get_distribution(len(self.matrice[0]), avaliable_space[1]) + + # this bullshit is to create tiles from the matrice and the distribution + x_padding = self.position.x + for x,xsize in enumerate(self.x_distribution): + z_padding = self.position.z + for z,zsize in enumerate(self.z_distribution): + if self.matrice[x][z] == 1: + for xi in range(xsize): + for zi in range(zsize): + tile = Tile(self.tile_size, (x_padding + xi*self.tile_size, z_padding + zi*self.tile_size)) + self.add_tile(tile) + z_padding += zsize * self.tile_size + x_padding += xsize * self.tile_size + + polygon.set_vertices_and_neighbors(self.tiles, self.vertices) + polygon.compress(self.tiles, self.vertices) + return polygon + + + + def get_distribution(self,length,avaliable_tiles): + # foundations are based on a matrice, + # this function gives the number of tiles for each row/collumn of the matrice, giving a more random shape + # The real shit start here + if length == avaliable_tiles: + return [1 for i in range(avaliable_tiles)] + + if length == 1: + return [avaliable_tiles] + + if length == 2: + l = rd.randint(1,avaliable_tiles-1) + return [l,avaliable_tiles-l] + + if length >= 3: + sizes = [] + intersections_count = math.ceil(length/2)-1 + tiles_per_side = avaliable_tiles//2 + correction = 0 + + intersect_values = np.random.choice(np.arange(1,tiles_per_side), size=intersections_count, replace=False) + + #we generate only half of the distribution + last_pos = 0 + intersect_values = np.append(intersect_values,tiles_per_side) + for intersect in intersect_values: + sizes.append(intersect - last_pos) + last_pos = intersect + + # we duplicate the side for the symetry + symetry = sizes.copy() + symetry.reverse() + if avaliable_tiles%2 == 1: correction = 1 # if there is a tile left, add it randomly + if length%2 == 1 : sizes[-1], symetry = sizes[-1]*2 + correction, symetry[1:] + sizes += symetry + + return sizes + + def get_columns(self) -> list[Collumn]: + collumns = [] + + for tile in self.tiles: + north_west_collumn = Collumn(Point(x = tile.north_west.x-1, z = tile.north_west.z-1), tile.north_west) + north_east_collumn = Collumn(Point(x = tile.north_east.x, z = tile.north_east.z-1), Point(x = tile.north_east.x+1, z = tile.north_east.z)) + south_west_collumn = Collumn(Point(x = tile.south_west.x-1, z = tile.south_west.z), Point(x = tile.south_west.x, z = tile.south_west.z+1)) + south_east_collumn = Collumn(tile.south_east, Point(x = tile.south_east.x+1, z = tile.south_east.z+1)) + + if tile.north_vertice != None or tile.west_vertice != None: north_west_collumn.set_is_outer(True) + + if tile.north_vertice != None or tile.east_vertice != None: north_east_collumn.set_is_outer(True) + + if tile.south_vertice != None or tile.west_vertice != None: south_west_collumn.set_is_outer(True) + + if tile.south_vertice != None or tile.east_vertice != None: south_east_collumn.set_is_outer(True) + + collumns.extend([north_west_collumn, north_east_collumn, south_west_collumn, south_east_collumn]) + + return self._suppr_doubblons_collumns(collumns) + + def _suppr_doubblons_collumns(self, collumns : list[Collumn]): + for index,collumn in enumerate(collumns): + if index == len(collumns)-1: break + for compare in collumns[index+1:]: + if collumn.point1.position == compare.point1.position : + if compare.is_outer : collumn.set_is_outer(True) + collumns.remove(compare) + + print(len(collumns)) + return collumns + + \ No newline at end of file diff --git a/buildings/elements/Collumn.py b/buildings/elements/Collumn.py new file mode 100644 index 0000000..6fd7c1e --- /dev/null +++ b/buildings/elements/Collumn.py @@ -0,0 +1,11 @@ +from buildings.geometry.Rectangle import Rectangle +from buildings.geometry.Point import Point + +class Collumn(Rectangle): + def __init__(self, point1 : Point, point2 : Point, is_outer : bool = False) : + Rectangle.__init__(self, point1, point2) + self.is_outer = is_outer + + def set_is_outer(self, is_outer : bool): + self.is_outer = is_outer + \ No newline at end of file diff --git a/buildings/elements/Window.py b/buildings/elements/Window.py new file mode 100644 index 0000000..81edaff --- /dev/null +++ b/buildings/elements/Window.py @@ -0,0 +1,10 @@ +class Window: + def __init__(self, size : tuple[int,int]): + self.size = size + + + def open(self): + pass + + def close(self): + pass \ No newline at end of file diff --git a/buildings/geometry/Point.py b/buildings/geometry/Point.py new file mode 100644 index 0000000..b8020ec --- /dev/null +++ b/buildings/geometry/Point.py @@ -0,0 +1,14 @@ +class Point: + def __init__(self, x : int = None, y : int = None, z : int = None, p : tuple[int] = None): + if p != None: x,y,z = p + self.x = x + self.y = y + self.z = z + self.position = (x,y,z) + + def set_position(self, x : int = None, y : int = None, z : int = None, p : tuple[int] = None): + if p != None: x,y,z = p + self.x = x if x != None else self.x + self.y = y if y != None else self.y + self.z = z if z != None else self.z + self.position = (self.x,self.y,self.z) \ No newline at end of file diff --git a/buildings/geometry/Polygon.py b/buildings/geometry/Polygon.py new file mode 100644 index 0000000..9e7ea8e --- /dev/null +++ b/buildings/geometry/Polygon.py @@ -0,0 +1,106 @@ +from Enums import DIRECTION +from gdpc import Editor, Block, geometry +from buildings.geometry.Tile import Tile +from buildings.geometry.Point import Point +from buildings.geometry.Rectangle import Rectangle +from buildings.geometry.Vertice import Vertice + +class Polygon: + def __init__(self, position : Point, size: tuple[int,int]): + self.position = position + self.size = size + self.shape = [] + self.vertices = [] + + def fill_polygon(self, editor : Editor, material : str, y : int, y2 : int = None): + if y2 == None: y2 = y + for rect in self.shape: + rect.fill(editor, material, y, y2) + + def fill_vertice(self, editor : Editor, material : str, y : int, y2 : int = None): + if y2 == None: y2 = y + for vertice in self.vertices: + vertice.fill(editor, Block(material), y, y2) + + def compress(self, tiles : list[Tile], vertices : list[Vertice]): + remaining_tiles = tiles.copy() + while len(remaining_tiles) > 0: + start = remaining_tiles[0] + neightbor = start.get_neighbor(DIRECTION.WEST) + row = [] + + # Find western border + while neightbor: + start = neightbor + neightbor = start.get_neighbor(DIRECTION.WEST) + + # Find eastern border + while True: + row.append(start) + remaining_tiles.remove(start) + neightbor = start.get_neighbor(DIRECTION.EAST) + if not neightbor: break + start = neightbor + + # Find northern border + north_row = self._find_row_border(row.copy(), DIRECTION.NORTH, remaining_tiles) + # Find southern border + south_row = self._find_row_border(row.copy(), DIRECTION.SOUTH, remaining_tiles) + + area = Rectangle(north_row[0].north_west, south_row[-1].south_east) + self.shape.append(area) + + remaining_vertices = vertices.copy() + current = remaining_vertices.pop() + while len(remaining_vertices) > 0: + neighbors = current.get_neighbors() + has_next1 = self._has_next(neighbors[0], current.facing, remaining_vertices) + has_next2 = self._has_next(neighbors[1], current.facing, remaining_vertices) + + if has_next1: + current = Vertice(has_next1.point1, current.point2, current.facing) + elif has_next2: + current = Vertice(current.point1, has_next2.point2, current.facing) + else: + self.vertices.append(current) + current = remaining_vertices.pop() + + if len(remaining_vertices) == 0: self.vertices.append(current) + + def set_vertices_and_neighbors(self, tiles : list[Tile], vertices : list[Vertice]): + for tile in tiles: + targets = tile.get_neighbors_coords() + for vertice_num,target in enumerate(targets): + has_neighbor = self._has_neighbor(target, tiles) + if not has_neighbor: + vertice = tile.get_vertice(vertice_num) + vertices.append(vertice) + tile.set_vertice(DIRECTION(vertice_num), vertice) + else : + tile.set_neighbor(vertice_num, has_neighbor) + + def _find_row_border(self, line : list[Tile], direction : str, remaining_tiles : list[Tile]) -> list[Tile]: + while True: + new_line = [] + for tile in line: + neightbor = tile.get_neighbor(direction) + if neightbor not in remaining_tiles: return line + new_line.append(neightbor) + for tile in new_line: remaining_tiles.remove(tile) + line = new_line + + def _has_neighbor(self, target : tuple[int], tiles : list[Tile]) -> bool|Tile: + for tile in tiles: + if tile.pos.position == target.position: + return tile + return False + + def _has_next(self, target : Point, facing : str, remaining_vertices : list[Vertice]) -> bool|Vertice: + for vertice in remaining_vertices: + if vertice.facing == facing: + if vertice.point1.position == target.position or vertice.point2.position == target.position: + remaining_vertices.remove(vertice) + return vertice + return False + + \ No newline at end of file diff --git a/buildings/geometry/Rectangle.py b/buildings/geometry/Rectangle.py new file mode 100644 index 0000000..8c7ea24 --- /dev/null +++ b/buildings/geometry/Rectangle.py @@ -0,0 +1,14 @@ +from gdpc import Editor, Block, geometry +from buildings.geometry.Point import Point + +class Rectangle: + def __init__(self, point1 : Point, point2 : Point): + self.point1 = point1 + self.point2 = point2 + + def get_position(self): + return (self.point1.position, self.point2.position) + + def fill(self,editor : Editor, material : str, y : int, y2 : int = None): + if y2 == None: y2 = y + geometry.placeCuboid(editor, (self.point1.x, y, self.point1.z), (self.point2.x, y2, self.point2.z), Block(material)) diff --git a/buildings/geometry/Tile.py b/buildings/geometry/Tile.py new file mode 100644 index 0000000..f1e2167 --- /dev/null +++ b/buildings/geometry/Tile.py @@ -0,0 +1,94 @@ +from gdpc import Editor, Block, geometry +from Enums import DIRECTION +from buildings.geometry.Point import Point +from buildings.geometry.Vertice import Vertice + +class Tile: + def __init__(self, size : int, position : tuple[int, int]): + self.size = size + x,z = position + leng = self.size-1 + self.pos = Point(x = x, z = z) + + self.has_vertice = False + + self.north_west = self.pos + self.north_east = Point(x = self.pos.x + leng, z =self.pos.z) + self.south_west = Point(x = self.pos.x, z = self.pos.z + leng) + self.south_east = Point(x = self.pos.x + leng, z = self.pos.z + leng) + + self.west_neighbor = None + self.east_neighbor = None + self.north_neighbor = None + self.south_neighbor = None + + self.west_vertice = None + self.east_vertice = None + self.north_vertice = None + self.south_vertice = None + + def fill(self, editor : Editor, material : str, y : int, y2 : int = None) -> list[Point]: + if y2 == None: y2 = y + geometry.placeCuboid(editor, (self.pos.x, y, self.pos.z), (self.pos.x+self.size-1, y2, self.pos.z+self.size-1), Block(material)) + + def get_neighbors_coords(self): + return [Point(x = self.pos.x - self.size, z = self.pos.z), # west + Point(x = self.pos.x + self.size, z = self.pos.z), # east + Point(x = self.pos.x, z = self.pos.z - self.size), # north + Point(x = self.pos.x, z = self.pos.z + self.size)] # south + + + def get_neighbor(self, direction) -> Point: + match(direction): + case DIRECTION.WEST: + return self.west_neighbor + case DIRECTION.EAST: + return self.east_neighbor + case DIRECTION.NORTH: + return self.north_neighbor + case DIRECTION.SOUTH: + return self.south_neighbor + + def set_neighbor(self, direction, neighbor : 'Tile'): + match(direction): + case DIRECTION.WEST: + self.west_neighbor = neighbor + case DIRECTION.EAST: + self.east_neighbor = neighbor + case DIRECTION.NORTH: + self.north_neighbor = neighbor + case DIRECTION.SOUTH: + self.south_neighbor = neighbor + + def get_vertice(self,vertice : int|DIRECTION) -> Vertice: + # gives the corresponding vertice : + # 0 = west, 1 = east, 2 = north, 3 = south + match(vertice): + case 0 : + return Vertice(self.north_west, self.south_west, DIRECTION.WEST) + case 1 : + return Vertice(self.north_east, self.south_east, DIRECTION.EAST) + case 2 : + return Vertice(self.north_west, self.north_east, DIRECTION.NORTH) + case 3 : + return Vertice(self.south_west, self.south_east, DIRECTION.SOUTH) + case DIRECTION.WEST : + return self.west_vertice + case DIRECTION.EAST : + return self.east_vertice + case DIRECTION.NORTH : + return self.north_vertice + case DIRECTION.SOUTH : + return self.south_vertice + + def set_vertice(self, direction : DIRECTION, vertice : Vertice): + self.has_vertice = True + match(direction): + case DIRECTION.WEST : + self.west_vertice = vertice + case DIRECTION.EAST : + self.east_vertice = vertice + case DIRECTION.NORTH : + self.north_vertice = vertice + case DIRECTION.SOUTH : + self.south_vertice = vertice \ No newline at end of file diff --git a/buildings/geometry/Vertice.py b/buildings/geometry/Vertice.py new file mode 100644 index 0000000..df58b40 --- /dev/null +++ b/buildings/geometry/Vertice.py @@ -0,0 +1,21 @@ +from Enums import DIRECTION +from buildings.geometry.Point import Point +from buildings.geometry.Rectangle import Rectangle + +class Vertice(Rectangle): + def __init__(self, point1 : Point, point2 : Point, facing : str): + Rectangle.__init__(self, point1, point2) + self.facing = facing + + def get_neighbors(self): + match self.facing: + case DIRECTION.NORTH | DIRECTION.SOUTH: + return [Point(x = self.point1.x - 1, z = self.point1.z), + Point(x = self.point2.x + 1, z = self.point2.z)] + case DIRECTION.EAST | DIRECTION.WEST: + return [Point(x = self.point1.x, z = self.point1.z - 1), + Point(x = self.point2.x, z = self.point2.z + 1)] + + def get_size(self): + return self.point2.x - self.point1.x + self.point2.z - self.point1.z + \ No newline at end of file diff --git a/buildings/shapes.json b/buildings/shapes.json new file mode 100644 index 0000000..4cf2882 --- /dev/null +++ b/buildings/shapes.json @@ -0,0 +1,59 @@ +[ + { + "id": 0, + "name": "basic_shape", + "matrice":[[1]] + }, + { + "id": 1, + "name": "long_shape", + "matrice":[[0,1]] + }, + { + "id": 2, + "name": "double_long_shape", + "matrice":[[1,0,1]] + }, + { + "id": 3, + "name": "L_shape", + "matrice":[[1,0], + [1,1]] + }, + { + "id": 4, + "name": "U_shape", + "matrice":[[1,0,1], + [1,1,1]] + }, + { + "id": 5, + "name": "H_shape", + "matrice":[[1,0,1], + [1,1,1], + [1,0,1]] + }, + { + "id": 6, + "name": "X_shape", + "matrice":[[0,1,0], + [1,1,1], + [0,1,0]] + }, + { + "id": 7, + "name": "O_shape", + "matrice":[[1,1,1], + [1,0,1], + [1,1,1]] + }, + { + "id": 8, + "name": "E_shape", + "matrice":[[1,1,1], + [1,0,0], + [1,1,1], + [1,0,0], + [1,1,1]] + } +] \ No newline at end of file diff --git a/main.py b/main.py index 1f43c45..818d02d 100644 --- a/main.py +++ b/main.py @@ -1,22 +1,36 @@ from gdpc import Editor, Block, geometry import networks.curve as curve import numpy as np +import json +from buildings.Building import Building editor = Editor(buffering=True) +f = open('buildings\shapes.json') +shapes = json.load(f) + +# F = Foundations((0,0), (20,20), shapes[0]['matrice']) +# F.polygon.fill_polygon(editor, "stone", -60) +geometry.placeCuboid(editor, (-10,-60,-10), (85,-55,85), Block("air")) +B = Building((0,0), (75,75), shapes[7]['matrice']) +B.foundations.polygon.fill_vertice(editor, "pink_wool", -60) +for collumn in B.foundations.collumns: + collumn.fill(editor, "white_concrete", -60, -55) +B.foundations.polygon.fill_polygon(editor, "white_concrete", -60) + # # Get a block # block = editor.getBlock((0,48,0)) # # Place a block -# editor.placeBlock((394, 132, 741), Block("stone")) +#editor.placeBlock((0 , 5, 0), Block("stone")) # # Build a cube # geometry.placeCuboid(editor, (458, 92, 488), (468, 99, 471), Block("oak_planks")) -curve = curve.Curve([(396, 132, 740), (435, 138, 730), - (443, 161, 758), (417, 73, 729)]) -curve.compute_curve() +# curve = curve.Curve([(396, 132, 740), (435, 138, 730), +# (443, 161, 758), (417, 73, 729)]) +# curve.compute_curve() -for point in curve.computed_points: - print(point) - editor.placeBlock(point, Block("stone")) +# for point in curve.computed_points: +# print(point) +# editor.placeBlock(point, Block("stone")) diff --git a/params.yml b/params.yml new file mode 100644 index 0000000..f879b5d --- /dev/null +++ b/params.yml @@ -0,0 +1 @@ +// contains all random variables