Completed Second Step of Maze Generation

Completed the second step of maze generation. In this step, a graph is
built that describes which rooms can be connected to each other by
doorways. A maze is created by removing nodes (rooms) from the graph.
The algorithm is incomplete, but it already produces interesting mazes
comparable to DD's hand-made mazes.

A few details for the future:
1. Doorways are currently carved into walls at default locations. This
is just for testing and placement should be improved later. Some
doorways should be removed and redundant doorways should be possible.
2. The size of a section should be assessed and the section should be
discarded if it has too few rooms.
3. An NPE occurs every so often when a maze is generated. It's possible
that it happens because of removing a node from the graph that is
coincidentally the current node for LinkedList's iterator. The solution
would be to add nodes to a list and defer removals until after the
iteration is done.
This commit is contained in:
SenseiKiwi 2013-12-28 22:49:11 -04:00
parent 81bac5d1c2
commit 31f0c1ca0c
9 changed files with 898 additions and 99 deletions

View file

@ -0,0 +1,225 @@
package StevenDimDoors.experimental;
/**
* Provides a complete implementation of a directed graph.
* @author SenseiKiwi
*
* @param <U> The type of data to store in the graph's nodes
* @param <V> The type of data to store in the graph's edges
*/
public class DirectedGraph<U, V>
{
private static class GraphNode<P, Q> implements IGraphNode<P, Q>
{
private LinkedList<Edge<P, Q>> inbound;
private LinkedList<Edge<P, Q>> outbound;
private ILinkedListNode<GraphNode<P, Q>> graphEntry;
private P data;
public GraphNode(P data, LinkedList<GraphNode<P, Q>> graphList)
{
this.data = data;
this.inbound = new LinkedList<Edge<P, Q>>();
this.outbound = new LinkedList<Edge<P, Q>>();
this.graphEntry = graphList.addLast(this);
}
public int indegree()
{
return inbound.size();
}
public int outdegree()
{
return outbound.size();
}
public Iterable<Edge<P, Q>> inbound()
{
return inbound;
}
public Iterable<Edge<P, Q>> outbound()
{
return outbound;
}
public P data()
{
return data;
}
public void remove()
{
graphEntry.remove();
graphEntry = null;
for (Edge<P, Q> edge : inbound)
edge.remove();
for (Edge<P, Q> edge : outbound)
edge.remove();
inbound = null;
outbound = null;
data = null;
}
}
private static class Edge<P, Q> implements IEdge<P, Q>
{
private GraphNode<P, Q> head;
private GraphNode<P, Q> tail;
private ILinkedListNode<Edge<P, Q>> headEntry;
private ILinkedListNode<Edge<P, Q>> tailEntry;
private ILinkedListNode<Edge<P, Q>> graphEntry;
private Q data;
public Edge(GraphNode<P, Q> head, GraphNode<P, Q> tail, Q data, LinkedList<Edge<P, Q>> graphList)
{
this.head = head;
this.tail = tail;
this.data = data;
this.graphEntry = graphList.addLast(this);
this.headEntry = head.outbound.addLast(this);
this.tailEntry = tail.inbound.addLast(this);
}
public IGraphNode<P, Q> head()
{
return head;
}
public IGraphNode<P, Q> tail()
{
return tail;
}
public Q data()
{
return data;
}
public void remove()
{
headEntry.remove();
tailEntry.remove();
graphEntry.remove();
headEntry = null;
tailEntry = null;
graphEntry = null;
head = null;
tail = null;
data = null;
}
}
private LinkedList<GraphNode<U, V>> nodes;
private LinkedList<Edge<U, V>> edges;
public DirectedGraph()
{
nodes = new LinkedList<GraphNode<U, V>>();
edges = new LinkedList<Edge<U, V>>();
}
public int nodeCount()
{
return nodes.size();
}
public int edgeCount()
{
return edges.size();
}
public boolean isEmpty()
{
return nodes.isEmpty();
}
public Iterable<? extends IGraphNode<U, V>> nodes()
{
return nodes;
}
public Iterable<? extends IEdge<U, V>> edges()
{
return edges;
}
private GraphNode<U, V> checkNode(IGraphNode<U, V> node)
{
GraphNode<U, V> innerNode = (GraphNode<U, V>) node;
// Check that this node actually belongs to this graph instance.
// Accepting foreign nodes could corrupt the graph's internal state.
if (innerNode.graphEntry.owner() != nodes)
{
throw new IllegalArgumentException("The specified node does not belong to this graph.");
}
return innerNode;
}
private Edge<U, V> checkEdge(IEdge<U, V> edge)
{
Edge<U, V> innerEdge = (Edge<U, V>) edge;
// Check that this node actually belongs to this graph instance.
// Accepting foreign nodes could corrupt the graph's internal state.
if (innerEdge.graphEntry.owner() != edges)
{
throw new IllegalArgumentException("The specified edge does not belong to this graph.");
}
return innerEdge;
}
public IGraphNode<U, V> addNode(U data)
{
return new GraphNode<U, V>(data, nodes);
}
public IEdge<U, V> addEdge(IGraphNode<U, V> head, IGraphNode<U, V> tail, V data)
{
GraphNode<U, V> innerHead = checkNode(head);
GraphNode<U, V> innerTail = checkNode(tail);
return new Edge<U, V>(innerHead, innerTail, data, edges);
}
public U removeNode(IGraphNode<U, V> node)
{
GraphNode<U, V> innerNode = checkNode(node);
U data = innerNode.data();
innerNode.remove();
return data;
}
public V removeEdge(IEdge<U, V> edge)
{
Edge<U, V> innerEdge = checkEdge(edge);
V data = innerEdge.data();
innerEdge.remove();
return data;
}
public IEdge<U, V> findEdge(IGraphNode<U, V> head, IGraphNode<U, V> tail)
{
for (IEdge<U, V> edge : head.outbound())
{
if (edge.tail() == tail)
return edge;
}
return null;
}
public void clear()
{
// Remove each node individually to guarantee that all external
// references are invalidated. That'll prevent memory leaks and
// keep external code from using removed nodes or edges.
for (GraphNode<U, V> node : nodes)
{
node.remove();
}
}
}

