/*******************************************************************************
* Copyright (c) 2015 - 2017
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package jsettlers.algorithms.partitions;
import jsettlers.common.movable.EDirection;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.common.utils.coordinates.CoordinateStream;
import java.util.BitSet;
/**
* An algorithm to calculate partitions for a given set of positions.
*
* @author Andreas Eberle
*
*/
public final class PartitionCalculatorAlgorithm {
public static final short NO_PARTITION = 0;
public static final short BLOCKED_PARTITION = 1;
public static final short NUMBER_OF_RESERVED_PARTITIONS = 2;
private static final int NUMBER_OF_START_PARTITIONS = 1000;
private static final int[] neighborX = { EDirection.WEST.gridDeltaX, EDirection.NORTH_WEST.gridDeltaX, EDirection.NORTH_EAST.gridDeltaX };
private static final int[] neighborY = { EDirection.WEST.gridDeltaY, EDirection.NORTH_WEST.gridDeltaY, EDirection.NORTH_EAST.gridDeltaY };
private static final int INCREASE_FACTOR = 2;
private final int minX;
private final int minY;
private final int width;
private final int height;
private final BitSet containing;
private final short[] partitionsGrid;
private final IBlockingProvider blockingProvider;
private short[] partitions = new short[NUMBER_OF_START_PARTITIONS];
private ShortPoint2D[] partitionBorderPositions = new ShortPoint2D[NUMBER_OF_START_PARTITIONS];
private short nextFreePartition = NUMBER_OF_RESERVED_PARTITIONS;
private short neededPartitions;
/**
* Creates a new {@link PartitionCalculatorAlgorithm}. The given positions are positions in the created partitions. Non mentioned positions are
* seen as outside of partitions.
*
* @param positions
* The positions of the calculated partitions.
* @param blockingProvider
* Provides the information if a position is blocked or not.
* @param minX
* The smallest x coordinate in the list of positions.
* @param minY
* The smallest y coordinate in the list of positions.
* @param maxX
* The biggest x coordinate in the list of positions.
* @param maxY
* The biggest y coordinate in the list of positions.
*/
public PartitionCalculatorAlgorithm(CoordinateStream positions, IBlockingProvider blockingProvider, int minX, int minY, int maxX, int maxY) {
this.minX = --minX; // this increases the window, so that no position can lay on the border.
this.minY = --minY;
maxX++;
maxY++;
this.width = maxX - minX + 1;
this.height = maxY - minY + 1;
this.blockingProvider = blockingProvider;
this.containing = new BitSet(width * height);
positions.forEach((x, y) -> containing.set((x - this.minX) + (y - this.minY) * width));
this.partitionsGrid = new short[width * height];
this.partitions[BLOCKED_PARTITION] = BLOCKED_PARTITION;
}
/**
* Creates a new {@link PartitionCalculatorAlgorithm}. The given {@link BitSet} defines the positions that need to be in the partitions and the
* ones that mustn't.
*
* @param minX
* The x offset of the {@link BitSet}.
* @param minY
* The y offset of the {@link BitSet}.
* @param width
* The width of the grid defined by the {@link BitSet}.
* @param height
* The height of the grid defined by the {@link BitSet}.
* @param containing
* The {@link BitSet} defining the positions in the partitions and the ones not. <br>
* NOTE: The {@link BitSet} must be indexed with x + y * width
* @param blockingProvider
* Provides the information if a position is blocked or not.
*/
public PartitionCalculatorAlgorithm(int minX, int minY, int width, int height, BitSet containing, IBlockingProvider blockingProvider) {
this.minX = minX;
this.minY = minY;
this.width = width;
this.height = height;
this.containing = containing;
this.blockingProvider = blockingProvider;
this.partitionsGrid = new short[width * height];
this.partitions[BLOCKED_PARTITION] = BLOCKED_PARTITION;
}
/**
* Calculates the partitions. <br>
* The results can be accessed with the supplied getter methods.
*/
public void calculatePartitions() {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index = x + y * width;
if (containing.get(index)) {
if (blockingProvider.isBlocked(minX + x, minY + y)) {
partitionsGrid[index] = BLOCKED_PARTITION;
continue;
}
int westX = x + neighborX[0];
int westY = y + neighborY[0];
int northWestX = x + neighborX[1];
int northWestY = y + neighborY[1];
int northEastX = x + neighborX[2];
int northEastY = y + neighborY[2];
int partition = -1;
int westPartition = -1;
int northEastPartition = -1;
if (containing.get(westX + westY * width)) {
short currPartition = partitions[partitionsGrid[westX + westY * width]];
if (currPartition != BLOCKED_PARTITION) {
westPartition = currPartition;
partition = currPartition;
}
}
if (containing.get(northWestX + northWestY * width)) {
short currPartition = partitions[partitionsGrid[northWestX + northWestY * width]];
if (currPartition != BLOCKED_PARTITION) {
partition = currPartition;
}
}
if (containing.get(northEastX + northEastY * width)) {
short currPartition = partitions[partitionsGrid[northEastX + northEastY * width]];
if (currPartition != BLOCKED_PARTITION) {
northEastPartition = currPartition;
partition = currPartition;
}
}
if (westPartition != -1 && northEastPartition != -1 && partitions[westPartition] != partitions[northEastPartition]) {
// mergePartitions if west and northeast are not equal, not blocked but already set
short newPartition = (short) Math.min(partitions[westPartition], partitions[northEastPartition]);
partitions[westPartition] = newPartition;
partitions[northEastPartition] = newPartition;
partitionsGrid[index] = newPartition;
} else if (partition != -1) { // just set the value.
partitionsGrid[index] = partitions[partition];
} else { // create a new partition
partitionsGrid[index] = createNewPartition(y, x);
}
}
}
}
// post processing
normalizePartitions();
}
private short createNewPartition(int y, int x) {
short newPartition = nextFreePartition;
partitions[newPartition] = newPartition;
partitionBorderPositions[newPartition] = new ShortPoint2D(minX + x, minY + y);
nextFreePartition++;
if (nextFreePartition >= partitions.length) {
increasePartitionArraySize();
}
return newPartition;
}
/**
* Normalizes the partitions and compacts them.
*/
private void normalizePartitions() {
short[] compacted = new short[nextFreePartition];
compacted[NO_PARTITION] = NO_PARTITION;
compacted[BLOCKED_PARTITION] = BLOCKED_PARTITION;
short compactedCount = NUMBER_OF_RESERVED_PARTITIONS;
for (short i = NUMBER_OF_RESERVED_PARTITIONS; i < nextFreePartition; i++) {
short representative = i;
short nextRep;
while (representative != (nextRep = partitions[representative])) {
representative = nextRep;
}
if (compacted[representative] == 0) {
short newPartitionId = compactedCount++;
compacted[representative] = newPartitionId;
partitionBorderPositions[newPartitionId] = partitionBorderPositions[representative];
}
partitions[i] = representative;
compacted[i] = compacted[representative];
}
partitions = compacted;
neededPartitions = compactedCount;
}
private void increasePartitionArraySize() {
short[] oldPartitions = partitions;
partitions = new short[oldPartitions.length * INCREASE_FACTOR];
System.arraycopy(oldPartitions, 0, partitions, 0, oldPartitions.length);
ShortPoint2D[] oldBorderPositions = partitionBorderPositions;
partitionBorderPositions = new ShortPoint2D[oldBorderPositions.length * INCREASE_FACTOR];
System.arraycopy(oldBorderPositions, 0, partitionBorderPositions, 0, oldBorderPositions.length);
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public short getPartitionAt(int x, int y) {
return partitions[partitionsGrid[x + y * width]];
}
public int getMinX() {
return minX;
}
public int getMinY() {
return minY;
}
public int getNumberOfPartitions() {
return neededPartitions;
}
public ShortPoint2D getPartitionBorderPos(int partitionIdx) {
return partitionBorderPositions[partitionIdx];
}
}