diff --git a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java index 9e31e9a..00348c1 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeBuilder.java +++ b/src/main/java/StevenDimDoors/experimental/MazeBuilder.java @@ -18,8 +18,8 @@ public class MazeBuilder Point3D offset = new Point3D(x - design.width() / 2, y - design.height() - 1, z - design.length() / 2); SphereDecayOperation decay = new SphereDecayOperation(random, 0, 0, Block.stoneBrick.blockID, 2); - buildRooms(design.getRoomGraph(), world, offset); - carveDoorways(design.getRoomGraph(), world, offset, decay, random); + buildRooms(design.getLayout(), world, offset); + carveDoorways(design.getLayout(), world, offset, decay, random); //placeDoors(design, world, offset); @@ -32,25 +32,25 @@ public class MazeBuilder //final int DECAY_BOX_SIZE = 8 } - private static void buildRooms(DirectedGraph roomGraph, World world, Point3D offset) + private static void buildRooms(DirectedGraph layout, World world, Point3D offset) { - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - PartitionNode room = node.data(); + PartitionNode room = node.data().getPartitionNode(); buildBox(world, offset, room.minCorner(), room.maxCorner(), Block.stoneBrick.blockID, 0); } } - private static void carveDoorways(DirectedGraph roomGraph, World world, + private static void carveDoorways(DirectedGraph layout, World world, Point3D offset, SphereDecayOperation decay, Random random) { char axis; Point3D lower; DoorwayData doorway; - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - for (IEdge passage : node.outbound()) + for (IEdge passage : node.outbound()) { doorway = passage.data(); axis = doorway.axis(); @@ -165,7 +165,6 @@ public class MazeBuilder setBlockDirectly(world, x, y, z, 0, 0); setBlockDirectly(world, x, y + 1, z, 0, 0); } - private static void buildBox(World world, Point3D offset, Point3D minCorner, Point3D maxCorner, int blockID, int metadata) { diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesign.java b/src/main/java/StevenDimDoors/experimental/MazeDesign.java index e7d4b4c..c88f1cd 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesign.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesign.java @@ -5,16 +5,12 @@ import java.util.ArrayList; public class MazeDesign { private PartitionNode root; - private DirectedGraph rooms; - private ArrayList> cores; - private ArrayList protectedAreas; + private DirectedGraph layout; - public MazeDesign(PartitionNode root, DirectedGraph rooms, - ArrayList> cores) + public MazeDesign(PartitionNode root, DirectedGraph layout) { this.root = root; - this.rooms = rooms; - this.cores = cores; + this.layout = layout; } public PartitionNode getRootPartition() @@ -22,19 +18,9 @@ public class MazeDesign return root; } - public DirectedGraph getRoomGraph() + public DirectedGraph getLayout() { - return rooms; - } - - public ArrayList> getCoreNodes() - { - return cores; - } - - public ArrayList getProtectedAreas() - { - return protectedAreas; + return layout; } public int width() diff --git a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java index 289931a..e9d7c31 100644 --- a/src/main/java/StevenDimDoors/experimental/MazeDesigner.java +++ b/src/main/java/StevenDimDoors/experimental/MazeDesigner.java @@ -25,55 +25,66 @@ public class MazeDesigner public static MazeDesign generate(Random random) { // Construct a random binary space partitioning of our maze volume - PartitionNode root = partitionRooms(MAZE_WIDTH, MAZE_HEIGHT, MAZE_LENGTH, SPLIT_COUNT, random); + PartitionNode root = partitionRooms(MAZE_WIDTH, MAZE_HEIGHT, MAZE_LENGTH, SPLIT_COUNT, random); - // List all the leaf nodes of the partition tree, which denote individual rooms - ArrayList partitions = new ArrayList(1 << SPLIT_COUNT); - listRoomPartitions(root, partitions); + // Attach rooms to all the leaf nodes of the partition tree + ArrayList rooms = new ArrayList(1 << SPLIT_COUNT); + attachRooms(root, rooms); + + // 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(rooms, random); // 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); + DirectedGraph layout = createRoomGraph(root, rooms, random); // Cut out random subgraphs from the adjacency graph - ArrayList> cores = createMazeSections(rooms, random); + ArrayList cores = createMazeSections(layout, random); // Remove unnecessary passages through floors/ceilings and some from the walls - for (IGraphNode core : cores) + for (RoomData core : cores) { - pruneDoorways(core, rooms, random); + pruneDoorways(core.getLayoutNode(), layout, random); } - return new MazeDesign(root, rooms, cores); + return new MazeDesign(root, layout); } - private static void listRoomPartitions(PartitionNode node, ArrayList partitions) + private static void attachRooms(PartitionNode node, ArrayList partitions) { if (node.isLeaf()) { - partitions.add(node); + partitions.add(new RoomData(node)); } else { - listRoomPartitions(node.leftChild(), partitions); - listRoomPartitions(node.rightChild(), partitions); + attachRooms(node.leftChild(), partitions); + attachRooms(node.rightChild(), partitions); } } - private static void removeRoomPartitions(PartitionNode node) + private static void removeRoom(RoomData room, DirectedGraph layout) { - // Remove a node and any of its ancestors that become leaf nodes + // Remove the room from the partition tree and from the layout graph. + // Also remove any ancestors that become leaf nodes. PartitionNode parent; PartitionNode current; - current = node; + current = room.getPartitionNode(); while (current != null && current.isLeaf()) { parent = current.parent(); current.remove(); current = parent; } + + // Remove the room from the layout graph + layout.removeNode(room.getLayoutNode()); + + // Wipe the room's data, as a precaution. + room.clear(); } private static PartitionNode partitionRooms(int width, int height, int length, int maxLevels, Random random) @@ -140,35 +151,25 @@ public class MazeDesigner } } - private static DirectedGraph createRoomGraph(PartitionNode root, ArrayList partitions, Random random) + private static DirectedGraph createRoomGraph(PartitionNode root, ArrayList rooms, Random random) { - 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); + DirectedGraph layout = new DirectedGraph(); // 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) + for (RoomData room : rooms) { - roomsToGraph.put(partition, roomGraph.addNode(partition)); + room.addToLayout(layout); } - // Add edges for each room - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - findDoorways(node, root, roomsToGraph, roomGraph); + findDoorways(node.data(), root, layout); } - - return roomGraph; + return layout; } - private static void findDoorways(IGraphNode roomNode, PartitionNode root, - HashMap> roomsToGraph, - DirectedGraph roomGraph) + private static void findDoorways(RoomData room, PartitionNode root, + DirectedGraph layout) { // 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 @@ -186,7 +187,7 @@ public class MazeDesigner // there will always be a way to walk from any room to any other room. boolean[][] detected; - PartitionNode adjacent; + PartitionNode adjacent; int a, b, c; int p, q, r; @@ -195,11 +196,10 @@ public class MazeDesigner Point3D otherMin; Point3D otherMax; DoorwayData doorway; - IGraphNode adjacentNode; - PartitionNode room = roomNode.data(); - Point3D minCorner = room.minCorner(); - Point3D maxCorner = room.maxCorner(); + PartitionNode partition = room.getPartitionNode(); + Point3D minCorner = partition.minCorner(); + Point3D maxCorner = partition.maxCorner(); int minX = minCorner.getX(); int minY = minCorner.getY(); @@ -209,9 +209,9 @@ public class MazeDesigner int maxY = maxCorner.getY(); int maxZ = maxCorner.getZ(); - int width = room.width(); - int height = room.height(); - int length = room.length(); + int width = partition.width(); + int height = partition.height(); + int length = partition.length(); if (maxZ < root.maxCorner().getZ()) { @@ -247,8 +247,7 @@ public class MazeDesigner 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); + layout.addEdge(room.getLayoutNode(), adjacent.getData().getLayoutNode(), doorway); } } else @@ -295,8 +294,7 @@ public class MazeDesigner 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); + layout.addEdge(room.getLayoutNode(), adjacent.getData().getLayoutNode(), doorway); } } else @@ -343,8 +341,7 @@ public class MazeDesigner 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); + layout.addEdge(room.getLayoutNode(), adjacent.getData().getLayoutNode(), doorway); } } else @@ -359,7 +356,7 @@ public class MazeDesigner //Done! } - private static ArrayList> createMazeSections(DirectedGraph roomGraph, Random random) + private static ArrayList createMazeSections(DirectedGraph layout, Random random) { // The randomness of the sections generated here hinges on // the nodes in the graph being in a random order. We assume @@ -369,66 +366,68 @@ public class MazeDesigner final int MIN_SECTION_ROOMS = 5; int distance; - IGraphNode current; - IGraphNode neighbor; + RoomData room; + RoomData neighbor; + IGraphNode current; - ArrayList> cores = new ArrayList>(); - ArrayList> removals = new ArrayList>(); - ArrayList> section = new ArrayList>(); + ArrayList cores = new ArrayList(); + ArrayList removals = new ArrayList(); + ArrayList section = new ArrayList(); - Queue> ordering = new LinkedList>(); - HashMap, Integer> distances = new HashMap, Integer>(); + Queue ordering = new LinkedList(); // Repeatedly generate sections until all nodes have been visited - for (IGraphNode node : roomGraph.nodes()) + for (IGraphNode node : layout.nodes()) { - // If this node hasn't been visited, then use it as the core of a new section + // If this room hasn't been visited (distance = -1), then use it as the core of a new section // Otherwise, ignore it, since it was already processed - if (!distances.containsKey(node)) + room = node.data(); + if (room.getDistance() < 0) { // Perform a breadth-first search to tag surrounding nodes with distances - distances.put(node, 0); - ordering.add(node); + room.setDistance(0); + ordering.add(room); section.clear(); while (!ordering.isEmpty()) { - current = ordering.remove(); - distance = distances.get(current) + 1; + room = ordering.remove(); + distance = room.getDistance() + 1; if (distance <= MAX_DISTANCE + 1) { - section.add(current); + section.add(room); + current = room.getLayoutNode(); - // Visit neighboring nodes and assign them distances, if they don't - // have a distance assigned already - for (IEdge edge : current.inbound()) + // Visit neighboring rooms and assign them distances, if they don't + // have a proper distance assigned already + for (IEdge edge : current.inbound()) { - neighbor = edge.head(); - if (!distances.containsKey(neighbor)) + neighbor = edge.head().data(); + if (neighbor.getDistance() < 0) { - distances.put(neighbor, distance); + neighbor.setDistance(distance); ordering.add(neighbor); } } - for (IEdge edge : current.outbound()) + for (IEdge edge : current.outbound()) { - neighbor = edge.tail(); - if (!distances.containsKey(neighbor)) + neighbor = edge.tail().data(); + if (neighbor.getDistance() < 0) { - distances.put(neighbor, distance); + neighbor.setDistance(distance); ordering.add(neighbor); } } } else { - removals.add(current); + removals.add(room); break; } } - // List nodes that have a distance of exactly MAX_DISTANCE + 1 + // List rooms that have a distance of exactly MAX_DISTANCE + 1 // Those are precisely the nodes that remain in the queue // We can't remove them immediately because that could break // the iterator for the graph. @@ -440,7 +439,7 @@ public class MazeDesigner // Check if this section contains enough rooms if (section.size() >= MIN_SECTION_ROOMS) { - cores.add(node); + cores.add(node.data()); } else { @@ -449,18 +448,17 @@ public class MazeDesigner } } - // Remove all the nodes that were listed for removal + // Remove all the rooms that were listed for removal // Also remove unused partitions from the partition tree - for (IGraphNode node : removals) + for (RoomData target : removals) { - removeRoomPartitions(node.data()); - roomGraph.removeNode(node); + removeRoom(target, layout); } return cores; } - private static void pruneDoorways(IGraphNode core, - DirectedGraph rooms, Random random) + private static void pruneDoorways(IGraphNode core, + DirectedGraph layout, Random random) { // We receive a node for one of the rooms in a section of the maze // and we need to remove as many floor doorways as possible while @@ -478,12 +476,12 @@ public class MazeDesigner // idea applies for the other doorways, plus some randomness. // First, list all nodes in the subgraph - IGraphNode current; - IGraphNode neighbor; + IGraphNode current; + IGraphNode neighbor; - Stack> ordering = new Stack>(); - ArrayList> subgraph = new ArrayList>(64); - DisjointSet> components = new DisjointSet>(128); + Stack> ordering = new Stack>(); + ArrayList> subgraph = new ArrayList>(64); + DisjointSet> components = new DisjointSet>(128); ordering.add(core); components.makeSet(core); @@ -492,7 +490,7 @@ public class MazeDesigner current = ordering.pop(); subgraph.add(current); - for (IEdge edge : current.inbound()) + for (IEdge edge : current.inbound()) { neighbor = edge.head(); if (components.makeSet(neighbor)) @@ -500,7 +498,7 @@ public class MazeDesigner ordering.add(neighbor); } } - for (IEdge edge : current.outbound()) + for (IEdge edge : current.outbound()) { neighbor = edge.tail(); if (components.makeSet(neighbor)) @@ -513,12 +511,12 @@ public class MazeDesigner // Now iterate over the list of nodes and merge their sets // We only have to look at outbound edges since inbound edges mirror them // Also list any Y_AXIS doorways we come across - ArrayList> targets = - new ArrayList>(); + ArrayList> targets = + new ArrayList>(); - for (IGraphNode room : subgraph) + for (IGraphNode room : subgraph) { - for (IEdge passage : room.outbound()) + for (IEdge passage : room.outbound()) { if (passage.data().axis() != DoorwayData.Y_AXIS) { @@ -535,11 +533,11 @@ public class MazeDesigner Collections.shuffle(targets, random); // Merge sets together and remove unnecessary doorways - for (IEdge passage : targets) + for (IEdge passage : targets) { if (!components.mergeSets(passage.head(), passage.tail())) { - rooms.removeEdge(passage); + layout.removeEdge(passage); } } @@ -548,13 +546,13 @@ public class MazeDesigner components.clear(); targets.clear(); - for (IGraphNode room : subgraph) + for (IGraphNode room : subgraph) { components.makeSet(room); } - for (IGraphNode room : subgraph) + for (IGraphNode room : subgraph) { - for (IEdge passage : room.outbound()) + for (IEdge passage : room.outbound()) { if (passage.data().axis() == DoorwayData.Y_AXIS) { @@ -567,11 +565,11 @@ public class MazeDesigner } } Collections.shuffle(targets, random); - for (IEdge passage : targets) + for (IEdge passage : targets) { if (!components.mergeSets(passage.head(), passage.tail()) && random.nextBoolean()) { - rooms.removeEdge(passage); + layout.removeEdge(passage); } } } diff --git a/src/main/java/StevenDimDoors/experimental/MazeLinkData.java b/src/main/java/StevenDimDoors/experimental/MazeLinkData.java new file mode 100644 index 0000000..efff141 --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/MazeLinkData.java @@ -0,0 +1,6 @@ +package StevenDimDoors.experimental; + +public class MazeLinkData +{ + +} diff --git a/src/main/java/StevenDimDoors/experimental/PartitionNode.java b/src/main/java/StevenDimDoors/experimental/PartitionNode.java index df53854..5226378 100644 --- a/src/main/java/StevenDimDoors/experimental/PartitionNode.java +++ b/src/main/java/StevenDimDoors/experimental/PartitionNode.java @@ -2,11 +2,12 @@ package StevenDimDoors.experimental; import StevenDimDoors.mod_pocketDim.Point3D; -public class PartitionNode extends BoundingBox +public class PartitionNode extends BoundingBox { private PartitionNode parent; private PartitionNode leftChild = null; private PartitionNode rightChild = null; + private T data = null; public PartitionNode(int width, int height, int length) { @@ -122,4 +123,14 @@ public class PartitionNode extends BoundingBox return this; } } + + public void setData(T value) + { + this.data = value; + } + + public T getData() + { + return data; + } } diff --git a/src/main/java/StevenDimDoors/experimental/RoomData.java b/src/main/java/StevenDimDoors/experimental/RoomData.java new file mode 100644 index 0000000..eacfcaf --- /dev/null +++ b/src/main/java/StevenDimDoors/experimental/RoomData.java @@ -0,0 +1,75 @@ +package StevenDimDoors.experimental; + +import java.util.ArrayList; + +public class RoomData +{ + private int distance; + private boolean decayed; + private PartitionNode partitionNode; + private ArrayList inboundLinks; + private ArrayList outboundLinks; + private IGraphNode layoutNode; + + public RoomData(PartitionNode partitionNode) + { + this.partitionNode = partitionNode; + this.inboundLinks = new ArrayList(); + this.outboundLinks = new ArrayList(); + this.layoutNode = null; + this.distance = -1; + this.decayed = false; + partitionNode.setData(this); + } + + public PartitionNode getPartitionNode() + { + return this.partitionNode; + } + + public IGraphNode getLayoutNode() + { + return this.layoutNode; + } + + public void addToLayout(DirectedGraph layout) + { + this.layoutNode = layout.addNode(this); + } + + public boolean isDecayed() + { + return decayed; + } + + public void setDecayed(boolean value) + { + this.decayed = value; + } + + public ArrayList getInboundLinks() + { + return this.inboundLinks; + } + + public ArrayList getOutboundLinks() + { + return this.outboundLinks; + } + + public int getDistance() + { + return distance; + } + + public void setDistance(int value) + { + distance = value; + } + + public void clear() + { + partitionNode = null; + layoutNode = null; + } +}