I'm having some issues with my BSP Dungeon Generation and I'm not sure why! I'd greatly appreciate the help :)
Occasionally I'll get an IllegalArgumentException in RandomHelper. That may help, but it's not the issue because it only happens rarely.
As you can see, this image shows that the Rooms are not in the right positions, and sometimes not even the right size!
It should also be spliting each leaf 4 times, but it only splits the room into 2 leafs, then stops.
Here's a link to help understand what I'm trying to accomplish.
And, all the relavent code:
BSPDungeon.java
public class BSPDungeon
{
private BSPTree dungeonTree;
public BSPDungeon(BSPTree dungeonTree)
{
this.dungeonTree = dungeonTree;
}
public void render(Graphics g)
{
dungeonTree.render(g);
}
}
BSPDungeonGenerator.java
public class BSPDungeonGenerator
{
public static BSPDungeon generate(int splitIterations, int minRoomWidth, int minRoomHeight, int dungeonW, int dungeonH)
{
Random rand = new Random();
BSPTree dungeonTree = new BSPTree(dungeonW, dungeonH);
dungeonTree.generateTree(rand, splitIterations);
dungeonTree.finalizeNode();
dungeonTree.generateRooms(rand, minRoomWidth, minRoomHeight);
return new BSPDungeon(dungeonTree);
}
}
BSPDungeonRoom.java
NOTE: TilePosition simply stores 2 Integers for an x and a y
public class BSPDungeonRoom
{
private static final int TILE_SIZE = Tile.TILE_SIZE / 4;
private TilePosition position;
private int w, h;
public BSPDungeonRoom(TilePosition position, int w, int h)
{
this.position = position;
this.w = w;
this.h = h;
}
public void render(Graphics g)
{
g.setColor(Color.GRAY);
g.fillRect(position.getTileX() * TILE_SIZE, position.getTileY() * TILE_SIZE, w * TILE_SIZE, h * TILE_SIZE);
g.setColor(Color.RED);
g.drawRect(position.getTileX() * TILE_SIZE, position.getTileY() * TILE_SIZE, w * TILE_SIZE, h * TILE_SIZE);
}
}
BSPNode.java
public class BSPNode
{
private static final int TILE_SIZE = Tile.TILE_SIZE / 4;
private boolean modifiable;
private BSPNode parentNode;
private BSPNode childNodeA;
private BSPNode childNodeB;
private BSPSplitType splitType;
private BSPDungeonRoom dungeonRoom;
private int nodeX, nodeY, nodeW, nodeH;
private int splitX, splitY;
public BSPNode(BSPNode parentNode, int nodeX, int nodeY, int nodeW, int nodeH)
{
this.parentNode = parentNode;
this.nodeX = nodeX;
this.nodeY = nodeY;
this.nodeW = nodeW;
this.nodeH = nodeH;
modifiable = true;
}
public boolean isRoot()
{
// If the parentNode is null, treat this Node as the Root Node
return parentNode == null;
}
public boolean isBalanced()
{
// Checks if this Node is NOT a Leaf, and ensures that both children Nodes are NOT null
return !isLeaf() && childNodeA != null && childNodeB != null;
}
public boolean isLeaf()
{
// True if both children Nodes are null
return childNodeA == null && childNodeB == null;
}
public BSPNode getChildNodeA()
{
return childNodeA;
}
public BSPNode getChildNodeB()
{
return childNodeB;
}
public int getNodeX()
{
return nodeX;
}
public int getNodeY()
{
return nodeY;
}
public int getNodeWidth()
{
return nodeW;
}
public int getNodeHeight()
{
return nodeH;
}
public void split(BSPSplitType splitType, int split)
{
if(modifiable)
{
this.splitType = splitType;
if (splitType == BSPSplitType.VERTICAL)
{
splitX = split;
childNodeA = new BSPNode(this, nodeX, nodeY, split, nodeH);
childNodeB = new BSPNode(this, nodeX + split, nodeY, nodeW - split, nodeH);
}
else if (splitType == BSPSplitType.HORIZONTAL)
{
splitY = split;
childNodeA = new BSPNode(this, nodeX, nodeY, nodeW, split);
childNodeB = new BSPNode(this, nodeX, nodeY + split, nodeW, nodeH - split);
}
}
}
public boolean wasSplit()
{
// Node was split if the splitType was set
return splitType != null;
}
public BSPSplitType getSplitType()
{
return splitType;
}
public boolean shouldHaveRoom()
{
// Should be a Room if this Node is a Leaf
return isLeaf();
}
public boolean shouldHaveCorridor()
{
// Should be a Corridor if this Node's children have Rooms
return childNodeA.shouldHaveRoom() && childNodeB.shouldHaveRoom();
}
public boolean isDungeonRoomNull()
{
return dungeonRoom == null;
}
public void setDungeonRoom(BSPDungeonRoom dungeonRoom)
{
this.dungeonRoom = dungeonRoom;
}
public BSPDungeonRoom getDungeonRoom()
{
return dungeonRoom;
}
public void render(Graphics g)
{
g.setColor(Color.LIGHT_GRAY);
g.fillRect(nodeX * TILE_SIZE, nodeY * TILE_SIZE, nodeW * TILE_SIZE, nodeH * TILE_SIZE);
g.setColor(Color.DARK_GRAY);
g.drawRect(nodeX * TILE_SIZE, nodeY * TILE_SIZE, nodeW * TILE_SIZE, nodeH * TILE_SIZE);
if(dungeonRoom != null)
dungeonRoom.render(g);
}
public void finalizeNode()
{
modifiable = false;
}
}
BSPSplitType.java
public enum BSPSplitType
{
VERTICAL(0),
HORIZONTAL(1);
private int splitValue;
BSPSplitType(int splitValue)
{
this.splitValue = splitValue;
}
public int getSplitValue()
{
return splitValue;
}
public static BSPSplitType getSplitTypeFromValue(int splitValue)
{
return splitValue == VERTICAL.getSplitValue() ? VERTICAL : HORIZONTAL;
}
public static BSPSplitType getRandomSplitType(Random rand)
{
return getSplitTypeFromValue(rand.nextInt(2));
}
}
BSPTree.java
public class BSPTree
{
private BSPNode rootNode;
private ArrayList<BSPNode> finalizedNodeList;
public BSPTree(int width, int height)
{
rootNode = new BSPNode(null, 0, 0, width, height);
}
public void generateTree(Random rand, int splitIterations)
{
ArrayList<BSPNode> nodeList;
for (int i = 0; i < splitIterations; i++)
{
nodeList = getTreeNodes();
for (BSPNode node : nodeList)
{
if(!node.isLeaf())
return;
BSPSplitType splitType = BSPSplitType.getRandomSplitType(rand);
int splitAt;
if(splitType == BSPSplitType.VERTICAL)
splitAt = rand.nextInt(node.getNodeWidth());
else if(splitType == BSPSplitType.HORIZONTAL)
splitAt = rand.nextInt(node.getNodeHeight());
else splitAt = 0;
node.split(splitType, splitAt);
}
}
}
public void generateRooms(Random rand, int minRoomWidth, int minRoomHeight)
{
ArrayList<BSPNode> nodeList = getTreeNodes();
for (BSPNode node : nodeList)
{
if(node.shouldHaveRoom() && node.isDungeonRoomNull())
{
// Generate the Dungeon Room
int roomX;
int roomY;
int roomW;
int roomH;
roomX = RandomHelper.randomInRange(rand, node.getNodeX(), node.getNodeX() + node.getNodeWidth());
roomY = RandomHelper.randomInRange(rand, node.getNodeY(), node.getNodeY() + node.getNodeHeight());
roomW = RandomHelper.randomInRange(rand, roomX + minRoomWidth, node.getNodeWidth());
roomH = RandomHelper.randomInRange(rand, roomY + minRoomHeight, node.getNodeHeight());
BSPDungeonRoom dungeonRoom = new BSPDungeonRoom(new TilePosition(roomX, roomY), roomW, roomH);
node.setDungeonRoom(dungeonRoom);
}
}
/*for (BSPNode node : nodeList)
{
if(node.shouldHaveCorridor() && node.isDungeonRoomNull())
{
// Generate the Dungeon Corridor
int corridorX;
int corridorY;
int corridorW;
int corridorH;
BSPNode childA = node.getChildNodeA();
BSPNode childB = node.getChildNodeB();
BSPDungeonRoom dungeonCorridor = null;
if(node.wasSplit() && node.getSplitType() == BSPSplitType.HORIZONTAL)
{
corridorX = childA.getNodeX() + childA.getNodeWidth();
corridorY = rand.nextInt(childA.getNodeHeight()) + childA.getNodeY();
}
else if(node.wasSplit() && node.getSplitType() == BSPSplitType.VERTICAL)
{
}
if(dungeonCorridor != null)
node.setDungeonRoom(dungeonCorridor);
}
}*/
}
public void render(Graphics g)
{
for (BSPNode node : finalizedNodeList)
{
node.render(g);
}
}
public ArrayList<BSPNode> getTreeNodes()
{
ArrayList<BSPNode> nodeList = new ArrayList<>();
BSPNode currentNode = rootNode;
getNodes(currentNode, nodeList);
return nodeList;
}
public void getNodes(BSPNode node, ArrayList<BSPNode> nodeList)
{
nodeList.add(node);
if(!node.isLeaf())
{
getNodes(node.getChildNodeA(), nodeList);
getNodes(node.getChildNodeB(), nodeList);
}
}
public void finalizeNode()
{
ArrayList<BSPNode> nodeList = getTreeNodes();
for (BSPNode node : nodeList)
{
node.finalizeNode();
}
finalizedNodeList = nodeList;
}
}
And to Generate a Dungeon I simply call BSPDungeon dungeon = BSPDungeonGenerator.generate(4, 6, 6, 60, 60);
.