/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package illarion.mapedit.processing;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TIntIntHashMap;
import illarion.common.graphics.TileInfo;
import illarion.common.types.Direction;
import illarion.common.types.ServerCoordinate;
import illarion.mapedit.data.Map;
import illarion.mapedit.data.MapTile;
import illarion.mapedit.data.MapTile.MapTileFactory;
import illarion.mapedit.resource.Overlay;
import illarion.mapedit.resource.loaders.OverlayLoader;
import javax.annotation.Nonnull;
import java.util.EnumMap;
import java.util.Map.Entry;
/**
* This class is used to calculate the proper overlays to be placed.
*
* @author Martin Karing
* @since 0.99
*/
public final class MapTransitions {
/**
* The singleton instance of this class.
*/
private static final MapTransitions INSTANCE = new MapTransitions();
/**
* List of the amount of tiles found of each type.
*/
private final TIntIntHashMap analysedTiles = new TIntIntHashMap();
/**
* Helper list that stores the references to the tiles around the checked
* tile.
*/
@Nonnull
private final java.util.Map<Direction, MapTile> checkTiles;
/**
* List of the found tiles.
*/
private final TIntList foundTiles = new TIntArrayList();
/**
* This list stores the definitions for all masks avaiable. The content of
* the list is build up at the first usage of this class.
*/
private final int[] transitions = new int[28];
/**
* Private constructor that prepares the required values and ensures that no
* instance but the singleton instance is created.
*/
private MapTransitions() {
checkTiles = new EnumMap<>(Direction.class);
/*
* Build up the transions list. The definion values store the locations
* around the actual tile where the tile with the largest layer value is
* located. Values start at the north location with bit 0 and go around
* the center tile clockwise.
*/
// +-+-+-+
// |7|0|1|
// +-+-+-+
// |6|C|2|
// +-+-+-+
// |5|4|3|
// +-+-+-+
// Transition 0 - Tiles at 0 and 7
transitions[0] = 1; // 1 << 0
transitions[0] |= 1 << 7;
// Transition 1 - Tiles at 0 and 1
transitions[1] = 1; // 1 << 0
transitions[1] |= 1 << 1;
// Transition 2 - Tiles at 6 and 7
transitions[2] = 1 << 6;
transitions[2] |= 1 << 7;
// Transition 3 - Tiles at 5 and 6
transitions[3] = 1 << 5;
transitions[3] |= 1 << 6;
// Transition 4 - Tiles at 4 and 5
transitions[4] = 1 << 4;
transitions[4] |= 1 << 5;
// Transition 5 - Tiles at 3 and 4
transitions[5] = 1 << 3;
transitions[5] |= 1 << 4;
// Transition 6 - Tiles at 1 and 2
transitions[6] = 1 << 1;
transitions[6] |= 1 << 2;
// Transition 7 - Tiles at 2 and 3
transitions[7] = 1 << 2;
transitions[7] |= 1 << 3;
// Transition 8 - Tiles at 0, 1 and 7
transitions[8] = 1; // 1 << 0
transitions[8] |= 1 << 1;
transitions[8] |= 1 << 7;
// Transition 9 - Tiles at 5, 6 and 7
transitions[9] = 1 << 5;
transitions[9] |= 1 << 6;
transitions[9] |= 1 << 7;
// Transition 10 - Tiles at 3, 4 and 5
transitions[10] = 1 << 3;
transitions[10] |= 1 << 4;
transitions[10] |= 1 << 5;
// Transition 11 - Tiles at 1, 2 and 3
transitions[11] = 1 << 1;
transitions[11] |= 1 << 2;
transitions[11] |= 1 << 3;
// Transition 12 - Tiles at 0, 6 and 7
transitions[12] = 1; // 1 << 0
transitions[12] |= 1 << 6;
transitions[12] |= 1 << 7;
// Transition 13 - Tiles at 0, 1 and 2
transitions[13] = 1; // 1 << 0
transitions[13] |= 1 << 1;
transitions[13] |= 1 << 2;
// Transition 14 - Tiles at 4, 5 and 6
transitions[14] = 1 << 4;
transitions[14] |= 1 << 5;
transitions[14] |= 1 << 6;
// Transition 15 - Tiles at 2, 3 and 4
transitions[15] = 1 << 2;
transitions[15] |= 1 << 3;
transitions[15] |= 1 << 4;
// Transition 16 - Tiles at 0, 1, 6 and 7
transitions[16] = 1; // 1 << 0
transitions[16] |= 1 << 1;
transitions[16] |= 1 << 6;
transitions[16] |= 1 << 7;
// Transition 17 - Tiles at 0, 1, 2 and 7
transitions[17] = 1; // 1 << 0
transitions[17] |= 1 << 1;
transitions[17] |= 1 << 2;
transitions[17] |= 1 << 7;
// Transition 18 - Tiles at 0, 5, 6 and 7
transitions[18] = 1; // 1 << 0
transitions[18] |= 1 << 5;
transitions[18] |= 1 << 6;
transitions[18] |= 1 << 7;
// Transition 19 - Tiles at 4, 5, 6 and 7
transitions[19] = 1 << 4;
transitions[19] |= 1 << 5;
transitions[19] |= 1 << 6;
transitions[19] |= 1 << 7;
// Transition 20 - Tiles at 2, 3, 4 and 5
transitions[20] = 1 << 2;
transitions[20] |= 1 << 3;
transitions[20] |= 1 << 4;
transitions[20] |= 1 << 5;
// Transition 21 - Tiles at 3, 4, 5 and 6
transitions[21] = 1 << 3;
transitions[21] |= 1 << 4;
transitions[21] |= 1 << 5;
transitions[21] |= 1 << 6;
// Transition 22 - Tiles at 0, 1, 2 and 3
transitions[22] = 1; // 1 << 0
transitions[22] |= 1 << 1;
transitions[22] |= 1 << 2;
transitions[22] |= 1 << 3;
// Transition 23 - Tiles at 1, 2, 3 and 4
transitions[23] = 1 << 1;
transitions[23] |= 1 << 2;
transitions[23] |= 1 << 3;
transitions[23] |= 1 << 4;
// Transition 24 - Tiles at 0, 1, 5, 6 and 7
transitions[24] = 1; // 1 << 0
transitions[24] |= 1 << 1;
transitions[24] |= 1 << 5;
transitions[24] |= 1 << 6;
transitions[24] |= 1 << 7;
// Transition 25 - Tiles at 0, 1, 2, 3 and 7
transitions[25] = 1; // 1 << 0
transitions[25] |= 1 << 1;
transitions[25] |= 1 << 2;
transitions[25] |= 1 << 3;
transitions[25] |= 1 << 7;
// Transition 26 - Tiles at 3, 4, 5, 6 and 7
transitions[26] = 1 << 3;
transitions[26] |= 1 << 4;
transitions[26] |= 1 << 5;
transitions[26] |= 1 << 6;
transitions[26] |= 1 << 7;
// Transition 27 - Tiles at 1, 2, 3, 4 and 5
transitions[27] = 1 << 1;
transitions[27] |= 1 << 2;
transitions[27] |= 1 << 3;
transitions[27] |= 1 << 4;
transitions[27] |= 1 << 5;
}
/**
* Get the instance of the map transitions utility.
*
* @return the singleton instance of this class
*/
@Nonnull
public static MapTransitions getInstance() {
return INSTANCE;
}
/**
* Check a single tile for the need of transitions.
*
* @param loc the location of the tile to check
*/
public void checkTile(@Nonnull Map map, @Nonnull ServerCoordinate loc/*, final GroupAction history*/) {
placeTransition(map, loc/*, history*/);
}
/**
* Check the tile at the location and all 8 tiles around it also.
*
* @param loc the location to check
*/
public void checkTileAndSurround(
@Nonnull Map map,
@Nonnull ServerCoordinate loc/*, final GroupAction history*/) {
checkTile(map, loc);
//noinspection ConstantConditions
for (Direction dir : Direction.values()) {
checkTile(map, new ServerCoordinate(loc, dir));
}
}
/**
* Checks all tiles in the map.
* WARNING: This may be very slow.
*
* @param map
*/
public void checkMap(@Nonnull Map map) {
for (int x = 0; x < map.getWidth(); x++) {
for (int y = 0; y < map.getHeight(); y++) {
checkTile(map, new ServerCoordinate(x, y, 0));
}
}
}
/**
* Check the found tiles and generate a list with all tile IDs that occur at
* least 2 times around the center tile. The IDs of the found tiles are
* stored in {@link #foundTiles} then.
*/
private void analyseTiles() {
analysedTiles.clear();
foundTiles.clear();
for (MapTile tile : checkTiles.values()) {
int tileId = TileInfo.getBaseID(tile.getId());
if (analysedTiles.contains(tileId)) {
analysedTiles.put(tileId, analysedTiles.get(tileId) + 1);
} else {
analysedTiles.put(tileId, 1);
foundTiles.add(tileId);
}
}
if (foundTiles.isEmpty()) {
return;
}
int length = foundTiles.size();
for (int i = 0; i < length; i++) {
int tileId = foundTiles.get(i);
if (analysedTiles.get(tileId) < 2) {
foundTiles.removeAt(i);
analysedTiles.remove(tileId);
length--;
i--;
}
}
}
/**
* Create the mask for one tile ID.
*
* @param id the ID of the tile
* @return the mask for a shape for this ID
*/
private int buildMask(int id) {
int mask = 0;
for (Entry<Direction, MapTile> entry : checkTiles.entrySet()) {
MapTile tile = entry.getValue();
if ((tile != null) && (tile.getId() == id)) {
mask |= 1 << entry.getKey().getServerId();
}
}
return mask;
}
/**
* Clean up the tiles list and remove all tiles that equal the ID of the
* tile in the center or that have a lower layer then the tile in the
* center. Also all tiles with ID 0 or a ID over 31 are removed.
*
* @param centerTileID the ID of the tile in the center
*/
private void cleanupTiles(int centerTileID) {
int centerLayer = 0;
Overlay ovl = OverlayLoader.getInstance().getOverlayFromId(centerTileID);
if (ovl != null) {
centerLayer = ovl.getLayer();
}
for (Direction dir : Direction.values()) {
MapTile tile = checkTiles.get(dir);
if (tile != null) {
int tileId = TileInfo.getBaseID(tile.getId());
if ((tileId == 0) || (tileId > 31) || (tileId == centerTileID)) {
checkTiles.remove(dir);
continue;
}
Overlay tileOvl = OverlayLoader.getInstance().getOverlayFromId(tileId);
if (tileOvl == null) {
checkTiles.remove(dir);
continue;
}
if (tileOvl.getLayer() <= centerLayer) {
checkTiles.remove(dir);
}
}
}
}
/**
* Find the tile with the largest layer and remove it from the list.
*
* @return the tile ID of the tile with the largest layer that was found
*/
private int findAndRemoveHighestLayer() {
int length = foundTiles.size();
int largestOffset = 0;
int largestLayer = 0;
int largestID = 0;
for (int i = 0; i < length; i++) {
int tileId = foundTiles.get(i);
Overlay tileOvl = OverlayLoader.getInstance().getOverlayFromId(tileId);
if (tileOvl.getLayer() > largestLayer) {
largestLayer = tileOvl.getLayer();
largestOffset = i;
largestID = tileOvl.getTileID();
}
}
foundTiles.removeAt(largestOffset);
return largestID;
}
/**
* Find the largest shake that fits the mask.
*
* @param mask the mask value
* @return the ID of the shape that fits the mask
*/
private int findMask(int mask) {
for (int i = transitions.length - 1; i >= 0; i--) {
if (transitions[i] == mask) {
return i;
}
}
return -1;
}
/**
* Place a transition at one tile in case its needed.
*
* @param loc the location where a transition could be placed
*/
private void placeTransition(@Nonnull Map map, @Nonnull ServerCoordinate loc/*, final GroupAction history*/) {
MapTile centerTile = map.getTileAt(loc);
if (centerTile == null) {
return;
}
int centerTileId = TileInfo.getBaseID(centerTile.getId());
if ((centerTileId == 0) || (centerTileId > 31)) {
return;
}
// at this point we have a tile that could possibly get a overlay
populateTiles(map, loc);
cleanupTiles(centerTileId);
analyseTiles();
while (!foundTiles.isEmpty()) {
int testId = findAndRemoveHighestLayer();
int mask = buildMask(testId);
int maskId = findMask(mask);
if (maskId == -1) {
continue;
}
MapTile newTile = MapTileFactory.setOverlay(centerTileId, testId, maskId + 1, centerTile);
//history.addAction(new TileIDChangedAction(loc.getScX(), loc.getScY(), map.getTileAt(loc), newTile, map));
map.setTileAt(loc, newTile);
return;
}
MapTile newTile = MapTileFactory.setId(centerTileId, centerTile);
//history.addAction(new TileIDChangedAction(loc.getScX(), loc.getScY(), map.getTileAt(loc), newTile, map));
map.setTileAt(loc, newTile);
}
/**
* Fill the {@link #checkTiles} array with the 8 tiles around the center
* location.
*
* @param centerLoc the center location
*/
private void populateTiles(@Nonnull Map map, @Nonnull ServerCoordinate centerLoc) {
//noinspection ConstantConditions
for (Direction dir : Direction.values()) {
MapTile tile = map.getTileAt(centerLoc.getX() + dir.getDirectionVectorX(),
centerLoc.getY() + dir.getDirectionVectorY());
if (tile == null) {
checkTiles.remove(dir);
} else {
checkTiles.put(dir, tile);
}
}
}
}