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

992 lines
34 KiB
C#

//$ Copyright 2015-22, Code Respawn Technologies Pvt Ltd - All Rights Reserved $//
using System.Collections.Generic;
using UnityEngine;
using DungeonArchitect.Utils;
namespace DungeonArchitect.Builders.BSP
{
public static class BSPDungeonMarkerNames
{
public static readonly string GroundRoom = "GroundRoom";
public static readonly string GroundCorridor = "GroundCorridor";
public static readonly string Door = "Door";
public static readonly string WallRoom = "WallRoom";
public static readonly string WallCorridor = "WallCorridor";
public static readonly string WallSeparator = "WallSeparator";
}
// Non-serialized node class, unlike the serialized version. This is easier to work with but cannot be serialized.
// These objects will finally be converted to the serialized version BSPNode and saved in the model.
// Use the BSPDungeonGraphQuery to traverse the serialized graph when loaded from disk
class BSPNodeObject
{
public Rectangle bounds;
public BSPNodeObject[] children = new BSPNodeObject[0];
public BSPNodeObject parent;
public DungeonUID id = DungeonUID.NewUID();
public int depthFromRoot;
public int padding;
public bool horizontalSplit = false;
//public bool customRoom;
//public string customRoomId;
public Color debugColor = Color.blue;
public bool discarded = false;
public List<BSPNodeObject> connectedRooms = new List<BSPNodeObject>();
public NodeConnection[] subtreeLeafConnections = new NodeConnection[0];
public Rectangle PaddedBounds
{
get
{
return Rectangle.ExpandBounds(bounds, -1 * padding);
}
}
/// <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 };
}
/// <summary>
/// Check if it is required to split this node. This happens if the size is greater than the max allowed room size.
/// In that case a split is required
/// </summary>
/// <param name="maxSize"></param>
/// <returns></returns>
public bool MustSplit(int maxSize)
{
// Make sure that if we split the largest side, the two smaller sides are still big enough
float largeSide = Mathf.Max(bounds.Width, bounds.Length);
return largeSide > maxSize;
}
public bool CanSplit(int minSize)
{
// Make sure that if we split the largest side, the two smaller sides are still big enough
float largeSide = Mathf.Max(bounds.Width, bounds.Length);
return largeSide / 2 >= minSize;
}
}
enum BSPNodeDirection {
Left,
Right,
Top,
Bottom
}
public class BSPDungeonBuilder : DungeonBuilder {
BSPDungeonConfig bspConfig;
BSPDungeonModel bspModel;
new System.Random random;
/// <summary>
/// Builds the dungeon layout. In this method, you should build your dungeon layout and save it in your model file
/// No markers should be emitted here. (EmitMarkers function will be called later by the engine to do that)
/// </summary>
/// <param name="config">The builder configuration</param>
/// <param name="model">The dungeon model that the builder will populate</param>
public override void BuildDungeon(DungeonConfig config, DungeonModel model)
{
base.BuildDungeon(config, model);
random = new System.Random((int)config.Seed);
// We know that the dungeon prefab would have the appropriate config and models attached to it
// Cast and save it for future reference
bspConfig = config as BSPDungeonConfig;
bspModel = model as BSPDungeonModel;
bspModel.Config = bspConfig;
// Generate the level layout and save it in a model. No markers are emitted here.
GenerateLevelLayout();
}
public override void OnDestroyed() {
base.OnDestroyed();
if (model != null)
{
model.ResetModel();
}
}
/// <summary>
/// Override the builder's emit marker function to emit our own markers based on the layout that we built
/// You should emit your markers based on the layout you have saved in the model generated previously
/// When the user is designing the theme interactively, this function will be called whenever the graph state changes,
/// so the theme engine can populate the scene (BuildDungeon will not be called if there is no need to rebuild the layout again)
/// </summary>
public override void EmitMarkers()
{
base.EmitMarkers();
EmitLevelMarkers();
ProcessMarkerOverrideVolumes();
}
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);
}
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;
}
}
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 GenerateCustomRooms(BSPNodeObject rootNode)
{
}
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 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);
}
}
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;
}
void DiscardSubtree(BSPNodeObject node) {
TraverseTree(node, n => n.discarded = true);
}
void TraverseTree(BSPNodeObject node, System.Action<BSPNodeObject> visit) {
// traverse the children
foreach (var child in node.children)
{
TraverseTree(child, visit);
}
visit(node);
}
void TraverseParentBranch(BSPNodeObject node, System.Action<BSPNodeObject> visit) {
if (node == null)
{
return;
}
visit(node);
TraverseParentBranch(node.parent, visit);
}
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);
}
}
NodeConnection[] GetConnectionCandidates(BSPNodeObject[] leftRooms, BSPNodeObject[] rightRooms) {
var connections = new List<NodeConnection>();
foreach (var leftRoom in leftRooms)
{
foreach (var rightRoom in rightRooms)
{
// 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)
{
var connection = new NodeConnection(leftRoom, rightRoom, bspConfig.roomPadding);
connections.Add(connection);
}
}
}
return connections.ToArray();
}
void Shuffle(List<BSPNodeObject> nodes) {
for (int i = 0; i < nodes.Count; i++)
{
int j = random.Next() % nodes.Count;
var temp = nodes[j];
nodes[j] = nodes[i];
nodes[i] = temp;
}
}
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();
}
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 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 EmitLevelMarkers()
{
var gridSize3D = new Vector3(bspConfig.gridSize.x, 0, bspConfig.gridSize.y);
foreach (var node in bspModel.nodes)
{
if (node.discarded) continue;
// Draw the ground tiles
if (node.children.Length == 0)
{
var paddedBounds = node.paddedBounds;
for (int x = 0; x < paddedBounds.Size.x; x++)
{
for (int z = 0; z < paddedBounds.Size.z; z++)
{
Vector3 position = Vector3.Scale(new Vector3(paddedBounds.Location.x + x + 0.5f, 0, paddedBounds.Location.z + z + 0.5f), gridSize3D);
var transform = Matrix4x4.TRS(position, Quaternion.identity, Vector3.one);
EmitMarker(BSPDungeonMarkerNames.GroundRoom, transform, new IntVector(x, 0, z), -1);
}
}
}
}
var doorPositions = new HashSet<IntVector>();
foreach (var connection in bspModel.connections)
{
var offset = connection.doorFacingX ? new Vector3(0, 0, 0.5f) : new Vector3(0.5f, 0, 0);
var pos0 = connection.doorPosition0;
var pos1 = connection.doorPosition1;
var pos0F = pos0.ToVector3();
var pos1F = pos1.ToVector3();
// Emit the doors
{
Vector3 worldPos0 = Vector3.Scale(pos0F + offset, gridSize3D);
Vector3 worldPos1 = Vector3.Scale(pos1F + offset, gridSize3D);
float angle0 = connection.doorFacingX ? 90 : 0;
float angle1 = connection.doorFacingX ? 270 : 180;
Matrix4x4 transform;
transform = Matrix4x4.TRS(worldPos0, Quaternion.Euler(0, angle0, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.Door, transform, pos0, -1);
transform = Matrix4x4.TRS(worldPos1, Quaternion.Euler(0, angle1, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.Door, transform, pos1, -1);
doorPositions.Add(pos0);
doorPositions.Add(pos1);
}
int x0 = Mathf.Min(pos0.x, pos1.x);
int x1 = Mathf.Max(pos0.x, pos1.x);
int z0 = Mathf.Min(pos0.z, pos1.z);
int z1 = Mathf.Max(pos0.z, pos1.z);
if (x0 == x1) z1--;
if (z0 == z1) x1--;
// Draw the corridor ground tiles
for (int x = x0; x <= x1; x++)
{
for (int z = z0; z <= z1; z++)
{
var doorGroundPosition = Vector3.Scale(new Vector3(x + 0.5f, 0, z + 0.5f), gridSize3D);
var transform = Matrix4x4.TRS(doorGroundPosition, Quaternion.identity, Vector3.one);
EmitMarker(BSPDungeonMarkerNames.GroundCorridor, transform, new IntVector(x, 0, z), -1);
}
}
// Draw the corridor walls
if (x0 == x1)
{
for (int z = z0; z <= z1; z++)
{
var worldPos = Vector3.Scale(new Vector3(x0, 0, z + 0.5f), gridSize3D);
var transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 90, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallCorridor, transform, new IntVector(x0, 0, z), -1);
worldPos = Vector3.Scale(new Vector3(x0 + 1, 0, z + 0.5f), gridSize3D);
transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 270, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallCorridor, transform, new IntVector(x1, 0, z), -1);
}
}
else
{
for (int x = x0; x <= x1; x++)
{
var worldPos = Vector3.Scale(new Vector3(x + 0.5f, 0, z0), gridSize3D);
var transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 0, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallCorridor, transform, new IntVector(x, 0, z0), -1);
worldPos = Vector3.Scale(new Vector3(x + 0.5f, 0, z0 + 1), gridSize3D);
transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 180, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallCorridor, transform, new IntVector(x, 0, z1), -1);
}
}
}
foreach (var node in bspModel.nodes)
{
if (node.discarded || node.children.Length > 0)
{
continue;
}
var loc = node.paddedBounds.Location;
var size = node.paddedBounds.Size;
int x0 = loc.x;
int x1 = loc.x + size.x;
int z0 = loc.z;
int z1 = loc.z + size.z;
// Emit he walls
{
for (int x = x0; x < x1; x++)
{
if (!doorPositions.Contains(new IntVector(x, 0, z0)))
{
var worldPos = Vector3.Scale(new Vector3(x + 0.5f, 0, z0), gridSize3D);
var transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 0, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallRoom, transform, new IntVector(x, 0, z0), -1);
}
if (!doorPositions.Contains(new IntVector(x, 0, z1)))
{
var worldPos = Vector3.Scale(new Vector3(x + 0.5f, 0, z1), gridSize3D);
var transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 180, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallRoom, transform, new IntVector(x, 0, z1), -1);
}
}
for (int z = z0; z < z1; z++)
{
if (!doorPositions.Contains(new IntVector(x0, 0, z)))
{
var worldPos = Vector3.Scale(new Vector3(x0, 0, z + 0.5f), gridSize3D);
var transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 90, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallRoom, transform, new IntVector(x0, 0, z), -1);
}
if (!doorPositions.Contains(new IntVector(x1, 0, z)))
{
var worldPos = Vector3.Scale(new Vector3(x1, 0, z + 0.5f), gridSize3D);
var transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 270, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallRoom, transform, new IntVector(x1, 0, z), -1);
}
}
}
// Emit the wall separators
{
for (int x = x0; x <= x1; x++)
{
var worldPos = Vector3.Scale(new Vector3(x, 0, z0), gridSize3D);
var transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 0, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallSeparator, transform, new IntVector(x, 0, z0), -1);
worldPos = Vector3.Scale(new Vector3(x, 0, z1), gridSize3D);
transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 0, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallSeparator, transform, new IntVector(x, 0, z1), -1);
}
for (int z = z0 + 1; z <= z1 - 1; z++)
{
var worldPos = Vector3.Scale(new Vector3(x0, 0, z), gridSize3D);
var transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 0, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallSeparator, transform, new IntVector(x0, 0, z), -1);
worldPos = Vector3.Scale(new Vector3(x1, 0, z), gridSize3D);
transform = Matrix4x4.TRS(worldPos, Quaternion.Euler(0, 0, 0), Vector3.one);
EmitMarker(BSPDungeonMarkerNames.WallSeparator, transform, new IntVector(x1, 0, z), -1);
}
}
}
}
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 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<DungeonUID>();
foreach (var child in node.children)
{
if (child != null)
{
childIds.Add(child.id);
}
}
serializedNode.children = childIds.ToArray();
var connectedIds = new List<DungeonUID>();
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);
}
}
public override void DebugDraw()
{
if (!bspModel) return;
var gridSize3D = new Vector3(bspConfig.gridSize.x, 0, bspConfig.gridSize.y);
var graphQuery = bspModel.CreateGraphQuery();
var discardedColor = new Color(0, 0, 0, 0.35f);
var debugTextItems = new List<DebugTextItem>();
int debugDoorIndex = 0;
foreach (var node in bspModel.nodes)
{
if (!node.discarded)
{
//continue;
}
//DebugDrawUtils.DrawBounds(node.bounds, Color.green, gridSize3D, false);
// Draw the room
if (node.children.Length == 0)
{
// only render leaf nodes
var paddedBounds = node.paddedBounds;
var color = node.discarded ? discardedColor : node.debugColor;
DebugDrawUtils.DrawBounds(paddedBounds, color, gridSize3D, false);
}
var connectionColor = Color.red;
bool renderedDoors = false;
// Draw the connected rooms
//foreach (var connectedNodeId in node.connectedRooms)
foreach (var leafConnection in node.subtreeLeafConnections)
{
var room0 = graphQuery.GetNode(leafConnection.room0);
var room1 = graphQuery.GetNode(leafConnection.room1);
//var connectedNode = graphQuery.GetNode(connectedNodeId);
var intersection = Rectangle.Intersect(room0.bounds, room1.bounds);
var center = intersection.Center();
var centerF = IntVector.ToV3(center);
var centerWorld = Vector3.Scale(centerF, gridSize3D);
var offsetStart = Vector3.zero;
var offsetEnd = Vector3.zero;
var padding = bspConfig.roomPadding;
var doorOffset0 = Vector3.zero;
var doorOffset1 = Vector3.zero;
var textOffset = Vector3.zero;
if (intersection.Size.x > 0)
{
offsetStart.z -= padding;
offsetEnd.z += padding;
doorOffset0.x -= 0.0f;
doorOffset1.x += 1.0f;
textOffset.x -= 1;
textOffset.z += 1.0f;
}
else
{
offsetStart.x -= padding;
offsetEnd.x += padding;
doorOffset0.z -= 0.0f;
doorOffset1.z += 1.0f;
textOffset.x -= 0.25f;
}
offsetStart = Vector3.Scale(offsetStart, gridSize3D);
offsetEnd = Vector3.Scale(offsetEnd, gridSize3D);
doorOffset0 = Vector3.Scale(doorOffset0, gridSize3D);
doorOffset1 = Vector3.Scale(doorOffset1, gridSize3D);
textOffset = Vector3.Scale(textOffset, gridSize3D);
bool discarded = (room0.discarded || room1.discarded);
var doorColor = discarded ? discardedColor : connectionColor;
Debug.DrawLine(centerWorld + offsetStart + doorOffset0, centerWorld + offsetEnd + doorOffset0, doorColor);
Debug.DrawLine(centerWorld + offsetStart + doorOffset1, centerWorld + offsetEnd + doorOffset1, doorColor);
if (!discarded)
{
var debugText = new DebugTextItem();
debugText.position = centerWorld + textOffset;
debugText.color = Color.black;
debugText.message = "" + (char)('A' + debugDoorIndex);
debugTextItems.Add(debugText);
renderedDoors = true;
}
}
if (renderedDoors)
{
debugDoorIndex++;
}
}
var debugText3D = GetComponent<DebugText3D>();
if (debugText3D != null)
{
debugText3D.items = debugTextItems.ToArray();
}
}
}
class NodeConnection {
BSPNodeObject room0;
public BSPNodeObject Room0
{
get
{
return room0;
}
}
BSPNodeObject room1;
public BSPNodeObject Room1
{
get
{
return room1;
}
}
bool doorFacingX;
public bool DoorFacingX
{
get { return doorFacingX; }
}
IntVector doorPosition0;
public IntVector DoorPosition0
{
get { return doorPosition0; }
set { doorPosition0 = value; }
}
IntVector doorPosition1;
public IntVector DoorPosition1
{
get { return doorPosition1; }
set { doorPosition1 = value; }
}
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;
}
}
}
}