001package squidpony.squidgrid.mapping; 002 003import squidpony.squidgrid.FOV; 004import squidpony.squidgrid.Radius; 005import squidpony.squidmath.Coord; 006import squidpony.squidmath.GreasedRegion; 007import squidpony.squidmath.OrderedSet; 008 009/** 010 * Utility class for finding areas where game-specific terrain features might be suitable to place. 011 * Example placement for alongStraightWalls, using all regions where there's an extended straight wall in a room to 012 * place a rack of bows (as curly braces): https://gist.github.com/tommyettinger/2b69a265bd93304f091b 013 * Created by Tommy Ettinger on 3/13/2016. 014 */ 015public class Placement { 016 017 /** 018 * The RoomFinder this uses internally to find placement areas only where they are appropriate. 019 */ 020 public RoomFinder finder; 021 022 private GreasedRegion allRooms, allCorridors, allCaves, allFloors, nonRoom; 023 private OrderedSet<OrderedSet<Coord>> alongStraightWalls, 024 corners, centers; 025 private OrderedSet<Coord> hidingPlaces; 026 private Placement() 027 { 028 029 } 030 031 /** 032 * Constructs a Placement using the given RoomFinder, which will have collections of rooms, corridors, and caves. 033 * A common use case for this class involves the Placement field that is constructed in a SectionDungeonGenerator 034 * when generate() or generateRespectingStairs() in that class is called; if you use SectionDungeonGenerator, there 035 * isn't much need for this constructor, since you can normally use the one created as a field in that class. 036 * @param finder a RoomFinder that must not be null. 037 */ 038 public Placement(RoomFinder finder) 039 { 040 if(finder == null) 041 throw new UnsupportedOperationException("RoomFinder passed to Placement constructor cannot be null"); 042 043 this.finder = finder; 044 045 /* 046 allRooms = new GreasedRegion(finder.width, finder.height); 047 allCorridors = new GreasedRegion(finder.width, finder.height); 048 allCaves = new GreasedRegion(finder.width, finder.height); 049 allFloors = new GreasedRegion(finder.width, finder.height); 050 051 for(GreasedRegion region : finder.rooms.keySet()) 052 { 053 allRooms.or(region); 054 } 055 for(GreasedRegion region : finder.corridors.keySet()) 056 { 057 allCorridors.or(region); 058 } 059 for(GreasedRegion region : finder.caves.keySet()) 060 { 061 allCaves.or(region); 062 } 063 */ 064 allCorridors = finder.allCorridors; 065 allRooms = finder.allRooms; 066 allCaves = finder.allCaves; 067 allFloors = allRooms.copy().or(allCorridors).or(allCaves); 068 nonRoom = allCorridors.copy().or(allCaves).expand(2); 069 } 070 071 /** 072 * Gets an OrderedSet of OrderedSet of Coord, where each inner OrderedSet of Coord refers to a placement 073 * region along a straight wall with length 3 or more, not including corners. Each Coord refers to a single cell 074 * along the straight wall. This could be useful for placing weapon racks in armories, chalkboards in schoolrooms 075 * (tutorial missions, perhaps?), or even large paintings/murals in palaces. 076 * @return a set of sets of Coord where each set of Coord is a wall's viable placement for long things along it 077 */ 078 public OrderedSet<OrderedSet<Coord>> getAlongStraightWalls() { 079 if(alongStraightWalls == null) 080 { 081 alongStraightWalls = new OrderedSet<>(32); 082 GreasedRegion working = new GreasedRegion(finder.width, finder.height); 083 for(GreasedRegion region : finder.rooms.keySet()) { 084 working.remake(region).retract().fringe().andNot(nonRoom); 085 for (GreasedRegion sp : working.split()) { 086 if (sp.size() >= 3) 087 alongStraightWalls.add(arrayToSet(sp.asCoords())); 088 } 089 } 090 } 091 return alongStraightWalls; 092 } 093 094 /** 095 * Gets an OrderedSet of OrderedSet of Coord, where each inner OrderedSet of Coord refers to a room's 096 * corners, and each Coord is one of those corners. There are more uses for corner placement than I can list. This 097 * doesn't always identify all corners, since it only finds ones in rooms, and a cave too close to a corner can 098 * cause that corner to be ignored. 099 * @return a set of sets of Coord where each set of Coord is a room's corners 100 */ 101 public OrderedSet<OrderedSet<Coord>> getCorners() { 102 if(corners == null) 103 { 104 corners = new OrderedSet<>(32); 105 GreasedRegion working = new GreasedRegion(finder.width, finder.height); 106 for(GreasedRegion region : finder.rooms.keySet()) { 107 working.remake(region).expand().retract8way().xor(region).andNot(nonRoom); 108 OrderedSet<Coord> os = new OrderedSet<>(working.asCoords()); 109 corners.add(os); 110 } 111 } 112 return corners; 113 } 114 /** 115 * Gets an OrderedSet of OrderedSet of Coord, where each inner OrderedSet of Coord refers to a room's cells 116 * that are furthest from the walls, and each Coord is one of those central positions. There are many uses for this, 117 * like finding a position to place a throne or shrine in a large room where it should be visible from all around. 118 * This doesn't always identify all centers, since it only finds ones in rooms, and it can also find multiple 119 * central points if they are all the same distance from a wall (common in something like a 3x7 room, where it will 120 * find a 1x5 column as the centers of that room). 121 * @return a set of sets of Coord where each set of Coord contains a room's cells that are furthest from the walls. 122 */ 123 public OrderedSet<OrderedSet<Coord>> getCenters() { 124 if(centers == null) 125 { 126 centers = new OrderedSet<>(32); 127 GreasedRegion working, working2; 128 for(GreasedRegion region : finder.rooms.keySet()) { 129 130 working = null; 131 working2 = region.copy().retract(); 132 for (int i = 2; i < 7; i++) { 133 if(working2.isEmpty()) 134 break; 135 working = working2.copy(); 136 working2.retract(); 137 } 138 if(working == null) 139 continue; 140 141 //working = 142 // differencePacked( 143 // working, 144 // nonRoom); 145 centers.add(arrayToSet(working.asCoords())); 146 147 } 148 } 149 return centers; 150 } 151 152 /** 153 * Gets an OrderedSet of Coord, where each Coord is hidden (using the given radiusStrategy and range for FOV 154 * calculations) from any doorways or similar narrow choke-points where a character might be easily ambushed. If 155 * multiple choke-points can see a cell (using shadow-casting FOV, which is asymmetrical), then the cell is very 156 * unlikely to be included in the returned Coords, but if a cell is visible from one or no choke-points and is far 157 * enough away, then it is more likely to be included. 158 * @param radiusStrategy a Radius object that will be used to determine visibility. 159 * @param range the minimum distance things are expected to hide at; often related to player FOV range 160 * @return a Set of Coord where each Coord is either far away from or is concealed from a door-like area 161 */ 162 public OrderedSet<Coord> getHidingPlaces(Radius radiusStrategy, int range) { 163 if(hidingPlaces == null) 164 { 165 double[][] composite = new double[finder.width][finder.height], 166 resMap = DungeonUtility.generateResistances(finder.map), 167 temp; 168 FOV fov = new FOV(FOV.SHADOW); 169 Coord pt; 170 for (int d = 0; d < finder.connections.length; d++) { 171 pt = finder.connections[d]; 172 temp = fov.calculateFOV(resMap, pt.x, pt.y, range, radiusStrategy); 173 for (int x = 0; x < finder.width; x++) { 174 for (int y = 0; y < finder.height; y++) { 175 composite[x][y] += temp[x][y] * temp[x][y]; 176 } 177 } 178 } 179 180 hidingPlaces = arrayToSet(new GreasedRegion(composite, 0.25).and(allFloors).asCoords()); 181 } 182 return hidingPlaces; 183 } 184 185 private static OrderedSet<Coord> arrayToSet(Coord[] arr) 186 { 187 return new OrderedSet<> (arr); 188 } 189}