From 1da68576f498a2cf86fe5737098a5477570f4488 Mon Sep 17 00:00:00 2001 From: KAymeric Date: Mon, 6 May 2024 08:54:27 +0200 Subject: [PATCH 1/2] base foudations --- Enums.py | 12 +++ buildings/Foundations.py | 158 ++++++++++++++++++++++++++++++++ buildings/geometry/Point.py | 14 +++ buildings/geometry/Polygon.py | 104 +++++++++++++++++++++ buildings/geometry/Rectangle.py | 14 +++ buildings/geometry/Tile.py | 64 +++++++++++++ buildings/geometry/Vertice.py | 18 ++++ buildings/shapes.json | 59 ++++++++++++ main.py | 27 ++++-- 9 files changed, 463 insertions(+), 7 deletions(-) create mode 100644 Enums.py create mode 100644 buildings/Foundations.py create mode 100644 buildings/geometry/Point.py create mode 100644 buildings/geometry/Polygon.py create mode 100644 buildings/geometry/Rectangle.py create mode 100644 buildings/geometry/Tile.py create mode 100644 buildings/geometry/Vertice.py create mode 100644 buildings/shapes.json 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")) From 235d9be35d15239cbc4eb6b9b410e9b8e54be509 Mon Sep 17 00:00:00 2001 From: KAymeric Date: Sat, 11 May 2024 11:21:24 +0200 Subject: [PATCH 2/2] fix collumns, start facade and clean shitty code --- buildings/Building.py | 31 ++++++++ buildings/Facade.py | 29 +++++++ buildings/Foundations.py | 144 +++++++++++++++------------------- buildings/elements/Collumn.py | 11 +++ buildings/elements/Window.py | 10 +++ buildings/geometry/Polygon.py | 56 ++++++------- buildings/geometry/Tile.py | 60 ++++++++++---- buildings/geometry/Vertice.py | 3 + main.py | 13 +-- params.yml | 1 + 10 files changed, 231 insertions(+), 127 deletions(-) create mode 100644 buildings/Building.py create mode 100644 buildings/Facade.py create mode 100644 buildings/elements/Collumn.py create mode 100644 buildings/elements/Window.py create mode 100644 params.yml 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 index a841773..b36c610 100644 --- a/buildings/Foundations.py +++ b/buildings/Foundations.py @@ -1,14 +1,30 @@ 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: - def __init__(self, position : tuple[int,int], size : tuple[int, int], matrice : list[list[int]]): + # 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 @@ -16,22 +32,13 @@ class Foundations: self.width = size[1] self.matrice = matrice self.tiles = [] - self.tile_size = self.define_tile_size() + 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 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) @@ -58,8 +65,8 @@ class Foundations: z_padding += zsize * self.tile_size x_padding += xsize * self.tile_size - polygon.set_vertices_and_neighbors(self.tiles) - polygon.compress(self.tiles) + polygon.set_vertices_and_neighbors(self.tiles, self.vertices) + polygon.compress(self.tiles, self.vertices) return polygon @@ -68,6 +75,9 @@ class Foundations: # 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] @@ -76,83 +86,59 @@ class Foundations: return [l,avaliable_tiles-l] if length >= 3: - is_len_even = length % 2 == 0 - is_availiable_even = avaliable_tiles % 2 == 0 sizes = [] + intersections_count = math.ceil(length/2)-1 + tiles_per_side = avaliable_tiles//2 + correction = 0 - # 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 + intersect_values = np.random.choice(np.arange(1,tiles_per_side), size=intersections_count, replace=False) - 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 + #we generate only half of the distribution 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 + sizes.append(intersect - last_pos) 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 + + # 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[Rectangle]: + def get_columns(self) -> list[Collumn]: 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 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)) - 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 + 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]) - 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))) + 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/Polygon.py b/buildings/geometry/Polygon.py index c5b14e4..9e7ea8e 100644 --- a/buildings/geometry/Polygon.py +++ b/buildings/geometry/Polygon.py @@ -6,23 +6,23 @@ 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] = []): + def __init__(self, position : Point, size: tuple[int,int]): self.position = position self.size = size - self.compressed = {"shape":[], "vertices":[]} - self.vertices = vertices + 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.compressed["shape"]: + 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.compressed["vertices"]: + for vertice in self.vertices: vertice.fill(editor, Block(material), y, y2) - def compress(self, tiles : list[Tile]): + def compress(self, tiles : list[Tile], vertices : list[Vertice]): remaining_tiles = tiles.copy() while len(remaining_tiles) > 0: start = remaining_tiles[0] @@ -43,31 +43,43 @@ class Polygon: start = neightbor # Find northern border - north_row = self.find_row_border(row.copy(), DIRECTION.NORTH, remaining_tiles) + 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) + 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) + self.shape.append(area) - remaining_vertices = self.vertices.copy() + 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) + 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) + self.vertices.append(current) current = remaining_vertices.pop() - if len(remaining_vertices) == 0: self.compressed["vertices"].append(current) + if len(remaining_vertices) == 0: self.vertices.append(current) - def find_row_border(self, line : list[Tile], direction : str, remaining_tiles : list[Tile]) -> list[Tile]: + 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: @@ -76,24 +88,14 @@ class Polygon: 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: + 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: + 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: diff --git a/buildings/geometry/Tile.py b/buildings/geometry/Tile.py index 8292d62..f1e2167 100644 --- a/buildings/geometry/Tile.py +++ b/buildings/geometry/Tile.py @@ -7,17 +7,26 @@ 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.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)) @@ -28,18 +37,6 @@ class Tile: 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): @@ -52,7 +49,7 @@ class Tile: case DIRECTION.SOUTH: return self.south_neighbor - def set_neighbor(self, direction, neighbor : Point): + def set_neighbor(self, direction, neighbor : 'Tile'): match(direction): case DIRECTION.WEST: self.west_neighbor = neighbor @@ -61,4 +58,37 @@ class Tile: case DIRECTION.NORTH: self.north_neighbor = neighbor case DIRECTION.SOUTH: - self.south_neighbor = neighbor \ No newline at end of file + 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 index 009a520..df58b40 100644 --- a/buildings/geometry/Vertice.py +++ b/buildings/geometry/Vertice.py @@ -15,4 +15,7 @@ class Vertice(Rectangle): 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/main.py b/main.py index c4ebd18..818d02d 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ from gdpc import Editor, Block, geometry import networks.curve as curve import numpy as np import json -from buildings.Foundations import Foundations +from buildings.Building import Building editor = Editor(buffering=True) @@ -12,11 +12,12 @@ 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) +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)) 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