void TraverseTree(BSPNodeObject node, System.Action <BSPNodeObject> visit) { // traverse the children foreach (var child in node.children) { TraverseTree(child, visit); } visit(node); }
/// <summary> /// This function assumes that the cell is big enough to split. /// Make sure to call CanSplit function before calling this /// </summary> public void Split(float splitRatio, System.Random random) { if (bounds.Width == bounds.Length) { horizontalSplit = random.NextFloat() < 0.5f; } else { horizontalSplit = (bounds.Width > bounds.Length); } int totalSize = horizontalSplit ? bounds.Width : bounds.Length; int left = Mathf.RoundToInt(totalSize * splitRatio); int right = totalSize - left; var child0 = new BSPNodeObject(); child0.parent = this; child0.padding = padding; child0.depthFromRoot = depthFromRoot + 1; var child1 = new BSPNodeObject(); child1.parent = this; child1.padding = padding; child1.depthFromRoot = depthFromRoot + 1; var loc0 = bounds.Location; var size0 = bounds.Size; var loc1 = bounds.Location; var size1 = bounds.Size; if (horizontalSplit) { size0.x = left; loc1.x += left; size1.x = right; } else { size0.z = left; loc1.z += left; size1.z = right; } child0.bounds = new Rectangle(loc0, size0); child1.bounds = new Rectangle(loc1, size1); children = new BSPNodeObject[] { child0, child1 }; }
void TraverseParentBranch(BSPNodeObject node, System.Action <BSPNodeObject> visit) { if (node == null) { return; } visit(node); TraverseParentBranch(node.parent, visit); }
BSPNodeObject GetCornerSubtreeNode(BSPNodeObject node, bool left) { if (node.children == null || node.children.Length == 0) { return(node); } var child = left ? node.children[0] : node.children[1]; return(GetCornerSubtreeNode(child, left)); }
void DebugRoomLayout(BSPNodeObject rootNode) { var edgeRooms = new List <BSPNodeObject>(); FindBoundaryEdgeRooms(rootNode.children[1], BSPNodeDirection.Left, edgeRooms); foreach (var room in edgeRooms) { room.debugColor = Color.red; } }
void BuildDungeonGraph(BSPNodeObject node) { int targetMinRoomSize = bspConfig.minRoomSize + bspConfig.roomPadding * 2; int targetMaxRoomSize = bspConfig.maxRoomSize + bspConfig.roomPadding * 2; if (!node.CanSplit(targetMinRoomSize)) { // Node is too small to split further return; } bool shouldSplit; if (node.MustSplit(targetMaxRoomSize)) { shouldSplit = true; } else { // Check if the aspect ratio would be correct after a split // Use a probability to decide if we split shouldSplit = random.NextFloat() < bspConfig.smallerRoomProbability; } if (shouldSplit) { float splitRatio = 0.5f; bool unevenSplit = random.NextFloat() < bspConfig.unevenSplitProbability; if (unevenSplit) { int sizeToSplit = Mathf.Max(node.bounds.Width, node.bounds.Length); int allowedSplitDistance = sizeToSplit - 2 * targetMinRoomSize; if (allowedSplitDistance > 0) { float allowedSplitRatio = allowedSplitDistance / (float)sizeToSplit; var randomValue = random.NextFloat(); // get a random value between 0..1 randomValue = randomValue * 2 - 1; // transform to -1..1 // From 0.5, we are going to move the split either to the left or right by half of the allowed ratio (-1..1 * halfAllowedSplitRatio) splitRatio = 0.5f + randomValue * allowedSplitRatio / 2.0f; } } node.Split(splitRatio, random); } // Process the children foreach (var child in node.children) { BuildDungeonGraph(child); } }
void SerializeGraph(BSPNodeObject rootNode) { var serializedNodes = new List <BSPNode>(); var serializedConnections = new List <BSPNodeConnection>(); SerializeGraph(rootNode, serializedNodes, serializedConnections); bspModel.nodes = serializedNodes.ToArray(); bspModel.connections = serializedConnections.ToArray(); bspModel.rootNode = rootNode.id; }
void FindBoundaryEdgeRooms(BSPNodeObject node, BSPNodeDirection direction, List <BSPNodeObject> result) { if (node.discarded) { return; } bool hasChildren = (node.children != null && node.children.Length > 0); if (!hasChildren) { result.Add(node); return; } if (node.horizontalSplit) { if (direction == BSPNodeDirection.Left) { FindBoundaryEdgeRooms(node.children[0], direction, result); } else if (direction == BSPNodeDirection.Right) { FindBoundaryEdgeRooms(node.children[1], direction, result); } else { FindBoundaryEdgeRooms(node.children[0], direction, result); FindBoundaryEdgeRooms(node.children[1], direction, result); } } else // Vertical split { if (direction == BSPNodeDirection.Bottom) { FindBoundaryEdgeRooms(node.children[0], direction, result); } else if (direction == BSPNodeDirection.Top) { FindBoundaryEdgeRooms(node.children[1], direction, result); } else { FindBoundaryEdgeRooms(node.children[0], direction, result); FindBoundaryEdgeRooms(node.children[1], direction, result); } } }
void FlagConnectedLeafNodes(BSPNodeObject node) { if (node.depthFromRoot >= bspConfig.randomKillDepthStart) { return; } foreach (var connection in node.subtreeLeafConnections) { TraverseParentBranch(connection.Room0, n => n.discarded = false); TraverseParentBranch(connection.Room1, n => n.discarded = false); } foreach (var child in node.children) { FlagConnectedLeafNodes(child); } }
void DiscardExtraRooms(BSPNodeObject rootNode) { TraverseTree(rootNode, n => n.discarded = true); FlagConnectedLeafNodes(rootNode); int numNodes = 0; TraverseTree(rootNode, n => numNodes++); int maxTries = numNodes; int numTries = 0; while (ConnectActiveSubtrees(rootNode) && numTries <= maxTries) { numTries++; } }
void ConnectDoors(BSPNodeObject node) { if (node.discarded || node.children == null) { return; } // Connect the children foreach (var child in node.children) { ConnectDoors(child); } // Connect the siblings if (node.children.Length == 2) { node.subtreeLeafConnections = ConnectPartitions(node.children [0], node.children [1], node.horizontalSplit); } }
void GenerateLevelLayout() { var rootNode = new BSPNodeObject(); rootNode.bounds = new Rectangle(0, 0, bspConfig.dungeonWidth, bspConfig.dungeonLength); rootNode.padding = bspConfig.roomPadding; rootNode.depthFromRoot = 0; BuildDungeonGraph(rootNode); ConnectDoors(rootNode); GenerateCustomRooms(rootNode); DiscardExtraRooms(rootNode); SerializeGraph(rootNode); }
bool ConnectActiveSubtrees(BSPNodeObject node) { bool stateModified = false; foreach (var child in node.children) { stateModified |= ConnectActiveSubtrees(child); } if (node.discarded) { return(stateModified); } bool bothChildrenActive = (node.children.Length == 2 && !node.children[0].discarded && !node.children[1].discarded); if (bothChildrenActive) { foreach (var connection in node.subtreeLeafConnections) { //connection.Room0.discarded = false; //connection.Room1.discarded = false; TraverseParentBranch(connection.Room0, n => { if (n.discarded) { n.discarded = false; stateModified = true; } }); TraverseParentBranch(connection.Room1, n => { if (n.discarded) { n.discarded = false; stateModified = true; } }); } } return(stateModified); }
public NodeConnection(BSPNodeObject room0, BSPNodeObject room1, int padding) { this.room0 = room0; this.room1 = room1; var intersection = Rectangle.Intersect(room0.bounds, room1.bounds); var center = intersection.Center(); if (intersection.Size.x > 0) { doorPosition0 = center + new IntVector(0, 0, padding); doorPosition1 = center - new IntVector(0, 0, padding); doorFacingX = false; } else { doorPosition0 = center + new IntVector(padding, 0, 0); doorPosition1 = center - new IntVector(padding, 0, 0); doorFacingX = true; } }
void SerializeGraph(BSPNodeObject node, List <BSPNode> serializedNodes, List <BSPNodeConnection> serializedConnections) { if (node == null) { return; } var serializedNode = new BSPNode(); serializedNode.id = node.id; serializedNode.bounds = node.bounds; serializedNode.paddedBounds = node.PaddedBounds; serializedNode.depthFromRoot = node.depthFromRoot; serializedNode.debugColor = node.debugColor; serializedNode.discarded = node.discarded; if (node.parent != null) { serializedNode.parent = node.parent.id; } var childIds = new List <System.Guid>(); foreach (var child in node.children) { if (child != null) { childIds.Add(child.id); } } serializedNode.children = childIds.ToArray(); var connectedIds = new List <System.Guid>(); foreach (var connectedRoom in node.connectedRooms) { connectedIds.Add(connectedRoom.id); } serializedNode.connectedRooms = connectedIds.ToArray(); var subtreeLeafConnections = new List <BSPNodeConnection>(); foreach (var connection in node.subtreeLeafConnections) { var serializedConnection = new BSPNodeConnection(); serializedConnection.room0 = connection.Room0.id; serializedConnection.room1 = connection.Room1.id; serializedConnection.doorPosition0 = connection.DoorPosition0; serializedConnection.doorPosition1 = connection.DoorPosition1; serializedConnection.doorFacingX = connection.DoorFacingX; subtreeLeafConnections.Add(serializedConnection); if (!connection.Room0.discarded && !connection.Room1.discarded) { serializedConnections.Add(serializedConnection); } } serializedNode.subtreeLeafConnections = subtreeLeafConnections.ToArray(); serializedNodes.Add(serializedNode); // Serialize the children foreach (var child in node.children) { SerializeGraph(child, serializedNodes, serializedConnections); } }
void DiscardSubtree(BSPNodeObject node) { TraverseTree(node, n => n.discarded = true); }
void GenerateCustomRooms(BSPNodeObject rootNode) { }
NodeConnection[] ConnectPartitions(BSPNodeObject leftPartition, BSPNodeObject rightPartition, bool horizontalSplit) { var connections = new List <NodeConnection>(); if (leftPartition.discarded || rightPartition.discarded) { return(connections.ToArray()); } var leftRooms = new List <BSPNodeObject>(); var rightRooms = new List <BSPNodeObject>(); if (horizontalSplit) { FindBoundaryEdgeRooms(leftPartition, BSPNodeDirection.Right, leftRooms); FindBoundaryEdgeRooms(rightPartition, BSPNodeDirection.Left, rightRooms); } else // Vertical split { // Left = bottom partition // Right = top partition FindBoundaryEdgeRooms(leftPartition, BSPNodeDirection.Top, leftRooms); FindBoundaryEdgeRooms(rightPartition, BSPNodeDirection.Bottom, rightRooms); } // Connect the two rooms together if (leftRooms.Count == 0 || rightRooms.Count == 0) { return(connections.ToArray()); } Shuffle(leftRooms); Shuffle(rightRooms); bool roomsConnected = false; foreach (var leftRoom in leftRooms) { // First check if any of the right rooms are connected foreach (var rightRoom in rightRooms) { if (leftRoom.connectedRooms.Contains(rightRoom)) { roomsConnected = true; break; } } foreach (var rightRoom in rightRooms) { if (leftRoom.connectedRooms.Contains(rightRoom)) { // Already connected continue; } bool shouldConnectRooms = true; if (roomsConnected) { // rooms are already connected along this edge. Check if can loop shouldConnectRooms = random.NextFloat() < bspConfig.loopingProbability; } if (shouldConnectRooms) { // Connect left and right together var intersection = Rectangle.Intersect(leftRoom.bounds, rightRoom.bounds); var minIntersection = bspConfig.roomPadding * 2; if (intersection.Size.x > minIntersection || intersection.Size.z > minIntersection) { // These two rooms can connect leftRoom.connectedRooms.Add(rightRoom); rightRoom.connectedRooms.Add(leftRoom); roomsConnected = true; var connection = new NodeConnection(leftRoom, rightRoom, bspConfig.roomPadding); connections.Add(connection); } } } } return(connections.ToArray()); }