View file

@ -4,35 +4,21 @@ import StevenDimDoors.mod_pocketDim.Point3D;
public class DoorwayData
{
public final char X_AXIS = 'X';
public final char Y_AXIS = 'Y';
public final char Z_AXIS = 'Z';
public static final char X_AXIS = 'X';
public static final char Y_AXIS = 'Y';
public static final char Z_AXIS = 'Z';
private RoomNode head;
private RoomNode tail;
private Point3D minCorner;
private Point3D maxCorner;
private char axis;
public DoorwayData(RoomNode head, RoomNode tail, Point3D minCorner, Point3D maxCorner, char axis)
public DoorwayData(Point3D minCorner, Point3D maxCorner, char axis)
{
this.head = head;
this.tail = tail;
this.minCorner = minCorner;
this.maxCorner = maxCorner;
this.axis = axis;
}
public RoomNode head()
{
return head;
}
public RoomNode tail()
{
return tail;
}
public Point3D minCorner()
{
return minCorner;

View file

@ -0,0 +1,8 @@
package StevenDimDoors.experimental;
public interface IEdge<U, V>
{
public IGraphNode<U, V> head();
public IGraphNode<U, V> tail();
public V data();
}

View file

@ -0,0 +1,10 @@
package StevenDimDoors.experimental;
public interface IGraphNode<U, V>
{
public Iterable<? extends IEdge<U, V>> inbound();
public Iterable<? extends IEdge<U, V>> outbound();
public int indegree();
public int outdegree();
public U data();
}

View file

@ -0,0 +1,11 @@
package StevenDimDoors.experimental;
public interface ILinkedListNode<T>
{
public ILinkedListNode<T> next();
public ILinkedListNode<T> prev();
public T data();
public void setData(T data);
public LinkedList<T> owner();
public T remove();
}

View file

@ -0,0 +1,236 @@
package StevenDimDoors.experimental;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Provides an implementation of a linked list that exposes its internal nodes.
* This differs from Java's implementation, which does not expose nodes. Access
* to the nodes allows certain operations to be implemented more efficiently.
* Not all operations are supported, but we can add them as the need arises.
* @author SenseiKiwi
*
* @param <T> The type of data to be stored in the LinkedList
*/
public class LinkedList<T> implements Iterable<T>
{
private static class Node<P> implements ILinkedListNode<P>
{
private Node<P> next;
private Node<P> prev;
private P data;
private LinkedList<P> owner;
public Node(Node<P> prev, Node<P> next, P data, LinkedList<P> owner)
{
this.prev = prev;
this.next = next;
this.data = data;
this.owner = owner;
}
@Override
public ILinkedListNode<P> next()
{
return next;
}
@Override
public ILinkedListNode<P> prev()
{
return prev;
}
@Override
public P data()
{
return data;
}
@Override
public void setData(P data)
{
if (this == owner.header || this == owner.trailer)
{
throw new IllegalStateException("Cannot set data for the header and trailer nodes of a list.");
}
this.data = data;
}
@Override
public LinkedList<P> owner()
{
return owner;
}
@Override
public P remove()
{
if (this == owner.header || this == owner.trailer)
{
throw new IllegalStateException("Cannot remove the header and trailer nodes of a list.");
}
P data = this.data;
this.prev.next = this.next;
this.next.prev = this.prev;
this.owner.size--;
this.clear();
return data;
}
public void clear()
{
this.data = null;
this.prev = null;
this.next = null;
this.owner = null;
}
}
private static class LinkedListIterator<P> implements Iterator<P>
{
private Node<P> current;
private Node<P> trailer;
public LinkedListIterator(LinkedList<P> list)
{
current = list.header.next;
trailer = list.trailer;
}
@Override
public boolean hasNext()
{
return (current != trailer);
}
@Override
public P next()
{
if (current == trailer)
{
throw new NoSuchElementException();
}
else
{
P result = current.data;
current = current.next;
return result;
}
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
}
private Node<T> header; // Sentinel node
private Node<T> trailer; // Sentinel node
private int size;
public LinkedList()
{
size = 0;
header = new Node<T>(null, null, null, this);
trailer = new Node<T>(null, null, null, this);
header.next = trailer;
trailer.prev = header;
}
public ILinkedListNode<T> header()
{
return header;
}
public ILinkedListNode<T> trailer()
{
return trailer;
}
public int size()
{
return size;
}
public boolean isEmpty()
{
return (size == 0);
}
public void clear()
{
// Go through the list and wipe everything out
Node<T> current;
Node<T> next;
size = 0;
current = header.next;
while (current != trailer)
{
next = current.next;
current.clear();
current = next;
}
header.next = trailer;
trailer.prev = header;
}
private Node<T> checkNode(ILinkedListNode<T> node)
{
Node<T> innerNode = (Node<T>) node;
// Check that this node actually belongs to this list instance.
// Accepting foreign nodes could corrupt the list's internal state.
if (innerNode.owner() != this)
{
throw new IllegalArgumentException("The specified node does not belong to this list.");
}
return innerNode;
}
public ILinkedListNode<T> addFirst(T data)
{
return addAfter(header, data);
}
public ILinkedListNode<T> addLast(T data)
{
return addBefore(trailer, data);
}
public ILinkedListNode<T> addBefore(ILinkedListNode<T> node, T data)
{
if (node == header)
{
throw new IllegalArgumentException("Cannot add a node before the header node.");
}
return addAfter( checkNode(node).prev, data );
}
public ILinkedListNode<T> addAfter(ILinkedListNode<T> node, T data)
{
if (node == trailer)
{
throw new IllegalArgumentException("Cannot add a node after the trailer node.");
}
return addAfter( checkNode(node), data );
}
private Node<T> addAfter(Node<T> node, T data)
{
Node<T> addition = new Node(node, node.next, data, this);
node.next = addition;
addition.next.prev = addition;
return addition;
}
public Iterator<T> iterator()
{
return new LinkedListIterator<T>(this);
}
}

View file

@ -2,6 +2,9 @@ package StevenDimDoors.experimental;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import net.minecraft.block.Block;
@ -24,24 +27,35 @@ public class MazeGenerator
public static void generate(World world, int x, int y, int z, Random random)
{
// Construct a random binary space partitioning of our maze volume
PartitionNode root = partitionRooms(ROOT_WIDTH, ROOT_HEIGHT, ROOT_LENGTH, SPLIT_COUNT, random);
// Collect all the leaf nodes by performing a tree traversal
ArrayList<PartitionNode> rooms = new ArrayList<PartitionNode>(1 << SPLIT_COUNT);
listRooms(root, rooms);
removeRandomRooms(rooms, random);
buildRooms(root, world, new Point3D(x - ROOT_WIDTH / 2, y - ROOT_HEIGHT - 1, z - ROOT_WIDTH / 2));
// List all the leaf nodes of the partition tree, which denote individual rooms
ArrayList<PartitionNode> partitions = new ArrayList<PartitionNode>(1 << SPLIT_COUNT);
listRoomPartitions(root, partitions);
// Construct an adjacency graph of the rooms we've carved out. Two rooms are
// considered adjacent if and only if a doorway could connect them. Their
// common boundary must be large enough for a doorway.
DirectedGraph<PartitionNode, DoorwayData> rooms = createRoomGraph(root, partitions, random);
// Cut out random subgraphs from the adjacency graph
ArrayList<IGraphNode<PartitionNode, DoorwayData>> cores = createMazeSections(rooms, random);
buildRooms(rooms, world, new Point3D(x - ROOT_WIDTH / 2, y - ROOT_HEIGHT - 1, z - ROOT_WIDTH / 2));
}
private static void listRooms(PartitionNode node, ArrayList<PartitionNode> rooms)
private static void listRoomPartitions(PartitionNode node, ArrayList<PartitionNode> partitions)
{
if (node.isLeaf())
{
rooms.add(node);
partitions.add(node);
}
else
{
listRooms(node.leftChild(), rooms);
listRooms(node.rightChild(), rooms);
listRoomPartitions(node.leftChild(), partitions);
listRoomPartitions(node.rightChild(), partitions);
}
}
@ -135,22 +149,352 @@ public class MazeGenerator
}
}
private static void buildRooms(PartitionNode node, World world, Point3D offset)
private static DirectedGraph<PartitionNode, DoorwayData> createRoomGraph(PartitionNode root, ArrayList<PartitionNode> partitions, Random random)
{
if (node.isLeaf())
DirectedGraph<PartitionNode, DoorwayData> roomGraph = new DirectedGraph<PartitionNode, DoorwayData>();
HashMap<PartitionNode, IGraphNode<PartitionNode, DoorwayData>> roomsToGraph = new HashMap<PartitionNode, IGraphNode<PartitionNode, DoorwayData>>(2 * partitions.size());
// Shuffle the list of rooms so that they're not listed in any ordered way in the room graph
// This is the only convenient way of randomizing the maze sections generated later
Collections.shuffle(partitions, random);
// Add all rooms to a graph
// Also add them to a map so we can associate rooms with their graph nodes
// The map is needed for linking graph nodes based on adjacent partitions
for (PartitionNode partition : partitions)
{
buildBox(world, offset, node.minCorner(), node.maxCorner());
roomsToGraph.put(partition, roomGraph.addNode(partition));
}
else
// Add edges for each room
for (IGraphNode<PartitionNode, DoorwayData> node : roomGraph.nodes())
{
if (node.leftChild() != null)
buildRooms(node.leftChild(), world, offset);
if (node.rightChild() != null)
buildRooms(node.rightChild(), world, offset);
findDoorways(node, root, roomsToGraph, roomGraph);
}
return roomGraph;
}
private static void findDoorways(IGraphNode<PartitionNode, DoorwayData> roomNode, PartitionNode root,
HashMap<PartitionNode, IGraphNode<PartitionNode, DoorwayData>> roomsToGraph,
DirectedGraph<PartitionNode, DoorwayData> roomGraph)
{
// This function finds rooms adjacent to a specified room that could be connected
// to it through a doorway. Edges are added to the room graph to denote rooms that
// could be connected. The areas of their common bounds that could be carved
// out for a passage are stored in the edges.
// Three directions have to be checked: up, forward, and right. The other three
// directions (down, back, left) aren't checked because other nodes will cover them.
// That is, down for this room is up for some other room, if it exists. Also, rooms
// are guaranteed to have at least one doorway to another room, because the minimum
// dimensions to which a room can be partitioned still allow passages along all
// its sides. A room's sibling in the partition tree is guaranteed to share a side
// through which a doorway could exist. Similar arguments guarantee the existence
// of passages such that the whole set of rooms is a connected graph - in other words,
// there will always be a way to walk from any room to any other room.
boolean[][] detected;
PartitionNode adjacent;
int a, b, c;
int p, q, r;
int minXI, minYI, minZI;
int maxXI, maxYI, maxZI;
Point3D otherMin;
Point3D otherMax;
DoorwayData doorway;
IGraphNode<PartitionNode, DoorwayData> adjacentNode;
PartitionNode room = roomNode.data();
Point3D minCorner = room.minCorner();
Point3D maxCorner = room.maxCorner();
int minX = minCorner.getX();
int minY = minCorner.getY();
int minZ = minCorner.getZ();
int maxX = maxCorner.getX();
int maxY = maxCorner.getY();
int maxZ = maxCorner.getZ();
int width = room.width();
int height = room.height();
int length = room.length();
if (maxZ < root.maxCorner().getZ())
{
// Check for adjacent rooms along the XY plane
detected = new boolean[width][height];
for (a = 0; a < width; a++)
{
for (b = 0; b < height; b++)
{
if (!detected[a][b])
{
adjacent = root.findPoint(minX + a, minY + b, maxZ + 1);
if (adjacent != null)
{
// Compute the dimensions available for a doorway
otherMin = adjacent.minCorner();
otherMax = adjacent.maxCorner();
minXI = Math.max(minX, otherMin.getX());
maxXI = Math.min(maxX, otherMax.getX());
minYI = Math.max(minY, otherMin.getY());
maxYI = Math.min(maxY, otherMax.getY());
for (p = a; p <= maxXI - minXI; p++)
{
for (q = b; q <= maxYI - minYI; q++)
{
detected[p][q] = true;
}
}
// Check if we meet the minimum dimensions needed for a doorway
if (maxXI - minXI + 1 >= MIN_SIDE && maxYI - minYI + 1 >= MIN_HEIGHT)
{
otherMin = new Point3D(minXI, minYI, maxZ);
otherMax = new Point3D(maxXI, maxYI, maxZ + 1);
doorway = new DoorwayData(otherMin, otherMax, DoorwayData.Z_AXIS);
adjacentNode = roomsToGraph.get(adjacent);
roomGraph.addEdge(roomNode, adjacentNode, doorway);
}
}
else
{
detected[a][b] = true;
}
}
}
}
}
if (maxX < root.maxCorner().getX())
{
// Check for adjacent rooms along the YZ plane
detected = new boolean[height][length];
for (b = 0; b < height; b++)
{
for (c = 0; c < length; c++)
{
if (!detected[b][c])
{
adjacent = root.findPoint(maxX + 1, minY + b, minZ + c);
if (adjacent != null)
{
// Compute the dimensions available for a doorway
otherMin = adjacent.minCorner();
otherMax = adjacent.maxCorner();
minYI = Math.max(minY, otherMin.getY());
maxYI = Math.min(maxY, otherMax.getY());
minZI = Math.max(minZ, otherMin.getZ());
maxZI = Math.min(maxZ, otherMax.getZ());
for (q = b; q <= maxYI - minYI; q++)
{
for (r = c; r <= maxZI - minZI; r++)
{
detected[q][r] = true;
}
}
// Check if we meet the minimum dimensions needed for a doorway
if (maxYI - minYI + 1 >= MIN_HEIGHT && maxZI - minZI + 1 >= MIN_SIDE)
{
otherMin = new Point3D(maxX, minYI, minZI);
otherMax = new Point3D(maxX + 1, maxYI, maxZI);
doorway = new DoorwayData(otherMin, otherMax, DoorwayData.X_AXIS);
adjacentNode = roomsToGraph.get(adjacent);
roomGraph.addEdge(roomNode, adjacentNode, doorway);
}
}
else
{
detected[b][c] = true;
}
}
}
}
}
if (maxY < root.maxCorner().getY())
{
// Check for adjacent rooms along the XZ plane
detected = new boolean[width][length];
for (a = 0; a < width; a++)
{
for (c = 0; c < length; c++)
{
if (!detected[a][c])
{
adjacent = root.findPoint(minX + a, maxY + 1, minZ + c);
if (adjacent != null)
{
// Compute the dimensions available for a doorway
otherMin = adjacent.minCorner();
otherMax = adjacent.maxCorner();
minXI = Math.max(minX, otherMin.getX());
maxXI = Math.min(maxX, otherMax.getX());
minZI = Math.max(minZ, otherMin.getZ());
maxZI = Math.min(maxZ, otherMax.getZ());
for (p = a; p <= maxXI - minXI; p++)
{
for (r = c; r <= maxZI - minZI; r++)
{
detected[p][r] = true;
}
}
// Check if we meet the minimum dimensions needed for a doorway
if (maxXI - minXI + 1 >= MIN_SIDE && maxZI - minZI + 1 >= MIN_SIDE)
{
otherMin = new Point3D(minXI, maxY, minZI);
otherMax = new Point3D(maxXI, maxY + 1, maxZI);
doorway = new DoorwayData(otherMin, otherMax, DoorwayData.Y_AXIS);
adjacentNode = roomsToGraph.get(adjacent);
roomGraph.addEdge(roomNode, adjacentNode, doorway);
}
}
else
{
detected[a][c] = true;
}
}
}
}
}
//Done!
}
private static ArrayList<IGraphNode<PartitionNode, DoorwayData>> createMazeSections(DirectedGraph<PartitionNode, DoorwayData> roomGraph, Random random)
{
// The randomness of the sections generated here hinges on
// the nodes in the graph being in a random order. We assume
// that was handled in a previous step!
final int MAX_DISTANCE = 2;
int distance;
IGraphNode<PartitionNode, DoorwayData> current;
IGraphNode<PartitionNode, DoorwayData> neighbor;
ArrayList<IGraphNode<PartitionNode, DoorwayData>> cores = new ArrayList<IGraphNode<PartitionNode, DoorwayData>>();
Queue<IGraphNode<PartitionNode, DoorwayData>> ordering = new LinkedList<IGraphNode<PartitionNode, DoorwayData>>();
HashMap<IGraphNode<PartitionNode, DoorwayData>, Integer> distances = new HashMap<IGraphNode<PartitionNode, DoorwayData>, Integer>();
// Repeatedly generate sections until all nodes have been visited
for (IGraphNode<PartitionNode, DoorwayData> node : roomGraph.nodes())
{
// If this node has an indegree and outdegree of 0, then it has no neighbors,
// which means it could not have been visited. This could happen if its neighbors
// were pruned away before. Single rooms look weird, so remove it.
if (node.indegree() == 0 && node.outdegree() == 0)
{
roomGraph.removeNode(node);
}
// If this node hasn't been visited, then use it as the core of a new section
// Otherwise, ignore it, since it already belongs to a section
else if (!distances.containsKey(node))
{
cores.add(node);
// Perform a breadth-first search to tag surrounding nodes with distances
distances.put(node, 0);
ordering.add(node);
while (!ordering.isEmpty())
{
current = ordering.remove();
distance = distances.get(current) + 1;
if (distance <= MAX_DISTANCE + 1)
{
// Visit neighboring nodes and assign them distances, if they don't
// have a distance assigned already
for (IEdge<PartitionNode, DoorwayData> edge : current.inbound())
{
neighbor = edge.head();
if (!distances.containsKey(neighbor))
{
distances.put(neighbor, distance);
ordering.add(neighbor);
}
}
for (IEdge<PartitionNode, DoorwayData> edge : current.outbound())
{
neighbor = edge.tail();
if (!distances.containsKey(neighbor))
{
distances.put(neighbor, distance);
ordering.add(neighbor);
}
}
}
else
{
roomGraph.removeNode(current);
break;
}
}
// Remove all nodes that have a distance of exactly MAX_DISTANCE + 1
// Those are precisely the nodes that remain in the queue
while (!ordering.isEmpty())
{
roomGraph.removeNode( ordering.remove() );
}
}
}
return cores;
}
private static void buildRooms(DirectedGraph<PartitionNode, DoorwayData> roomGraph, World world, Point3D offset)
{
for (IGraphNode<PartitionNode, DoorwayData> node : roomGraph.nodes())
{
PartitionNode room = node.data();
buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0);
}
// TESTING!!!
// This code carves out cheap doorways
// The final system will be better
// This has to happen after all the rooms have been built or the passages will be overwritten sometimes
for (IGraphNode<PartitionNode, DoorwayData> node : roomGraph.nodes())
{
for (IEdge<PartitionNode, DoorwayData> doorway : node.outbound())
{
char axis = doorway.data().axis();
Point3D lower = doorway.data().minCorner();
if (axis == DoorwayData.Z_AXIS)
{
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ(), 0, 0);
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ(), 0, 0);
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0);
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0);
}
else if (axis == DoorwayData.X_AXIS)
{
setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0);
setBlockDirectly(world, offset.getX() + lower.getX(), offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0);
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0);
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 2, offset.getZ() + lower.getZ() + 1, 0, 0);
}
else
{
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0);
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY(), offset.getZ() + lower.getZ() + 1, 0, 0);
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0);
setBlockDirectly(world, offset.getX() + lower.getX() + 1, offset.getY() + lower.getY() + 1, offset.getZ() + lower.getZ() + 1, 0, 0);
}
}
}
}
private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner)
private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata)
{
int minX = minCorner.getX() + offset.getX();
int minY = minCorner.getY() + offset.getY();
@ -161,30 +505,29 @@ public class MazeGenerator
int maxZ = maxCorner.getZ() + offset.getZ();
int x, y, z;
int blockID = Block.stoneBrick.blockID;
for (x = minX; x <= maxX; x++)
{
for (z = minZ; z <= maxZ; z++)
{
setBlockDirectly(world, x, minY, z, blockID, 0);
setBlockDirectly(world, x, maxY, z, blockID, 0);
setBlockDirectly(world, x, minY, z, blockID, metadata);
setBlockDirectly(world, x, maxY, z, blockID, metadata);
}
}
for (x = minX; x <= maxX; x++)
{
for (y = minY; y <= maxY; y++)
{
setBlockDirectly(world, x, y, minZ, blockID, 0);
setBlockDirectly(world, x, y, maxZ, blockID, 0);
setBlockDirectly(world, x, y, minZ, blockID, metadata);
setBlockDirectly(world, x, y, maxZ, blockID, metadata);
}
}
for (z = minZ; z <= maxZ; z++)
{
for (y = minY; y <= maxY; y++)
{
setBlockDirectly(world, minX, y, z, blockID, 0);
setBlockDirectly(world, maxX, y, z, blockID, 0);
setBlockDirectly(world, minX, y, z, blockID, metadata);
setBlockDirectly(world, maxX, y, z, blockID, metadata);
}
}
}

