Mazes #117
225
src/main/java/StevenDimDoors/experimental/DirectedGraph.java
Normal file
225
src/main/java/StevenDimDoors/experimental/DirectedGraph.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
8
src/main/java/StevenDimDoors/experimental/IEdge.java
Normal file
8
src/main/java/StevenDimDoors/experimental/IEdge.java
Normal 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();
|
||||
}
|
||||
10
src/main/java/StevenDimDoors/experimental/IGraphNode.java
Normal file
10
src/main/java/StevenDimDoors/experimental/IGraphNode.java
Normal 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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
236
src/main/java/StevenDimDoors/experimental/LinkedList.java
Normal file
236
src/main/java/StevenDimDoors/experimental/LinkedList.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
// Add edges for each room
|
||||
for (IGraphNode<PartitionNode, DoorwayData> node : roomGraph.nodes())
|
||||
{
|
||||
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
|
||||
{
|
||||
if (node.leftChild() != null)
|
||||
buildRooms(node.leftChild(), world, offset);
|
||||
if (node.rightChild() != null)
|
||||
buildRooms(node.rightChild(), world, offset);
|
||||
detected[a][b] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner)
|
||||
|
||||
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, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user