/*
* Copyright (c) 2014 tabletoptool.com team.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* rptools.com team - initial implementation
* tabletoptool.com team - further development
*/
package com.t3.macro.api.functions;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import com.t3.client.AppPreferences;
import com.t3.client.TabletopTool;
import com.t3.client.ui.zone.ZoneRenderer;
import com.t3.client.walker.WalkerMetric;
import com.t3.client.walker.ZoneWalker;
import com.t3.macro.api.views.TokenView;
import com.t3.model.CellPoint;
import com.t3.model.Path;
import com.t3.model.Zone;
import com.t3.model.ZonePoint;
import com.t3.model.grid.Grid;
import com.t3.util.math.IntLine;
import com.t3.util.math.IntPoint;
public class PathFunctions {
/**
* This method calculates the cost of a given path
* @param path the path of which you want the cost
* @param followGrid if the cost should be calculated following the grid cells
* @return the cost of the path
*/
public double getCost(List<IntPoint> path, boolean followGrid) {
ZoneWalker walker = null;
WalkerMetric metric = TabletopTool.isPersonalServer() ?
AppPreferences.getMovementMetric() : TabletopTool.getServerPolicy().getMovementMetric();
ZoneRenderer zr = TabletopTool.getFrame().getCurrentZoneRenderer();
Zone zone = zr.getZone();
Grid grid = zone.getGrid();
Path<ZonePoint> gridlessPath;
/*
* Lee: causes an NPE when used on a newly dropped token. While a true solution would probably be to create a
* "path" based on the token's coords when it is dropped on the map, the easy out here would be to just return a
* "0".
*
* Final Edit: attempting to create a default path for new drops had undesirable effects. Therefore, let's opt
* for the easy fix
*/
if(path==null)
return 0;
IntPoint p=path.get(0);
if (followGrid && grid.getCapabilities().isSnapToGridSupported()) {
if (zone.getGrid().getCapabilities().isPathingSupported()) {
List<CellPoint> cplist = new ArrayList<CellPoint>();
walker = grid.createZoneWalker();
walker.replaceLastWaypoint(new CellPoint(p.getX(), p.getY()));
for (IntPoint point : path) {
CellPoint tokenPoint = new CellPoint(point.getX(), point.getY());
//walker.setWaypoints(tokenPoint);
walker.replaceLastWaypoint(tokenPoint);
cplist.add(tokenPoint);
}
return calculateGridDistance(cplist, zone.getUnitsPerCell(), metric);
}
} else {
gridlessPath = new Path<ZonePoint>();
for (IntPoint point : path) {
gridlessPath.addPathCell(new ZonePoint(point.getX(), point.getY()));
}
double c = 0;
ZonePoint lastPoint = null;
for (ZonePoint zp : gridlessPath.getCellPath()) {
if (lastPoint == null) {
lastPoint = zp;
continue;
}
int a = lastPoint.x - zp.x;
int b = lastPoint.y - zp.y;
c += Math.hypot(a, b);
lastPoint = zp;
}
c /= zone.getGrid().getSize(); // Number of "cells"
c *= zone.getUnitsPerCell(); // "actual" distance traveled
return c;
}
return -1;
}
private int calculateGridDistance(List<CellPoint> path, int feetPerCell, WalkerMetric metric) {
if (path == null || path.size() == 0)
return 0;
final int feetDistance;
{
int numDiag = 0;
int numStrt = 0;
CellPoint previousPoint = null;
for (CellPoint point : path) {
if (previousPoint != null) {
int change = Math.abs(previousPoint.x - point.x) + Math.abs(previousPoint.y - point.y);
if(change==1)
numStrt++;
else if(change==2)
numDiag++;
else {
assert false : String.format("Illegal path, cells are not contiguous change=%d", change);
return -1;
}
}
previousPoint = point;
}
final int cellDistance;
switch (metric) {
case MANHATTAN:
case NO_DIAGONALS:
cellDistance = (numStrt + numDiag * 2);
break;
case ONE_ONE_ONE:
cellDistance = (numStrt + numDiag);
break;
default:
case ONE_TWO_ONE:
cellDistance = (numStrt + numDiag + numDiag / 2);
break;
}
feetDistance = cellDistance * feetPerCell;
}
return feetDistance;
}
/**
* This method allows you to determine if a token moved over a number of points
* @param token the token that moved on the path
* @param path the path the token took (gridless unit)
* @param points the points of which you want to check if the token walked over
* @return the points that the token actually walked over
*/
public List<IntPoint> movedOverPoints(TokenView token, List<IntPoint> path, List<IntPoint> points) {
List<IntPoint> returnPoints = new ArrayList<IntPoint>(points.size());
Polygon targetArea = new Polygon();
for (IntPoint point : points) {
int x = point.getX();
int y = point.getY();
targetArea.addPoint(x, y);
}
for (IntPoint entry : path) {
Rectangle2D oa = token.getBounds(entry.getX(),entry.getY()).getBounds2D();
if (targetArea.contains(oa) || targetArea.intersects(oa)) {
returnPoints.add(entry);
}
}
return returnPoints;
}
/**
* This method allows you to determine if a token moved over another token.
* @param token the token that moved on the path
* @param path the path the token took
* @param target the token you want to test against
* @return the points where the path of the token crossed the target
*/
public List<IntPoint> movedOverToken(TokenView token, List<IntPoint> path, TokenView target) {
List<IntPoint> returnPoints = new ArrayList<IntPoint>();
Rectangle targetArea = target.getBounds();
if (path == null) {
return returnPoints;
}
if (token.isSnapToGrid()) {
for (IntPoint entry : path) {
Rectangle originalArea = token.getBounds(entry.getX(), entry.getY());
if (targetArea.intersects(originalArea) || originalArea.intersects(targetArea)) {
returnPoints.add(entry);
}
}
} else {
//Lee: establish first point, then process line intersection when a line can be drawn.
int ctr = 0;
Point previousPoint = new Point();
for (IntPoint entry : path) {
Rectangle tokenArea = token.getBounds();
Point currentPoint = new Point(entry.getX(), entry.getY());
if (ctr > 0) {
if (targetArea.intersectsLine(new Line2D.Double(previousPoint, currentPoint)) ||
targetArea.intersects(tokenArea)) {
returnPoints.add(new IntLine(
(int) previousPoint.getX(),
(int) previousPoint.getY(),
entry.getX(),
entry.getY()));
}
}
previousPoint = currentPoint;
ctr += 1;
}
//Lee: commenting this out
//originalArea = tokenInContext.getBounds(zone);
}
return returnPoints;
}
}