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/Foundations.py b/buildings/Foundations.py new file mode 100644 index 0000000..a841773 --- /dev/null +++ b/buildings/Foundations.py @@ -0,0 +1,158 @@ +import random as rd +import numpy as np +import math +from buildings.geometry.Tile import Tile +from buildings.geometry.Polygon import Polygon +from buildings.geometry.Point import Point +from buildings.geometry.Rectangle import Rectangle + +class Foundations: + def __init__(self, position : tuple[int,int], size : tuple[int, int], matrice : list[list[int]]): + # Foundations are the base of the building, they are made of tiles and based on a matrice + 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.tile_size = self.define_tile_size() + 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 define_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)) + + 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) + polygon.compress(self.tiles) + 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 == 1: + return [avaliable_tiles] + + if length == 2: + l = rd.randint(1,avaliable_tiles-1) + return [l,avaliable_tiles-l] + + if length >= 3: + is_len_even = length % 2 == 0 + is_availiable_even = avaliable_tiles % 2 == 0 + sizes = [] + + # This is to keep symetry in case of an even matrice + if not is_len_even: + center = rd.randint(1,avaliable_tiles-length+1) + avaliable_tiles -= center + is_availiable_even = avaliable_tiles % 2 == 0 + + if not is_availiable_even: center += 1 + + sizes.append(center) + is_availiable_even = True + + intersection_number = length // 2 - 1 + tiles_per_side = avaliable_tiles // 2 + # we keep symetry we randomize only one side + intersect_values = np.random.choice(np.arange(1,tiles_per_side), size=intersection_number, replace=False) + + # we duplicate the side for the symetry + last_pos = 0 + intersect_values = np.append(intersect_values,tiles_per_side) + for intersect in intersect_values: + size = [intersect - last_pos] + sizes = size + sizes + size + last_pos = intersect + + # if there is a tile left, add it randomly + if not is_availiable_even: sizes[rd.randint(0,len(sizes)-1)] += 1 + return sizes + + def get_columns(self) -> list[Rectangle]: + collumns = [] + is_full_tile = bool(rd.getrandbits(1)) + x_padding = self.position.x + + for x,row in enumerate(self.matrice): + z_padding = self.position.z + lenx = self.x_distribution[x] + + for z,value in enumerate(row): + lenz = self.z_distribution[z] + + # conditions to not make a collumn on the facade of the building (no outter collumns) + + skip_first_x,skip_first_z = False,False + # if it's the first or last row/collumn + if x == 0 : skip_first_x = True + if z == 0 : skip_first_z = True + + last_value_x,last_value_z = self.matrice[x-1][z],self.matrice[x][z-1] + # if the previous row/collumn is empty + if last_value_x == 0 : skip_first_x = True + if last_value_z == 0 : skip_first_z = True + + next_value_x,next_value_z = 0,0 + try : next_value_x = self.matrice[x+1][z] + except : pass + try : next_value_z = self.matrice[x][z+1] + except : pass + # if this part of the building is too tiny + if last_value_x == 0 and next_value_x == 0 and self.x_distribution[x] == 1: continue + if last_value_z == 0 and next_value_z == 0 and self.z_distribution[z] == 1: continue + + if value == 1: + self.create_collumns(x_padding, z_padding, lenx, lenz, skip_first_x, skip_first_z, collumns) + + z_padding += lenz * self.tile_size + x_padding += lenx * self.tile_size + + return collumns + + def create_collumns(self, basex : int, basez : int, lenx : int, lenz : int, skip_first_x : bool, skip_first_z : bool, collumns : list[Rectangle]): + for x in range(lenx): + if x==0 and skip_first_x: continue + for z in range(lenz): + if z==0 and skip_first_z: continue + collumns.append(Rectangle(Point(x = basex+x*self.tile_size, z = basez+z*self.tile_size), Point(x = basex+x*self.tile_size-1, z = basez+z*self.tile_size-1))) + \ 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..c5b14e4 --- /dev/null +++ b/buildings/geometry/Polygon.py @@ -0,0 +1,104 @@ +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], vertices : list[Vertice] = []): + self.position = position + self.size = size + self.compressed = {"shape":[], "vertices":[]} + self.vertices = vertices + + def fill_polygon(self, editor : Editor, material : str, y : int, y2 : int = None): + if y2 == None: y2 = y + for rect in self.compressed["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.compressed["vertices"]: + vertice.fill(editor, Block(material), y, y2) + + def compress(self, tiles : list[Tile]): + 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.compressed["shape"].append(area) + + remaining_vertices = self.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.compressed["vertices"].append(current) + current = remaining_vertices.pop() + + if len(remaining_vertices) == 0: self.compressed["vertices"].append(current) + + 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 set_vertices_and_neighbors(self, tiles : list[Tile]): + 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: + self.vertices.append(tile.get_vertice(vertice_num)) + else : + tile.set_neighbor(vertice_num, has_neighbor) + + 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..8292d62 --- /dev/null +++ b/buildings/geometry/Tile.py @@ -0,0 +1,64 @@ +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 + self.pos = Point(x = x, z = z) + leng = self.size-1 + 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 + + 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_vertice(self,vertice : int) -> 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) + + 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 : Point): + 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 \ No newline at end of file diff --git a/buildings/geometry/Vertice.py b/buildings/geometry/Vertice.py new file mode 100644 index 0000000..009a520 --- /dev/null +++ b/buildings/geometry/Vertice.py @@ -0,0 +1,18 @@ +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)] + \ 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..c4ebd18 100644 --- a/main.py +++ b/main.py @@ -1,22 +1,35 @@ from gdpc import Editor, Block, geometry import networks.curve as curve import numpy as np +import json +from buildings.Foundations import Foundations 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")) +F = Foundations((0,0), (75,75), shapes[8]['matrice']) +F.polygon.fill_polygon(editor, "stone", -60) +F.polygon.fill_vertice(editor, "pink_wool", -60) +for collumn in F.collumns: + collumn.fill(editor, "stone", -60, -55) # # 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"))