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