ZeroVR/ZeroPacientVR/Assets/CodeRespawn/DungeonArchitect/Scripts/Builders/Grid/GridDungeonBuilder.cs

3292 lines
127 KiB
C#

//$ Copyright 2015-22, Code Respawn Technologies Pvt Ltd - All Rights Reserved $//
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using DungeonArchitect.Builders.Grid.Mirroring;
using DungeonArchitect.Utils;
using DungeonArchitect.Themeing;
namespace DungeonArchitect.Builders.Grid
{
using PropBySocketType_t = Dictionary<string, List<DungeonThemeItem>>;
using PropBySocketTypeByTheme_t = Dictionary<DungeonThemeData, Dictionary<string, List<DungeonThemeItem>>>;
/// <summary>
/// Contains meta data about the cells. This structure is used for caching cell
/// information for faster lookup during and after generation of the dungeon
/// </summary>
public class GridCellInfo
{
public int CellId;
public CellType CellType;
public bool ContainsDoor;
public GridCellInfo()
{
CellId = 0;
CellType = CellType.Unknown;
ContainsDoor = false;
}
public GridCellInfo(int pCellId, CellType pCellType)
{
this.CellId = pCellId;
this.CellType = pCellType;
this.ContainsDoor = false;
}
}
public class GridBuilderDoorMetadata
{
public GridBuilderDoorMetadata(int cellIdA, int cellIdB)
{
this.CellA = cellIdA;
this.CellB = cellIdB;
}
public int CellA;
public int CellB;
}
/// <summary>
/// Temporary data-structure to hold the height data of the cell node
/// A graph is build of the dungeon layout while the heights are assigned and this
/// node contains the cell's height information
/// </summary>
public class CellHeightNode
{
public int CellId;
public int Height;
public bool MarkForIncrease;
public bool MarkForDecrease;
};
/// <summary>
/// Temporary data-structure used while assigning stairs on the dungeon.
/// </summary>
public class StairAdjacencyQueueNode
{
public StairAdjacencyQueueNode(int pCellId, int pDepth)
{
this.cellId = pCellId;
this.depth = pDepth;
}
public int cellId;
public int depth;
};
/// <summary>
/// Temporary data-structure used while assigning heights on the dungeon.
/// </summary>
public class CellHeightFrameInfo
{
public CellHeightFrameInfo(int pCellId, int pCurrentHeight)
{
this.CellId = pCellId;
this.CurrentHeight = pCurrentHeight;
}
public int CellId;
public int CurrentHeight;
};
/// <summary>
/// Data structure to hold the adjacent cells connected to the stairs (entry / exit)
/// </summary>
public struct StairEdgeInfo
{
public StairEdgeInfo(int pCellIdA, int pCellIdB)
{
this.CellIdA = pCellIdA;
this.CellIdB = pCellIdB;
}
public int CellIdA;
public int CellIdB;
};
/// <summary>
/// A Dungeon Builder implementation that builds a grid based dungeon.
///
/// It is based on the awesome algorithm described here by the TinyKeep game's author
/// https://www.reddit.com/r/gamedev/comments/1dlwc4/procedural_dungeon_generation_algorithm_explained/
/// </summary>
[ExecuteInEditMode]
public class GridDungeonBuilder : DungeonBuilder
{
int _CellIdCounter = 0;
Dictionary<int, List<StairInfo>> CellStairs
{
get
{
return gridModel.CellStairs;
}
}
GridDungeonModel gridModel;
GridDungeonConfig gridConfig;
Dictionary<int, Dictionary<int, GridCellInfo>> GridCellInfoLookup
{
get { return gridModel.GridCellInfoLookup; }
}
GridCellInfo GetGridCellLookup(int x, int z)
{
return gridModel.GetGridCellLookup(x, z);
}
Vector3 GridToMeshScale
{
get
{
if (gridConfig != null)
{
return gridConfig.GridCellSize;
}
return Vector3.one;
}
}
void Awake()
{
model = GetComponent<GridDungeonModel>();
if (model is GridDungeonModel)
{
gridModel = model as GridDungeonModel;
}
else
{
Debug.LogError("Invalid dungeon model type provided to grid based dungeon builder");
return;
}
config = GetComponent<GridDungeonConfig>();
if (config is GridDungeonConfig)
{
gridConfig = config as GridDungeonConfig;
}
else
{
Debug.LogError("Invalid dungeon config type provided to grid based dungeon builder");
return;
}
}
/// <summary>
/// Builds the dungeon
/// </summary>
/// <param name="config">The dungeon configuration</param>
/// <param name="model">The dungeon model</param>
public override void BuildDungeon(DungeonConfig config, DungeonModel model)
{
base.BuildDungeon(config, model);
if (model is GridDungeonModel)
{
gridModel = model as GridDungeonModel;
}
else
{
Debug.LogError("Invalid dungeon model type provided to grid based dungeon builder");
return;
}
if (config is GridDungeonConfig)
{
gridConfig = config as GridDungeonConfig;
}
else
{
Debug.LogError("Invalid dungeon config type provided to grid based dungeon builder");
return;
}
if (gridConfig.UseFastCellDistribution)
{
BuildCellsWithDistribution();
}
else
{
BuildCellsWithSeparation();
}
ApplyBaseOffset();
// Add cells defined by platform volumes in the world
AddUserDefinedPlatforms();
ClipForMirroring();
// Connect the rooms with delaunay triangulation to have nice evenly spaced triangles
TriangulateRooms();
// Build a minimum spanning tree of the above triangulation, to avoid having lots of loops
BuildMinimumSpanningTree();
// Connect the rooms by converting cells between the rooms into corridors. Also adds new corridor cells if needed for the connection
ConnectCorridors();
// Apply negation volumes by removing procedural geometry that lie within it
ApplyNegationVolumes();
// Build a lookup of adjacent tiles for later use with height and stair creation
GenerateAdjacencyLookup();
GenerateDungeonHeights();
ConnectStairs(100);
//ConnectStairs(60);
ConnectStairs(50);
ConnectStairs(0);
ConnectStairs(-100);
MirrorDungeon();
RemoveAdjacentDoors();
}
void Initialize()
{
Markers.Clear();
gridModel.GridCellInfoLookup.Clear();
CellStairs.Clear();
gridModel.Cells.Clear();
gridModel.DoorManager.Clear();
_CellIdCounter = 0;
}
protected override LevelMarkerList CreateMarkerListObject(DungeonConfig config)
{
var gridConfig = config as GridDungeonConfig;
var bucketSize = Mathf.Max(gridConfig.GridCellSize.x, gridConfig.GridCellSize.z) * 2;
bucketSize = Mathf.Max(0.1f, bucketSize);
return new SpatialPartionedLevelMarkerList(bucketSize);
}
bool CanFitDistributionCell(HashSet<IntVector> Occupancy, ref Rectangle bounds)
{
for (int x = bounds.X; x < bounds.X + bounds.Width; x++)
{
for (int z = bounds.Z; z < bounds.Z + bounds.Length; z++)
{
if (Occupancy.Contains(new IntVector(x, 0, z)))
{
return false;
}
}
}
// No cells within the bounds are occupied
return true;
}
void SetDistributionCellOccupied(HashSet<IntVector> Occupancy, ref Rectangle bounds)
{
for (int x = bounds.X; x < bounds.X + bounds.Width; x++)
{
for (int z = bounds.Z; z < bounds.Z + bounds.Length; z++)
{
Occupancy.Add(new IntVector(x, 0, z));
}
}
}
public void BuildCellsWithDistribution()
{
int sx = -gridConfig.CellDistributionWidth / 2;
int ex = gridConfig.CellDistributionWidth / 2;
int sz = -gridConfig.CellDistributionLength / 2;
int ez = gridConfig.CellDistributionLength / 2;
int FitnessTries = 10;
var Occupied = new HashSet<IntVector>();
var cells = new List<Cell>();
for (int ix = sx; ix <= ex; ix++)
{
for (int iz = sz; iz <= ez; iz++)
{
for (int t = 0; t < FitnessTries; t++)
{
int offsetX, offsetZ;
{
var offset = GenerateCellSize() / 2;
offsetX = Mathf.RoundToInt(offset.x * random.GetNextUniformFloat());
offsetZ = Mathf.RoundToInt(offset.z * random.GetNextUniformFloat());
}
int x = ix + offsetX;
int z = iz + offsetZ;
var bounds = new Rectangle();
bounds.Size = GenerateCellSize();
bounds.Location = new IntVector(x, 0, z);
if (CanFitDistributionCell(Occupied, ref bounds))
{
var cell = BuildCell(ref bounds);
cells.Add(cell);
SetDistributionCellOccupied(Occupied, ref bounds);
break;
}
}
}
}
gridModel.Config = gridConfig;
gridModel.Cells = cells;
gridModel.BuildCellLookup();
}
/// <summary>
/// builds the cells in the dungeon
/// </summary>
public void BuildCellsWithSeparation()
{
if (gridConfig.Seed == 0)
{
//gridConfig.Seed = (uint)Mathf.FloorToInt(Random.value * int.MaxValue);
}
var cells = new List<Cell>();
_CellIdCounter = 0;
for (int i = 0; i < gridConfig.NumCells; i++)
{
var cell = BuildCell();
cells.Add(cell);
}
gridModel.Config = gridConfig;
gridModel.Cells = cells;
gridModel.BuildCellLookup();
// Separate the cells
int separationTries = 0;
int maxSeparationTries = 100;
while (separationTries < maxSeparationTries)
{
bool separated = Seperate(gridModel);
if (!separated)
{
break;
}
separationTries++;
}
}
IntVector GenerateCellSize()
{
var baseSize = GetRandomRoomSize();
var width = baseSize;
var aspectRatio = 1 + (random.GetNextUniformFloat() * 2 - 1) * gridConfig.RoomAspectDelta;
var length = Mathf.RoundToInt(width * aspectRatio);
if (random.GetNextUniformFloat() < 0.5f)
{
// Swap width / length
var temp = width;
width = length;
length = temp;
}
return new IntVector(width, 0, length);
}
Cell BuildCell()
{
var bounds = new Rectangle();
bounds.Location = GetRandomPointInCircle(gridConfig.InitialRoomRadius);
bounds.Size = GenerateCellSize();
return BuildCell(ref bounds);
}
Cell BuildCell(ref Rectangle bounds)
{
var cell = new Cell();
cell.Id = GetNextCellId();
cell.Bounds = bounds;
var area = bounds.Size.x * bounds.Size.z;
if (area >= gridConfig.RoomAreaThreshold)
{
cell.CellType = CellType.Room;
}
return cell;
}
int GetNextCellId()
{
++_CellIdCounter;
return _CellIdCounter;
}
void ApplyBaseOffset()
{
var dungeonPosition = gameObject.transform.position;
var dungeonGridPosF = MathUtils.Divide(dungeonPosition, gridConfig.GridCellSize);
var dungeonGridPos = MathUtils.RoundToIntVector(dungeonGridPosF);
foreach (var cell in gridModel.Cells)
{
var bounds = cell.Bounds;
var location = bounds.Location;
location += dungeonGridPos;
bounds.Location = location;
cell.Bounds = bounds;
}
}
void ApplyNegationVolumes()
{
var dungeon = GetComponent<Dungeon>();
var negationVolumes = GameObject.FindObjectsOfType<NegationVolume>();
foreach (var negationVolume in negationVolumes)
{
if (dungeon != null && negationVolume.dungeon == dungeon) {
ApplyNegationVolume(negationVolume);
}
}
gridModel.BuildCellLookup();
}
void ApplyNegationVolume(NegationVolume volume)
{
IntVector position, scale;
volume.GetVolumeGridTransform(out position, out scale, GridToMeshScale);
position = position - scale / 2;
var bounds = new Rectangle();
bounds.Location = position;
bounds.Size = scale;
var cellsToRemove = new List<Cell>();
foreach (var cell in gridModel.Cells)
{
bool removeCell;
if (volume.inverse)
{
// Inverse the negation and remove everything outside the volume
removeCell = !bounds.Contains(cell.Bounds);
}
else
{
removeCell = cell.Bounds.IntersectsWith(bounds);
}
if (removeCell)
{
cellsToRemove.Add(cell);
}
}
foreach (var cell in cellsToRemove)
{
gridModel.Cells.Remove(cell);
}
}
void GetCellBounds(Cell cell, ref Bounds bounds)
{
var gridSize = gridConfig.GridCellSize;
var width = cell.Bounds.Width * gridSize.x;
var length = cell.Bounds.Width * gridSize.z;
var center = Vector3.Scale(cell.Bounds.CenterF(), gridSize);
bounds.center = center;
bounds.size = new Vector3(width, 2, length);
}
void AddUserDefinedPlatforms()
{
var toolData = GetComponent<GridDungeonToolData>();
// Add geometry defined by the editor paint tool
if (toolData != null && toolData.paintedCells != null)
{
foreach (var cellPoint in toolData.paintedCells)
{
var cell = new Cell();
cell.Id = GetNextCellId();
cell.UserDefined = true;
var bounds = new Rectangle();
bounds.Location = cellPoint;
bounds.Size = new IntVector(1, 1, 1);
cell.Bounds = bounds;
cell.CellType = CellType.Corridor;
gridModel.Cells.Add(cell);
}
}
var dungeon = GetComponent<Dungeon>();
// Add platform volumes defined in the world
var platformVolumes = GameObject.FindObjectsOfType<PlatformVolume>();
foreach (var platformVolume in platformVolumes)
{
if (dungeon != null && platformVolume.dungeon == dungeon) {
AddPlatformVolume(platformVolume);
}
}
gridModel.BuildCellLookup();
}
void AddPlatformVolume(PlatformVolume platform)
{
var cell = new Cell();
cell.Id = GetNextCellId();
IntVector position, scale;
platform.GetVolumeGridTransform(out position, out scale, GridToMeshScale);
position = position - scale / 2;
var bounds = new Rectangle();
bounds.Location = position;
bounds.Size = scale;
cell.Bounds = bounds;
cell.CellType = platform.cellType;
cell.UserDefined = true;
// Remove any cells that intersect with this cell
var insertedCells = gridModel.Cells.ToArray();
foreach (var insertedCell in insertedCells)
{
if (insertedCell.Bounds.IntersectsWith(cell.Bounds))
{
gridModel.Cells.Remove(insertedCell);
}
}
gridModel.Cells.Add(cell);
}
void ClipForMirroring()
{
var dungeon = GetComponent<Dungeon>();
if (dungeon != null)
{
bool appliedMirroring = false;
var mirrorVolumes = GameObject.FindObjectsOfType<MirrorVolume>();
foreach (var mirrorVolume in mirrorVolumes)
{
if (mirrorVolume.dungeon == dungeon)
{
ClipForMirroring(mirrorVolume);
appliedMirroring = true;
}
}
if (appliedMirroring)
{
gridModel.BuildCellLookup();
GenerateAdjacencyLookup();
}
}
}
void MirrorDungeon()
{
var dungeon = GetComponent<Dungeon>();
if (dungeon != null)
{
bool appliedMirroring = false;
var mirrorVolumes = GameObject.FindObjectsOfType<MirrorVolume>();
foreach (var mirrorVolume in mirrorVolumes)
{
if (mirrorVolume.dungeon == dungeon)
{
MirrorDungeon(mirrorVolume);
appliedMirroring = true;
}
}
if (appliedMirroring)
{
gridModel.BuildCellLookup();
GenerateAdjacencyLookup();
}
}
}
void ClipForMirroring(MirrorVolume volume)
{
var volumePosition = volume.transform.position;
var volumeDirections = new List<MirrorVolumeDirection>();
if (volume.direction == MirrorVolumeDirection.AxisXZ)
{
volumeDirections.Add(MirrorVolumeDirection.AxisX);
volumeDirections.Add(MirrorVolumeDirection.AxisZ);
}
else
{
volumeDirections.Add(volume.direction);
}
foreach (var volumeDirection in volumeDirections)
{
var mirror = GridDungeonMirror.Create(volumePosition, gridConfig.GridCellSize, volumeDirection);
var cellsToDiscard = new List<Cell>();
var cellsToDiscardId = new List<int>();
foreach (var cell in gridModel.Cells)
{
if (mirror.CanDiscardBounds(cell.Bounds))
{
// discard this cell
cellsToDiscard.Add(cell);
cellsToDiscardId.Add(cell.Id);
}
else if (mirror.CanCropBounds(cell.Bounds))
{
// Crop this cell
mirror.CropCell(cell);
}
}
foreach (var cell in cellsToDiscard)
{
gridModel.Cells.Remove(cell);
}
// Remove the doors
{
var doorsToDiscard = new List<CellDoor>();
foreach (var door in gridModel.Doors)
{
bool validDoor = true;
foreach (var doorCell in door.AdjacentCells)
{
if (cellsToDiscardId.Contains(doorCell))
{
validDoor = false;
break;
}
}
if (!validDoor)
{
doorsToDiscard.Add(door);
}
}
foreach (var door in doorsToDiscard)
{
gridModel.DoorManager.RemoveDoor(door);
}
}
}
}
void MirrorDungeon(MirrorVolume volume)
{
var volumePosition = volume.transform.position;
var volumeDirections = new List<MirrorVolumeDirection>();
if (volume.direction == MirrorVolumeDirection.AxisXZ)
{
volumeDirections.Add(MirrorVolumeDirection.AxisX);
volumeDirections.Add(MirrorVolumeDirection.AxisZ);
}
else
{
volumeDirections.Add(volume.direction);
}
foreach (var volumeDirection in volumeDirections)
{
var mirror = GridDungeonMirror.Create(volumePosition, gridConfig.GridCellSize, volumeDirection);
var cellToMirrorMap = new Dictionary<int, int>();
// Mirror the cells
{
var cellsToAdd = new List<Cell>();
foreach (var cell in gridModel.Cells)
{
var mirroredCellBounds = mirror.CalculateMirrorReflection(cell.Bounds);
var mergedBounds = new Rectangle();
if (cell.CellType == CellType.Room && mirror.CanMergeCells(cell.Bounds, mirroredCellBounds, ref mergedBounds))
{
cell.Bounds = mergedBounds;
cellToMirrorMap[cell.Id] = cell.Id;
}
else
{
var mirrorCell = new Cell();
mirrorCell.Id = GetNextCellId();
mirrorCell.UserDefined = false;
mirrorCell.Bounds = mirroredCellBounds;
mirrorCell.CellType = cell.CellType;
cellsToAdd.Add(mirrorCell);
cellToMirrorMap[cell.Id] = mirrorCell.Id;
}
}
foreach (var cell in cellsToAdd)
{
gridModel.Cells.Add(cell);
}
}
// Mirror the doors
{
var doorsCopy = new List<CellDoor>(gridModel.Doors);
foreach (var door in doorsCopy)
{
if (door.AdjacentCells.Length != 2 || door.AdjacentTiles.Length != 2) continue;
if (!cellToMirrorMap.ContainsKey(door.AdjacentCells[0])) continue;
if (!cellToMirrorMap.ContainsKey(door.AdjacentCells[1])) continue;
var mirroredCell0 = cellToMirrorMap[door.AdjacentCells[0]];
var mirroredCell1 = cellToMirrorMap[door.AdjacentCells[1]];
var mirroredTile0 = mirror.CalculateMirrorReflection(door.AdjacentTiles[0]);
var mirroredTile1 = mirror.CalculateMirrorReflection(door.AdjacentTiles[1]);
gridModel.DoorManager.CreateDoor(mirroredTile0, mirroredTile1, mirroredCell0, mirroredCell1);
}
}
// Mirror the stairs
{
var stairsCopy = new Dictionary<int, List<StairInfo>>(CellStairs);
foreach (var entry in stairsCopy)
{
var ownerCellId = entry.Key;
var stairList = entry.Value;
if (!cellToMirrorMap.ContainsKey(ownerCellId)) continue;
var mirrorOwnerCell = cellToMirrorMap[ownerCellId];
var mirrorStairList = new List<StairInfo>();
foreach (var stair in stairList)
{
if (!cellToMirrorMap.ContainsKey(stair.ConnectedToCell)) continue;
var mirroredStair = new StairInfo();
mirroredStair.OwnerCell = mirrorOwnerCell;
mirroredStair.ConnectedToCell = cellToMirrorMap[stair.ConnectedToCell];
mirroredStair.Position = mirror.CalculateMirrorReflection(stair.Position);
mirroredStair.Rotation = mirror.CalculateMirrorReflection(stair.Rotation);
mirroredStair.IPosition = mirror.CalculateMirrorReflection(stair.IPosition);
mirrorStairList.Add(mirroredStair);
}
CellStairs[mirrorOwnerCell] = mirrorStairList;
}
}
}
}
int GetRandomRoomSize()
{
float r = 0;
while (r <= 0) r = nrandom.NextGaussianFloat(gridConfig.NormalMean, gridConfig.NormalStd);
var roomSize = gridConfig.MinCellSize + r * (gridConfig.MaxCellSize - gridConfig.MinCellSize);
return Mathf.RoundToInt(roomSize);
}
IntVector GetRandomPointInCircle(float radius)
{
var angle = random.GetNextUniformFloat() * Mathf.PI * 2;
var u = random.GetNextUniformFloat() + random.GetNextUniformFloat();
var r = (u > 1) ? 2 - u : u;
r *= radius;
var x = Mathf.RoundToInt(Mathf.Cos(angle) * r);
var z = Mathf.RoundToInt(Mathf.Sin(angle) * r);
return new IntVector(x, 0, z);
}
static void Shuffle(GridDungeonModel gridModel)
{
if (gridModel == null) return;
var cells = gridModel.Cells;
if (cells == null) return;
var random = new PMRandom(gridModel.Config.Seed);
int n = cells.Count;
for (int i = 0; i < n; i++)
{
int r = i + (int)(random.GetNextUniformFloat() * (n - i));
Cell t = cells[r];
cells[r] = cells[i];
cells[i] = t;
}
}
static int CompareFromCenter(Cell cellA, Cell cellB)
{
float distA = cellA.Center.DistanceSq();
float distB = cellB.Center.DistanceSq();
if (distA == distB)
{
return 0;
}
return (distA < distB) ? -1 : 1;
}
/// <summary>
/// Separates the cells built in the previous phase
/// </summary>
/// <param name="gridModel"></param>
public static bool Seperate(GridDungeonModel gridModel)
{
if (gridModel == null) return false;
if (gridModel.Cells == null) return false;
var cells = gridModel.Cells.ToArray();
System.Array.Sort(cells, CompareFromCenter);
//Shuffle(model);
var count = gridModel.Cells.Count;
var forces = new IntVector[count];
for (int a = 0; a < count; a++)
{
forces[a] = new IntVector();
}
bool separated = false;
var random = new PMRandom(gridModel.Config.Seed);
for (int a = 0; a < count; a++)
{
for (int b = 0; b < count; b++)
{
if (a == b) continue;
var c0 = cells[a].Bounds;
var c1 = cells[b].Bounds;
if (c0.IntersectsWith(c1))
{
var force = new IntVector();
var intersection = Rectangle.Intersect(c0, c1);
//var intersection = new Rectangle(c0.Location, c0.Size);
//intersection.Intersect(c1);
bool applyOnX = (intersection.Width < intersection.Length);
if (intersection.Width == intersection.Length)
{
applyOnX = random.GetNextUniformFloat() > 0.5;
}
if (applyOnX)
{
force.x = intersection.Width;
force.x *= GetForceDirectionMultiplier(c0.X, c1.X, c0.Z, c1.Z);
}
else
{
force.z = intersection.Length;
force.z *= GetForceDirectionMultiplier(c0.Z, c1.Z, c0.X, c1.X);
}
forces[a].x += force.x;
forces[a].z += force.z;
forces[b].x -= force.x;
forces[b].z -= force.z;
separated = true;
}
}
{
var cell = cells[a];
var location = cell.Bounds.Location;
location += forces[a];
var size = cell.Bounds.Size;
cell.Bounds = new Rectangle(location, size);
}
}
return separated;
}
/// <summary>
/// Triangulates the rooms identified in the previous phase
/// This is required to connect the corridors.
/// Delauney triangulation is used to find nice evenly spaced triangles for good connections
/// </summary>
/// <param name="gridModel"></param>
public void TriangulateRooms()
{
var vertices = new List<Vector2>();
var rooms = new List<Cell>();
foreach (var cell in gridModel.Cells)
{
if (cell.CellType == CellType.Room)
{
rooms.Add(cell);
var center = cell.CenterF;
//var offset = random.RandomPointOnCircle() * 0.1f;
var x = center.x; // + offset.x;
var z = center.z; // + offset.y;
vertices.Add(new Vector2(x, z));
}
}
vertices = vertices.OrderBy(v => v.x).ToList();
if (rooms.Count > 2)
{
var triangles = Triangulator.DelauneyV2.Triangulate(vertices.ToArray());
if (triangles.Length > 0)
{
foreach (var triangle in triangles)
{
var c1 = rooms[triangle.p1];
var c2 = rooms[triangle.p2];
var c3 = rooms[triangle.p3];
ConnectCells(c1, c2);
ConnectCells(c2, c3);
ConnectCells(c3, c1);
}
}
else
{
// manually connect them since we cannot successfully triangulate (happens if the room positions lie in the same line)
for (int i = 0; i < rooms.Count; i++)
{
int nextIndex = (i + 1) % rooms.Count;
var c1 = rooms[i];
var c2 = rooms[nextIndex];
ConnectCells(c1, c2);
}
}
}
else if (rooms.Count == 2) {
ConnectCells(rooms[0], rooms[1]);
}
}
static void ConnectCells(Cell c1, Cell c2)
{
if (!c1.ConnectedRooms.Contains(c2.Id))
{
c1.ConnectedRooms.Add(c2.Id);
}
if (!c2.ConnectedRooms.Contains(c1.Id))
{
c2.ConnectedRooms.Add(c1.Id);
}
}
class Edge : System.IComparable<Edge>
{
public Edge(int cellA, int cellB, float weight)
{
this.cellA = cellA;
this.cellB = cellB;
this.weight = weight;
}
public int cellA;
public int cellB;
public float weight;
public int CompareTo(Edge other)
{
if (weight == other.weight) return 0;
return (weight < other.weight) ? -1 : 1;
}
}
static List<Cell> GetRooms(List<Cell> cells)
{
var rooms = new List<Cell>();
foreach (var cell in cells)
{
if (cell.CellType == CellType.Room)
{
rooms.Add(cell);
}
}
return rooms;
}
void AddUnique<T>(List<T> list, T value)
{
if (!list.Contains(value))
{
list.Add(value);
}
}
void BuildMinimumSpanningTree()
{
List<Cell> rooms = GetCellsOfType(CellType.Room);
var edgesMapped = new Dictionary<int, HashSet<int>>();
// Generate unique edge list
var edges = new List<Edge>();
foreach (Cell room in rooms)
{
if (room == null) continue;
foreach (int connectedRoomId in room.ConnectedRooms)
{
Cell other = gridModel.GetCell(connectedRoomId);
if (other == null) continue;
float distance = GetDistance(room.Center, other.Center);
int id0 = room.Id;
int id1 = other.Id;
if (!edgesMapped.ContainsKey(id0) || !edgesMapped[id0].Contains(id1))
{
Edge edge = new Edge(id0, id1, distance);
edges.Add(edge);
if (!edgesMapped.ContainsKey(id0)) edgesMapped.Add(id0, new HashSet<int>());
if (!edgesMapped.ContainsKey(id1)) edgesMapped.Add(id1, new HashSet<int>());
edgesMapped[id0].Add(id1);
edgesMapped[id1].Add(id0);
}
}
}
edges.Sort();
foreach (Edge edge in edges)
{
Cell cell0 = gridModel.GetCell(edge.cellA);
Cell cell1 = gridModel.GetCell(edge.cellB);
if (cell0 != null && cell1 != null)
{
cell0.FixedRoomConnections.Add(cell1.Id);
cell1.FixedRoomConnections.Add(cell0.Id);
// Check if this new edge insertion caused a loop in the MST
bool loop = ContainsLoop(rooms);
if (loop)
{
cell0.FixedRoomConnections.Remove(cell1.Id);
cell1.FixedRoomConnections.Remove(cell0.Id);
}
}
}
// Add some edges from the Delauney triangulation based on a probability
PMRandom srandom = new PMRandom(gridConfig.Seed);
foreach (Cell room in rooms)
{
foreach (int otherDelauney in room.ConnectedRooms)
{
if (!room.FixedRoomConnections.Contains(otherDelauney))
{
float probability = srandom.GetNextUniformFloat();
if (probability < gridConfig.SpanningTreeLoopProbability)
{
Cell other = gridModel.GetCell(otherDelauney);
if (other != null)
{
room.FixedRoomConnections.Add(otherDelauney);
other.FixedRoomConnections.Add(room.Id);
}
}
}
}
}
}
bool ContainsLoop(List<Cell> rooms)
{
foreach (Cell room in rooms)
{
if (room != null)
{
HashSet<Cell> visited = new HashSet<Cell>();
bool hasLoop = CheckLoop(room, null, visited);
if (hasLoop) return true;
}
}
return false;
}
bool CheckLoop(Cell currentNode, Cell comingFrom, HashSet<Cell> visited)
{
visited.Add(currentNode);
// check if any of the children have already been visited
foreach (int childId in currentNode.FixedRoomConnections)
{
Cell child = gridModel.GetCell(childId);
if (child == null) continue;
if (child == comingFrom) continue;
if (visited.Contains(child))
{
return true;
}
bool branchHasLoop = CheckLoop(child, currentNode, visited);
if (branchHasLoop) return true;
}
return false;
}
static float GetDistance(IntVector a, IntVector b)
{
var dx = a.x - b.x;
var dy = a.z - b.z;
return Mathf.Sqrt(dx * dx + dy * dy);
}
List<Cell> GetCellsOfType(CellType cellType)
{
List<Cell> filtered = new List<Cell>();
foreach (var cell in gridModel.Cells)
{
if (cell.CellType == cellType)
{
filtered.Add(cell);
}
}
return filtered;
}
void ConnectCorridors()
{
List<Cell> rooms = GetCellsOfType(CellType.Room);
if (rooms.Count < 2) return;
HashSet<int> visited = new HashSet<int>();
Cell startingRoom = rooms[0];
ConnectCooridorRecursive(-1, startingRoom.Id, visited);
// TODO: Remove unused cells
for (int i = 0; i < gridModel.Cells.Count; )
{
if (gridModel.Cells[i].CellType == CellType.Unknown)
{
gridModel.Cells.RemoveAt(i);
}
else
{
i++;
}
}
// Rebuild the cell cache list, since it has been modified
gridModel.BuildCellLookup();
}
void ConnectCooridorRecursive(int incomingRoomId, int currentRoomId, HashSet<int> visited)
{
if (incomingRoomId >= 0)
{
int c0 = incomingRoomId;
int c1 = currentRoomId;
if (visited.Contains(HASH(c0, c1))) return;
visited.Add(HASH(c0, c1));
visited.Add(HASH(c1, c0));
ConnectRooms(incomingRoomId, currentRoomId);
}
Cell currentRoom = gridModel.GetCell(currentRoomId);
if (currentRoom == null)
{
return;
}
HashSet<int> children = currentRoom.FixedRoomConnections;
foreach (int otherRoomId in children)
{
Cell otherRoom = gridModel.GetCell(otherRoomId);
if (otherRoom == null) continue;
int i0 = currentRoomId;
int i1 = otherRoomId;
if (!visited.Contains(HASH(i0, i1)))
{
ConnectCooridorRecursive(currentRoomId, otherRoomId, visited);
}
}
}
bool AreCellsAdjacent(int cellAId, int cellBId)
{
Cell cellA = gridModel.GetCell(cellAId);
Cell cellB = gridModel.GetCell(cellBId);
if (cellA == null || cellB == null)
{
return false;
}
Rectangle intersection = Rectangle.Intersect(cellA.Bounds, cellB.Bounds);
bool adjacent = (intersection.Width > 0 || intersection.Length > 0);
return adjacent;
}
void GetCorners(Rectangle bounds, out HashSet<IntVector> corners)
{
corners = new HashSet<IntVector>();
int W = bounds.Width - 1;
int H = bounds.Length - 1;
corners.Add(new IntVector(bounds.X, 0, bounds.Z));
corners.Add(new IntVector(bounds.X + W, 0, bounds.Z));
corners.Add(new IntVector(bounds.X + W, 0, bounds.Z + H));
corners.Add(new IntVector(bounds.X, 0, bounds.Z + H));
}
void ConnectAdjacentCells(int roomA, int roomB)
{
Cell cellA = gridModel.GetCell(roomA);
Cell cellB = gridModel.GetCell(roomB);
if (cellA == null || cellB == null)
{
return;
}
Rectangle intersection = Rectangle.Intersect(cellA.Bounds, cellB.Bounds);
bool adjacent = (intersection.Width > 0 || intersection.Length > 0);
if (adjacent)
{
IntVector doorPointA = new IntVector();
IntVector doorPointB = new IntVector();
doorPointA.y = cellA.Bounds.Location.y;
doorPointB.y = cellB.Bounds.Location.y;
if (intersection.Width > 0)
{
// shares a horizontal edge
doorPointA.x = intersection.X + intersection.Width / 2;
doorPointA.z = intersection.Z - 1;
doorPointB.x = doorPointA.x;
doorPointB.z = doorPointA.z + 1;
}
else
{
// shares a vertical edge
doorPointA.x = intersection.X - 1;
doorPointA.z = intersection.Z + intersection.Length / 2;
doorPointB.x = doorPointA.x + 1;
doorPointB.z = doorPointA.z;
}
bool isOnCornerTile = false;
if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks)
{
HashSet<IntVector> cornersA, cornersB;
GetCorners(cellA.Bounds, out cornersA);
GetCorners(cellB.Bounds, out cornersB);
if (cellA.CellType == CellType.Room && (cornersA.Contains(doorPointA) || cornersA.Contains(doorPointB))) {
isOnCornerTile = true;
}
else if (cellB.CellType == CellType.Room && (cornersB.Contains(doorPointA) || cornersB.Contains(doorPointB)))
{
isOnCornerTile = true;
}
}
// Add a door and return (no corridors needed for adjacent rooms)
if (!isOnCornerTile)
{
gridModel.DoorManager.CreateDoor(doorPointA, doorPointB, roomA, roomB);
}
}
}
void Swap(ref int A, ref int B)
{
int T = A;
A = B;
B = T;
}
void GetManhattanPathBetween(IntVector start, IntVector end, ref List<IntVector> path)
{
if (start == end)
{
path.Add(start);
return;
}
int lengthX = end.x - start.x;
int lengthZ = end.z - start.z;
int length = Mathf.Max(Mathf.Abs(lengthX), Mathf.Abs(lengthZ));
int dx = (lengthX == 0) ? 0 : (int)Mathf.Sign(lengthX);
int dz = (lengthZ == 0) ? 0 : (int)Mathf.Sign(lengthZ);
IntVector p = start;
for (int i = 0; i < length; i++)
{
if (i > 0 && i < length)
{
path.Add(p);
}
p.x += dx;
p.z += dz;
}
}
List<IntVector> GetManhattanPath(IntVector start, IntVector mid, IntVector end)
{
var path0 = new List<IntVector>();
var path1 = new List<IntVector>();
GetManhattanPathBetween(start, mid, ref path0);
GetManhattanPathBetween(mid, end, ref path1);
var path = new List<IntVector>();
path.Add(start);
path.AddRange(path0);
if (mid != start)
{
path.Add(mid);
}
path.AddRange(path1);
if (end != mid)
{
path.Add(end);
}
return path;
}
class ConnectionPointSorter : IComparer<IntVector>
{
public ConnectionPointSorter(Vector3 center)
{
this.center = center;
}
public int Compare(IntVector x, IntVector y)
{
float distX = (x.ToVector3() - center).sqrMagnitude;
float distY = (y.ToVector3() - center).sqrMagnitude;
if (distX == distY) return 0;
return distX < distY ? -1 : 1;
}
Vector3 center;
}
void RemovePointsInsideBounds(Rectangle bounds, List<IntVector> points)
{
var pointsToRemove = new List<IntVector>();
foreach (var point in points)
{
if (bounds.Contains(point))
{
pointsToRemove.Add(point);
}
}
foreach (var point in pointsToRemove)
{
points.Remove(point);
}
}
bool ContainsPointInsideBounds(Rectangle bounds, List<IntVector> points)
{
foreach (var point in points)
{
if (bounds.Contains(point))
{
return true;
}
}
return false;
}
bool ContainsPointsInsideRoomCells(Cell[] roomCells, List<IntVector> points)
{
foreach (var room in roomCells)
{
if (ContainsPointInsideBounds(room.Bounds, points))
{
return true;
}
}
return false;
}
List<IntVector> FindConnectionPath(Cell roomA, Cell roomB)
{
var centerA = roomA.Bounds.CenterF();
var centerB = roomB.Bounds.CenterF();
var center = (centerA + centerB) / 2.0f;
var connectionPointsA = new List<IntVector>();
var connectionPointsB = new List<IntVector>();
bool skipCornersOnPathSelection = (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks);
GridBuilderUtils.GetRoomConnectionPointsForTiledMode(roomA.Bounds, ref connectionPointsA, skipCornersOnPathSelection);
GridBuilderUtils.GetRoomConnectionPointsForTiledMode(roomB.Bounds, ref connectionPointsB, skipCornersOnPathSelection);
// Remove points that are inside the other room bounds
RemovePointsInsideBounds(roomA.Bounds, connectionPointsB);
RemovePointsInsideBounds(roomB.Bounds, connectionPointsA);
// Sort based on the center point
connectionPointsA.Sort(new ConnectionPointSorter(center));
connectionPointsB.Sort(new ConnectionPointSorter(center));
var roomCells = GetCellsOfType(CellType.Room).ToArray();
List<IntVector> worstPath = new List<IntVector>();
List<IntVector> path = new List<IntVector>();
bool solutionFound = false;
foreach (var cpA in connectionPointsA)
{
foreach (var cpB in connectionPointsB)
{
var start = cpA;
var end = cpB;
var mid1 = new IntVector(start.x, 0, end.z);
var mid2 = new IntVector(end.x, 0, start.z);
path = GetManhattanPath(start, mid1, end);
if (!ContainsPointsInsideRoomCells(roomCells, path))
{
solutionFound = true;
break;
}
path = GetManhattanPath(start, mid2, end);
if (!ContainsPointsInsideRoomCells(roomCells, path))
{
solutionFound = true;
break;
}
if (worstPath.Count == 0)
{
worstPath = path;
}
}
if (solutionFound)
{
break;
}
}
if (!solutionFound)
{
//Debug.LogError("Failed to find a valid connection between the cells");
path = worstPath;
}
return path;
}
void ConnectRooms(int roomAId, int roomBId)
{
Cell roomA = gridModel.GetCell(roomAId);
Cell roomB = gridModel.GetCell(roomBId);
if (roomA == null || roomB == null) return;
Rectangle intersection = Rectangle.Intersect(roomA.Bounds, roomB.Bounds);
int minIntersectionSize = 1;
if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks)
{
// Compensate for the wall block tile
minIntersectionSize = 3;
}
int intersectionSize = Mathf.Max(intersection.Width, intersection.Length);
bool adjacent = intersectionSize >= minIntersectionSize;
if (adjacent)
{
ConnectAdjacentCells(roomAId, roomBId);
}
else
{
var pathList = FindConnectionPath(roomA, roomB);
int previousCellId = -1;
var visitedCells = new HashSet<IntVector2>();
foreach (var pathPoint in pathList)
{
// add a corridor cell
int x = pathPoint.x;
int z = pathPoint.z;
int CurrentCellId = RegisterCorridorCell(x, z, roomAId, roomBId, true);
visitedCells.Add(new IntVector2(x, z));
// Check if we need to create a door between the two.
// This is needed in case we have an extra room through the corridor.
// This room needs to have doors created
{
if (previousCellId != -1 && CurrentCellId != previousCellId)
{
Cell PreviousCell = gridModel.GetCell(previousCellId);
Cell CurrentCell = gridModel.GetCell(CurrentCellId);
if (PreviousCell != null && CurrentCell != null && PreviousCell != CurrentCell)
{
if (PreviousCell.CellType == CellType.Room || PreviousCell.CellType == CellType.Room)
{
ConnectAdjacentCells(previousCellId, CurrentCellId);
}
}
}
previousCellId = CurrentCellId;
}
int laneWidth = Mathf.Clamp(gridConfig.CorridorWidth, 1, 10);
if (laneWidth > 1)
{
int leftHalf = laneWidth / 2;
int rightHalf = laneWidth - leftHalf;
for (int dx = -leftHalf; dx < rightHalf; dx++)
{
for (int dz = -leftHalf; dz < rightHalf; dz++)
{
if (dx == 0 && dz == 0) continue;
int px = x + dx;
int pz = z + dz;
var visitedKey = new IntVector2(px, pz);
if (!visitedCells.Contains(visitedKey))
{
RegisterCorridorCell(px, pz, roomAId, roomBId);
visitedCells.Add(visitedKey);
}
}
}
}
}
}
}
void ConnectIfRoomCorridor(int cellAX, int cellAZ, int cellBX, int cellBZ)
{
var cellA = gridModel.FindCellByPosition(new IntVector(cellAX, 0, cellAZ));
var cellB = gridModel.FindCellByPosition(new IntVector(cellBX, 0, cellBZ));
if (cellA == null || cellB == null)
{
return;
}
int roomCount = 0;
int corridorCount = 0;
roomCount += (cellA.CellType == CellType.Room) ? 1 : 0;
roomCount += (cellB.CellType == CellType.Room) ? 1 : 0;
corridorCount += (cellA.CellType == CellType.Corridor || cellA.CellType == CellType.CorridorPadding) ? 1 : 0;
corridorCount += (cellB.CellType == CellType.Corridor || cellB.CellType == CellType.CorridorPadding) ? 1 : 0;
if (roomCount == 1 && corridorCount == 1)
{
ConnectAdjacentCells(cellA.Id, cellB.Id);
}
}
int RegisterCorridorCell(int cellX, int cellZ, int roomA, int roomB)
{
return RegisterCorridorCell(cellX, cellZ, roomA, roomB, /* canRegisterDoors = */ false);
}
int RegisterCorridorCell(int cellX, int cellZ, int roomA, int roomB, bool canRegisterDoors)
{
Cell cellA = gridModel.GetCell(roomA);
Cell cellB = gridModel.GetCell(roomB);
Rectangle PaddingBounds = new Rectangle(cellX, cellZ, 1, 1);
if (cellA.Bounds.Contains(PaddingBounds.Location) || cellB.Bounds.Contains(PaddingBounds.Location))
{
// ignore
return -1;
}
bool bRequiresPadding = true;
int CurrentCellId = -1;
foreach (Cell cell in gridModel.Cells)
{
if (cell.Bounds.Contains(PaddingBounds.Location))
{
if (cell.Id == roomA || cell.Id == roomB)
{
// collides with inside of the room.
return -1;
}
if (cell.CellType == CellType.Unknown)
{
// Convert this cell into a corridor
cell.CellType = CellType.Corridor;
}
// Intersects with an existing cell. do not add corridor padding
bRequiresPadding = false;
CurrentCellId = cell.Id;
break;
}
}
if (bRequiresPadding)
{
Cell corridorCell = new Cell();
corridorCell.Id = GetNextCellId();
corridorCell.UserDefined = false;
corridorCell.Bounds = PaddingBounds;
corridorCell.CellType = CellType.CorridorPadding;
gridModel.Cells.Add(corridorCell);
gridModel.BuildCellLookup();
CurrentCellId = corridorCell.Id;
}
if (canRegisterDoors)
{
// Check if we are adjacent to to any of the room nodes
if (AreCellsAdjacent(CurrentCellId, roomA))
{
ConnectAdjacentCells(CurrentCellId, roomA);
}
if (AreCellsAdjacent(CurrentCellId, roomB))
{
ConnectAdjacentCells(CurrentCellId, roomB);
}
}
return CurrentCellId; // Return the cell id of the registered cell
/*
Cell corridorCell = new Cell();
corridorCell.Id = GetNextCellId();
corridorCell.UserDefined = false;
corridorCell.Bounds = new Rectangle(cellX, cellZ, 1, 1);
corridorCell.CellType = CellType.CorridorPadding;
Cell cellA = gridModel.GetCell(roomA);
Cell cellB = gridModel.GetCell(roomB);
if (cellA == null || cellB == null)
{
outCellId = -1;
return false;
}
if (cellA.Bounds.Contains(corridorCell.Bounds.Location))
{
// ignore
outCellId = cellA.Id;
return false;
}
if (cellB.Bounds.Contains(corridorCell.Bounds.Location))
{
// ignore
outCellId = cellB.Id;
return false;
}
foreach (Cell cell in gridModel.Cells)
{
if (cell.Bounds.Contains(corridorCell.Bounds.Location))
{
if (cell.Id == roomA)
{
// collides with inside of the room.
outCellId = roomA;
return false;
}
if (cell.Id == roomB)
{
// collides with inside of the room.
outCellId = roomB;
return false;
}
if (cell.CellType == CellType.Unknown)
{
// Convert this cell into a corridor
cell.CellType = CellType.Corridor;
if (canRegisterDoors)
{
// Check if we are adjacent to to any of the room nodes
if (AreCellsAdjacent(cell.Id, roomA))
{
ConnectAdjacentCells(cell.Id, roomA);
}
if (AreCellsAdjacent(cell.Id, roomB))
{
ConnectAdjacentCells(cell.Id, roomB);
}
}
}
// Intersects with a cell. do not add corridor padding
return true;
}
}
gridModel.Cells.Add(corridorCell);
gridModel.BuildCellLookup();
{
var corridorX = corridorCell.Bounds.X;
var corridorZ = corridorCell.Bounds.Z;
if (!gridModel.GridCellInfoLookup.ContainsKey(corridorX))
{
gridModel.GridCellInfoLookup.Add(corridorX, new Dictionary<int, GridCellInfo>());
}
var cellInfo = new GridCellInfo(corridorCell.Id, corridorCell.CellType);
if (!gridModel.GridCellInfoLookup[corridorX].ContainsKey(corridorZ))
{
gridModel.GridCellInfoLookup[corridorX].Add(corridorZ, cellInfo);
}
else
{
gridModel.GridCellInfoLookup[corridorX][corridorZ] = cellInfo;
}
}
if (canRegisterDoors)
{
// Check if we are adjacent to to any of the room nodes
if (AreCellsAdjacent(corridorCell.Id, roomA))
{
ConnectAdjacentCells(corridorCell.Id, roomA);
}
if (AreCellsAdjacent(corridorCell.Id, roomB))
{
ConnectAdjacentCells(corridorCell.Id, roomB);
}
}
return true; // Indicate used
*/
}
static int GetForceDirectionMultiplier(float a, float b, float a1, float b1)
{
if (a == b)
{
return (a1 < b1) ? -1 : 1;
}
return (a < b) ? -1 : 1;
}
void GenerateDungeonHeights()
{
// build the adjacency graph in memory
if (gridModel.Cells.Count == 0) return;
Dictionary<int, CellHeightNode> CellHeightNodes = new Dictionary<int, CellHeightNode>();
HashSet<int> visited = new HashSet<int>();
Stack<CellHeightFrameInfo> stack = new Stack<CellHeightFrameInfo>(); ;
var initialCell = gridModel.Cells[0];
stack.Push(new CellHeightFrameInfo(initialCell.Id, initialCell.Bounds.Location.y));
var srandom = new PMRandom(gridConfig.Seed);
while (stack.Count > 0)
{
CellHeightFrameInfo top = stack.Pop();
if (visited.Contains(top.CellId)) continue;
visited.Add(top.CellId);
Cell cell = gridModel.GetCell(top.CellId);
if (cell == null) continue;
bool applyHeightVariation = (cell.Bounds.Size.x > 1 && cell.Bounds.Size.z > 1);
applyHeightVariation &= (cell.CellType != CellType.Room);
applyHeightVariation &= (cell.CellType != CellType.CorridorPadding);
applyHeightVariation &= !cell.UserDefined;
if (applyHeightVariation)
{
float rand = srandom.GetNextUniformFloat();
if (rand < gridConfig.HeightVariationProbability / 2.0f)
{
top.CurrentHeight--;
}
else if (rand < gridConfig.HeightVariationProbability)
{
top.CurrentHeight++;
}
}
if (cell.UserDefined)
{
top.CurrentHeight = cell.Bounds.Location.y;
}
CellHeightNode node = new CellHeightNode();
node.CellId = cell.Id;
node.Height = top.CurrentHeight;
node.MarkForIncrease = false;
node.MarkForDecrease = false;
CellHeightNodes.Add(node.CellId, node);
// Add the child nodes
foreach (int childId in cell.AdjacentCells)
{
if (visited.Contains(childId)) continue;
stack.Push(new CellHeightFrameInfo(childId, top.CurrentHeight));
}
}
// Fix the dungeon heights
const int FIX_MAX_TRIES = 50; // TODO: Move to config
int fixIterations = 0;
while (fixIterations < FIX_MAX_TRIES && FixDungeonCellHeights(CellHeightNodes))
{
fixIterations++;
}
// Assign the calculated heights
foreach (Cell cell in gridModel.Cells)
{
if (CellHeightNodes.ContainsKey(cell.Id))
{
CellHeightNode node = CellHeightNodes[cell.Id];
var bounds = cell.Bounds;
var location = cell.Bounds.Location;
location.y = node.Height;
bounds.Location = location;
cell.Bounds = bounds;
}
}
}
bool FixDungeonCellHeights(Dictionary<int, CellHeightNode> CellHeightNodes)
{
bool bContinueIteration = false;
if (gridModel.Cells.Count == 0) return bContinueIteration;
HashSet<int> visited = new HashSet<int>();
Stack<int> stack = new Stack<int>();
Cell rootCell = gridModel.Cells[0];
stack.Push(rootCell.Id);
while (stack.Count > 0)
{
int cellId = stack.Pop();
if (visited.Contains(cellId)) continue;
visited.Add(cellId);
Cell cell = gridModel.GetCell(cellId);
if (cell == null) continue;
if (!CellHeightNodes.ContainsKey(cellId)) continue;
CellHeightNode heightNode = CellHeightNodes[cellId];
heightNode.MarkForIncrease = false;
heightNode.MarkForDecrease = false;
// Check if the adjacent cells have unreachable heights
foreach (int childId in cell.AdjacentCells)
{
Cell childCell = gridModel.GetCell(childId);
if (childCell == null || !CellHeightNodes.ContainsKey(childId)) continue;
CellHeightNode childHeightNode = CellHeightNodes[childId];
int heightDifference = Mathf.Abs(childHeightNode.Height - heightNode.Height);
if (heightDifference > gridConfig.MaxAllowedStairHeight)
{
if (heightNode.Height > childHeightNode.Height)
{
heightNode.MarkForDecrease = true;
}
else
{
heightNode.MarkForIncrease = true;
}
break;
}
}
// Add the child nodes
foreach (int childId in cell.AdjacentCells)
{
if (visited.Contains(childId)) continue;
stack.Push(childId);
}
}
bool bHeightChanged = false;
foreach (int cellId in CellHeightNodes.Keys)
{
CellHeightNode heightNode = CellHeightNodes[cellId];
if (heightNode.MarkForDecrease)
{
heightNode.Height--;
bHeightChanged = true;
}
else if (heightNode.MarkForIncrease)
{
heightNode.Height++;
bHeightChanged = true;
}
}
// Iterate this function again if the height was changed in this step
bContinueIteration = bHeightChanged;
return bContinueIteration;
}
int HASH(int a, int b)
{
return (a << 16) + b;
}
void RemoveAdjacentDoors()
{
var doorsToRemove = new List<CellDoor>();
foreach (var door in gridModel.DoorManager.Doors)
{
var cellIdA = door.AdjacentCells[0];
var cellIdB = door.AdjacentCells[1];
door.Enabled = false;
// Check if it is really required to have a door here. We do this by disabling the door and
// check if the two adjacent cells (now with a wall instead of a door) can reach each other
// within the specified steps
bool pathExists = ContainsAdjacencyPath(cellIdA, cellIdB, (int)gridConfig.DoorProximitySteps);
if (!pathExists)
{
// No path exists. We need a door here
door.Enabled = true;
}
else
{
// Path exists between the doors even when the door is removed. Remove the door for good
// Remove the door for good
doorsToRemove.Add(door);
}
}
foreach (var doorToRemove in doorsToRemove)
{
gridModel.DoorManager.RemoveDoor(doorToRemove);
}
}
bool ContainsAdjacencyPath(int cellIdA, int cellIdB, int maxDepth)
{
Cell cellA = gridModel.GetCell(cellIdA);
Cell cellB = gridModel.GetCell(cellIdB);
if (cellA == null || cellB == null)
{
return false;
}
if (cellA.CellType == CellType.Room || cellB.CellType == CellType.Room)
{
// Force a connection if any one is a room
//return false;
}
var queue = new Queue<StairAdjacencyQueueNode>();
var visited = new HashSet<int>();
queue.Enqueue(new StairAdjacencyQueueNode(cellIdA, 0));
while (queue.Count > 0)
{
StairAdjacencyQueueNode topNode = queue.Dequeue();
if (topNode.depth > maxDepth) continue;
int topId = topNode.cellId;
if (visited.Contains(topId)) continue;
visited.Add(topId);
if (topId == cellIdB)
{
// Reached the target cell
return true;
}
Cell top = gridModel.GetCell(topId);
if (top == null) continue;
foreach (int adjacentCellId in top.AdjacentCells)
{
if (visited.Contains(adjacentCellId)) continue;
// Check if we have a valid path between these two adjacent cells
// (either through same height or by a already registered stair)
Cell adjacentCell = gridModel.GetCell(adjacentCellId);
if (adjacentCell == null) continue;
bool pathExists = (adjacentCell.Bounds.Location.y == top.Bounds.Location.y);
if (!pathExists)
{
// Cells are on different heights. Check if we have a stair connecting these cells
StairInfo stair = new StairInfo();
if (GetStair(topId, adjacentCellId, ref stair))
{
pathExists = true;
}
if (!pathExists)
{
if (GetStair(adjacentCellId, topId, ref stair))
{
pathExists = true;
}
}
}
if (pathExists) {
// If any one of the cells is a room, then make sure we have a door between them
if (top.CellType == CellType.Room || adjacentCell.CellType == CellType.Room) {
var containsDoor = gridModel.DoorManager.ContainsDoorBetweenCells(top.Id, adjacentCellId);
if (!containsDoor) {
pathExists = false;
}
}
}
if (pathExists)
{
queue.Enqueue(new StairAdjacencyQueueNode(adjacentCellId, topNode.depth + 1));
}
}
}
return false;
}
void AddCorridorPadding(int x, int y, int z) {
Cell padding = new Cell();
padding.Id = GetNextCellId();
padding.UserDefined = false;
var bounds = new Rectangle(x, z, 1, 1);
bounds.SetY(y);
padding.Bounds = bounds;
padding.CellType = CellType.CorridorPadding;
gridModel.Cells.Add(padding);
}
class StairConnectionWeight {
public StairConnectionWeight(int position, int weight) {
this.position = position;
this.weight = weight;
}
public int position;
public int weight;
}
class StairConnectionWeightComparer : IComparer<StairConnectionWeight>
{
public int Compare(StairConnectionWeight x, StairConnectionWeight y)
{
if (x.weight == y.weight) return 0;
return (x.weight < y.weight) ? 1 : -1;
}
}
void ConnectStairs(int WeightThreshold)
{
if (gridModel.Cells.Count == 0) return;
Stack<StairEdgeInfo> stack = new Stack<StairEdgeInfo>();
HashSet<int> visited = new HashSet<int>();
HashSet<int> islandsVisited = new HashSet<int>();
for (int i = 0; i < gridModel.Cells.Count; i++) {
var startCell = gridModel.Cells[i];
if (islandsVisited.Contains (startCell.Id)) {
continue;
}
stack.Push(new StairEdgeInfo(-1, startCell.Id));
while (stack.Count > 0)
{
StairEdgeInfo top = stack.Pop();
if (top.CellIdA >= 0)
{
int hash1 = HASH(top.CellIdA, top.CellIdB);
int hash2 = HASH(top.CellIdB, top.CellIdA);
if (visited.Contains(hash1) || visited.Contains(hash2))
{
// Already processed
continue;
}
// Mark as processed
visited.Add(hash1);
visited.Add(hash2);
// Mark the island as processed
islandsVisited.Add(top.CellIdA);
islandsVisited.Add(top.CellIdB);
// Check if it is really required to place a stair here. There might be other paths nearby to this cell
bool pathExists = ContainsAdjacencyPath(top.CellIdA, top.CellIdB, (int)gridConfig.StairConnectionTollerance);
bool stairConnectsToDoor = gridModel.DoorManager.ContainsDoorBetweenCells(top.CellIdA, top.CellIdB);
if (!pathExists || stairConnectsToDoor)
{
// Process the edge
Cell cellA = gridModel.GetCell(top.CellIdA);
Cell cellB = gridModel.GetCell(top.CellIdB);
if (cellA == null || cellB == null) continue;
if (cellA.Bounds.Location.y != cellB.Bounds.Location.y)
{
// Find the adjacent line
Rectangle intersection = Rectangle.Intersect(cellA.Bounds, cellB.Bounds);
if (intersection.Size.x > 0)
{
bool cellAAbove = (cellA.Bounds.Location.y > cellB.Bounds.Location.y);
Cell stairOwner = (cellAAbove ? cellB : cellA);
Cell stairConnectedTo = (!cellAAbove ? cellB : cellA);
if (ContainsStair(stairOwner.Id, stairConnectedTo.Id))
{
// Stair already exists here. Move to the next one
continue;
}
bool cellOwnerOnLeft = (stairOwner.Bounds.Center().z < intersection.Location.z);
int validX = intersection.Location.x;
//int preferedLocation = MathUtils.INVALID_LOCATION;
int validZ = intersection.Location.z;
if (cellOwnerOnLeft) validZ--;
var StairConnectionCandidates = new List<StairConnectionWeight>();
for (validX = intersection.Location.x; validX < intersection.Location.x + intersection.Size.x; validX++)
{
var currentPointInfo = gridModel.GetGridCellLookup(validX, validZ);
if (stairOwner.CellType == CellType.Room || stairConnectedTo.CellType == CellType.Room) {
// Make sure the stair is on a door cell
GridCellInfo stairCellInfo = gridModel.GetGridCellLookup(validX, validZ);
if (!stairCellInfo.ContainsDoor) {
// Stair not connected to a door. Probably trying to attach itself to a room wall. ignore
continue;
}
// We have a door here. A stair case is a must, but first make sure we have a door between these two cells
bool hasDoor = gridModel.DoorManager.ContainsDoorBetweenCells(stairOwner.Id, stairConnectedTo.Id);
if (!hasDoor) continue;
// Check again in more detail
var tz1 = validZ;
var tz2 = validZ - 1;
if (cellOwnerOnLeft) {
tz2 = validZ + 1;
}
hasDoor = gridModel.DoorManager.ContainsDoor(validX, tz1, validX, tz2);
if (hasDoor) {
StairConnectionCandidates.Add(new StairConnectionWeight(validX, 100));
break;
}
}
else { // Both the cells are non-rooms (corridors)
int weight = 0;
GridCellInfo cellInfo0 = gridModel.GetGridCellLookup(validX, validZ - 1);
GridCellInfo cellInfo1 = gridModel.GetGridCellLookup(validX, validZ + 1);
weight += (cellInfo0.CellType != CellType.Unknown) ? 10 : 0;
weight += (cellInfo1.CellType != CellType.Unknown) ? 10 : 0;
int adjacentOwnerZ = cellOwnerOnLeft ? (validZ - 1) : (validZ + 1);
int adjacentConnectedToZ = !cellOwnerOnLeft ? (validZ - 1) : (validZ + 1);
if (currentPointInfo.ContainsDoor) {
// Increase the weight if we connect into a door
int adjacentZ = cellOwnerOnLeft ? (validZ - 1) : (validZ + 1);
bool ownerOnDoor = gridModel.DoorManager.ContainsDoor(validX, validZ, validX, adjacentZ);
if (ownerOnDoor) {
// Connect to this
weight += 100;
}
else {
// Add a penalty if we are creating a stair blocking a door entry/exit
weight -= 100;
}
}
else {
// Make sure we don't connect to a wall
GridCellInfo adjacentOwnerCellInfo = gridModel.GetGridCellLookup(validX, adjacentOwnerZ);
if (adjacentOwnerCellInfo.CellType == CellType.Room) {
// We are connecting to a wall. Add a penalty
weight -= 100;
}
}
// Check the side of the stairs to see if we are not blocking a stair entry / exit
if (gridModel.ContainsStairAtLocation(validX - 1, validZ)) {
weight -= 60;
}
if (gridModel.ContainsStairAtLocation(validX + 1, validZ))
{
weight -= 60;
}
for (int dx = -1; dx <= 1; dx++) {
var adjacentStair = gridModel.GetStairAtLocation(validX + dx, adjacentOwnerZ);
if (adjacentStair != null) {
var currentRotation = Quaternion.AngleAxis(cellOwnerOnLeft ? -90 : 90, new Vector3(0, 1, 0));
var angle = Quaternion.Angle(adjacentStair.Rotation, currentRotation);
if (dx == 0) {
// If we have a stair case in a perpendicular direction right near the owner, add a penalty
var angleDelta = Mathf.Abs (Mathf.Abs(angle) - 90);
if (angleDelta < 2) {
weight -= 100;
}
} else {
var angleDelta = Mathf.Abs (Mathf.Abs(angle) - 180);
if (angleDelta < 2) {
weight -= 60;
}
}
}
}
// If we connect to another stair with the same angle, then increase the weight
if (gridModel.ContainsStairAtLocation(validX, adjacentConnectedToZ)) {
var adjacentStair = gridModel.GetStairAtLocation(validX, adjacentConnectedToZ);
if (adjacentStair != null) {
var currentRotation = Quaternion.AngleAxis(cellOwnerOnLeft ? -90 : 90, new Vector3(0, 1, 0));
var angle = Quaternion.Angle(adjacentStair.Rotation, currentRotation);
var angleDelta = Mathf.Abs(angle) % 360;
if (angleDelta < 2) {
weight += 50;
}
else {
weight -= 50;
}
}
}
// check if the entry of the stair is not in a different height
{
var adjacentEntryCellInfo = gridModel.GetGridCellLookup(validX, adjacentOwnerZ);
if (adjacentEntryCellInfo.CellType != CellType.Unknown) {
var adjacentEntryCell = gridModel.GetCell(adjacentEntryCellInfo.CellId);
if (stairOwner.Bounds.Location.y != adjacentEntryCell.Bounds.Location.y) {
// The entry is in a different height. Check if we have a stair here
if (!gridModel.ContainsStair(validX, adjacentOwnerZ)) {
//Add a penalty
weight -= 10;
}
}
}
}
StairConnectionCandidates.Add(new StairConnectionWeight(validX, weight));
}
}
// Create a stair if necessary
if (StairConnectionCandidates.Count > 0)
{
StairConnectionCandidates.Sort(new StairConnectionWeightComparer());
var candidate = StairConnectionCandidates[0];
if (candidate.weight < WeightThreshold)
{
continue;
}
validX = candidate.position;
int stairY = stairOwner.Bounds.Location.y;
var paddingOffset = (stairOwner.Bounds.Z > stairConnectedTo.Bounds.Z) ? 1 : -1;
// Add a corridor padding here
//AddCorridorPadding(validX, stairY, validZ - 1);
for (int dx = -1; dx <= 1; dx++) {
bool requiresPadding = false;
if (dx == 0) {
requiresPadding = true;
} else {
var cellInfo = GetGridCellLookup(validX + dx, validZ);
if (cellInfo.CellType != CellType.Unknown) {
requiresPadding = true;
}
}
if (requiresPadding) {
var paddingInfo = GetGridCellLookup(validX + dx, validZ + paddingOffset);
if (paddingInfo.CellType == CellType.Unknown) {
AddCorridorPadding(validX + dx, stairY, validZ + paddingOffset);
}
}
}
gridModel.BuildCellLookup();
gridModel.BuildSpatialCellLookup();
GenerateAdjacencyLookup();
}
else
{
continue;
}
float validY = stairOwner.Bounds.Location.y;
Vector3 StairLocation = new Vector3(validX, validY, validZ);
StairLocation += new Vector3(0.5f, 0, 0.5f);
StairLocation = Vector3.Scale(StairLocation, GridToMeshScale);
Quaternion StairRotation = Quaternion.AngleAxis(cellOwnerOnLeft ? -90 : 90, new Vector3(0, 1, 0));
if (!CellStairs.ContainsKey(stairOwner.Id))
{
CellStairs.Add(stairOwner.Id, new List<StairInfo>());
}
StairInfo Stair = new StairInfo();
Stair.OwnerCell = stairOwner.Id;
Stair.ConnectedToCell = stairConnectedTo.Id;
Stair.Position = StairLocation;
Stair.IPosition = new IntVector(validX, (int)validY, validZ);
Stair.Rotation = StairRotation;
if (!gridModel.ContainsStairAtLocation(validX, validZ))
{
CellStairs[stairOwner.Id].Add(Stair);
}
}
else if (intersection.Size.z > 0)
{
bool cellAAbove = (cellA.Bounds.Location.y > cellB.Bounds.Location.y);
Cell stairOwner = (cellAAbove ? cellB : cellA);
Cell stairConnectedTo = (!cellAAbove ? cellB : cellA);
if (ContainsStair(stairOwner.Id, stairConnectedTo.Id))
{
// Stair already exists here. Move to the next one
continue;
}
bool cellOwnerOnLeft = (stairOwner.Bounds.Center().x < intersection.Location.x);
int validX = intersection.Location.x;
if (cellOwnerOnLeft) validX--;
int validZ = intersection.Location.z;
var StairConnectionCandidates = new List<StairConnectionWeight>();
for (validZ = intersection.Location.z; validZ < intersection.Location.z + intersection.Size.z; validZ++)
{
var currentPointInfo = gridModel.GetGridCellLookup(validX, validZ);
if (stairOwner.CellType == CellType.Room || stairConnectedTo.CellType == CellType.Room) {
// Make sure the stair is on a door cell
GridCellInfo stairCellInfo = gridModel.GetGridCellLookup(validX, validZ);
if (!stairCellInfo.ContainsDoor) {
// Stair not connected to a door. Probably trying to attach itself to a room wall. ignore
continue;
}
// We have a door here. A stair case is a must, but first make sure we have a door between these two cells
bool hasDoor = gridModel.DoorManager.ContainsDoorBetweenCells(stairOwner.Id, stairConnectedTo.Id);
if (!hasDoor) continue;
// Check again in more detail
var tx1 = validX;
var tx2 = validX - 1;
if (cellOwnerOnLeft) {
tx2 = validX + 1;
}
hasDoor = gridModel.DoorManager.ContainsDoor(tx1, validZ, tx2, validZ);
if (hasDoor) {
StairConnectionCandidates.Add(new StairConnectionWeight(validZ, 100));
break;
}
}
else { // Both the cells are non-rooms (corridors)
int weight = 0;
GridCellInfo cellInfo0 = gridModel.GetGridCellLookup(validX - 1, validZ);
GridCellInfo cellInfo1 = gridModel.GetGridCellLookup(validX + 1, validZ);
weight += (cellInfo0.CellType != CellType.Unknown) ? 10 : 0;
weight += (cellInfo1.CellType != CellType.Unknown) ? 10 : 0;
int adjacentOwnerX = cellOwnerOnLeft ? (validX - 1) : (validX + 1);
int adjacentConnectedToX = !cellOwnerOnLeft ? (validX - 1) : (validX + 1);
if (currentPointInfo.ContainsDoor) {
// Increase the weight if we connect into a door
bool ownerOnDoor = gridModel.DoorManager.ContainsDoor(validX, validZ, adjacentOwnerX, validZ);
if (ownerOnDoor) {
// Connect to this
weight += 100;
}
else {
// Add a penalty if we are creating a stair blocking a door entry/exit
weight -= 100;
}
}
else {
// Make sure we don't connect to a wall
int adjacentX = cellOwnerOnLeft ? (validX - 1) : (validX + 1);
GridCellInfo adjacentOwnerCellInfo = gridModel.GetGridCellLookup(adjacentX, validZ);
if (adjacentOwnerCellInfo.CellType == CellType.Room) {
// We are connecting to a wall. Add a penalty
weight -= 100;
}
}
// Check the side of the stairs to see if we are not blocking a stair entry / exit
if (gridModel.ContainsStairAtLocation(validX, validZ - 1)) {
weight -= 60;
}
if (gridModel.ContainsStairAtLocation(validX, validZ + 1)) {
weight -= 60;
}
// If we have a stair coming out in the opposite direction, near the entry of the stair, add a penalty
for (int dz = -1; dz <= 1; dz++) {
var adjacentStair = gridModel.GetStairAtLocation(adjacentOwnerX, validZ + dz);
if (adjacentStair != null) {
var currentRotation = Quaternion.AngleAxis(cellOwnerOnLeft ? 0 : 180, new Vector3(0, 1, 0));
var angle = Quaternion.Angle(adjacentStair.Rotation, currentRotation);
if (dz == 0) {
// If we have a stair case in a perpendicular direction right near the owner, add a penalty
var angleDelta = Mathf.Abs (Mathf.Abs(angle) - 90);
if (angleDelta < 2) {
weight -= 100;
}
} else {
var angleDelta = Mathf.Abs (Mathf.Abs(angle) - 180);
if (angleDelta < 2) {
weight -= 60;
}
}
}
}
// If we connect to another stair with the same angle, the increase the weight
if (gridModel.ContainsStairAtLocation(adjacentConnectedToX, validZ)) {
var adjacentStair = gridModel.GetStairAtLocation(adjacentConnectedToX, validZ);
if (adjacentStair != null) {
var currentRotation = Quaternion.AngleAxis(cellOwnerOnLeft ? 0 : 180, new Vector3(0, 1, 0));
var angle = Quaternion.Angle(adjacentStair.Rotation, currentRotation);
var angleDelta = Mathf.Abs(angle) % 360;
if (angleDelta < 2) {
weight += 50;
}
else {
weight -= 50;
}
}
}
// check if the entry of the stair is not in a different height
{
var adjacentEntryCellInfo = gridModel.GetGridCellLookup(adjacentOwnerX, validZ);
if (adjacentEntryCellInfo.CellType != CellType.Unknown) {
var adjacentEntryCell = gridModel.GetCell(adjacentEntryCellInfo.CellId);
if (stairOwner.Bounds.Location.y != adjacentEntryCell.Bounds.Location.y) {
// The entry is in a different height. Check if we have a stair here
if (!gridModel.ContainsStair(adjacentOwnerX, validZ)) {
//Add a penalty
weight -= 10;
}
}
}
}
StairConnectionCandidates.Add(new StairConnectionWeight(validZ, weight));
}
}
// Connect the stairs if necessary
if (StairConnectionCandidates.Count > 0)
{
StairConnectionCandidates.Sort(new StairConnectionWeightComparer());
StairConnectionWeight candidate = StairConnectionCandidates[0];
if (candidate.weight < WeightThreshold)
{
continue;
}
validZ = candidate.position;
int stairY = stairOwner.Bounds.Location.y;
var paddingOffset = (stairOwner.Bounds.X > stairConnectedTo.Bounds.X) ? 1 : -1;
// Add a corridor padding here
for (int dz = -1; dz <= 1; dz++) {
bool requiresPadding = false;
if (dz == 0) {
requiresPadding = true;
} else {
var cellInfo = GetGridCellLookup(validX, validZ + dz);
if (cellInfo.CellType != CellType.Unknown) {
requiresPadding = true;
}
}
if (requiresPadding) {
var paddingInfo = GetGridCellLookup(validX + paddingOffset, validZ + dz);
if (paddingInfo.CellType == CellType.Unknown) {
AddCorridorPadding(validX + paddingOffset, stairY, validZ + dz);
}
}
}
gridModel.BuildCellLookup();
gridModel.BuildSpatialCellLookup();
GenerateAdjacencyLookup();
}
else
{
continue;
}
float validY = stairOwner.Bounds.Location.y;
Vector3 StairLocation = new Vector3(validX, validY, validZ);
StairLocation += new Vector3(0.5f, 0, 0.5f);
StairLocation = Vector3.Scale(StairLocation, GridToMeshScale);
Quaternion StairRotation = Quaternion.AngleAxis(cellOwnerOnLeft ? 0 : 180, new Vector3(0, 1, 0));
if (!CellStairs.ContainsKey(stairOwner.Id))
{
CellStairs.Add(stairOwner.Id, new List<StairInfo>());
}
StairInfo Stair = new StairInfo();
Stair.OwnerCell = stairOwner.Id;
Stair.ConnectedToCell = stairConnectedTo.Id;
Stair.Position = StairLocation;
Stair.IPosition = new IntVector(validX, (int)validY, validZ);
Stair.Rotation = StairRotation;
if (!gridModel.ContainsStairAtLocation(validX, validZ))
{
CellStairs[stairOwner.Id].Add(Stair);
}
}
}
}
}
// Move to the next adjacent nodes
{
Cell cellB = gridModel.GetCell(top.CellIdB);
if (cellB == null) continue;
foreach (int adjacentCell in cellB.AdjacentCells)
{
int hash1 = HASH(cellB.Id, adjacentCell);
int hash2 = HASH(adjacentCell, cellB.Id);
if (visited.Contains(hash1) || visited.Contains(hash2)) continue;
StairEdgeInfo edge = new StairEdgeInfo(top.CellIdB, adjacentCell);
stack.Push(edge);
}
}
}
}
}
public override void OnVolumePositionModified(Volume volume, out IntVector newPositionOnGrid, out IntVector newSizeOnGrid)
{
volume.GetVolumeGridTransform(out newPositionOnGrid, out newSizeOnGrid, GridToMeshScale);
}
void CheckAndMarkAdjacent(Cell cell, int otherCellX, int otherCellZ)
{
GridCellInfo info = gridModel.GetGridCellLookup(otherCellX, otherCellZ);
if (info.CellId == cell.Id) return;
Cell otherCell = gridModel.GetCell(info.CellId);
if (otherCell == null) return;
if (otherCell.CellType == CellType.Unknown || cell.CellType == CellType.Unknown) return;
// Mark the two cells as adjacent
cell.AdjacentCells.Add(otherCell.Id);
otherCell.AdjacentCells.Add(cell.Id);
}
public void GenerateAdjacencyLookup()
{
// Cache the cell types based on their positions
gridModel.BuildSpatialCellLookup();
// Create cell adjacency list
foreach (var cell in gridModel.Cells)
{
if (cell.CellType == CellType.Unknown) continue;
int SizeX = cell.Bounds.Size.x;
int SizeZ = cell.Bounds.Size.z;
for (int dx = 0; dx < SizeX; dx++)
{
for (int dz = 0; dz < SizeZ; dz++)
{
if (dx >= 0 && dx < SizeX - 1 && dz >= 0 && dz < SizeZ - 1)
{
// Ignore the cells in the middle
continue;
}
int x = cell.Bounds.Location.x + dx;
int z = cell.Bounds.Location.z + dz;
CheckAndMarkAdjacent(cell, x + 1, z);
CheckAndMarkAdjacent(cell, x, z + 1);
}
}
}
// Cache the positions of the doors in the grid
foreach (CellDoor Door in gridModel.DoorManager.Doors)
{
int x0 = Door.AdjacentTiles[0].x;
int z0 = Door.AdjacentTiles[0].z;
int x1 = Door.AdjacentTiles[1].x;
int z1 = Door.AdjacentTiles[1].z;
if (GridCellInfoLookup.ContainsKey(x0) && GridCellInfoLookup[x0].ContainsKey(z0)) GridCellInfoLookup[x0][z0].ContainsDoor = true;
if (GridCellInfoLookup.ContainsKey(x1) && GridCellInfoLookup[x1].ContainsKey(z1)) GridCellInfoLookup[x1][z1].ContainsDoor = true;
}
}
public override void EmitMarkers()
{
base.EmitMarkers ();
if (gridModel.Cells.Count == 0) return;
// Populate the prop sockets all over the map
foreach (var cell in gridModel.Cells)
{
switch (cell.CellType)
{
case CellType.Room:
BuildMesh_Room(cell);
BuildMesh_RoomDecoration(cell);
break;
case CellType.Corridor:
case CellType.CorridorPadding:
{
if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsEdges)
{
BuildMesh_Corridor(cell);
}
else
{
BuildMesh_Corridor_BlockWalls(cell);
}
}
break;
}
BuildMesh_Stairs(cell);
}
RemoveOverlappingMarkers();
ProcessMarkerOverrideVolumes();
}
int GetElevation(Cell baseCell, int x, int z, out int OutYOffset)
{
OutYOffset = 0;
GridCellInfo info = GetGridCellLookup(x, z);
int elevation = gridConfig.FloorHeight;
if (info.CellType == CellType.Unknown) return elevation;
Cell otherCell = gridModel.GetCell(info.CellId);
if (otherCell == null)
{
return elevation;
}
OutYOffset = otherCell.Bounds.Location.y - baseCell.Bounds.Location.y;
elevation = Mathf.Max(elevation, Mathf.Abs(OutYOffset));
OutYOffset = Mathf.Max(0, OutYOffset);
//return FMath::Max(elevation, baseCell.Bounds.Location.Z - otherCell->Bounds.Location.Z);
return elevation;
}
void OffsetTransformY(float Y, ref Matrix4x4 OutTransform)
{
Vector3 Location = Matrix.GetTranslation(ref OutTransform);
Location.y += Y;
Matrix.SetTranslation(ref OutTransform, Location);
}
bool GetStair(int ownerCell, int connectedToCell, ref StairInfo outStair)
{
if (CellStairs.ContainsKey(ownerCell))
{
foreach (StairInfo stair in CellStairs[ownerCell])
{
if (stair.ConnectedToCell == connectedToCell)
{
outStair = stair;
return true;
}
}
}
return false;
}
bool ContainsStair(int ownerCellId, int connectedToCellId)
{
if (!gridModel.CellStairs.ContainsKey(ownerCellId))
{
return false;
}
foreach (var stairInfo in gridModel.CellStairs[ownerCellId])
{
if (stairInfo.ConnectedToCell == connectedToCellId)
{
return true;
}
}
return false;
}
bool ContainsStair(Cell baseCell, int x, int z)
{
GridCellInfo info = GetGridCellLookup(x, z);
if (info.CellType == CellType.Unknown) return false;
Cell cell = gridModel.GetCell(info.CellId);
if (cell == null) return false;
StairInfo stair = new StairInfo();
if (GetStair(cell.Id, baseCell.Id, ref stair))
{
Vector3 IPosition = MathUtils.Divide(stair.Position, GridToMeshScale);
int ix = Mathf.FloorToInt(IPosition.x);
int iz = Mathf.FloorToInt(IPosition.z);
if (ix == x && iz == z)
{
return true;
}
}
return false;
}
public bool V3Equal(Vector3 a, Vector3 b)
{
return Vector3.SqrMagnitude(a - b) < 1e-6f;
}
bool CanDrawFence(Cell baseCell, int x, int z, out bool isElevatedFence, out bool drawPillar, out int elevationHeight)
{
GridCellInfo info = GetGridCellLookup(x, z);
isElevatedFence = false;
drawPillar = false;
elevationHeight = 0;
if (info.CellType == CellType.Unknown)
{
isElevatedFence = false;
drawPillar = true;
return true;
}
Cell otherCell = gridModel.GetCell(info.CellId);
if (otherCell != null && otherCell.Bounds.Location.y < baseCell.Bounds.Location.y)
{
isElevatedFence = true;
elevationHeight = baseCell.Bounds.Location.y - otherCell.Bounds.Location.y;
drawPillar = true;
// Make sure we dont have a stair between the two cells
if (!ContainsStair(baseCell, x, z))
{
return true;
}
}
return false;
}
bool ShouldMakeDoor(int x1, int z1, int x2, int z2, out GridCellInfo cellA, out GridCellInfo cellB)
{
cellA = GetGridCellLookup(x1, z1);
cellB = GetGridCellLookup(x2, z2);
bool makeDoor = (cellA.ContainsDoor && cellB.ContainsDoor);
if (makeDoor)
{
// Now perform an exhaustive search to see if a door really exists between them
makeDoor = gridModel.DoorManager.ContainsDoorBetweenCells(cellA.CellId, cellB.CellId);
}
return makeDoor;
}
void BuildMesh_Room(Cell cell)
{
BuildMesh_Floor(cell);
Vector3 HalfWallOffset = Vector3.Scale(GridToMeshScale, new Vector3(0, -1, 0));
// Build the room walls
IntVector basePosition = cell.Bounds.Location;
int elevation;
GridCellInfo doorCellA, doorCellB;
object markerMetadata = null;
var gridPosition = new IntVector();
// build walls along the width
for (int dx = 0; dx < cell.Bounds.Size.x; dx++)
{
int x = basePosition.x + dx;
int y = basePosition.y;
int z = basePosition.z;
gridPosition.Set(x, y, z);
Matrix4x4 transform = Matrix4x4.identity;
Vector3 position = new Vector3(x, y, z);
if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsEdges)
{
position += new Vector3(0.5f, 0, 0);
}
else if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks)
{
position += new Vector3(0.5f, 0, 0.5f);
}
position = Vector3.Scale(position, GridToMeshScale);
var rotation = Quaternion.AngleAxis(180, new Vector3(0, 1, 0));
transform = Matrix4x4.TRS(position, rotation, Vector3.one);
int OffsetY = 0;
//var cellLookupA = GetGridCellLookup(x, z);
//var cellLookupB = GetGridCellLookup(x, z - 1);
bool makeDoor = ShouldMakeDoor(x, z, x, z - 1, out doorCellA, out doorCellB);
elevation = GetElevation(cell, x, z - 1, out OffsetY);
OffsetTransformY(OffsetY * GridToMeshScale.y, ref transform);
string SocketType = makeDoor ? GridDungeonMarkerNames.Door : GridDungeonMarkerNames.Wall;
markerMetadata = makeDoor ? new GridBuilderDoorMetadata(doorCellA.CellId, doorCellB.CellId) : null;
EmitMarker(SocketType, transform, gridPosition, cell.Id, markerMetadata);
EmitMarker(GridDungeonMarkerNames.WallHalf, transform, elevation, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
// Add the pillar
Matrix.SetTranslation(ref transform, Vector3.Scale(new Vector3(x, y, z), GridToMeshScale));
OffsetTransformY(OffsetY * GridToMeshScale.y, ref transform);
EmitMarker(GridDungeonMarkerNames.WallSeparator, transform, gridPosition, cell.Id);
EmitMarker(GridDungeonMarkerNames.WallHalfSeparator, transform, elevation, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
z += cell.Bounds.Size.z;
gridPosition.Set(x, y, z);
elevation = GetElevation(cell, x, z, out OffsetY);
GridCellInfo AdjacentCellInfo = GetGridCellLookup(x, z);
if (AdjacentCellInfo.CellType != CellType.Room || gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks)
{
if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsEdges)
{
position.z = z;
}
else if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks)
{
position.z = z - 0.5f;
}
position.z *= GridToMeshScale.z;
rotation = Quaternion.AngleAxis(0, new Vector3(0, 1, 0));
transform = Matrix4x4.TRS(position, rotation, Vector3.one);
makeDoor = ShouldMakeDoor(x, z, x, z - 1, out doorCellA, out doorCellB);
SocketType = makeDoor ? GridDungeonMarkerNames.Door : GridDungeonMarkerNames.Wall;
markerMetadata = makeDoor ? new GridBuilderDoorMetadata(doorCellA.CellId, doorCellB.CellId) : null;
OffsetTransformY(OffsetY * GridToMeshScale.y, ref transform);
// The adjacent room will already have a door, so we ignore this if this is a door
bool bIgnoreMainMarker = (makeDoor && AdjacentCellInfo.CellType == CellType.Room && gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks);
if (!bIgnoreMainMarker)
{
EmitMarker(SocketType, transform, gridPosition, cell.Id, markerMetadata);
}
EmitMarker(GridDungeonMarkerNames.WallHalf, transform, elevation, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
// Add the pillar
Matrix.SetTranslation(ref transform, Vector3.Scale(new Vector3(x + 1, y, z), GridToMeshScale));
OffsetTransformY(OffsetY * GridToMeshScale.y, ref transform);
gridPosition.x++;
EmitMarker(GridDungeonMarkerNames.WallSeparator, transform, gridPosition, cell.Id);
EmitMarker(GridDungeonMarkerNames.WallHalfSeparator, transform, elevation, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
// build walls along the length
for (int dz = 0; dz < cell.Bounds.Size.z; dz++)
{
int x = basePosition.x;
int y = basePosition.y;
int z = basePosition.z + dz;
gridPosition.Set(x, y, z);
Matrix4x4 transform = Matrix4x4.identity;
Vector3 position = new Vector3(x, y, z);
if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsEdges)
{
position += new Vector3(0, 0, 0.5f);
}
else if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks)
{
position += new Vector3(0.5f, 0, 0.5f);
}
position = Vector3.Scale(position, GridToMeshScale);
var rotation = Quaternion.AngleAxis(-90, new Vector3(0, 1, 0));
transform = Matrix4x4.TRS(position, rotation, Vector3.one);
int OffsetY = 0;
bool makeDoor = ShouldMakeDoor(x, z, x - 1, z, out doorCellA, out doorCellB);
elevation = GetElevation(cell, x - 1, z, out OffsetY);
string SocketType = makeDoor ? GridDungeonMarkerNames.Door : GridDungeonMarkerNames.Wall;
markerMetadata = makeDoor ? new GridBuilderDoorMetadata(doorCellA.CellId, doorCellB.CellId) : null;
OffsetTransformY(OffsetY * GridToMeshScale.y, ref transform);
EmitMarker(SocketType, transform, gridPosition, cell.Id, markerMetadata);
EmitMarker(GridDungeonMarkerNames.WallHalf, transform, elevation, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
// Add the pillar
Matrix.SetTranslation(ref transform, Vector3.Scale(new Vector3(x, y, z + 1), GridToMeshScale));
OffsetTransformY(OffsetY * GridToMeshScale.y, ref transform);
gridPosition.z++;
EmitMarker(GridDungeonMarkerNames.WallSeparator, transform, gridPosition, cell.Id);
EmitMarker(GridDungeonMarkerNames.WallHalfSeparator, transform, elevation, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
x += cell.Bounds.Size.x;
gridPosition.Set(x, y, z);
elevation = GetElevation(cell, x, z, out OffsetY);
GridCellInfo AdjacentCellInfo = GetGridCellLookup(x, z);
if (AdjacentCellInfo.CellType != CellType.Room || gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks)
{
if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsEdges)
{
position.x = x;
}
else if (gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks)
{
position.x = x - 0.5f;
}
position.x *= GridToMeshScale.x;
rotation = Quaternion.AngleAxis(90, new Vector3(0, 1, 0));
transform = Matrix4x4.TRS(position, rotation, Vector3.one);
makeDoor = ShouldMakeDoor(x, z, x - 1, z, out doorCellA, out doorCellB);
SocketType = makeDoor ? GridDungeonMarkerNames.Door : GridDungeonMarkerNames.Wall;
markerMetadata = makeDoor ? new GridBuilderDoorMetadata(doorCellA.CellId, doorCellB.CellId) : null;
OffsetTransformY(OffsetY * GridToMeshScale.y, ref transform);
// The adjacent room will already have a door, so we ignore this if this is a door
bool bIgnoreMainMarker = (makeDoor && AdjacentCellInfo.CellType == CellType.Room && gridConfig.WallLayoutType == GridDungeonWallType.WallsAsTileBlocks);
if (!bIgnoreMainMarker)
{
EmitMarker(SocketType, transform, gridPosition, cell.Id, markerMetadata);
}
EmitMarker(GridDungeonMarkerNames.WallHalf, transform, elevation, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
// Add the pillar
Matrix.SetTranslation(ref transform, Vector3.Scale(new Vector3(x, y, z), GridToMeshScale));
OffsetTransformY(OffsetY * GridToMeshScale.y, ref transform);
EmitMarker(GridDungeonMarkerNames.WallSeparator, transform, gridPosition, cell.Id);
EmitMarker(GridDungeonMarkerNames.WallHalfSeparator, transform, elevation, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
}
void BuildMesh_RoomDecoration(Cell cell)
{
}
void BuildMesh_Corridor_BlockWalls(Cell cell)
{
BuildMesh_Floor(cell);
var fencePositions = new HashSet<IntVector>();
var border = Rectangle.ExpandBounds(cell.Bounds, 1);
var borderPoints = border.GetBorderPoints();
foreach (var p in borderPoints)
{
if (fencePositions.Contains(p))
{
continue;
}
var cellInfo = GetGridCellLookup(p.x, p.z);
if (cellInfo.CellType == CellType.Unknown)
{
fencePositions.Add(p);
}
}
foreach (var p in fencePositions)
{
var gridPosition = p;
Vector3 position = p.ToVector3();
position += new Vector3(0.5f, 0, 0.5f);
position = Vector3.Scale(position, GridToMeshScale);
var rotation = Quaternion.AngleAxis(0, new Vector3(0, 1, 0));
var transform = Matrix4x4.TRS(position, rotation, Vector3.one);
EmitMarker(GridDungeonMarkerNames.Fence, transform, gridPosition, cell.Id);
}
}
void BuildMesh_Corridor(Cell cell)
{
BuildMesh_Floor(cell);
Vector3 HalfWallOffset = Vector3.Scale(GridToMeshScale, new Vector3(0, -1, 0));
IntVector basePosition = cell.Bounds.Location;
var gridPosition = new IntVector();
// build fence along the width
for (int dx = 0; dx < cell.Bounds.Size.x; dx++)
{
int x = basePosition.x + dx;
int y = basePosition.y;
int z = basePosition.z;
gridPosition.Set(x, y, z);
int elevationHeight;
bool isElevatedFence, drawPillar;
bool drawFence = CanDrawFence(cell, x, z - 1, out isElevatedFence, out drawPillar, out elevationHeight);
Matrix4x4 transform = Matrix4x4.identity;
if (drawFence || isElevatedFence)
{
Vector3 position = new Vector3(x, y, z);
position += new Vector3(0.5f, 0, 0);
position = Vector3.Scale(position, GridToMeshScale);
var rotation = Quaternion.AngleAxis(180, new Vector3(0, 1, 0));
transform = Matrix4x4.TRS(position, rotation, Vector3.one);
if (drawFence)
{
EmitMarker(GridDungeonMarkerNames.Fence, transform, gridPosition, cell.Id);
}
if (isElevatedFence)
{
EmitMarker(GridDungeonMarkerNames.WallHalf, transform, elevationHeight, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
if (drawFence || drawPillar)
{
Matrix.SetTranslation(ref transform, Vector3.Scale(new Vector3(x, y, z), GridToMeshScale));
EmitMarker(GridDungeonMarkerNames.FenceSeparator, transform, gridPosition, cell.Id);
if (isElevatedFence)
{
EmitMarker(GridDungeonMarkerNames.WallHalfSeparator, transform, elevationHeight, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
z += cell.Bounds.Size.z;
gridPosition.Set(x, y, z);
drawFence = CanDrawFence(cell, x, z, out isElevatedFence, out drawPillar, out elevationHeight);
transform = Matrix4x4.identity;
if (drawFence || isElevatedFence)
{
Vector3 position = new Vector3(x, y, z);
position += new Vector3(0.5f, 0, 0);
position = Vector3.Scale(position, GridToMeshScale);
var rotation = Quaternion.AngleAxis(0, new Vector3(0, 1, 0));
transform = Matrix4x4.TRS(position, rotation, Vector3.one);
if (drawFence)
{
EmitMarker(GridDungeonMarkerNames.Fence, transform, gridPosition, cell.Id);
}
if (isElevatedFence)
{
EmitMarker(GridDungeonMarkerNames.WallHalf, transform, elevationHeight, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
if (drawFence || drawPillar)
{
Matrix.SetTranslation(ref transform, Vector3.Scale(new Vector3(x + 1, y, z), GridToMeshScale));
gridPosition.x++;
EmitMarker(GridDungeonMarkerNames.FenceSeparator, transform, gridPosition, cell.Id);
if (isElevatedFence)
{
EmitMarker(GridDungeonMarkerNames.WallHalfSeparator, transform, elevationHeight, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
}
// build fence along the length
for (int dz = 0; dz < cell.Bounds.Size.z; dz++)
{
int x = basePosition.x;
int y = basePosition.y;
int z = basePosition.z + dz;
gridPosition.Set(x, y, z);
int elevationHeight;
bool isElevatedFence, drawPillar;
bool drawFence = CanDrawFence(cell, x - 1, z, out isElevatedFence, out drawPillar, out elevationHeight);
Matrix4x4 transform = Matrix4x4.identity;
if (drawFence || isElevatedFence)
{
Vector3 position = new Vector3(x, y, z);
position += new Vector3(0, 0, 0.5f);
position = Vector3.Scale(position, GridToMeshScale);
var rotation = Quaternion.AngleAxis(-90, new Vector3(0, 1, 0));
transform = Matrix4x4.TRS(position, rotation, Vector3.one);
if (drawFence)
{
EmitMarker(GridDungeonMarkerNames.Fence, transform, gridPosition, cell.Id);
}
if (isElevatedFence)
{
EmitMarker(GridDungeonMarkerNames.WallHalf, transform, elevationHeight, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
if (drawFence || drawPillar)
{
Matrix.SetTranslation(ref transform, Vector3.Scale(new Vector3(x, y, z + 1), GridToMeshScale));
gridPosition.z++;
EmitMarker(GridDungeonMarkerNames.FenceSeparator, transform, gridPosition, cell.Id);
if (isElevatedFence)
{
EmitMarker(GridDungeonMarkerNames.WallHalfSeparator, transform, elevationHeight, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
x += cell.Bounds.Size.x;
gridPosition.Set(x, y, z);
drawFence = CanDrawFence(cell, x, z, out isElevatedFence, out drawPillar, out elevationHeight);
transform = Matrix4x4.identity;
if (drawFence || isElevatedFence)
{
Vector3 position = new Vector3(x, y, z);
position += new Vector3(0, 0, 0.5f);
position = Vector3.Scale(position, GridToMeshScale);
var rotation = Quaternion.AngleAxis(90, new Vector3(0, 1, 0));
transform = Matrix4x4.TRS(position, rotation, Vector3.one);
if (drawFence)
{
EmitMarker(GridDungeonMarkerNames.Fence, transform, gridPosition, cell.Id);
}
if (isElevatedFence)
{
EmitMarker(GridDungeonMarkerNames.WallHalf, transform, elevationHeight, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
if (drawFence || drawPillar)
{
Matrix.SetTranslation(ref transform, Vector3.Scale(new Vector3(x, y, z), GridToMeshScale));
EmitMarker(GridDungeonMarkerNames.FenceSeparator, transform, gridPosition, cell.Id);
if (isElevatedFence)
{
EmitMarker(GridDungeonMarkerNames.WallHalfSeparator, transform, elevationHeight, HalfWallOffset, gridPosition, cell.Id, GridToMeshScale);
}
}
}
}
int GetStairHeight(StairInfo stair)
{
Cell owner = gridModel.GetCell(stair.OwnerCell);
Cell target = gridModel.GetCell(stair.ConnectedToCell);
if (owner == null || target == null) return 1;
return Mathf.Abs(owner.Bounds.Location.y - target.Bounds.Location.y);
}
void RemoveOverlappingMarkers()
{
var wallPositions = new HashSet<Vector3>();
var wallSeparaterPositions = new HashSet<Vector3>();
foreach (PropSocket marker in markers)
{
if (marker.SocketType == GridDungeonMarkerNames.Wall)
{
var position = Matrix.GetTranslation(ref marker.Transform);
wallPositions.Add(position);
}
if (marker.SocketType == GridDungeonMarkerNames.WallSeparator)
{
var position = Matrix.GetTranslation(ref marker.Transform);
wallSeparaterPositions.Add(position);
}
}
var overlappingMarkers = new List<PropSocket>();
foreach (PropSocket marker in markers)
{
if (marker.SocketType == GridDungeonMarkerNames.Fence)
{
var position = Matrix.GetTranslation(ref marker.Transform);
if (wallPositions.Contains(position))
{
overlappingMarkers.Add(marker);
}
}
if (marker.SocketType == GridDungeonMarkerNames.FenceSeparator)
{
var position = Matrix.GetTranslation(ref marker.Transform);
if (wallSeparaterPositions.Contains(position))
{
overlappingMarkers.Add(marker);
}
}
}
// Remove all the overlapping markers
foreach (var overlappingMarker in overlappingMarkers)
{
markers.Remove(overlappingMarker);
}
}
void BuildMesh_Stairs(Cell cell)
{
// Draw all the stairs registered with this cell
if (!CellStairs.ContainsKey(cell.Id))
{
// No stairs registered here
return;
}
foreach (StairInfo stair in CellStairs[cell.Id])
{
Matrix4x4 transform = Matrix4x4.TRS(stair.Position, stair.Rotation, Vector3.one);
int stairHeight = GetStairHeight(stair);
string StairType = (stairHeight > 1) ? GridDungeonMarkerNames.Stair2X : GridDungeonMarkerNames.Stair;
EmitMarker(StairType, transform, stair.IPosition, cell.Id);
}
}
void BuildMesh_Floor(Cell cell)
{
var basePosition = cell.Bounds.Location;
var gridPosition = new IntVector();
for (int dx = 0; dx < cell.Bounds.Width; dx++)
{
for (int dz = 0; dz < cell.Bounds.Length; dz++)
{
int x = basePosition.x + dx;
int y = basePosition.y;
int z = basePosition.z + dz;
gridPosition.Set(x, y, z);
Vector3 position = new Vector3(x, y, z);
position += new Vector3(0.5f, 0, 0.5f);
position.Scale(GridToMeshScale);
Matrix4x4 transform = Matrix4x4.TRS(position, Quaternion.identity, Vector3.one);
EmitMarker(GridDungeonMarkerNames.Ground, transform, gridPosition, cell.Id);
}
}
}
public override void DebugDraw()
{
if (!gridModel) return;
foreach (var cell in gridModel.Cells)
{
GridDebugDrawUtils.DrawCell(cell, Color.white, gridConfig.GridCellSize, gridConfig.Mode2D);
GridDebugDrawUtils.DrawAdjacentCells(cell, gridModel, Color.green, gridConfig.Mode2D);
GridDebugDrawUtils.DrawCellConnectionPoints(cell, gridModel, Color.red, gridConfig.Mode2D);
}
foreach (var door in gridModel.DoorManager.Doors)
{
var start = door.AdjacentTiles[0];
var end = door.AdjacentTiles[1];
var boundsStart = new Rectangle(start.x, start.z, 1, 1);
var boundsEnd = new Rectangle(end.x, end.z, 1, 1);
IntVector location = boundsStart.Location;
location.y = start.y;
boundsStart.Location = location;
location = boundsEnd.Location;
location.y = end.y;
boundsEnd.Location = location;
DebugDrawUtils.DrawBounds(boundsStart, Color.yellow, gridConfig.GridCellSize, gridConfig.Mode2D);
DebugDrawUtils.DrawBounds(boundsEnd, Color.yellow, gridConfig.GridCellSize, gridConfig.Mode2D);
}
}
}
public static class GridDungeonMarkerNames {
public static readonly string Fence = "Fence";
public static readonly string FenceSeparator = "FenceSeparator";
public static readonly string Door = "Door";
public static readonly string Door2D = "Door2D";
public static readonly string Door2D_90 = "Door2D_90";
public static readonly string Wall = "Wall";
public static readonly string Wall2D = "Wall2D";
public static readonly string WallSeparator = "WallSeparator";
public static readonly string Ground = "Ground";
public static readonly string Ground2D = "Ground2D";
public static readonly string Stair = "Stair";
public static readonly string Stair2X = "Stair2X";
public static readonly string WallHalf = "WallHalf";
public static readonly string WallHalfSeparator = "WallHalfSeparator";
public static readonly string None = "None";
public static readonly string RoomWall = "RoomWall";
public static readonly string RoomWallSeparator = "RoomWallSeparator";
public static readonly string RoomOpenSpace = "RoomOpenSpace";
public static readonly string Light = "Light";
}
}