package com.kartoflane.superluminal2.core;
import java.util.ArrayList;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import com.kartoflane.superluminal2.mvc.controllers.CellController;
import com.kartoflane.superluminal2.ui.ShipContainer;
/**
* A class representing the grid, consisting of individual {@link Cell} objects.
*
* Acts as a central system for positioning various entities on the canvas.
*
* @author kartoFlane
*/
public class Grid {
public enum Snapmodes {
/** No snapping occurs. */
FREE,
/** Snaps to the center of the nearest grid cell. */
CELL,
/** Snaps to the nearest horizontal grid line. */
LINE_H,
/** Snaps to the nearest vertical grid line. */
LINE_V,
/** Snaps to the nearest intersection of grid lines (LINE_H and LINE_V). */
CROSS,
/** Snaps to the center of the nearest horizontal grid wall. */
EDGE_H,
/** Snaps to the center of the nearest vertical grid wall. */
EDGE_V,
/** Snaps to the center of the nearest grid wall (EDGE_H or EDGE_V). */
EDGES,
/** Snaps to the center of the nearest northern horizontal grid wall. */
EDGE_TOP_H,
/** Snaps to the center of the nearest western vertical grid wall. */
EDGE_TOP_V,
/** Snaps to any multiple of 17.5 (CELL or CROSS or EDGES). */
ANY,
/** Snaps to the top-left corner of the given cell. */
CORNER_TL,
/** Snaps to the top-right corner of the given cell. */
CORNER_TR,
/** Snaps to the bottom-left corner of the given cell. */
CORNER_BL,
/** Snaps to the bottom-right corner of the given cell. */
CORNER_BR;
}
private static final Grid instance = new Grid();
private boolean drawGrid = true;
private Rectangle bounds = null;
private ArrayList<ArrayList<CellController>> cells = null;
private Grid() {
cells = new ArrayList<ArrayList<CellController>>();
bounds = new Rectangle(0, 0, 0, 0);
}
public static Grid getInstance() {
return instance;
}
/**
* Updates the grid to the desired dimensions.
*
* @param w
* width of the grid, in grid cells
* @param h
* height of the grid, in grid cells
*/
public void setCells(int w, int h) {
updateBounds(w * ShipContainer.CELL_SIZE, h * ShipContainer.CELL_SIZE);
}
/**
* Updates the grid, instantiating new cells or hiding existing ones, as required
* to accomodate the new size.
*
* @param width
* new width of the grid, in pixels
* @param height
* new height of the grid, in pixels
*/
public void updateBounds(int width, int height) {
// Add one after the division so that cells fill all of the available space
bounds.width = (width / ShipContainer.CELL_SIZE + 1) * ShipContainer.CELL_SIZE;
bounds.height = (height / ShipContainer.CELL_SIZE + 1) * ShipContainer.CELL_SIZE;
for (int i = 1; i <= bounds.width / ShipContainer.CELL_SIZE; i++) {
if (cells.size() < i)
cells.add(new ArrayList<CellController>());
for (int j = 1; j <= bounds.height / ShipContainer.CELL_SIZE; j++)
if (cells.get(i - 1).size() < j)
cells.get(i - 1).add(CellController.newInstance(i - 1, j - 1));
}
for (ArrayList<CellController> list : cells) {
for (CellController c : list) {
Rectangle b = c.getBounds();
c.setVisible(bounds.contains(b.x + 1, b.y + 1) && bounds.contains(b.x + b.width - 2, b.y + b.height - 2));
}
}
}
/**
* @return the rectangle describing the grid's area relative to the canvas, in pixels.
*/
public Rectangle getBounds() {
return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
}
/**
* @return size of the grid, in pixels
*/
public Point getSize() {
return new Point(bounds.width, bounds.height);
}
public void setVisible(boolean visible) {
drawGrid = visible;
for (ArrayList<CellController> list : cells) {
for (CellController c : list)
c.redraw();
}
}
public boolean isVisible() {
return drawGrid;
}
/** @return a Cell at the given coordinates, or null if out of grid. */
public CellController getCellAt(int x, int y) {
return getCell(x / ShipContainer.CELL_SIZE, y / ShipContainer.CELL_SIZE);
}
/**
* Intended to only be used when the coordinates fall outside of the grid.
* If the coordinates are inside the grid, use {@link #getCellAt(int, int)} instead.
*/
public CellController getClosestCell(int x, int y) {
int width = bounds.width - bounds.width % ShipContainer.CELL_SIZE - ShipContainer.CELL_SIZE;
int height = bounds.height - bounds.height % ShipContainer.CELL_SIZE - ShipContainer.CELL_SIZE;
x = x < width ? x / ShipContainer.CELL_SIZE : width / ShipContainer.CELL_SIZE - 1;
y = y < height ? y / ShipContainer.CELL_SIZE : height / ShipContainer.CELL_SIZE - 1;
return getCell(x, y);
}
/**
* @return a Cell with the given position in the grid.
*/
public CellController getCell(int i, int j) {
try {
int sx = Math.min(i, cells.size() - 1);
int sy = Math.min(j, cells.get(sx).size() - 1);
return cells.get(sx).get(sy);
} catch (Exception e) {
return cells.get(0).get(0);
}
}
/**
* @return true if a visible {@link CellController} can be found at the given coordinates, false otherwise.
*/
public boolean isLocAccessible(int x, int y) {
CellController c = getCellAt(x, y);
return c != null && c.isVisible();
}
/**
* Aligns the given coordinates to the grid using the specified snapmode.
*
* @param snapmode
* the snapping method used to align the point to the grid
*
* @see Snapmodes#FREE
* @see Snapmodes#CELL
* @see Snapmodes#LINE_H
* @see Snapmodes#LINE_V
* @see Snapmodes#CROSS
* @see Snapmodes#EDGE_H
* @see Snapmodes#EDGE_V
* @see Snapmodes#EDGES
* @see Snapmodes#ANY
* @see Snapmodes#EDGE_TOP_H
* @see Snapmodes#EDGE_TOP_V
* @see Snapmodes#CORNER_TL
* @see Snapmodes#CORNER_TR
* @see Snapmodes#CORNER_BL
* @see Snapmodes#CORNER_BR
*/
public Point snapToGrid(int x, int y, Snapmodes snapmode) {
// Don't want values outside of the canvas area
x = Math.min(Math.max(x, 0), bounds.width);
y = Math.min(Math.max(y, 0), bounds.height);
Point p = new Point(x, y);
// Try to find a cell at the given coordinates
CellController c = getCellAt(x, y);
// If none is found (ie. point is outside of grid area), try to find the closest one
if (c == null || !c.isVisible())
c = getClosestCell(x, y);
switch (snapmode) {
case FREE:
break;
case CELL:
p = c.getLocation();
// Correct by 1 so that boxes appear centered
p.x--;
p.y--;
break;
case LINE_H:
if (Math.abs(c.getBounds().y - y) < Math.abs(c.getBounds().y + ShipContainer.CELL_SIZE - y))
p.y = c.getBounds().y;
else
p.y = c.getBounds().y + ShipContainer.CELL_SIZE;
break;
case LINE_V:
if (Math.abs(c.getBounds().x - x) < Math.abs(c.getBounds().x + ShipContainer.CELL_SIZE - x))
p.x = c.getBounds().x;
else
p.x = c.getBounds().x + ShipContainer.CELL_SIZE;
break;
case CROSS:
if (Math.abs(c.getBounds().x - x) < Math.abs(c.getBounds().x + ShipContainer.CELL_SIZE - x))
p.x = c.getBounds().x + 1;
else
p.x = c.getBounds().x + ShipContainer.CELL_SIZE + 1;
if (Math.abs(c.getBounds().y - y) < Math.abs(c.getBounds().y + ShipContainer.CELL_SIZE - y))
p.y = c.getBounds().y + 1;
else
p.y = c.getBounds().y + ShipContainer.CELL_SIZE + 1;
break;
case EDGE_H:
p.x = c.getLocation().x - 1;
if (Math.abs(c.getBounds().y - y) < Math.abs(c.getBounds().y + ShipContainer.CELL_SIZE - y))
p.y = c.getBounds().y + 1;
else
p.y = c.getBounds().y + ShipContainer.CELL_SIZE + 1;
break;
case EDGE_V:
p.y = c.getLocation().y - 1;
if (Math.abs(c.getBounds().x - x) < Math.abs(c.getBounds().x + ShipContainer.CELL_SIZE - x))
p.x = c.getBounds().x + 1;
else
p.x = c.getBounds().x + ShipContainer.CELL_SIZE + 1;
break;
case EDGES:
Point a = snapToGrid(x, y, Snapmodes.EDGE_H);
Point b = snapToGrid(x, y, Snapmodes.EDGE_V);
if (Math.sqrt(Math.pow(a.x - x, 2) + Math.pow(a.y - y, 2)) < Math.sqrt(Math.pow(b.x - x, 2) + Math.pow(b.y - y, 2)))
p = a;
else
p = b;
break;
case ANY:
// Round to any multiple of 17.5 (ShipContainer.CELL_SIZE / 2.0)
// Just subtract the remainder of division, and add 1 to correct
p.x += -p.x % 17.5 + 1;
p.y += -p.y % 17.5 + 1;
break;
case EDGE_TOP_H:
p.x = c.getLocation().x;
p.y = c.getBounds().y + 1;
break;
case EDGE_TOP_V:
p.x = c.getBounds().x + 1;
p.y = c.getLocation().y;
break;
case CORNER_TL:
p = c.getLocation();
p.x -= ShipContainer.CELL_SIZE / 2 + 1;
p.y -= ShipContainer.CELL_SIZE / 2 + 1;
break;
case CORNER_TR:
p = c.getLocation();
p.x += ShipContainer.CELL_SIZE / 2 + 1;
p.y -= ShipContainer.CELL_SIZE / 2 + 1;
break;
case CORNER_BL:
p = c.getLocation();
p.x -= ShipContainer.CELL_SIZE / 2 + 1;
p.y += ShipContainer.CELL_SIZE / 2 + 1;
break;
case CORNER_BR:
p = c.getLocation();
p.x += ShipContainer.CELL_SIZE / 2 + 1;
p.y += ShipContainer.CELL_SIZE / 2 + 1;
break;
default:
throw new IllegalArgumentException("Invalid snap mode.");
}
return p;
}
/**
* {@linkplain #snapToGrid(int, int, Snapmodes)}
*/
public Point snapToGrid(Point p, Snapmodes snapmode) {
return snapToGrid(p.x, p.y, snapmode);
}
/**
* Disposes of the grid, clearing all cells and lists.
*/
public void dispose() {
for (ArrayList<CellController> list : cells) {
for (CellController c : list)
c.dispose();
list.clear();
list = null;
}
cells.clear();
cells = null;
System.gc();
}
}