/*******************************************************************************
* 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;
import java.util.Hashtable;
import go.graphics.UIPoint;
import jsettlers.common.position.FloatRectangle;
/**
* This class defines and handles the position of the map view.
*
* @author Michael Zangl
*/
public class ScreenPosition {
/**
* Minimum zoom allowed
*/
private static final float MINIMUM_ZOOM = .2f;
/**
* Maximum zoom allowed
*/
private static final float MAXIMUM_ZOOM = 3f;
private static final int TOPBORDER = 100;
private FloatRectangle screen = new FloatRectangle(0, 0, 1, 1);
/**
* zoom factor. The smaller the smaller the settlers get.
*/
private float zoom = 1;
private float oldZoom = 1;
/**
* the pointing position when zooming
*/
private UIPoint pointer = null;
/**
* The x coordinate of the current screen, without extra panning.
*/
private float screenCenterX;
private float screenCenterY;
private final Hashtable<Object, UIPoint> panProgresses = new Hashtable<Object, UIPoint>();
private final int mapWidth;
private final int mapHeight;
private final float incline;
/**
* Sets the map size, the max border, without the automatically added additional border.
*
* @param mapWidth
* The map width in pixel on the screen.
* @param mapHeight
* The map height in pixel.
* @param incline
* The incline of the parallelogram side.
*/
public ScreenPosition(int mapWidth, int mapHeight, float incline) {
this.mapWidth = mapWidth;
this.mapHeight = mapHeight;
this.incline = incline;
}
private static float clamp(float min, float max, float value) {
if (min > max) {
return (min + max) / 2;
} else if (value < min) {
return min;
} else if (value > max) {
return max;
} else {
return value;
}
}
/**
* Sets the size of the screen without the zoom level applied.
* When called for zooming, screen at pointer stands still.
*
* @param newWidth
* The width.
* @param newHeight
* The height.
*/
public void setSize(float newWidth, float newHeight) {
if (newHeight / zoom > mapHeight + TOPBORDER) {
zoom = newHeight / (mapHeight + TOPBORDER);
}
float x = screen.getCenterX();
float y = screen.getCenterY();
if (pointer != null) {
float w = screen.getWidth();
float h = screen.getHeight();
float px = (float) pointer.getX();
float py = (float) pointer.getY();
x -= w / 2 - px / oldZoom + px / zoom - w / 2 * oldZoom / zoom;
y -= h / 2 - py / oldZoom + py / zoom - h / 2 * oldZoom / zoom;
screenCenterX = x;
screenCenterY = y;
}
setScreen(x, y, newWidth / zoom, newHeight / zoom);
}
/**
* Set the new zoom factor.
*
* @param newZoom
* The new zoom factor. It is automatically clamped.
*/
public void setZoom(float newZoom, UIPoint pointingPosition) {
oldZoom = zoom;
zoom = clamp(MINIMUM_ZOOM, MAXIMUM_ZOOM, newZoom);
pointer = pointingPosition;
}
/**
* Sets the center of the screen.
*
* @param x
* X in pixels.
* @param y
* Y in pixels.
*/
public synchronized void setScreenCenter(float x, float y) {
this.screenCenterX = x;
this.screenCenterY = y;
recalculateScreen();
}
/**
* Gets the current center of the screen.
*
* @return The x coordinate of the center. This also includes any ongoing pan operations.
*/
public float getScreenCenterX() {
return this.screen.getCenterX();
}
/**
* Gets the current center of the screen.
*
* @return The y coordinate of the center. This also includes any ongoing pan operations.
*/
public float getScreenCenterY() {
return screenCenterY;
}
/**
* Recalculates the x and y position of the screen by the current pan values.
*/
private void recalculateScreen() {
float x = this.screenCenterX;
float y = this.screenCenterY;
int xoffset = 0;
int yoffset = 0;
for (UIPoint p : this.panProgresses.values()) {
xoffset += p.getX() / zoom;
yoffset += p.getY() / zoom;
}
setScreen(x - xoffset, y - yoffset, this.screen.getWidth(),
this.screen.getHeight());
this.screenCenterX = this.screen.getCenterX() + xoffset;
this.screenCenterY = this.screen.getCenterY() + yoffset;
}
/**
* Sets the screen, and clamps it.
*
* @param centerx
* Screen center, in pixel.
* @param centery
* Screen center, in pixel.
* @param newWidth
* Screen width in pixel
* @param newHeight
* Screen height in pixel
*/
private void setScreen(float centerx, float centery, float newWidth,
float newHeight) {
// clamp to top and bottom
// top = height in px.
int top = this.mapHeight + TOPBORDER;
int bottom = 0;
float newCenterY = clamp(bottom + newHeight / 2, top - newHeight / 2, centery);
float miny = newCenterY - newHeight / 2;
float maxy = miny + newHeight;
// calculate left/right according to current y pos.
int left = (int) (this.incline * miny);
int right = (int) (this.incline * maxy) + this.mapWidth;
float newCenterX = clamp(left + newWidth / 2, right - newWidth / 2, centerx);
float minx = newCenterX - newWidth / 2;
float maxx = minx + newWidth;
this.screen = new FloatRectangle(minx, miny, maxx, maxy);
}
public float getBottom() {
return this.screen.getMinY();
}
public float getTop() {
return this.screen.getMaxY();
}
public float getLeft() {
return this.screen.getMinX();
}
public float getRight() {
return this.screen.getMaxX();
}
public float getWidth() {
return this.screen.getWidth();
}
public float getHeight() {
return this.screen.getHeight();
}
public float getZoom() {
return zoom;
}
/**
* Sets the temporary pan progress for a given pan operation.
*
* @param key
* The identifier of the event
* @param distance
* The distance we panned.
*/
public synchronized void setPanProgress(Object key, UIPoint distance) {
this.panProgresses.put(key, distance);
recalculateScreen();
}
/**
* Sets the temporary pan progress for a given pan operation.
*
* @param key
* The identifier of the event
* @param distance
* The actual distance when the event ended.
*/
public synchronized void finishPanProgress(Object key, UIPoint distance) {
this.panProgresses.remove(key);
setScreenCenter((int) (this.screenCenterX - distance.getX() / zoom),
(int) (this.screenCenterY - distance.getY() / zoom));
}
public FloatRectangle getPosition() {
return this.screen;
}
}