Skip to content

Commit

Permalink
Ensure all atrium exit are connected
Browse files Browse the repository at this point in the history
  • Loading branch information
Niels-NTG committed Jun 7, 2024
1 parent c6ea73f commit 3910beb
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 64 deletions.
83 changes: 20 additions & 63 deletions LibraryFloor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from typing import Dict, List, Set, Iterator
from typing import Dict, List, Iterator

import numpy as np
from glm import ivec3, ivec2
from ordered_set import OrderedSet
from termcolor import cprint
Expand Down Expand Up @@ -64,14 +63,6 @@ def __init__(
self.volumeRotation,
)

# TODO offset=ivec3(0, 0, self.atriumBox.last.z),
# - 23 attempts (x_junction only)
# - 15 attempts (x_junction + t_junction + book_hallway_outer)
# - 12 attempts without locking air tiles
# TODO offset=self.volumeGrid.offset
# - 41 attempts without locking air tiles
# - 59 attempts without locking air tiles (replace book_hallway_outer with book_hallway_inner)

subGrid1 = vectorTools.intersectionBox(Box(
offset=self.volumeGrid.offset,
size=self.volumeGrid.size,
Expand All @@ -85,7 +76,6 @@ def __init__(
onResolve=self.onResolve,
)

self.removeOrphanedBuildings()
self.buildStructure(volume.offset)

def reinitWFC(self, wfc: WaveFunctionCollapse):
Expand Down Expand Up @@ -200,75 +190,42 @@ def generateWeights(condition: str = '') -> Dict[str, float]:
print('No weight set preset specified')
return globals.defaultStructureWeights

@staticmethod
def isValid(wfc: WaveFunctionCollapse) -> bool:
def isValid(self, wfc: WaveFunctionCollapse) -> bool:
structuresUsed = set(wfc.structuresUsed)
isAirOnly = structuresUsed.issubset(Adjacency.getAllRotations('air'))
if isAirOnly:
print('Invalid WFC result! Volume has only air!')
return False
wfc.foundBuildings = wfc.scanForBuildings()
if len(wfc.foundBuildings) == 0:
print('Invalid WFC result! Volume has no buildings!')
return False
largestBuilding = max(wfc.foundBuildings, key=lambda x: len(x))
if (
self.atriumExitPos1 not in largestBuilding or
self.atriumExitPos2 not in largestBuilding or
self.atriumExitPos3 not in largestBuilding
):
print('Invalid WFC result! Volume has missing atrium exit!')
return False
return True

def onResolve(self, wfc: WaveFunctionCollapse):
self.removeOrphanedBuildings(wfc)
self.allStateSpace.update(wfc.stateSpace)
self.allLockedTiles.update(wfc.lockedTiles)
self.allDefaultAdjacencies.update(wfc.defaultAdjacencies)
self.subGridVolumes.append(wfc.stateSpaceBox)

def scanForBuildings(self) -> List[Set[ivec3]]:
buildings: List[Set[ivec3]] = []
newBuilding: Set[ivec3] = set()
cellsVisited: Set[ivec3] = set()
stateSpaceKeys: List[ivec3] = list(self.allStateSpace.keys())

rng = np.random.default_rng(seed=globals.rngSeed)

while len(cellsVisited) != len(self.allStateSpace):
# noinspection PyTypeChecker
randomIndex = ivec3(rng.choice(stateSpaceKeys))
if randomIndex in cellsVisited:
continue

if self.allStateSpace[randomIndex][0].structureName.endswith('air'):
cellsVisited.add(randomIndex)
continue

scanWorkList: List[ivec3] = [randomIndex]
while len(scanWorkList) > 0:
cellIndex = scanWorkList.pop()
if cellIndex in cellsVisited:
continue
if cellIndex in self.allLockedTiles and self.allLockedTiles[cellIndex]:
newBuilding.add(cellIndex)
cellsVisited.add(cellIndex)
continue

cellState: StructureRotation = self.allStateSpace[cellIndex][0]
if cellState.structureName.endswith('air'):
raise Exception(f'Wall leak found at {cellIndex} {cellState} in building {newBuilding}')

openPositions: Set[ivec3] = self.allDefaultAdjacencies[cellState.structureName].getNonWallPositions(
cellState.rotation,
cellIndex,
stateSpaceKeys,
)
newBuilding.add(cellIndex)
newBuilding.update(openPositions)
scanWorkList.extend(openPositions)
cellsVisited.add(cellIndex)
buildings.append(newBuilding.copy())
newBuilding.clear()

return buildings

def removeOrphanedBuildings(self):
buildings = self.scanForBuildings()
@staticmethod
def removeOrphanedBuildings(wfc: WaveFunctionCollapse):
buildings = wfc.lockedTiles
print(f'Found {len(buildings)} distinct buildings, removing orphaned buildings…')
largestBuilding = max(buildings, key=lambda x: len(x))
for building in buildings:
if building != largestBuilding:
for pos in building:
self.allStateSpace[pos] = OrderedSet({StructureRotation(structureName='air', rotation=0)})
for index in building:
wfc.stateSpace[index] = OrderedSet({StructureRotation(structureName='air', rotation=0)})

def getCollapsedState(self, buildVolumeOffset: ivec3 = ivec3(0, 0, 0)) -> Iterator[Structure]:
for cellIndex in self.allStateSpace:
Expand Down
48 changes: 47 additions & 1 deletion WaveFunctionCollapse.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from concurrent.futures import ProcessPoolExecutor, Future
from copy import deepcopy
from typing import Tuple, Callable, Iterator, Dict, Set
from typing import Tuple, Callable, Iterator, Dict, Set, List

import numpy as np
from glm import ivec3
Expand All @@ -30,6 +30,7 @@ class WaveFunctionCollapse:
rng: Generator
validationFunction: Callable[[WaveFunctionCollapse], bool] | None
workList: Dict[ivec3, OrderedSet[StructureRotation]]
foundBuildings: List[Set[ivec3]]

def __init__(
self,
Expand Down Expand Up @@ -60,6 +61,8 @@ def __init__(
self.workList = dict()
self.firstPositions = set()

self.foundBuildings = []

self.initStateSpaceWithDefaultDomain()
if initFunction:
initFunction(self)
Expand Down Expand Up @@ -289,6 +292,49 @@ def structuresUsed(self) -> Iterator[StructureRotation]:
raise Exception(f'Structure file {cellState[0].structureName} not found in {globals.structureFolders}')
yield cellState[0]

def scanForBuildings(self) -> List[Set[ivec3]]:
buildings: List[Set[ivec3]] = []
newBuilding: Set[ivec3] = set()
cellsVisited: Set[ivec3] = set()
stateSpaceKeys: List[ivec3] = list(self.stateSpace.keys())

while len(cellsVisited) != len(stateSpaceKeys):
randomIndex = self.randomCollapsedCellIndex
if randomIndex in cellsVisited:
continue

if self.stateSpace[randomIndex][0].structureName.endswith('air'):
cellsVisited.add(randomIndex)
continue

scanWorkList: List[ivec3] = [randomIndex]
while len(scanWorkList) > 0:
cellIndex = scanWorkList.pop()
if cellIndex in cellsVisited:
continue
if cellIndex in self.lockedTiles and self.lockedTiles[cellIndex] is True:
newBuilding.add(cellIndex)
cellsVisited.add(cellIndex)
continue

cellState: StructureRotation = self.stateSpace[cellIndex][0]
if cellState.structureName.endswith('air'):
raise Exception(f'Wall leak found at {cellIndex} {cellState} in building {newBuilding}')

openPositions: Set[ivec3] = self.defaultAdjacencies[cellState.structureName].getNonWallPositions(
cellState.rotation,
cellIndex,
stateSpaceKeys,
)
newBuilding.add(cellIndex)
newBuilding.update(openPositions)
scanWorkList.extend(openPositions)
cellsVisited.add(cellIndex)
buildings.append(newBuilding.copy())
newBuilding.clear()

return buildings


def startWFCInstance(
attempt: int,
Expand Down

0 comments on commit 3910beb

Please sign in to comment.