//$ 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>; using PropBySocketTypeByTheme_t = Dictionary>>; /// /// Contains meta data about the cells. This structure is used for caching cell /// information for faster lookup during and after generation of the dungeon /// 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; } /// /// 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 /// public class CellHeightNode { public int CellId; public int Height; public bool MarkForIncrease; public bool MarkForDecrease; }; /// /// Temporary data-structure used while assigning stairs on the dungeon. /// public class StairAdjacencyQueueNode { public StairAdjacencyQueueNode(int pCellId, int pDepth) { this.cellId = pCellId; this.depth = pDepth; } public int cellId; public int depth; }; /// /// Temporary data-structure used while assigning heights on the dungeon. /// public class CellHeightFrameInfo { public CellHeightFrameInfo(int pCellId, int pCurrentHeight) { this.CellId = pCellId; this.CurrentHeight = pCurrentHeight; } public int CellId; public int CurrentHeight; }; /// /// Data structure to hold the adjacent cells connected to the stairs (entry / exit) /// public struct StairEdgeInfo { public StairEdgeInfo(int pCellIdA, int pCellIdB) { this.CellIdA = pCellIdA; this.CellIdB = pCellIdB; } public int CellIdA; public int CellIdB; }; /// /// 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/ /// [ExecuteInEditMode] public class GridDungeonBuilder : DungeonBuilder { int _CellIdCounter = 0; Dictionary> CellStairs { get { return gridModel.CellStairs; } } GridDungeonModel gridModel; GridDungeonConfig gridConfig; Dictionary> 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(); if (model is GridDungeonModel) { gridModel = model as GridDungeonModel; } else { Debug.LogError("Invalid dungeon model type provided to grid based dungeon builder"); return; } config = GetComponent(); if (config is GridDungeonConfig) { gridConfig = config as GridDungeonConfig; } else { Debug.LogError("Invalid dungeon config type provided to grid based dungeon builder"); return; } } /// /// Builds the dungeon /// /// The dungeon configuration /// The dungeon model 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 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 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(); var cells = new List(); 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(); } /// /// builds the cells in the dungeon /// public void BuildCellsWithSeparation() { if (gridConfig.Seed == 0) { //gridConfig.Seed = (uint)Mathf.FloorToInt(Random.value * int.MaxValue); } var cells = new List(); _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(); var negationVolumes = GameObject.FindObjectsOfType(); 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(); 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(); // 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(); // Add platform volumes defined in the world var platformVolumes = GameObject.FindObjectsOfType(); 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(); if (dungeon != null) { bool appliedMirroring = false; var mirrorVolumes = GameObject.FindObjectsOfType(); foreach (var mirrorVolume in mirrorVolumes) { if (mirrorVolume.dungeon == dungeon) { ClipForMirroring(mirrorVolume); appliedMirroring = true; } } if (appliedMirroring) { gridModel.BuildCellLookup(); GenerateAdjacencyLookup(); } } } void MirrorDungeon() { var dungeon = GetComponent(); if (dungeon != null) { bool appliedMirroring = false; var mirrorVolumes = GameObject.FindObjectsOfType(); 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(); 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(); var cellsToDiscardId = new List(); 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(); 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(); 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(); // Mirror the cells { var cellsToAdd = new List(); 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(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>(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(); 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; } /// /// Separates the cells built in the previous phase /// /// 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; } /// /// 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 /// /// public void TriangulateRooms() { var vertices = new List(); var rooms = new List(); 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 { 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 GetRooms(List cells) { var rooms = new List(); foreach (var cell in cells) { if (cell.CellType == CellType.Room) { rooms.Add(cell); } } return rooms; } void AddUnique(List list, T value) { if (!list.Contains(value)) { list.Add(value); } } void BuildMinimumSpanningTree() { List rooms = GetCellsOfType(CellType.Room); var edgesMapped = new Dictionary>(); // Generate unique edge list var edges = new List(); 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()); if (!edgesMapped.ContainsKey(id1)) edgesMapped.Add(id1, new HashSet()); 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 rooms) { foreach (Cell room in rooms) { if (room != null) { HashSet visited = new HashSet(); bool hasLoop = CheckLoop(room, null, visited); if (hasLoop) return true; } } return false; } bool CheckLoop(Cell currentNode, Cell comingFrom, HashSet 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 GetCellsOfType(CellType cellType) { List filtered = new List(); foreach (var cell in gridModel.Cells) { if (cell.CellType == cellType) { filtered.Add(cell); } } return filtered; } void ConnectCorridors() { List rooms = GetCellsOfType(CellType.Room); if (rooms.Count < 2) return; HashSet visited = new HashSet(); 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 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 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 corners) { corners = new HashSet(); 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 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 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 GetManhattanPath(IntVector start, IntVector mid, IntVector end) { var path0 = new List(); var path1 = new List(); GetManhattanPathBetween(start, mid, ref path0); GetManhattanPathBetween(mid, end, ref path1); var path = new List(); 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 { 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 points) { var pointsToRemove = new List(); 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 points) { foreach (var point in points) { if (bounds.Contains(point)) { return true; } } return false; } bool ContainsPointsInsideRoomCells(Cell[] roomCells, List points) { foreach (var room in roomCells) { if (ContainsPointInsideBounds(room.Bounds, points)) { return true; } } return false; } List 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(); var connectionPointsB = new List(); 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 worstPath = new List(); List path = new List(); 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(); 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()); } 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 CellHeightNodes = new Dictionary(); HashSet visited = new HashSet(); Stack stack = new Stack(); ; 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 CellHeightNodes) { bool bContinueIteration = false; if (gridModel.Cells.Count == 0) return bContinueIteration; HashSet visited = new HashSet(); Stack stack = new Stack(); 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(); 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(); var visited = new HashSet(); 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 { 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 stack = new Stack(); HashSet visited = new HashSet(); HashSet islandsVisited = new HashSet(); 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(); 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 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(); 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 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(); 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(); var wallSeparaterPositions = new HashSet(); 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(); 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"; } }