Merge pull request #5 from KAymeric/main

Buildings v1
This commit is contained in:
Xeon0X
2024-06-14 14:10:38 +02:00
committed by GitHub
28 changed files with 837 additions and 118 deletions

View File

@@ -1,12 +0,0 @@
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

View File

@@ -1,30 +1,42 @@
import random as rd
from Enums import COLLUMN_STYLE
from utils.Enums import DIRECTION
from gdpc import Editor, Block, geometry
from buildings.Foundations import Foundations
from buildings.Facade import Facade
from buildings.Entrance import Entrance
from buildings.Roof import Roof
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.entrance = Entrance(rdata, self.foundations.vertices, DIRECTION.EAST, self.foundations.is_inner_or_outer)
self.roof = Roof(rdata["roof"], self.foundations.polygon)
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 build(self, editor : Editor, materials : list[str]):
for y in range(self.floors+1):
with editor.pushTransform((self.position[0], y*(self.foundations.floor_height+1) -1, self.position[1])):
if y == self.floors:
self.roof.build(editor, materials)
break
self.foundations.build(editor, materials)
if y == 0: self.entrance.build(editor, materials)
else : self.facade.build(editor, materials)
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 <= 5 : return smaller_side
if smaller_side <= 15 : return smaller_side // 5
return rd.randint(3, smaller_side // len(self.matrice))

114
buildings/Entrance.py Normal file
View File

@@ -0,0 +1,114 @@
import random as rd
from gdpc import Editor, Block, geometry
from utils.Enums import DIRECTION,COLLUMN_STYLE
from buildings.geometry.Vertice import Vertice
from buildings.Facade import Facade
class Entrance:
def __init__(self,
rdata,
vertices : list[Vertice],
direction : DIRECTION,
collumn_style : COLLUMN_STYLE):
self.vertices = self.correct_vertices(vertices)
self.direction = direction
self.rdata = rdata
self.collumn_style = collumn_style
self.is_centered = self.is_centered()
self.door_vertice, self.facade = self.get_door_and_facade()
self.door_width, self.door_height, self.padding, self.ypadding = self.get_door_dimention()
self.editor, self.materials = None,None
def build(self, editor : Editor, materials : list[str]):
self.editor = editor
self.materials = materials
self.correct_facade()
with self.editor.pushTransform((0,1,0)):
self.facade.build(self.editor, self.materials)
self.build_door()
def build_door(self):
# self.padding is the padding from the door to the facade, padding is the padding from the door+self.padding to the end of the vertice
padding = (len(self.door_vertice) - (self.padding*2 + self.door_width // 2)) // 2
self.door_vertice.fill(self.editor, self.materials[0],
y = self.door_height+self.ypadding,
xpadding = padding,
zpadding = padding)
# padding is now the padding from the door to the end of the vertice
padding += self.padding
self.door_vertice.fill(self.editor, "air",
y = self.door_height,
xpadding = padding,
zpadding = padding)
def correct_facade(self):
self.facade.has_balcony = False
def correct_vertices(self, vertices : list[Vertice]) -> list[Vertice]:
for v in vertices:
v.point2.set_position(y=v.point2.y-1)
return vertices
def is_centered(self) -> bool:
return rd.random() <= self.rdata["entrance"]["centered"]
def get_door_and_facade(self) -> tuple[Vertice, Facade]:
oriented_vertices = self.get_oriented_vertices()
door_vertice = None
if self.is_centered:
oriented_vertices.sort(key = lambda v: v.point1.x if self.direction.value % 2 == 0 else v.point1.z) # if direction is north or south, sort by x, else sort by z
mid = len(oriented_vertices) // 2
ver1, ver2 = oriented_vertices[mid], oriented_vertices[-mid-1]
if ver1.point1.x != ver2.point1.x and ver1.point1.z != ver2.point1.z:
door_vertice = rd.choice([ver1, ver2])
elif ver1.point1.position == ver2.point1.position:
door_vertice = ver1
else :
door_vertice = Vertice(ver2.point1.copy(), ver1.point2.copy())
else:
door_vertice = rd.choice(oriented_vertices)
facade = Facade(self.rdata["facade"], self.vertices, self.collumn_style)
return(door_vertice, facade)
def get_oriented_vertices(self) -> list[Vertice]:
# Get all the vertice that can contain the door
# if direction is north or south, compare by x, else compare by z
compare = lambda v: (v.point1.z,v.point1.x) if self.direction.value % 2 == 0 else (v.point1.x,v.point1.z)
# if direction is north or west, the most off_centered is the maximum, else it is the minimum
off_centered = lambda p1,p2: max(p1,p2) if self.direction == DIRECTION.NORTH or self.direction == DIRECTION.WEST else min(p1,p2)
oriented_vertices = []
for v in self.vertices:
if v.facing != self.direction: continue
sortby,position = compare(v)
alreadyset = False
for ov in oriented_vertices:
ov_sorted, ov_position = compare(ov)
if position == ov_position:
if off_centered(sortby,ov_sorted) == sortby: oriented_vertices.remove(ov)
else: alreadyset = True
if not alreadyset: oriented_vertices.append(v)
return oriented_vertices
def get_door_dimention(self) -> tuple[int,int,int,int]: # return width, height, padding, ypadding
max_width = len(self.door_vertice) - 2
max_height = self.door_vertice.get_height() - 1
door_width = rd.randint(self.rdata["entrance"]["door"]["size"]["min_width"], self.rdata["entrance"]["door"]["size"]["max_width"])
door_height = rd.randint(self.rdata["entrance"]["door"]["size"]["min_height"], self.rdata["entrance"]["door"]["size"]["max_height"])
xpadding = rd.randint(1, self.rdata["entrance"]["door"]["padding"]["max"])
ypadding = rd.randint(1, self.rdata["entrance"]["door"]["padding"]["max_top"])
if door_width > max_width: door_width = max_width
if door_height > max_height: door_height = max_height
if xpadding*2 + door_width > max_width: xpadding += (max_width - (xpadding*2 + door_width)-1)//2
if ypadding + door_height > max_height: ypadding += max_height - (ypadding + door_height)
return door_width,door_height,xpadding,ypadding

View File

@@ -1,29 +1,89 @@
from Enums import COLLUMN_STYLE
import random as rd
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.Rectangle import Rectangle
from buildings.geometry.Point import Point
from buildings.elements.Window import Window
from buildings.elements.Balcony import Balcony
class Facade:
def __init__(self, vertices : list[Vertice], height : 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.window_size = self.get_window_size()
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_facade(self):
pass
def get_window_size(self) -> tuple[int,int]:
pass
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, 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 points.count(v.point1) >= 2:
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 points.count(v.point2) >= 2:
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 points.count(v.point1) >= 2:
self.editor.placeBlock((-1,self.height,-1), material)
if points.count(v.point2) >= 2:
self.editor.placeBlock((self.length,self.height,-1), material)
def get_window(self) -> Window:
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, self.length, self.height)
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:
pass
return self.rdata["balcony"]["proba"] >= rd.random()
def has_inter_floor(self) -> bool:
pass
return (self.rdata["inter_floor"]["proba"] >= rd.random(), select_random(self.rdata["inter_floor"]["border_style"], INTER_FLOOR_BORDER))
def get_window(self) -> Window:
pass
def get_dimentions(self) -> tuple[int,int]:
return ( self.vertices[0].get_height(), len(self.vertices[0]))

View File

@@ -1,35 +1,31 @@
import random as rd
import numpy as np
import math
from Enums import COLLUMN_STYLE
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,
position : tuple[int,int],
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
x,z = position
self.position = Point(x = x, z = z)
self.size = size
self.length = size[0]
self.width = size[1]
self.length, self.width = size
self.matrice = matrice
self.tiles = []
self.vertices = []
@@ -39,23 +35,33 @@ 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+1)
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)
polygon = Polygon(self.size)
# 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 = self.position.x
x_padding = 0
for x,xsize in enumerate(self.x_distribution):
z_padding = self.position.z
z_padding = 0
for z,zsize in enumerate(self.z_distribution):
if self.matrice[x][z] == 1:
for xi in range(xsize):
@@ -65,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
@@ -110,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:
@@ -137,8 +144,7 @@ class Foundations:
if collumn.point1.position == compare.point1.position :
if compare.is_outer : collumn.set_is_outer(True)
collumns.remove(compare)
print(len(collumns))
return collumns

15
buildings/Roof.py Normal file
View File

@@ -0,0 +1,15 @@
import random as rd
from buildings.geometry.Polygon import Polygon
class Roof:
def __init__(self,rdata, polygon : Polygon):
self.rdata = rdata
self.polygon = polygon
self.has_rembard = self.has_rembard()
def build(self, editor, materials : list[str]):
self.polygon.fill(editor, materials[0])
if self.has_rembard: self.polygon.fill_vertice(editor, materials[9],1)
def has_rembard(self):
return rd.random() <= self.rdata["rembard"]

20
buildings/TODO Normal file
View File

@@ -0,0 +1,20 @@
Encadrement fenêtre
toit de balcon avec/sans pilliers
collumn style
rembard object
détails facade
rdc
toit (clim, chateau deau, pubs)
tiles 3d
textures object
opti textures
opti géométrique
opti gdpc
pilliers quand trop de fenêtres + pas de pilliers si tile trop petite
limitateur taille
facade lisses/ immeubles collés
matrices pré-distribués
angles 270
bug entrée au milieu du O
bug entrée dans le pillier
center le building dans son area (ou pas)

View File

@@ -0,0 +1,135 @@
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,
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"])

View File

@@ -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"

View File

@@ -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,int,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

View File

@@ -0,0 +1,3 @@
class Buttons:
def __init__(self):
pass

View File

@@ -1,10 +1,150 @@
import random as rd
import math
from gdpc import Editor, Block, geometry, Transform
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, size : tuple[int,int]):
self.size = size
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.is_alternate = self.is_alternate()
self.border_radius = self.border_radius()
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, 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 open(self):
pass
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)
return windows
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
inter_count = slices - windows_count
window_size = rd.randint(self.rdata["size"]["min_width"], (self.width-inter_count) // windows_count)
inter_size = (self.width - window_size*windows_count) // inter_count
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
if is_window:
windows.append(Glass(gap, gap+wsize-1,[self.create_window(gap, wsize)]))
gap += wsize
else :
gap += isize
is_window = not is_window
return windows
def close(self):
pass
def alternate(self, windows : list[Glass]):
for g in windows:
g.reset_groups()
leng = len(g)
mid = g.x1 + leng//2
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
elif self.width >= self.rdata["multiple"]["min_width"]:
return self.rdata["multiple"]["proba"] >= rd.random()
else : return False
def is_alternate(self):
# 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,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"]
return (data["vertical_crossbar"] >= rd.random(), data["horizontal_crossbar"] >= rd.random())
def border_radius(self):
return select_random(self.rdata["border_radius"], WINDOW_BORDER_RADIUS)

View File

@@ -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

View File

@@ -1,14 +1,20 @@
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,int,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 = None, y : int = None, z : int = None, p : tuple[int,int,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)
self.position = (self.x,self.y,self.z)
def __repr__(self):
return f"Point({self.position})"
def copy(self) -> 'Point':
return Point(self.x, self.y, self.z)

View File

@@ -1,26 +1,27 @@
from Enums import DIRECTION
from gdpc import Editor, Block, geometry
from utils.Enums import DIRECTION
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
from buildings.geometry.Vertice import Vertice
class Polygon:
def __init__(self, position : Point, size: tuple[int,int]):
self.position = position
def __init__(self, size: tuple[int,int]):
self.size = size
self.shape = []
self.vertices = []
def fill_polygon(self, editor : Editor, material : str, y : int, y2 : int = None):
if y2 == None: y2 = y
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
if y2 == None: y2 = 0
for vertice in self.vertices:
vertice.fill(editor, Block(material), y, y2)
with editor.pushTransform(Transform((0,y,0))):
vertice.fill(editor, material, y2)
def compress(self, tiles : list[Tile], vertices : list[Vertice]):
remaining_tiles = tiles.copy()
@@ -58,16 +59,16 @@ class Polygon:
has_next2 = self._has_next(neighbors[1], current.facing, remaining_vertices)
if has_next1:
current = Vertice(has_next1.point1, current.point2, current.facing)
current = Vertice(has_next1.point1.copy(), current.point2.copy(), current.facing)
elif has_next2:
current = Vertice(current.point1, has_next2.point2, current.facing)
current = Vertice(current.point1.copy(), has_next2.point2.copy(), current.facing)
else:
self.vertices.append(current)
current = remaining_vertices.pop()
if len(remaining_vertices) == 0: self.vertices.append(current)
def set_vertices_and_neighbors(self, tiles : list[Tile], vertices : list[Vertice]):
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):
@@ -75,7 +76,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)
@@ -89,7 +90,7 @@ class Polygon:
for tile in new_line: remaining_tiles.remove(tile)
line = new_line
def _has_neighbor(self, target : tuple[int], tiles : list[Tile]) -> bool|Tile:
def _has_neighbor(self, target : Point, tiles : list[Tile]) -> bool|Tile:
for tile in tiles:
if tile.pos.position == target.position:
return tile

