/*******************************************************************************
* Copyright (c) 2015
*
* 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.common.map.shapes;
import jsettlers.common.position.RelativePoint;
import jsettlers.common.position.SRectangle;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.common.utils.coordinates.CoordinateStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* This class gives a fast lookup (in O(1)) for contains if a MapArea is given by a list of n positions.<br>
* This class should only be used if the given positions are NOT distributed over big parts of the map. They should be positioned quite close to each
* other.
* <p />
* The iterator is able to remove positions from the area!
*
* @author Andreas Eberle
*/
public final class FreeMapArea implements IMapArea {
private static final long serialVersionUID = 6331090134655931952L;
private final List<ShortPoint2D> positions;
private final int xOffset;
private final int yOffset;
private final boolean[][] areaMap;
private final int width;
private final int height;
private final ShortPoint2D upperLeftPosition;
/**
* @param positions
* the positions this map area will contain.
*/
public FreeMapArea(List<ShortPoint2D> positions) {
assert positions.size() > 0 : "positions must contain at least one value!!";
this.positions = positions;
SRectangle bounds = getBounds(positions);
this.xOffset = bounds.xMin;
this.yOffset = bounds.yMin;
this.width = bounds.getWidth() + 1;
this.height = bounds.getHeight() + 1;
this.areaMap = new boolean[width][height];
this.upperLeftPosition = setPositionsToMap(areaMap, positions);
}
/**
*
* @param positions
* @param minX
* Minimum x value in the list of positions.
* @param minY
* Minimum y value in the list of positions.
* @param width
* minX + width -1 is the maximum x value in the list of positions.
* @param height
* minY + height -1 is the maximum y value in the list of positions.
*/
public FreeMapArea(List<ShortPoint2D> positions, int minX, int minY, int width, int height) {
assert positions.size() > 0 : "positions must contain at least one value!!";
this.positions = positions;
this.xOffset = minX;
this.yOffset = minY;
this.width = width;
this.height = height;
this.areaMap = new boolean[width][height];
this.upperLeftPosition = setPositionsToMap(areaMap, positions);
}
/**
* Creates a free map area by converting the relative points to absolute ones.
*
* @param pos
* The origin for the relative points
* @param relativePoints
* The relative points
*/
public FreeMapArea(ShortPoint2D pos, RelativePoint[] relativePoints) {
this(convertRelative(pos, relativePoints));
}
private final static ArrayList<ShortPoint2D> convertRelative(ShortPoint2D pos, RelativePoint[] relativePoints) {
ArrayList<ShortPoint2D> list = new ArrayList<ShortPoint2D>();
for (RelativePoint relative : relativePoints) {
list.add(relative.calculatePoint(pos));
}
return list;
}
/**
* Sets the positions to the map and returns the upper left position
*
* @param areaMap
* @param positions
* @return
*/
private final ShortPoint2D setPositionsToMap(boolean[][] areaMap, List<ShortPoint2D> positions) {
if (positions.isEmpty()) {
return null;
}
ShortPoint2D upperLeft = positions.get(0);
for (ShortPoint2D curr : positions) {
areaMap[getMapX(curr)][getMapY(curr)] = true;
if (curr.y < upperLeft.y || curr.y == upperLeft.y && curr.x < upperLeft.x) {
upperLeft = curr;
}
}
return upperLeft;
}
final int getMapY(ShortPoint2D pos) {
return pos.y - yOffset;
}
final int getMapX(ShortPoint2D pos) {
return pos.x - xOffset;
}
private final SRectangle getBounds(List<ShortPoint2D> positions) {
short xMin = Short.MAX_VALUE, xMax = 0, yMin = Short.MAX_VALUE, yMax = 0;
for (ShortPoint2D curr : positions) {
short x = curr.x;
short y = curr.y;
if (x < xMin)
xMin = x;
if (x > xMax)
xMax = x;
if (y < yMin)
yMin = y;
if (y > yMax)
yMax = y;
}
return new SRectangle(xMin, yMin, xMax, yMax);
}
@Override
public final boolean contains(ShortPoint2D pos) {
return contains(pos.x, pos.y);
}
public boolean contains(int x, int y) {
int dx = x - xOffset;
int dy = y - yOffset;
return isValidMapPos(dx, dy) && areaMap[dx][dy];
}
private final boolean isValidMapPos(int dx, int dy) {
return dx >= 0 && dy >= 0 && dx < width && dy < height;
}
@Override
public final Iterator<ShortPoint2D> iterator() {
return new FreeMapAreaIterator(this);
}
public final int size() {
return positions.size();
}
public final ShortPoint2D get(int i) {
return positions.get(i);
}
public final boolean isEmpty() {
return positions.isEmpty();
}
final void setPosition(ShortPoint2D pos, boolean value) {
areaMap[getMapX(pos)][getMapY(pos)] = value;
}
public ShortPoint2D getUpperLeftPosition() {
return upperLeftPosition;
}
private final static class FreeMapAreaIterator implements Iterator<ShortPoint2D> {
private final FreeMapArea freeMapArea;
private final Iterator<ShortPoint2D> iterator;
private ShortPoint2D currPos;
public FreeMapAreaIterator(FreeMapArea freeMapArea) {
this.freeMapArea = freeMapArea;
this.iterator = freeMapArea.positions.iterator();
}
@Override
public final boolean hasNext() {
return iterator.hasNext();
}
@Override
public final ShortPoint2D next() {
currPos = iterator.next();
return currPos;
}
@Override
public final void remove() {
iterator.remove();
freeMapArea.setPosition(currPos, false);
}
}
@Override
public CoordinateStream stream() {
return CoordinateStream.fromList(positions);
}
}