Files
ZeroVR/ZeroPacientVR/Assets/CodeRespawn/DungeonArchitect/Scripts/Builders/Grid/GridDungeonModel.cs
T
2022-04-18 19:17:20 +03:00

637 lines
18 KiB
C#

//$ Copyright 2015-22, Code Respawn Technologies Pvt Ltd - All Rights Reserved $//
using UnityEngine;
using System;
using System.Collections.Generic;
namespace DungeonArchitect.Builders.Grid
{
/// <summary>
/// The type of cell used in the grid builder
/// </summary>
[System.Serializable]
public enum CellType
{
Room,
Corridor,
CorridorPadding,
Unknown
}
/// <summary>
/// Data-structure to hold the stair information in the grid based builder
/// </summary>
[System.Serializable]
public class StairInfo
{
[SerializeField]
public int OwnerCell;
[SerializeField]
public int ConnectedToCell;
[SerializeField]
public Vector3 Position;
[SerializeField]
public Quaternion Rotation;
[SerializeField]
public IntVector IPosition;
};
/// <summary>
/// Data-structure to hold the Cell information. A cell is a piece of the dungeon layout and can be either a room or a corridor
/// </summary>
[Serializable]
public class Cell
{
public Cell()
{
cellType = CellType.Unknown;
}
public Cell(int x, int z, int width, int length)
{
bounds = new Rectangle(x, z, width, length);
cellType = CellType.Unknown;
}
[SerializeField]
int id;
public int Id
{
get
{
return id;
}
set
{
id = value;
}
}
[SerializeField]
Rectangle bounds = new Rectangle();
public Rectangle Bounds
{
get { return bounds; }
set { bounds = value; }
}
[SerializeField]
CellType cellType = CellType.Unknown;
public CellType CellType
{
get { return cellType; }
set { cellType = value; }
}
public Bounds GetWorldBounds(Vector3 gridSize)
{
var bounds = new Bounds();
var width = this.Bounds.Width * gridSize.x;
var length = this.Bounds.Width * gridSize.z;
var center = Vector3.Scale(this.Bounds.CenterF(), gridSize);
bounds.center = center;
bounds.size = new Vector3(width, 2, length);
return bounds;
}
/// <summary>
/// Indicates if the cell is user defined and not procedurally generated
/// </summary>
[SerializeField]
bool userDefined;
public bool UserDefined
{
get
{
return userDefined;
}
set
{
userDefined = value;
}
}
[SerializeField]
HashSet<int> connectedRooms = new HashSet<int>();
public HashSet<int> ConnectedRooms
{
get { return connectedRooms; }
set { connectedRooms = value; }
}
[SerializeField]
HashSet<int> fixedRoomConnections = new HashSet<int>();
public HashSet<int> FixedRoomConnections
{
get { return fixedRoomConnections; }
set { fixedRoomConnections = value; }
}
[SerializeField]
HashSet<int> adjacentCells = new HashSet<int>();
public HashSet<int> AdjacentCells
{
get
{
return adjacentCells;
}
set
{
adjacentCells = value;
}
}
public IntVector Center
{
get
{
return new IntVector(bounds.X + bounds.Width / 2, 0, bounds.Z + bounds.Length / 2);
}
}
public Vector3 CenterF
{
get
{
return new Vector3(bounds.X + bounds.Width / 2.0f, bounds.Location.y, bounds.Z + bounds.Length / 2.0f);
}
}
public override bool Equals(System.Object obj)
{
if (obj == null) return false;
if (obj is Cell)
{
var cell = obj as Cell;
return this.Id == cell.Id;
}
return false;
}
public override int GetHashCode()
{
return id;
}
}
/// <summary>
/// Data-structure to hold the door information
/// </summary>
[Serializable]
public class CellDoor
{
[SerializeField]
IntVector[] adjacentTiles = new IntVector[2];
/// <summary>
/// The adjacent tile positions shared by this door (entry / exit tiles)
/// </summary>
public IntVector[] AdjacentTiles
{
get
{
return adjacentTiles;
}
}
[SerializeField]
bool enabled = true;
public bool Enabled
{
get { return enabled; }
set { enabled = value; }
}
/// <summary>
/// Cell Ids of one of the cells that owns this door
/// </summary>
[SerializeField]
int[] adjacentCells = new int[2];
public int[] AdjacentCells
{
get
{
return adjacentCells;
}
set
{
adjacentCells = value;
}
}
public override string ToString()
{
return string.Format("[CellDoor: {0} <=> {1}]", AdjacentCells[0], AdjacentCells[1]);
}
}
/// <summary>
/// Data-structure for IntVector pair. Used for caching
/// </summary>
[Serializable]
public struct IntVector2Key
{
[SerializeField]
public IntVector a;
[SerializeField]
public IntVector b;
public IntVector2Key(IntVector a, IntVector b)
{
this.a = a;
this.b = b;
}
public override bool Equals(System.Object obj)
{
if (obj != null && obj is IntVector2Key)
{
var other = (IntVector2Key)obj;
return a.Equals(other.a) &&
b.Equals(other.b);
}
return false;
}
public override int GetHashCode()
{
return (a.GetHashCode() << 16) + b.GetHashCode();
}
}
/// <summary>
/// Manages the doors in the grid based builder
/// </summary>
[Serializable]
public class DoorManager
{
[SerializeField]
Dictionary<IntVector2Key, CellDoor> doorLookupCache = new Dictionary<IntVector2Key, CellDoor>();
[SerializeField]
List<CellDoor> doors = new List<CellDoor>();
public void Clear()
{
doorLookupCache.Clear();
doors.Clear();
}
public void RemoveDoor(CellDoor door)
{
var keysToRemove = new List<IntVector2Key>();
foreach (var key in doorLookupCache.Keys)
{
if (doorLookupCache[key] == door)
{
keysToRemove.Add(key);
}
}
foreach (var key in keysToRemove)
{
doorLookupCache.Remove(key);
}
doors.Remove(door);
}
/// <summary>
/// Creates a door between the two grid points
/// </summary>
/// <param name="p1">The grid poition 1</param>
/// <param name="p2">The grid poition 2</param>
/// <param name="cellId1">Cell Id of the first adjacent cell</param>
/// <param name="cellId2">Cell Id of the second adjacent cell</param>
/// <returns></returns>
public CellDoor CreateDoor(IntVector p1, IntVector p2, int cellId1, int cellId2)
{
var key1 = new IntVector2Key(p1, p2);
if (doorLookupCache.ContainsKey(key1)) return doorLookupCache[key1];
var key2 = new IntVector2Key(p2, p1);
if (doorLookupCache.ContainsKey(key2)) return doorLookupCache[key2];
// Create a new door
var door = new CellDoor();
door.AdjacentTiles[0] = new IntVector(p1.x, p1.y, p1.z);
door.AdjacentTiles[1] = new IntVector(p2.x, p2.y, p2.z);
door.AdjacentCells[0] = cellId1;
door.AdjacentCells[1] = cellId2;
// Add to the memo lookup
doorLookupCache.Add(key1, door);
doorLookupCache.Add(key2, door);
doors.Add(door);
return door;
}
/// <summary>
/// Check if a door exists between the two cells
/// </summary>
/// <param name="cellA">Cell Id of the first cell</param>
/// <param name="cellB">Cell Id of the second cell</param>
/// <returns></returns>
public bool ContainsDoorBetweenCells(int cellA, int cellB)
{
foreach (var door in doors)
{
if (!door.Enabled)
{
continue;
}
if ((door.AdjacentCells[0] == cellA && door.AdjacentCells[1] == cellB) ||
(door.AdjacentCells[0] == cellB && door.AdjacentCells[1] == cellA))
{
return true;
}
}
return false;
}
public bool ContainsDoor(int x1, int z1, int x2, int z2) {
foreach (var door in doors) {
if (!door.Enabled) { continue; }
var containsDoor =
door.AdjacentTiles[0].x == x1 && door.AdjacentTiles[0].z == z1 &&
door.AdjacentTiles[1].x == x2 && door.AdjacentTiles[1].z == z2;
if (containsDoor) {
return true;
}
containsDoor =
door.AdjacentTiles[1].x == x1 && door.AdjacentTiles[1].z == z1 &&
door.AdjacentTiles[0].x == x2 && door.AdjacentTiles[0].z == z2;
if (containsDoor) {
return true;
}
}
return false;
}
/// <summary>
/// List of registered doors
/// </summary>
public CellDoor[] Doors
{
get
{
return doors.ToArray();
}
}
}
/// <summary>
/// Data model for the grid based dungeon builder
/// </summary>
//[System.Serializable]
public class GridDungeonModel : DungeonModel
{
[HideInInspector]
[SerializeField]
public DoorManager DoorManager = new DoorManager();
[HideInInspector]
public GridDungeonConfig Config;
[SerializeField]
[HideInInspector]
public List<Cell> Cells = new List<Cell>();
[HideInInspector]
[SerializeField]
public Dictionary<int, List<StairInfo>> CellStairs = new Dictionary<int, List<StairInfo>>();
[HideInInspector]
public Dictionary<int, Dictionary<int, GridCellInfo>> GridCellInfoLookup = new Dictionary<int, Dictionary<int, GridCellInfo>>();
/// <summary>
/// Get meta-data about the grid in x, z grid coordinate
/// </summary>
/// <param name="x">X value in grid coordinate</param>
/// <param name="z">Z value in grid cooridnate</param>
/// <returns></returns>
public GridCellInfo GetGridCellLookup(int x, int z)
{
if (!GridCellInfoLookup.ContainsKey(x) || !GridCellInfoLookup[x].ContainsKey(z))
{
return new GridCellInfo();
}
return GridCellInfoLookup[x][z];
}
/// <summary>
/// Builds a lookup for fast data retrieval
/// </summary>
public void BuildSpatialCellLookup()
{
// Cache the cell types based on their positions
GridCellInfoLookup.Clear();
foreach (var cell in Cells)
{
if (cell.CellType == CellType.Unknown) continue;
IntVector basePosition = cell.Bounds.Location;
for (int dx = 0; dx < cell.Bounds.Size.x; dx++)
{
for (int dz = 0; dz < cell.Bounds.Size.z; dz++)
{
int x = basePosition.x + dx;
int z = basePosition.z + dz;
// register the cell type in the lookup
if (!GridCellInfoLookup.ContainsKey(x)) GridCellInfoLookup.Add(x, new Dictionary<int, GridCellInfo>());
if (!GridCellInfoLookup[x].ContainsKey(z))
{
GridCellInfoLookup[x].Add(z, new GridCellInfo(cell.Id, cell.CellType));
}
}
}
}
}
/// <summary>
/// The list of registered doors
/// </summary>
public CellDoor[] Doors
{
get
{
return DoorManager.Doors;
}
}
/// <summary>
/// Builds the cell lookup for faster cell retrieval
/// </summary>
public void BuildCellLookup()
{
CellLookup.Clear();
foreach (var cell in Cells)
{
CellLookup.Add(cell.Id, cell);
}
}
/// <summary>
/// Gets the cell information
/// </summary>
/// <param name="Id">Id of the cell to lookup</param>
/// <returns></returns>
public Cell GetCell(int Id)
{
return CellLookup.ContainsKey(Id) ? CellLookup[Id] : null;
}
/// <summary>
/// Cell lookup based on the Cell Id
/// </summary>
public Dictionary<int, Cell> CellLookup = new Dictionary<int, Cell>();
/// <summary>
/// Finds the cell based on the position in grid coordinates
/// </summary>
/// <param name="position">Position to lookup in grid cooridnates</param>
/// <returns>Cell information at that location. Returns null if none found</returns>
public Cell FindCellByPosition(IntVector position)
{
foreach (var cell in Cells)
{
if (cell.Bounds.Contains(position))
{
return cell;
}
}
return null;
}
/// <summary>
/// Clears the dungeon data model
/// </summary>
public override void ResetModel()
{
DoorManager = new DoorManager();
Config = null;
Cells = new List<Cell>();
CellStairs.Clear();
}
public bool ContainsStairAtLocation(int x, int z)
{
foreach (var stairList in CellStairs.Values) {
foreach (var stair in stairList) {
if (stair.IPosition.x == x && stair.IPosition.z == z) {
return true;
}
}
}
return false;
}
public StairInfo GetStairAtLocation(int x, int z) {
foreach (var stairList in CellStairs.Values) {
foreach (var stair in stairList) {
if (stair.IPosition.x == x && stair.IPosition.z == z) {
return stair;
}
}
}
return null;
}
/// <summary>
/// Check if a stair exists between the two cells
/// </summary>
/// <param name="cellA"></param>
/// <param name="cellB"></param>
/// <returns></returns>
public bool ContainsStair(int cellA, int cellB)
{
return CheckContainStair(cellA, cellB) || CheckContainStair(cellB, cellA);
}
bool CheckContainStair(int cellA, int cellB)
{
if (!CellStairs.ContainsKey(cellA)) return false;
foreach (var stair in CellStairs[cellA])
{
if (stair.ConnectedToCell == cellB) return true;
}
return false;
}
}
public class GridDungeonModelUtils {
struct LongestPathBFSData {
public Cell cell;
public int distance;
}
public static Cell[] FindFurthestRooms(GridDungeonModel model)
{
return FindFurthestRooms(model, CellType.Room, CellType.Room);
}
public static Cell[] FindFurthestRooms(GridDungeonModel model, CellType startCellType, CellType endCellType) {
var result = new Cell[2];
var bestDistance = 0;
foreach (var startCell in model.Cells) {
if (startCell.CellType != startCellType)
{
continue;
}
var queue = new Queue<LongestPathBFSData>();
var startData = new LongestPathBFSData { cell = startCell, distance = 0 };
queue.Enqueue(startData);
LongestPathBFSData bestEndCell = startData;
var cellDistances = new Dictionary<int, int>(); // CellId -> Distance mapping
while (queue.Count > 0) {
var front = queue.Dequeue();
if (front.cell == null) continue;
var processCell = false;
if (!cellDistances.ContainsKey(front.cell.Id))
{
// We never processed this cell
processCell = true;
}
else if (cellDistances[front.cell.Id] > front.distance)
{
// We found a shorter path to this cell
processCell = true;
}
if (processCell)
{
cellDistances[front.cell.Id] = front.distance;
foreach (var childId in front.cell.AdjacentCells) {
var child = model.GetCell(childId);
var childData = new LongestPathBFSData { cell = child, distance = front.distance + 1 };
queue.Enqueue(childData);
}
}
if (front.cell.CellType == endCellType && front.distance > bestEndCell.distance)
{
bestEndCell = front;
}
}
if (bestEndCell.distance > bestDistance)
{
result[0] = startData.cell;
result[1] = bestEndCell.cell;
bestDistance = bestEndCell.distance;
}
}
return result;
}
}
}