diff --git a/buildings/Building.py b/buildings/Building.py index 5df0fc1..2b6729b 100644 --- a/buildings/Building.py +++ b/buildings/Building.py @@ -1,30 +1,33 @@ import random as rd -from utils.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]]): + def __init__(self,rdata, position : tuple[int,int], size : tuple[int, int], matrice : list[list[int]], floors : int): self.position = position self.length, self.width = size self.matrice = matrice + self.floors = floors # 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(rdata["foundations"], size, matrice, tile_size,) + self.facade = Facade(rdata["facade"], self.foundations.vertices, self.foundations.is_inner_or_outer) - self.foundations = Foundations(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 build(self, editor, materials : list[str]): + for y in range(self.floors): + with editor.pushTransform((self.position[0], y*(self.foundations.floor_height+1), self.position[1])): + self.foundations.build(editor, materials) + self.facade.build(editor, materials) def gen_tile_size(self) -> int: # Tiles are constant square units different for each buildings + return self.length smaller_side = min(self.length, self.width) # area is too small, will work but not very well + if smaller_side <= 5 : return smaller_side if smaller_side <= 15 : return smaller_side // 5 return rd.randint(3, smaller_side // len(self.matrice)) diff --git a/buildings/Entrance.py b/buildings/Entrance.py new file mode 100644 index 0000000..e69de29 diff --git a/buildings/Facade.py b/buildings/Facade.py index ea53ae7..eab9e98 100644 --- a/buildings/Facade.py +++ b/buildings/Facade.py @@ -1,48 +1,86 @@ import random as rd -from utils.Enums import COLLUMN_STYLE +from utils.functions import * +from utils.Enums import COLLUMN_STYLE,DIRECTION,INTER_FLOOR_BORDER from gdpc import Editor, Block, geometry, Transform from buildings.geometry.Vertice import Vertice +from buildings.geometry.Point import Point from buildings.elements.Window import Window +from buildings.elements.Balcony import Balcony class Facade: - def __init__(self, rdata, vertices : list[Vertice], height : int, length : int, is_inner_or_outer : COLLUMN_STYLE): + def __init__(self, rdata, vertices : list[Vertice], collumn_style : COLLUMN_STYLE): self.rdata = rdata self.vertices = vertices - self.is_inner_or_outer = is_inner_or_outer - self.height = height - self.length = length + self.collumn_style = collumn_style + self.height, self.length = self.get_dimentions() self.padding = 0 self.window = self.get_window() self.has_balcony = self.has_balcony() - self.has_inter_floor = self.has_inter_floor() + self.balcony = self.get_balcony() + self.has_inter_floor, self.inter_floor_border_style = self.has_inter_floor() self.editor, self.materials = None,None def build(self, editor : Editor, materials : list[str]): self.editor = editor self.materials = materials + points = sum([[vertice.point1, vertice.point2] for vertice in self.vertices], []) for vertice in self.vertices: + flip=(vertice.facing == DIRECTION.WEST or vertice.facing == DIRECTION.SOUTH, False, False) vertice.fill(editor, materials[0], self.height, xpadding = self.padding, zpadding = self.padding) - with editor.pushTransform(Transform(vertice.point1.position,rotation = vertice.facing.value)): - self.build_inter_floor(vertice) - self.window.build(editor, vertice.get_len(), self.height, materials) + with editor.pushTransform(Transform(vertice.point1.position,rotation = vertice.facing.value, flip = flip)): + self.window.build(editor, materials) + if self.has_inter_floor: self.build_inter_floor() + if self.has_balcony: self.balcony.build(editor, materials) + self.correct_corners(points,vertice) + + def correct_corners(self,points : list[Point], v : Vertice): + if self.padding == 0: + if self.window.border_radius != 0 and self.window.width == self.length: + if v.point1 in points: + self.editor.placeBlock((0,self.window.ypadding,0), Block(self.materials[8])) + self.editor.placeBlock((0,self.window.ypadding+self.window.height,0), Block(self.materials[8], {"type": "top"})) + if v.point2 in points: + self.editor.placeBlock((self.length-1,self.window.ypadding,0), Block(self.materials[8])) + self.editor.placeBlock((self.length-1,self.window.ypadding+self.window.height,0), Block(self.materials[8], {"type": "top"})) + + if self.has_inter_floor: + material = Block("air") + if self.inter_floor_border_style == INTER_FLOOR_BORDER.SLAB: + material = Block(self.materials[8], {"type": "top"}) + elif self.inter_floor_border_style == INTER_FLOOR_BORDER.STAIRS: + material = Block(self.materials[4], {"facing": "south", "half": "top"}) + + if v.point1 in points: + self.editor.placeBlock((-1,self.height,-1), material) + if v.point2 in points: + self.editor.placeBlock((self.length,self.height,-1), material) + def get_window(self) -> Window: - if self.is_inner_or_outer == COLLUMN_STYLE.OUTER or self.is_inner_or_outer == COLLUMN_STYLE.BOTH: + if self.collumn_style.value >= 2: # collumn_style >= 2 = outer collumns self.padding = 1 max_width = self.length-2*self.padding max_height = min(self.height, self.rdata["windows"]["size"]["max_height"]) - return Window(self.rdata["windows"] ,max_width, max_height) + return Window(self.rdata["windows"] ,max_width, max_height, self.length, self.height) - def build_inter_floor(self, vertice : Vertice): - if self.has_inter_floor: - geometry.placeCuboid(self.editor,(0,self.height,-1),(self.length,self.height,-1),Block(self.materials[4], {"facing": "south", "half": "top"})) + def get_balcony(self) -> Balcony|None: + if not self.has_balcony: return None + max_width = self.length-2*self.padding + return Balcony(self.rdata["balcony"], max_width, self.window, self.collumn_style) + def build_inter_floor(self): + geometry.placeCuboid(self.editor,(self.padding,self.height,0),(self.length-1-self.padding,self.height,0),Block(self.materials[0])) + geometry.placeCuboid(self.editor,(self.padding,self.height,-1),(self.length-1-self.padding,self.height,-1),Block(self.materials[4], {"facing": "south", "half": "top"})) + def has_balcony(self) -> bool: - return self.rdata["balcony"] >= rd.random() + return self.rdata["balcony"]["proba"] >= rd.random() def has_inter_floor(self) -> bool: - return self.rdata["inter_floor"] >= rd.random() + return (self.rdata["inter_floor"]["proba"] >= rd.random(), select_random(self.rdata["inter_floor"]["border_style"], INTER_FLOOR_BORDER)) + + def get_dimentions(self) -> tuple[int]: + return ( self.vertices[0].get_height(), len(self.vertices[0])) \ No newline at end of file diff --git a/buildings/Foundations.py b/buildings/Foundations.py index 51a365e..419f051 100644 --- a/buildings/Foundations.py +++ b/buildings/Foundations.py @@ -1,28 +1,28 @@ import random as rd import numpy as np import math + from utils.Enums import COLLUMN_STYLE +from utils.functions import * 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 + # TODO : gérer les collones sur les tiles trop petites et les colones 1tile/2 def __init__(self, + rdata, size : tuple[int, int], matrice : list[list[int]], - tile_size : int, - is_collumn_full_tile : bool, - is_inner_or_outer : COLLUMN_STYLE): + tile_size : int): # 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 + self.is_inner_or_outer = select_random(rdata["collumn_style"], COLLUMN_STYLE) + self.floor_height = rd.randint(rdata["floor"]["min_height"], rdata["floor"]["max_height"])-1 self.size = size self.length, self.width = size @@ -35,6 +35,17 @@ class Foundations: self.z_distribution = [] self.polygon = self.get_polygon() self.collumns = self.get_columns() + + def build(self, editor, materials : list[str]): + self.polygon.fill(editor, materials[5],0) + self.polygon.fill(editor, materials[6], self.floor_height) + self.build_collumns(editor, materials) + + def build_collumns(self, editor, materials : list[str]): + for collumn in self.collumns: + if collumn.is_outer and self.is_inner_or_outer == COLLUMN_STYLE.INNER: continue + if not collumn.is_outer and self.is_inner_or_outer == COLLUMN_STYLE.OUTER: continue + collumn.fill(editor, materials[7], self.floor_height) def add_tile(self, tile : Tile): self.tiles.append(tile) @@ -42,11 +53,10 @@ class Foundations: def get_polygon(self) -> Polygon: ## The polygon is a shape of tiles representing the foundation shape polygon = Polygon(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]) + self.x_distribution = self.get_distribution(len(self.matrice), self.length_in_tiles) + self.z_distribution = self.get_distribution(len(self.matrice[0]), self.width_in_tiles) # this bullshit is to create tiles from the matrice and the distribution x_padding = 0 @@ -61,7 +71,7 @@ class Foundations: z_padding += zsize * self.tile_size x_padding += xsize * self.tile_size - polygon.set_vertices_and_neighbors(self.tiles, self.vertices) + polygon.set_vertices_and_neighbors(self.tiles, self.vertices, self.floor_height) polygon.compress(self.tiles, self.vertices) return polygon @@ -106,6 +116,7 @@ class Foundations: return sizes def get_columns(self) -> list[Collumn]: + if self.is_inner_or_outer == COLLUMN_STYLE.NONE: return [] collumns = [] for tile in self.tiles: diff --git a/buildings/TODO b/buildings/TODO new file mode 100644 index 0000000..2b34928 --- /dev/null +++ b/buildings/TODO @@ -0,0 +1,13 @@ +Encadrement fenêtre +toit de balcon avec/sans pilliers +border radius balcon +collumn style +rembard object +détails facade +rdc +toit +tiles 3d +textures object +opti textures +opti géométrique +opti gdpc \ No newline at end of file diff --git a/buildings/elements/Balcony.py b/buildings/elements/Balcony.py index 55691ab..f2aeb65 100644 --- a/buildings/elements/Balcony.py +++ b/buildings/elements/Balcony.py @@ -1,4 +1,131 @@ +import random as rd +from gdpc import Editor, Block, geometry +from utils.functions import * +from utils.Enums import BALCONY_BORDER_RADIUS,COLLUMN_STYLE +from buildings.geometry.Point import Point +from buildings.geometry.Vertice import Vertice +from buildings.elements.Window import Window + class Balcony: - def __init__(self, length, width): - self.length = length - self.width = width \ No newline at end of file + def __init__(self, rdata, max_width : int, windows : Window, collumn_style : COLLUMN_STYLE): + self.rdata = rdata + self.windows = windows + self.max_width = max_width + self.collumn_style = collumn_style + self.length = self.get_len() + self.has_multiple = self.has_multiple() + self.has_details = self.has_details() + self.border_radius = self.has_border_radius() + self.follow_window = self.follow_window() + self.structure = self.get_structures() + self.editor, self.materials = None,None + + def build(self, editor : Editor, materials : list[str]): + self.editor = editor + self.materials = materials + for s in self.structure: + s.fill(editor, materials[0]) + self.build_rembard(s) + self.build_details(s) + self.build_border_radius(s) + + def build_rembard(self, s : Vertice): + geometry.placeCuboid(self.editor,(s.point1.x,1,-1),(s.point1.x,1,-self.length),Block(self.materials[3])) + geometry.placeCuboid(self.editor,(s.point2.x,1,-1),(s.point2.x,1,-self.length),Block(self.materials[3])) + geometry.placeCuboid(self.editor,(s.point1.x,1,-self.length),(s.point2.x,1,-self.length),Block(self.materials[3])) + + def build_details(self, s : Vertice): + if not self.has_details: return + geometry.placeCuboid(self.editor,(s.point1.x,0,-1),(s.point1.x,0,-self.length),Block(self.materials[4], {"facing": "east", "half": "top"})) + geometry.placeCuboid(self.editor,(s.point2.x,0,-1),(s.point2.x,0,-self.length),Block(self.materials[4], {"facing": "west", "half": "top"})) + geometry.placeCuboid(self.editor,(s.point1.x,0,-self.length),(s.point2.x,0,-self.length),Block(self.materials[4], {"facing": "south", "half": "top"})) + + def build_border_radius(self, s : Vertice): + if self.border_radius == BALCONY_BORDER_RADIUS.NONE: return + + geometry.placeCuboid(self.editor,(s.point1.x,0,-self.length),(s.point1.x,1,-self.length),Block("air")) + geometry.placeCuboid(self.editor,(s.point2.x,0,-self.length),(s.point2.x,1,-self.length),Block("air")) + self.editor.placeBlock((s.point1.x+1,1,-self.length+1), Block(self.materials[3])) + self.editor.placeBlock((s.point2.x-1,1,-self.length+1), Block(self.materials[3])) + + if self.has_details: + self.editor.placeBlock((s.point1.x,0,-self.length+1), Block(self.materials[4], {"facing": "south", "half": "top"})) + self.editor.placeBlock((s.point1.x+1,0,-self.length), Block(self.materials[4], {"facing": "east", "half": "top"})) + self.editor.placeBlock((s.point2.x,0,-self.length+1), Block(self.materials[4], {"facing": "south", "half": "top"})) + self.editor.placeBlock((s.point2.x-1,0,-self.length), Block(self.materials[4], {"facing": "west", "half": "top"})) + + if self.border_radius == BALCONY_BORDER_RADIUS.FULL: + self.editor.placeBlock((s.point1.x+1,0,-self.length+1), Block(self.materials[4], {"facing": "east", "half": "top"})) + self.editor.placeBlock((s.point2.x-1,0,-self.length+1), Block(self.materials[4], {"facing": "west", "half": "top"})) + + def get_structures(self) -> list[Vertice]: + # structures are the base shape of the balcony + attach_points = self.get_attach_points() + len_attach_points = len(attach_points)-1 + min_wid = self.rdata["size"]["min_width"] + min_gap = self.rdata["multiple"]["min_gap"] + growth_chance = self.rdata["growth"] + midpoint = len_attach_points//2 + x1,x2 = midpoint, len_attach_points - midpoint + + structures = [] + centered = True + while x1 > 0: + x1 -= 1 + x2 += 1 if centered else 0 + leng = attach_points[x2] - attach_points[x1] - 1 + + if x1 == 0: + if leng >= min_wid: self.append_structure(structures, x1, x2, attach_points, len_attach_points, centered) + break + if leng < min_wid: continue + + if growth_chance < rd.random(): + self.append_structure(structures, x1, x2, attach_points, len_attach_points, centered) + + if not self.has_multiple: break + else: + centered = False + if attach_points[x1]-min_wid < min_gap: break + gap = rd.randint(min_gap, attach_points[x1]-min_wid) + x2 = x1-gap + x1 = x2-min_wid+1 + + return structures + + def get_attach_points(self) -> list[int]: + # points where the structures can start/finish + padding = 0 if self.collumn_style.value < 2 else 1 # collumn_style < 2 = no outer collumns + points = [i + padding for i in range(self.max_width)] + if self.follow_window: + pad = self.windows.padding + for w in self.windows.windows: + for i in range(pad+w.x1, pad+w.x2+1): + points.remove(i) + + return points + + def create_structure(self, x1 : int, x2 : int) -> Vertice: + return Vertice(Point(x = x1), Point(x = x2,z = -self.length)) + + def append_structure(self, structures : list[Vertice], x1 : int, x2 : int, attach_points : list[int], len_attach_points : int, centered : bool): + structures.append(self.create_structure(attach_points[x1], attach_points[x2])) + if not centered: + structures.append(self.create_structure(attach_points[len_attach_points-x1], attach_points[len_attach_points-x2])) + + def follow_window(self) -> bool: + return not self.windows.ypadding > 3 + + def has_multiple(self) -> bool: + if self.max_width < self.rdata["multiple"]["min_width"]: return False + return self.rdata["multiple"]["proba"] >= rd.random() + + def has_details(self) -> bool: + return self.rdata["details"] >= rd.random() + + def has_border_radius(self) -> bool: + if self.length < 2: return BALCONY_BORDER_RADIUS.NONE + return select_random(self.rdata["border_radius"], BALCONY_BORDER_RADIUS) + + def get_len(self) -> int: + return rd.randint(self.rdata["size"]["min_len"], self.rdata["size"]["max_len"]) \ No newline at end of file diff --git a/buildings/elements/Collumn.py b/buildings/elements/Collumn.py index 6fd7c1e..40e644d 100644 --- a/buildings/elements/Collumn.py +++ b/buildings/elements/Collumn.py @@ -8,4 +8,7 @@ class Collumn(Rectangle): def set_is_outer(self, is_outer : bool): self.is_outer = is_outer + + def __repr__(self): + return super().__repr__() + f"\nIs outer : {self.is_outer}\n\n" \ No newline at end of file diff --git a/buildings/elements/FacadeDetails.py b/buildings/elements/FacadeDetails.py new file mode 100644 index 0000000..9770cdc --- /dev/null +++ b/buildings/elements/FacadeDetails.py @@ -0,0 +1,19 @@ +from buildings.geometry.Vertice import Vertice + +class FacadeDetails: + def __init__(self,rdata , zones : list[Vertice]): + self.zones = zones + self.sizes = self.get_sizes() + + def get_sizes(self) -> list[tuple[int]]: + # foreach different zone sizes in self.zones, we will gen different details + sizes = [] + center_for_symetry = len(self.zones) // 2 + for zone in self.zones: + size = zone.point2.position - zone.point1.position + if size not in sizes : + sizes.append(size) + + return sizes + + \ No newline at end of file diff --git a/buildings/elements/FacadedetailsElt/Buttons.py b/buildings/elements/FacadedetailsElt/Buttons.py new file mode 100644 index 0000000..3436a95 --- /dev/null +++ b/buildings/elements/FacadedetailsElt/Buttons.py @@ -0,0 +1,3 @@ +class Buttons: + def __init__(self): + pass \ No newline at end of file diff --git a/buildings/elements/FacadedetailsElt/InterFloor.py b/buildings/elements/FacadedetailsElt/InterFloor.py new file mode 100644 index 0000000..e69de29 diff --git a/buildings/elements/FacadedetailsElt/Moldings.py b/buildings/elements/FacadedetailsElt/Moldings.py new file mode 100644 index 0000000..e69de29 diff --git a/buildings/elements/FacadedetailsElt/Pillar.py b/buildings/elements/FacadedetailsElt/Pillar.py new file mode 100644 index 0000000..e69de29 diff --git a/buildings/elements/Window.py b/buildings/elements/Window.py index 4d211da..75d355e 100644 --- a/buildings/elements/Window.py +++ b/buildings/elements/Window.py @@ -1,39 +1,61 @@ import random as rd import math from gdpc import Editor, Block, geometry, Transform -from utils.Enums import COLLUMN_STYLE, BORDER_RADIUS +from utils.Enums import WINDOW_BORDER_RADIUS from utils.functions import * from buildings.geometry.Point import Point from buildings.geometry.Vertice import Vertice +from buildings.elements.WindowElt.Glass import Glass class Window: - def __init__(self, rdata, max_width : int, max_height : int): + def __init__(self, rdata, max_width : int, max_height : int, facade_len : int, facade_height : int): self.rdata = rdata self.width, self.height = self.get_size(max_width, max_height) self.is_grounded = self.is_grounded() - self.has_multiple_windows = self.has_multiple_windows() self.is_alternate = self.is_alternate() - self.has_vertical_crossbar, self.has_horizontal_crossbar = self.has_crossbars() self.border_radius = self.border_radius() - self.padding = 0 + self.has_multiple = self.has_multiple_windows() + self.has_vertical_crossbar, self.has_horizontal_crossbar = self.has_crossbars() + self.padding, self.ypadding = self.get_padding(facade_len, facade_height) + self.windows = self.get_windows() self.editor, self.materials = None,None - def build(self, editor : Editor, facade_len : int, facade_height : int, materials : list[str]): + def build(self, editor : Editor, materials : list[str]): self.editor = editor self.materials = materials + with editor.pushTransform(Transform((self.padding,self.ypadding,0))): + for g in self.windows: + leng = len(g) + g.build(editor, materials[1], materials[2]) + self.build_crossbars(g.x1, g.x2, leng) + if leng > 1: self.build_border_radius(g.x1, g.x2) + + def build_crossbars(self, x1 : int, x2 : int, len : int): + if self.has_vertical_crossbar and self.height+1 >= self.rdata["crossbars"]["min_height_for_vertical_crossbar"]: + y = self.height//2 + geometry.placeCuboid(self.editor,(x1,y,0),(x2,y,0),Block(self.materials[3])) + if self.has_horizontal_crossbar and len >= self.rdata["crossbars"]["min_width_for_horizontal_crossbar"]: + x = len//2 + geometry.placeCuboid(self.editor,(x1+x,0,0),(x2-x,self.height,0),Block(self.materials[3], {"up" : "true"})) + + def build_border_radius(self, x1 : int, x2 : int): + if self.border_radius != WINDOW_BORDER_RADIUS.NONE: + self.editor.placeBlock((x1,self.height,0),Block(self.materials[4], {"facing": "west", "half": "top"})) + self.editor.placeBlock((x2,self.height,0),Block(self.materials[4], {"facing": "east", "half": "top"})) + if self.border_radius == WINDOW_BORDER_RADIUS.TOP_AND_BOTTOM: + self.editor.placeBlock((x1,0,0),Block(self.materials[4], {"facing": "west"})) + self.editor.placeBlock((x2,0,0),Block(self.materials[4], {"facing": "east"})) + + def get_windows(self) -> list[Glass]: + windows = [] + if not self.has_multiple: windows = [Glass(0,self.width-1,[self.create_window(0, self.width)])] + else: windows = self.get_multiple_windows() + if self.is_alternate: self.alternate(windows) - # correction to avoid asymetry - self.padding = (facade_len - self.width)//2 - self.width = facade_len - self.padding*2 + return windows - if not self.is_grounded: editor.transform @= Transform((0,(facade_height-self.height)//2,0)) - editor.transform @= Transform((self.padding,0,0)) - - if self.has_multiple_windows: self.build_multiple_windows() - else : - self.place_glasses(0, self.width) - - def build_multiple_windows(self): + def get_multiple_windows(self) -> list[Glass]: + windows = [] slices = rd.randint(3, self.width//self.rdata["size"]["min_width"]) mid = math.ceil(slices/2) windows_count = mid @@ -44,63 +66,43 @@ class Window: is_even= slices % 2 == 0 is_window, gap = True, 0 remainder = self.width - (window_size*windows_count + inter_size*inter_count) + + if windows_count % 2 == 1 and inter_count % 2 == 1: + inter_count -= 1 + remainder += inter_size + is_even = not is_even + for i in range(1,slices+1): wsize,isize = window_size, inter_size if is_even and i == mid: wsize, isize = wsize*2, isize*2 if i == mid: wsize, isize = wsize + remainder, isize + remainder - # kepp a spacing between windows, "is revert" is used to keep symetry if is_window: - self.place_glasses(gap, gap+wsize) + windows.append(Glass(gap, gap+wsize-1,[self.create_window(gap, wsize)])) gap += wsize else : gap += isize is_window = not is_window - - def place_glasses(self, x1 : int, x2 : int): - len = x2 - x1 - if self.is_alternate: - mid = x1 + len//2 - - is_block, is_even = False, len % 2 == 0 - for x in range(x1,x2): - if is_even and x == mid: is_block = not is_block # to keep symetry - id = 1 if not is_block else 2 - geometry.placeCuboid(self.editor,(x,0,0),(x,self.height,0),Block(self.materials[id])) - is_block = not is_block - - else: - geometry.placeCuboid(self.editor,(x1,0,0),(x2-1,self.height,0),Block(self.materials[1])) - - self.build_crossbars(x1, x2-1, len) - if len > 1: self.build_border_radius(x1, x2-1) - def get_size(self, max_width : int ,max_height : int) -> tuple[int,int]: - return ( - rd.randint(self.rdata["size"]["min_width"],max_width), - rd.randint(self.rdata["size"]["min_height"],max_height) - ) - - def build_crossbars(self, x1 : int, x2 : int, len : int): - if self.has_vertical_crossbar and self.height >= self.rdata["crossbars"]["min_height_for_vertical_crossbar"]: - y = self.height//2 - geometry.placeCuboid(self.editor,(x1,y,0),(x2,y,0),Block(self.materials[3])) - if self.has_horizontal_crossbar and len >= self.rdata["crossbars"]["min_width_for_horizontal_crossbar"]: - x = len//2 - geometry.placeCuboid(self.editor,(x1+x,0,0),(x2-x,self.height,0),Block(self.materials[3], {"up" : "true"})) + return windows + + def alternate(self, windows : list[Glass]): + for g in windows: + g.reset_groups() + leng = len(g) + mid = g.x1 + leng//2 - def build_border_radius(self, x1 : int, x2 : int): - if self.border_radius != BORDER_RADIUS.NONE: - self.editor.placeBlock((x1,self.height,0),Block(self.materials[4], {"facing": "west", "half": "top"})) - self.editor.placeBlock((x2,self.height,0),Block(self.materials[4], {"facing": "east", "half": "top"})) - if self.border_radius == BORDER_RADIUS.TOP_AND_BOTTOM: - self.editor.placeBlock((x1,0,0),Block(self.materials[4], {"facing": "west"})) - self.editor.placeBlock((x2,0,0),Block(self.materials[4], {"facing": "east"})) - - def is_grounded(self): - # if the window is grounded or if there is a padding between the window and the ground - return self.rdata["grounded"] >= rd.random() + is_block, is_even = False, leng % 2 == 0 + for x in range(g.x1,g.x2+1): + if is_even and x == mid: is_block = not is_block # to keep symetry + if is_block: g.group2.append(self.create_window(x)) + else : g.group1.append(self.create_window(x)) + is_block = not is_block + + def create_window(self, x1 : int, length : int = None) -> Vertice: + x2 = x1 if length is None else x1 + length -1 + return Vertice(Point(x = x1), Point(x2,self.height)) def has_multiple_windows(self): if self.width > self.rdata["size"]["max_width"]: return True @@ -112,6 +114,27 @@ class Window: # if the window alternate between glass_blocks and glass_panes return self.rdata["alternate"] >= rd.random() + + def get_size(self, max_width : int ,max_height : int) -> tuple[int,int]: + return ( + rd.randint(self.rdata["size"]["min_width"],max_width), + rd.randint(self.rdata["size"]["min_height"],max_height) + ) + + def get_padding(self, facade_len : int, facade_height : int) -> tuple[int]: + padding,ypadding = 0,0 + if not self.is_grounded: ypadding = (facade_height - self.height)//2 + + # correction to avoid asymetry + padding = (facade_len - self.width)//2 + self.width = facade_len - padding*2 + + return (padding, ypadding) + + def is_grounded(self): + # if the window is grounded or if there is a padding between the window and the ground + return self.rdata["grounded"] >= rd.random() + def has_crossbars(self): # if the window has crossbars data = self.rdata["crossbars"] @@ -119,4 +142,4 @@ class Window: return (data["vertical_crossbar"] >= rd.random(), data["horizontal_crossbar"] >= rd.random()) def border_radius(self): - return select_random(self.rdata["border_radius"], BORDER_RADIUS) \ No newline at end of file + return select_random(self.rdata["border_radius"], WINDOW_BORDER_RADIUS) \ No newline at end of file diff --git a/buildings/elements/WindowElt/Glass.py b/buildings/elements/WindowElt/Glass.py new file mode 100644 index 0000000..e9f40ee --- /dev/null +++ b/buildings/elements/WindowElt/Glass.py @@ -0,0 +1,22 @@ +from gdpc import Editor +from buildings.geometry.Vertice import Vertice + +class Glass: + def __init__(self, x1 : int, x2 : int, group1 : list[Vertice], group2 : list[Vertice] = None): + self.x1, self.x2 = x1, x2 + self.group1, self.group2 = group1, group2 + + + def build(self, editor : Editor, material1 : str, material2 : str): + for elt in self.group1: + elt.fill(editor, material1) + if self.group2 is None: return + for elt in self.group2: + elt.fill(editor, material2) + + def reset_groups(self): + self.group1, self.group2 = [], [] + + def __len__(self): + return self.x2 - self.x1 + 1 + \ No newline at end of file diff --git a/buildings/geometry/Point.py b/buildings/geometry/Point.py index b8020ec..88c6be7 100644 --- a/buildings/geometry/Point.py +++ b/buildings/geometry/Point.py @@ -1,14 +1,17 @@ class Point: - def __init__(self, x : int = None, y : int = None, z : int = None, p : tuple[int] = None): + def __init__(self, x : int = 0, y : int = 0, z : int = 0, 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): + def set_position(self, x : int = 0, y : int = 0, z : int = 0, 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 + self.position = (self.x,self.y,self.z) + + def __repr__(self): + return f"Point({self.position})" \ No newline at end of file diff --git a/buildings/geometry/Polygon.py b/buildings/geometry/Polygon.py index 4783523..4afb6b8 100644 --- a/buildings/geometry/Polygon.py +++ b/buildings/geometry/Polygon.py @@ -1,5 +1,5 @@ from utils.Enums import DIRECTION -from gdpc import Editor, Block, geometry +from gdpc import Editor, Block, geometry, Transform from buildings.geometry.Tile import Tile from buildings.geometry.Point import Point from buildings.geometry.Rectangle import Rectangle @@ -11,10 +11,11 @@ class Polygon: self.shape = [] self.vertices = [] - def fill_polygon(self, editor : Editor, material : str, y : int, y2 : int = None): - if y2 == None: y2 = y + def fill(self, editor : Editor, material : str, y : int = 0, y2 : int = None): + if y2 == None: y2 = 0 for rect in self.shape: - rect.fill(editor, material, y, y2) + with editor.pushTransform(Transform((0,y,0))): + rect.fill(editor, material, y2) def fill_vertice(self, editor : Editor, material : str, y : int, y2 : int = None): if y2 == None: y2 = y @@ -66,7 +67,7 @@ class Polygon: if len(remaining_vertices) == 0: self.vertices.append(current) - def set_vertices_and_neighbors(self, tiles : list[Tile], vertices : list[Vertice]): + def set_vertices_and_neighbors(self, tiles : list[Tile], vertices : list[Vertice], height : int): for tile in tiles: targets = tile.get_neighbors_coords() for vertice_num,target in enumerate(targets): @@ -74,7 +75,7 @@ class Polygon: if not has_neighbor: vertice = tile.get_vertice(vertice_num) vertices.append(vertice) - tile.set_vertice(DIRECTION(vertice_num), vertice) + tile.set_vertice(DIRECTION(vertice_num), vertice, height) else : tile.set_neighbor(vertice_num, has_neighbor) diff --git a/buildings/geometry/Rectangle.py b/buildings/geometry/Rectangle.py index a199982..47a7a4f 100644 --- a/buildings/geometry/Rectangle.py +++ b/buildings/geometry/Rectangle.py @@ -9,8 +9,15 @@ class Rectangle: def get_position(self): return (self.point1.position, self.point2.position) + def get_height(self): + return self.point2.y - self.point1.y + def fill(self,editor : Editor, material : str, y : int = None, xpadding : int = 0, zpadding : int = 0): if self.point2.x - self.point1.x < 2*xpadding: xpadding = 0 if self.point2.z - self.point1.z < 2*zpadding: zpadding = 0 + if y is None: y = self.point2.y geometry.placeCuboid(editor, (self.point1.x+xpadding, 0, self.point1.z+zpadding), (self.point2.x-xpadding, y, self.point2.z-zpadding), Block(material)) + + def __repr__(self): + return f"{type(self).__name__}\n1 : {str(self.point1)},\n2 : {str(self.point2)}" \ No newline at end of file diff --git a/buildings/geometry/Tile.py b/buildings/geometry/Tile.py index 45f6370..37c5059 100644 --- a/buildings/geometry/Tile.py +++ b/buildings/geometry/Tile.py @@ -32,9 +32,9 @@ class Tile: def get_neighbors_coords(self): return [Point(x = self.pos.x, z = self.pos.z - self.size), # north - 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), # south - Point(x = self.pos.x + self.size, z = self.pos.z)] # east + Point(x = self.pos.x - self.size, z = self.pos.z)] # west def get_neighbor(self, direction) -> Point: @@ -80,8 +80,9 @@ class Tile: case DIRECTION.SOUTH : return self.south_vertice - def set_vertice(self, direction : DIRECTION, vertice : Vertice): + def set_vertice(self, direction : DIRECTION, vertice : Vertice, height : int): self.has_vertice = True + vertice.point2.y = height match(direction): case DIRECTION.WEST : self.west_vertice = vertice diff --git a/buildings/geometry/Vertice.py b/buildings/geometry/Vertice.py index c576af5..ca26622 100644 --- a/buildings/geometry/Vertice.py +++ b/buildings/geometry/Vertice.py @@ -3,7 +3,7 @@ from buildings.geometry.Point import Point from buildings.geometry.Rectangle import Rectangle class Vertice(Rectangle): - def __init__(self, point1 : Point, point2 : Point, facing : DIRECTION): + def __init__(self, point1 : Point, point2 : Point, facing : DIRECTION = None): Rectangle.__init__(self, point1, point2) self.facing = facing @@ -15,7 +15,10 @@ 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_len(self): + + def __len__(self): return self.point2.x - self.point1.x + self.point2.z - self.point1.z + 1 + + def __repr__(self): + return super().__repr__() + f"\nFacing : {self.facing} \n\n" \ No newline at end of file diff --git a/main.py b/main.py index 9dd676e..45a620d 100644 --- a/main.py +++ b/main.py @@ -5,11 +5,6 @@ from utils.JsonReader import JsonReader from utils.YamlReader import YamlReader from buildings.Building import Building -from buildings.geometry.Vertice import Vertice -from buildings.geometry.Point import Point -from utils.Enums import DIRECTION,COLLUMN_STYLE,BORDER_RADIUS -from buildings.Facade import Facade - from utils.functions import * editor = Editor(buffering=True) @@ -20,32 +15,23 @@ shapes = f.data y = YamlReader('params.yml') random_data = y.data +# transform = Transform((0,-60,-20),rotation = 0) +# editor.transform.push(transform) +# for i in range(4): +# with editor.pushTransform(Transform(rotation = i)): +# geometry.placeCuboid(editor, (0,0,0), (0,3,5), Block("stone")) -transform = Transform((0,-60,-5),rotation = 0) +transform = Transform((0,-60,80),rotation = 0) editor.transform.push(transform) -geometry.placeCuboid(editor, (0,0,-1), (100,15,1), Block("air")) +geometry.placeCuboid(editor, (-5,0,-8), (170,25,25), Block("air")) -x = 0 -facade = [] -for i in range(3,13): - facade.append(Facade(random_data["buildings"]["facade"],[Vertice(Point(x,0,0), Point(x+i,0,0), DIRECTION.NORTH)],i,i,COLLUMN_STYLE.NONE)) - x += i+2 - -for f in facade: - f.build(editor, ["stone_bricks","glass_pane","glass","cobblestone_wall","stone_brick_stairs"]) - - -# 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) +padd = 0 +for i in range(4,13): + building = Building(random_data["buildings"], (padd, 0), (i,i), shapes[0]['matrice'], 3) + building.build(editor, ["stone_bricks","glass_pane","glass","cobblestone_wall","stone_brick_stairs","oak_planks","white_concrete","cobblestone","stone_brick_slab"]) + padd += i + 10 diff --git a/params.yml b/params.yml index 7cd9afc..6ed391a 100644 --- a/params.yml +++ b/params.yml @@ -4,7 +4,7 @@ buildings: min_tile_size: 3 max_tile_size: 12 - foudations: + foundations: collumn_style : # proportion of each style none: 1 @@ -16,6 +16,7 @@ buildings: max_height: 7 facade: + windows: size: min_height: 2 @@ -39,5 +40,39 @@ buildings: none: 2 top: 1 top_and_bottom: 1 - inter_floor: 0.5 - balcony: 0.25 \ No newline at end of file + + balcony: + proba : 0.25 + growth: 0.5 # [growth]% chance to have min_width + 1 balcony length, [growth**2]% chance to have min_width + 2 balcony length, etc + size: + min_len : 1 + max_len : 3 + min_width : 3 + multiple: + # probability to have multiple balcony IF POSSIBLE + # this feature need a very large facade + proba: 1 + min_width: 5 + min_gap: 1 + details: 0.35 + border_radius: + # proportion of each style + none: 6 + # no difference if there is no details + medium: 1 + full: 1 + + Entrance: + centered: 0.8 + different_facade: 0.75 + size: + min_height: 5 + max_height: 9 + + inter_floor: + proba: 0.5 + border_style: + # bloc used to fill the corner of the interfloor + none: 1 + slab: 2 + stairs: 2 \ No newline at end of file diff --git a/utils/Enums.py b/utils/Enums.py index 4da1c62..a40fcbe 100644 --- a/utils/Enums.py +++ b/utils/Enums.py @@ -2,9 +2,9 @@ from enum import Enum class DIRECTION(Enum): NORTH = 0 - WEST = 1 + EAST = 1 SOUTH = 2 - EAST = 3 + WEST = 3 class COLLUMN_STYLE(Enum): NONE = 0 @@ -12,7 +12,17 @@ class COLLUMN_STYLE(Enum): OUTER = 2 BOTH = 3 -class BORDER_RADIUS(Enum): +class WINDOW_BORDER_RADIUS(Enum): NONE = 0 TOP = 1 - TOP_AND_BOTTOM = 2 \ No newline at end of file + TOP_AND_BOTTOM = 2 + +class BALCONY_BORDER_RADIUS(Enum): + NONE = 0 + MEDIUM = 1 + FULL = 2 + +class INTER_FLOOR_BORDER(Enum): + NONE = 0 + SLAB = 1 + STAIRS = 2 \ No newline at end of file