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}