From 31f0c1ca0c234634171ae6efcffb1e173ce036e6 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sat, 28 Dec 2013 22:49:11 -0400 Subject: [PATCH] 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. --- .../experimental/DirectedGraph.java | 225 ++++++++++ .../experimental/DoorwayData.java | 22 +- .../StevenDimDoors/experimental/IEdge.java | 8 + .../experimental/IGraphNode.java | 10 + .../experimental/ILinkedListNode.java | 11 + .../experimental/LinkedList.java | 236 +++++++++++ .../experimental/MazeGenerator.java | 393 ++++++++++++++++-- .../experimental/PartitionNode.java | 36 ++ .../StevenDimDoors/experimental/RoomNode.java | 56 --- 9 files changed, 898 insertions(+), 99 deletions(-) create mode 100644 src/main/java/StevenDimDoors/experimental/DirectedGraph.java create mode 100644 src/main/java/StevenDimDoors/experimental/IEdge.java create mode 100644 src/main/java/StevenDimDoors/experimental/IGraphNode.java create mode 100644 src/main/java/StevenDimDoors/experimental/ILinkedListNode.java create mode 100644 src/main/java/StevenDimDoors/experimental/LinkedList.java delete mode 100644 src/main/java/StevenDimDoors/experimental/RoomNode.java diff --git a/src/main/java/StevenDimDoors/experimental/DirectedGraph.java b/src/main/java/StevenDimDoors/experimental/DirectedGraph.java new file mode 100644 index 00000000..cd56c751 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/DirectedGraph.java @@ -0,0 +1,225 @@ +package StevenDimDoors.experimental; + +/** + * Provides a complete implementation of a directed graph. + * @author SenseiKiwi + * + * @param The type of data to store in the graph's nodes + * @param The type of data to store in the graph's edges + */ +public class DirectedGraph +{ + private static class GraphNode implements IGraphNode + { + private LinkedList> inbound; + private LinkedList> outbound; + private ILinkedListNode> graphEntry; + private P data; + + public GraphNode(P data, LinkedList> graphList) + { + this.data = data; + this.inbound = new LinkedList>(); + this.outbound = new LinkedList>(); + this.graphEntry = graphList.addLast(this); + } + + public int indegree() + { + return inbound.size(); + } + + public int outdegree() + { + return outbound.size(); + } + + public Iterable> inbound() + { + return inbound; + } + + public Iterable> outbound() + { + return outbound; + } + + public P data() + { + return data; + } + + public void remove() + { + graphEntry.remove(); + graphEntry = null; + + for (Edge edge : inbound) + edge.remove(); + + for (Edge edge : outbound) + edge.remove(); + + inbound = null; + outbound = null; + data = null; + } + } + + private static class Edge implements IEdge + { + private GraphNode head; + private GraphNode tail; + private ILinkedListNode> headEntry; + private ILinkedListNode> tailEntry; + private ILinkedListNode> graphEntry; + private Q data; + + public Edge(GraphNode head, GraphNode tail, Q data, LinkedList> 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 head() + { + return head; + } + + public IGraphNode 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> nodes; + private LinkedList> edges; + + public DirectedGraph() + { + nodes = new LinkedList>(); + edges = new LinkedList>(); + } + + public int nodeCount() + { + return nodes.size(); + } + + public int edgeCount() + { + return edges.size(); + } + + public boolean isEmpty() + { + return nodes.isEmpty(); + } + + public Iterable> nodes() + { + return nodes; + } + + public Iterable> edges() + { + return edges; + } + + private GraphNode checkNode(IGraphNode node) + { + GraphNode innerNode = (GraphNode) 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 checkEdge(IEdge edge) + { + Edge innerEdge = (Edge) 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 addNode(U data) + { + return new GraphNode(data, nodes); + } + + public IEdge addEdge(IGraphNode head, IGraphNode tail, V data) + { + GraphNode innerHead = checkNode(head); + GraphNode innerTail = checkNode(tail); + return new Edge(innerHead, innerTail, data, edges); + } + + public U removeNode(IGraphNode node) + { + GraphNode innerNode = checkNode(node); + U data = innerNode.data(); + innerNode.remove(); + return data; + } + + public V removeEdge(IEdge edge) + { + Edge innerEdge = checkEdge(edge); + V data = innerEdge.data(); + innerEdge.remove(); + return data; + } + + public IEdge findEdge(IGraphNode head, IGraphNode tail) + { + for (IEdge 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 node : nodes) + { + node.remove(); + } + } +} diff --git a/src/main/java/StevenDimDoors/experimental/DoorwayData.java b/src/main/java/StevenDimDoors/experimental/DoorwayData.java index a1a09f1c..ec1c5cc8 100644 --- a/src/main/java/StevenDimDoors/experimental/DoorwayData.java +++ b/src/main/java/StevenDimDoors/experimental/DoorwayData.java @@ -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; diff --git a/src/main/java/StevenDimDoors/experimental/IEdge.java b/src/main/java/StevenDimDoors/experimental/IEdge.java new file mode 100644 index 00000000..1d268116 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/IEdge.java @@ -0,0 +1,8 @@ +package StevenDimDoors.experimental; + +public interface IEdge +{ + public IGraphNode head(); + public IGraphNode tail(); + public V data(); +} diff --git a/src/main/java/StevenDimDoors/experimental/IGraphNode.java b/src/main/java/StevenDimDoors/experimental/IGraphNode.java new file mode 100644 index 00000000..65fd2fbf --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/IGraphNode.java @@ -0,0 +1,10 @@ +package StevenDimDoors.experimental; + +public interface IGraphNode +{ + public Iterable> inbound(); + public Iterable> outbound(); + public int indegree(); + public int outdegree(); + public U data(); +} diff --git a/src/main/java/StevenDimDoors/experimental/ILinkedListNode.java b/src/main/java/StevenDimDoors/experimental/ILinkedListNode.java new file mode 100644 index 00000000..a7a54adc --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/ILinkedListNode.java @@ -0,0 +1,11 @@ +package StevenDimDoors.experimental; + +public interface ILinkedListNode +{ + public ILinkedListNode next(); + public ILinkedListNode prev(); + public T data(); + public void setData(T data); + public LinkedList owner(); + public T remove(); +} diff --git a/src/main/java/StevenDimDoors/experimental/LinkedList.java b/src/main/java/StevenDimDoors/experimental/LinkedList.java new file mode 100644 index 00000000..8d6b9751 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/LinkedList.java @@ -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 The type of data to be stored in the LinkedList + */ +public class LinkedList implements Iterable +{ + private static class Node

implements ILinkedListNode

+ { + private Node

next; + private Node

prev; + private P data; + private LinkedList

owner; + + public Node(Node

prev, Node

next, P data, LinkedList

owner) + { + this.prev = prev; + this.next = next; + this.data = data; + this.owner = owner; + } + + @Override + public ILinkedListNode

next() + { + return next; + } + + @Override + public ILinkedListNode

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

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

implements Iterator

+ { + private Node

current; + private Node

trailer; + + public LinkedListIterator(LinkedList

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 header; // Sentinel node + private Node trailer; // Sentinel node + private int size; + + public LinkedList() + { + size = 0; + header = new Node(null, null, null, this); + trailer = new Node(null, null, null, this); + header.next = trailer; + trailer.prev = header; + } + + public ILinkedListNode header() + { + return header; + } + + public ILinkedListNode 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 current; + Node next; + + size = 0; + current = header.next; + while (current != trailer) + { + next = current.next; + current.clear(); + current = next; + } + header.next = trailer; + trailer.prev = header; + } + + private Node checkNode(ILinkedListNode node) + { + Node innerNode = (Node) 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 addFirst(T data) + { + return addAfter(header, data); + } + + public ILinkedListNode addLast(T data) + { + return addBefore(trailer, data); + } + + public ILinkedListNode addBefore(ILinkedListNode 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 addAfter(ILinkedListNode node, T data) + { + if (node == trailer) + { + throw new IllegalArgumentException("Cannot add a node after the trailer node."); + } + return addAfter( checkNode(node), data ); + } + + private Node addAfter(Node node, T data) + { + Node addition = new Node(node, node.next, data, this); + node.next = addition; + addition.next.prev = addition; + return addition; + } + + public Iterator iterator() + { + return new LinkedListIterator(this); + } +} diff --git a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java index ba328405..4b5f076d 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeGenerator.java +++ b/src/main/java/StevenDimDoors/experimental/MazeGenerator.java @@ -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 rooms = new ArrayList(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 partitions = new ArrayList(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 rooms = createRoomGraph(root, partitions, random); + + // Cut out random subgraphs from the adjacency graph + ArrayList> 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 rooms) + private static void listRoomPartitions(PartitionNode node, ArrayList 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 createRoomGraph(PartitionNode root, ArrayList partitions, Random random) { - if (node.isLeaf()) + DirectedGraph roomGraph = new DirectedGraph(); + HashMap> roomsToGraph = new HashMap>(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 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 roomNode, PartitionNode root, + HashMap> roomsToGraph, + DirectedGraph 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 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> createMazeSections(DirectedGraph 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 current; + IGraphNode neighbor; + + ArrayList> cores = new ArrayList>(); + Queue> ordering = new LinkedList>(); + HashMap, Integer> distances = new HashMap, Integer>(); + + // Repeatedly generate sections until all nodes have been visited + for (IGraphNode 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 edge : current.inbound()) + { + neighbor = edge.head(); + if (!distances.containsKey(neighbor)) + { + distances.put(neighbor, distance); + ordering.add(neighbor); + } + } + for (IEdge 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 roomGraph, World world, Point3D offset) + { + for (IGraphNode 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 node : roomGraph.nodes()) + { + for (IEdge 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); } } } diff --git a/src/main/java/StevenDimDoors/experimental/PartitionNode.java b/src/main/java/StevenDimDoors/experimental/PartitionNode.java index 9c14eb01..25d8d6d1 100644 --- a/src/main/java/StevenDimDoors/experimental/PartitionNode.java +++ b/src/main/java/StevenDimDoors/experimental/PartitionNode.java @@ -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; + } + } } diff --git a/src/main/java/StevenDimDoors/experimental/RoomNode.java b/src/main/java/StevenDimDoors/experimental/RoomNode.java deleted file mode 100644 index 78a70027..00000000 --- a/src/main/java/StevenDimDoors/experimental/RoomNode.java +++ /dev/null @@ -1,56 +0,0 @@ -package StevenDimDoors.experimental; - -import java.util.ArrayList; - -public class RoomNode -{ - private ArrayList outbound; - private ArrayList 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(); - this.inbound = new ArrayList(); - } - - 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); - } -}