View File

@@ -9,6 +9,15 @@ class Rectangle:
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))
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)}"

View File

@@ -1,5 +1,5 @@
from gdpc import Editor, Block, geometry
from Enums import DIRECTION
from utils.Enums import DIRECTION
from buildings.geometry.Point import Point
from buildings.geometry.Vertice import Vertice
@@ -27,15 +27,14 @@ class Tile:
self.north_vertice = None
self.south_vertice = None
def fill(self, editor : Editor, material : str, y : int, y2 : int = None) -> list[Point]:
if y2 == None: y2 = y
geometry.placeCuboid(editor, (self.pos.x, y, self.pos.z), (self.pos.x+self.size-1, y2, self.pos.z+self.size-1), Block(material))
def fill(self, editor : Editor, material : str, y : int = 0) -> list[Point]:
geometry.placeCuboid(editor, (self.pos.x, 0, self.pos.z), (self.pos.x+self.size-1, y, 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
return [Point(x = self.pos.x, z = self.pos.z - self.size), # north
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
Point(x = self.pos.x, z = self.pos.z + self.size), # south
Point(x = self.pos.x - self.size, z = self.pos.z)] # west
def get_neighbor(self, direction) -> Point:
@@ -62,16 +61,16 @@ class Tile:
def get_vertice(self,vertice : int|DIRECTION) -> Vertice:
# gives the corresponding vertice :
# 0 = west, 1 = east, 2 = north, 3 = south
# 0 = north, 1 = east, 2 = south, 3 = west
match(vertice):
case 0 :
return Vertice(self.north_west, self.south_west, DIRECTION.WEST)
return Vertice(self.north_west.copy(), self.north_east.copy(), DIRECTION.NORTH)
case 1 :
return Vertice(self.north_east, self.south_east, DIRECTION.EAST)
return Vertice(self.north_east.copy(), self.south_east.copy(), DIRECTION.EAST)
case 2 :
return Vertice(self.north_west, self.north_east, DIRECTION.NORTH)
return Vertice(self.south_west.copy(), self.south_east.copy(), DIRECTION.SOUTH)
case 3 :
return Vertice(self.south_west, self.south_east, DIRECTION.SOUTH)
return Vertice(self.north_west.copy(), self.south_west.copy(), DIRECTION.WEST)
case DIRECTION.WEST :
return self.west_vertice
case DIRECTION.EAST :
@@ -81,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.set_position(y = height)
match(direction):
case DIRECTION.WEST :
self.west_vertice = vertice

View File

@@ -1,9 +1,9 @@
from Enums import DIRECTION
from utils.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):
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_size(self):
return self.point2.x - self.point1.x + self.point2.z - self.point1.z
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"

44
main.py
View File

@@ -1,28 +1,44 @@
from gdpc import Editor, Block, geometry
from gdpc import Editor, Block, geometry, Transform
import networks.curve as curve
import numpy as np
import json
from utils.JsonReader import JsonReader
from utils.YamlReader import YamlReader
from buildings.Building import Building
from utils.functions import *
editor = Editor(buffering=True)
f = open('buildings\shapes.json')
shapes = json.load(f)
# F = Foundations((0,0), (20,20), shapes[0]['matrice'])
# F.polygon.fill_polygon(editor, "stone", -60)
geometry.placeCuboid(editor, (-10,-60,-10), (85,-55,85), Block("air"))
B = Building((0,0), (75,75), shapes[7]['matrice'])
B.foundations.polygon.fill_vertice(editor, "pink_wool", -60)
for collumn in B.foundations.collumns:
collumn.fill(editor, "white_concrete", -60, -55)
B.foundations.polygon.fill_polygon(editor, "white_concrete", -60)
# get every differents buildings shapes
f = JsonReader('buildings\shapes.json')
shapes = f.data
# get the random data for the buildings
y = YamlReader('params.yml')
random_data = y.data
#move your editor to the position you wanna build on
transform = Transform((0,-60,110),rotation = 0)
editor.transform.push(transform)
# clear the area you build on
geometry.placeCuboid(editor, (-5,0,-8), (25,100,25), Block("air"))
# create a building at the relative position 0,0 with 20 blocks length and 20 blocks width, with a normal shape and 10 floors
building = Building(random_data["buildings"], (0, 0), (20,20), shapes[0]['matrice'], 10)
# build it with your custom materials
building.build(editor, ["stone_bricks","glass_pane","glass","cobblestone_wall","stone_brick_stairs","oak_planks","white_concrete","cobblestone","stone_brick_slab","iron_bars"])
# # Get a block
# block = editor.getBlock((0,48,0))
# # Place a block
#editor.placeBlock((0 , 5, 0), Block("stone"))
# editor.placeBlock((0 , 5, 0), Block("stone"))
# # Build a cube
# geometry.placeCuboid(editor, (458, 92, 488), (468, 99, 471), Block("oak_planks"))

View File

@@ -1 +1,91 @@
// contains all random variables
# contains all random variables
buildings:
tile_size:
min_tile_size: 3
max_tile_size: 12
foundations:
collumn_style :
# proportion of each style
none: 1
inner: 5
outer: 1
both: 1
floor:
min_height: 4
max_height: 7
facade:
windows:
size:
min_height: 2
max_height: 6
min_width: 1
max_width: 12
crossbars:
min_height_for_vertical_crossbar: 3
vertical_crossbar: 0.25
min_width_for_horizontal_crossbar: 3
horizontal_crossbar: 0.25
grounded: 0.5
# alternate between block and pane
alternate: 0.5
multiple:
# min size and probability of multiple windows on the same vertice
min_width: 5
proba: 0.5
border_radius:
# proportion of each style
none: 2
top: 1
top_and_bottom: 1
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
inter_floor:
proba: 0.5
border_style:
# bloc used to fill the corner of the interfloor
none: 1
slab: 2
stairs: 2
entrance:
centered: 0.8
different_facade: 0.75
size:
min_height: 5
max_height: 9
door:
size:
min_height: 2
max_height: 4
min_width: 1
max_width: 3
padding:
max: 2
max_top: 2
roof:
rembard: 0.5

View File

@@ -5,3 +5,4 @@ pygame==2.5.2
scipy==1.13.1
skan==0.11.1
skimage==0.0
pyyaml==6.0.1

28
utils/Enums.py Normal file
View File

@@ -0,0 +1,28 @@
from enum import Enum
class DIRECTION(Enum):
NORTH = 0
EAST = 1
SOUTH = 2
WEST = 3
class COLLUMN_STYLE(Enum):
NONE = 0
INNER = 1
OUTER = 2
BOTH = 3
class WINDOW_BORDER_RADIUS(Enum):
NONE = 0
TOP = 1
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

11
utils/JsonReader.py Normal file
View File

@@ -0,0 +1,11 @@
import json
class JsonReader:
def __init__(self, json_file):
self.data = self._load_json(json_file)
def _load_json(self, json_file : str):
f = open(json_file)
js = json.load(f)
return js

11
utils/YamlReader.py Normal file
View File

@@ -0,0 +1,11 @@
import yaml
class YamlReader:
def __init__(self, yaml_file):
self.data = self._load_yaml(yaml_file)
def _load_yaml(self, yaml_file : str):
with open(yaml_file, 'r') as stream:
data_loaded = yaml.safe_load(stream)
return data_loaded

6
utils/functions.py Normal file
View File

@@ -0,0 +1,6 @@
from enum import Enum
import random as rd
def select_random(rdata : dict, enum : Enum) -> Enum:
# select a random value of the dict according to his coef and return the corresponding value in the enum
return enum[rd.choice([elt for elt,num in rdata.items() for _ in range(num)]).upper()]