View file

@ -122,4 +122,40 @@ public class PartitionNode
parent = null;
}
}
public boolean contains(int x, int y, int z)
{
return ((minCorner.getX() <= x && x <= maxCorner.getX()) &&
(minCorner.getY() <= y && y <= maxCorner.getY()) &&
(minCorner.getZ() <= z && z <= maxCorner.getZ()));
}
public PartitionNode findPoint(int x, int y, int z)
{
// Find the lowest node that contains the specified point or return null
if (this.contains(x, y, z))
{
return this.findPointInternal(x, y, z);
}
else
{
return null;
}
}
private PartitionNode findPointInternal(int x, int y, int z)
{
if (leftChild != null && leftChild.contains(x, y, z))
{
return leftChild.findPointInternal(x, y, z);
}
else if (rightChild != null && rightChild.contains(x, y, z))
{
return rightChild.findPointInternal(x, y, z);
}
else
{
return this;
}
}
}

View file

@ -1,56 +0,0 @@
package StevenDimDoors.experimental;
import java.util.ArrayList;
public class RoomNode
{
private ArrayList<DoorwayData> outbound;
private ArrayList<DoorwayData> inbound;
private PartitionNode bounds;
private int distance;
private boolean visited;
public RoomNode(PartitionNode bounds)
{
this.bounds = bounds;
this.distance = 0;
this.visited = false;
this.outbound = new ArrayList<DoorwayData>();
this.inbound = new ArrayList<DoorwayData>();
}
public int distance()
{
return distance;
}
public boolean isVisited()
{
return visited;
}
public void setDistance(int value)
{
distance = value;
}
public void setVisited(boolean value)
{
visited = value;
}
public PartitionNode bounds()
{
return bounds;
}
public void addInboundDoorway(DoorwayData data)
{
inbound.add(data);
}
public void addOutboundDoorway(DoorwayData data)
{
outbound.add(data);
}
}