/*******************************************************************************
* 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.graphics.map.geometry;
import go.graphics.UIPoint;
import jsettlers.common.map.shapes.IMapArea;
import jsettlers.common.map.shapes.MapRectangle;
import jsettlers.common.map.shapes.Parallelogram;
import jsettlers.common.position.FloatRectangle;
import jsettlers.common.position.ShortPoint2D;
import java.awt.geom.AffineTransform;
/**
* This class converts map coordinates to e.g. draw coordinates.
* <p>
* For the conversion formulas used and what the witdh/height parameters mean, see:
* <p>
* <img src="doc-files/MapCoordinateConverter.png">
*
* @author michael
*/
public final class MapCoordinateConverter {
private static final int M_HX = 2 * 4 + 0;
private static final int M_HY = 2 * 4 + 1;
/**
* The part of the matrix that is called m00 in {@link AffineTransform}.
*/
private static final int M_00 = 4 * 0;
private static final int M_01 = 4 * 1;
private static final int M_02 = 4 * 3;
private static final int M_10 = 4 * 0 + 1;
private static final int M_11 = 4 * 1 + 1;
private static final int M_12 = 4 * 3 + 1;
private static final float HEIGHT_X_DISPLACEMENT = 0;
private static final float HEIGHT_Y_DISPLACEMENT = 2;
private float[] matrix = new float[4 * 4];
// matrix that also converts heights
private float[] heightmatrix = new float[4 * 4];
private float[] inverse = new float[4 * 4];
/**
* The width of a tile in view space.
*/
private float xscale;
/**
* The height of a tile in view space.
*/
private float yscale;
/**
* Creates a new converter.
*
* @param mapwidth
* The map width
* @param mapheight
* The map height
* @param viewwidth
* The view width
* @param viewheight
* The view height
*/
public MapCoordinateConverter(short mapwidth, short mapheight,
float viewwidth, float viewheight) {
if (mapwidth <= 1 || mapheight <= 1) {
throw new IllegalArgumentException("Map size too small");
}
if (viewwidth <= 0 || viewheight <= 0) {
throw new IllegalArgumentException("View size too small");
}
int realMapWidth = mapwidth - 1;
int realMapHeight = mapheight - 1;
this.xscale = viewwidth / realMapWidth;
this.yscale = viewheight / realMapHeight;
this.matrix[M_00] = this.xscale;
this.matrix[M_01] = -.5f * this.xscale;
this.matrix[M_02] = .5f * realMapHeight * this.xscale;
this.matrix[M_10] = 0;
this.matrix[M_11] = -this.yscale;
this.matrix[M_12] = viewheight;
this.matrix[2 + 2 * 4] = 1;
this.matrix[3 + 3 * 4] = 1;
System.arraycopy(this.matrix, 0, this.heightmatrix, 0,
this.matrix.length);
this.heightmatrix[M_HX] = HEIGHT_X_DISPLACEMENT;
this.heightmatrix[M_HY] = HEIGHT_Y_DISPLACEMENT;
this.inverse[M_00] = 1 / this.xscale;
this.inverse[M_01] = -.5f / this.yscale;
this.inverse[M_02] = 0;
this.inverse[M_10] = 0;
this.inverse[M_11] = -1 / this.yscale;
this.inverse[M_12] = realMapHeight;
this.inverse[2 + 2 * 4] = 1;
this.inverse[3 + 3 * 4] = 1;
}
/**
* Gets the x coordinate of a point in view space.
*
* @param x
* The x coordinate in map space.
* @param y
* The y coordinate in map space.
* @param height
* The height of the tile.
* @return The view x coordinate
*/
public float getViewX(float x, float y, float height) {
return x * this.heightmatrix[M_00] + y * this.heightmatrix[M_01]
+ height * this.heightmatrix[M_HX] + this.heightmatrix[M_02];
}
/**
* Gets the y coordinate of a point in view space.
*
* @param x
* The x coordinate in map space.
* @param y
* The y coordinate in map space.
* @param height
* The height of the tile.
* @return The view y coordinate
*/
public float getViewY(float x, float y, float height) {
return x * this.heightmatrix[M_10] + y * this.heightmatrix[M_11]
+ height * this.heightmatrix[M_HY] + this.heightmatrix[M_12];
}
private float getExactMapX(float x, float y) {
return x * this.inverse[M_00] + y * this.inverse[M_01]
+ this.inverse[M_02];
}
/**
* Gets the closest map coordinates of a given pixel.
*
* @param x
* The x coordinate in draw space
* @param y
* The y coordinate in draw space
* @return THe map coordinate x part
*/
public short getMapX(float x, float y) {
return (short) Math.round(getExactMapX(x, y));
}
private float getExactMapY(float x, float y) {
return x * this.inverse[M_10] + y * this.inverse[M_11]
+ this.inverse[M_12];
}
/**
* Gets the closest map coordinates of a given pixel.
*
* @param x
* The x coordinate in draw space
* @param y
* The y coordinate in draw space
* @return The map coordinate y part
*/
public short getMapY(float x, float y) {
return (short) Math.round(getExactMapY(x, y));
}
/**
* Gets the matrix for the conversion from map coordinates to screen coordinates. The matrix must not be changed.
*
* @return The opengl-compatibel matrix.
*/
public float[] getMatrix() {
return this.matrix;
}
private short roundUpShort(float f) {
return (short) Math.ceil(f);
}
/**
* Gets all tiles that are mapped to the given pixel.
*
* @param x
* the x pixel pos.
* @param y
* the y pixel pos.
* @return The area. Might be empty but not null;
*/
public IMapArea getAreaForPixel(int x, int y) {
float mapx = getExactMapX(x, y);
float mapy = getExactMapY(x, y);
float mapstartx = mapx - .5f / this.xscale;
float mapstarty = mapy - .5f / this.yscale;
float mapendx = mapx + .5f / this.xscale;
float mapendy = mapy + .5f / this.yscale;
return new Parallelogram(roundUpShort(mapstartx),
roundUpShort(mapstarty), roundUpShort(mapendx - 1),
roundUpShort(mapendy - 1));
}
/**
* Gets the position of a map point on the screen
*
* @param x
* The x coordinate
* @param y
* The y coordinate
* @return The position.
*/
public UIPoint getView(int x, int y, int height) {
return new UIPoint(getViewX(x, y, height), getViewY(x, y, height));
}
/**
* Gets the closest position on the map for a position.
*
* @param x
* The x position in draw space.
* @param y
* The y position in draw space
* @return The map point
*/
public ShortPoint2D getMap(float x, float y) {
return new ShortPoint2D(getMapX(x, y), getMapY(x, y));
}
/**
* Gets the x distance between two tile origins.
*
* @return The distance.
*/
public float getTileXDistance() {
return this.matrix[M_00];
}
/**
* Gets the y disntance between two tiles.
*
* @return The distance in y direction.
*/
public float getTileYDistance() {
return this.matrix[M_11];
}
/**
* Gets a rectangle that is almost covered by the given int rectangle.
* <p>
* No assumptions can be made about the rect.
*
* @param screen
* The screen positions
* @return A MapRectangle
*/
public MapRectangle getMapForScreen(FloatRectangle screen) {
float width = screen.getWidth() * this.inverse[M_00];
float maxMountainHeight = HEIGHT_Y_DISPLACEMENT * Byte.MAX_VALUE;
float height = -(screen.getHeight() + maxMountainHeight) * this.inverse[M_11];
float minX = getMapX(screen.getMinX(), screen.getMaxY());
float minY = getMapY(screen.getMinX(), screen.getMaxY());
return new MapRectangle((short) minX, (short) minY, (short) Math.max(Math.ceil(width), 0), (short) Math.max(Math.ceil(height + 10), 0));
}
/**
* Gets a coordinate converter by the width and the height of the map, rathe than by the final size of the draw space.
*
* @param xTileDistance
* The x distance between two tiles in the same row.
* @param yTileDistance
* The distance between two rows
* @param mapWidth
* The width of the map in tile columns.
* @param mapHeight
* The height of the map in rows.
* @return The new created converter.
*/
public static MapCoordinateConverter get(int xTileDistance,
int yTileDistance, short mapWidth, short mapHeight) {
return new MapCoordinateConverter(mapWidth, mapHeight, (mapWidth - 1)
* xTileDistance, (mapHeight - 1) * yTileDistance);
}
public float[] getMatrixWithHeight() {
return this.heightmatrix;
}
}