/*
Copyright 2006 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.portrayal.grid;
import sim.portrayal.*;
import sim.field.grid.*;
import java.awt.*;
import java.awt.geom.*;
import sim.util.*;
/**
Portrayal for hexagonal grids (each cell has six equally-distanced neighbors) with double-precision real values.
*/
public class HexaValueGridPortrayal2D extends ValueGridPortrayal2D
{
int[] xPoints = new int[6];
int[] yPoints = new int[6];
float[] xPointsf = new float[6];
float[] yPointsf = new float[6];
double[] xyC = new double[2];
double[] xyC_ul = new double[2];
double[] xyC_up = new double[2];
double[] xyC_ur = new double[2];
public HexaValueGridPortrayal2D()
{
super();
}
public HexaValueGridPortrayal2D(String valueName)
{
super(valueName);
}
final static void getxyC( final int x, final int y, final double xScale, final double yScale, final double tx, final double ty, final double[] xyC )
{
xyC[0] = tx + xScale * (1.5 * x + 1);
xyC[1] = ty + yScale * (1.0 + 2.0 * y + (x<0?(-x)%2:x%2) );
}
public Double2D getScale(DrawInfo2D info)
{
synchronized(info.gui.state.schedule)
{
final Grid2D field = (Grid2D) this.field;
if (field==null) return null;
int maxX = field.getWidth();
int maxY = field.getHeight();
if (maxX == 0 || maxY == 0) return null;
final double divideByX = ((maxX%2==0)?(3.0*maxX/2.0+0.5):(3.0*maxX/2.0+2.0));
final double divideByY = (1.0+2.0*maxY);
final double xScale = info.draw.width / divideByX;
final double yScale = info.draw.height / divideByY;
return new Double2D(xScale, yScale);
}
}
public Object getPositionLocation(Point2D.Double position, DrawInfo2D info)
{
Double2D scale = getScale(info);
double xScale = scale.x;
double yScale = scale.y;
int startx = (int)(((position.getX() - info.draw.x)/xScale-0.5)/1.5);
int starty = (int)((position.getY() - info.draw.y)/(yScale*2.0));
return new Int2D(startx, starty);
}
static final double HEXAGONAL_RATIO = 2/Math.sqrt(3);
public Point2D.Double getLocationPosition(Object location, DrawInfo2D info)
{
synchronized(info.gui.state.schedule)
{
final Grid2D field = (Grid2D) this.field;
if (field==null) return null;
int maxX = field.getWidth();
int maxY = field.getHeight();
if (maxX == 0 || maxY == 0) return null;
final double divideByX = ((maxX%2==0)?(3.0*maxX/2.0+0.5):(3.0*maxX/2.0+2.0));
final double divideByY = (1.0+2.0*maxY);
final double xScale = info.draw.width / divideByX;
final double yScale = info.draw.height / divideByY;
//int startx = (int)(((info.clip.x - info.draw.x)/xScale-0.5)/1.5)-2;
//int starty = (int)((info.clip.y - info.draw.y)/(yScale*2.0))-2;
//int endx = /*startx +*/ (int)(((info.clip.x - info.draw.x + info.clip.width)/xScale-0.5)/1.5) + 4; // with rounding, width be as much as 1 off
//int endy = /*starty +*/ (int)((info.clip.y - info.draw.y + info.clip.height)/(yScale*2.0)) + 4; // with rounding, height be as much as 1 off
DrawInfo2D newinfo = new DrawInfo2D(info.gui, info.fieldPortrayal, new Rectangle2D.Double(0,0,
Math.ceil(info.draw.width / (HEXAGONAL_RATIO * ((maxX - 1) * 3.0 / 4.0 + 1))),
Math.ceil(info.draw.height / (maxY + 0.5))),
info.clip/*, xPoints, yPoints*/, info); // we don't do further clipping
newinfo.precise = info.precise;
Int2D loc = (Int2D) location;
if (loc == null) return null;
final int x = loc.x;
final int y = loc.y;
getxyC( x, y, xScale, yScale, info.draw.x, info.draw.y, xyC );
getxyC( field.ulx(x,y), field.uly(x,y), xScale, yScale, info.draw.x, info.draw.y, xyC_ul );
getxyC( field.upx(x,y), field.upy(x,y), xScale, yScale, info.draw.x, info.draw.y, xyC_up );
getxyC( field.urx(x,y), field.ury(x,y), xScale, yScale, info.draw.x, info.draw.y, xyC_ur );
xPoints[0] = (int)(xyC_ur[0]-0.5*xScale);
//yPoints[0] = (int)(xyC_ur[1]+yScale);
//xPoints[1] = (int)(xyC_up[0]+0.5*xScale);
yPoints[1] = (int)(xyC_up[1]+yScale);
//xPoints[2] = (int)(xyC_up[0]-0.5*xScale);
//yPoints[2] = (int)(xyC_up[1]+yScale);
xPoints[3] = (int)(xyC_ul[0]+0.5*xScale);
//yPoints[3] = (int)(xyC_ul[1]+yScale);
//xPoints[4] = (int)(xyC[0]-0.5*xScale);
yPoints[4] = (int)(xyC[1]+yScale);
//xPoints[5] = (int)(xyC[0]+0.5*xScale);
//yPoints[5] = (int)(xyC[1]+yScale);
// compute the width of the object -- we tried computing the EXACT width each time, but
// it results in weird-shaped circles etc, so instead we precomputed a standard width
// and height, and just compute the x values here.
newinfo.draw.x = xPoints[3];
newinfo.draw.y = yPoints[1];
// adjust drawX and drawY to center
newinfo.draw.x +=(xPoints[0]-xPoints[3]) / 2.0;
newinfo.draw.y += (yPoints[4]-yPoints[1]) / 2.0;
return new Point2D.Double(newinfo.draw.x, newinfo.draw.y);
}
}
// our object to pass to the portrayal
//final MutableDouble valueToPass = new MutableDouble(0);
protected void hitOrDraw(Graphics2D graphics, DrawInfo2D info, Bag putInHere)
{
final Grid2D field = (Grid2D)(this.field);
if (field==null) return;
// first question: determine the range in which we need to draw.
final int maxX = field.getWidth();
final int maxY = field.getHeight();
if (maxX == 0 || maxY == 0) return;
final double divideByX = ((maxX%2==0)?(3.0*maxX/2.0+0.5):(3.0*maxX/2.0+2.0));
final double divideByY = (1.0+2.0*maxY);
final double xScale = info.draw.width / divideByX;
final double yScale = info.draw.height / divideByY;
int startx = (int)(((info.clip.x - info.draw.x)/xScale-0.5)/1.5)-2;
int starty = (int)((info.clip.y - info.draw.y)/(yScale*2.0))-2;
int endx = /*startx +*/ (int)(((info.clip.x - info.draw.x + info.clip.width)/xScale-0.5)/1.5) + 4; // with rounding, width be as much as 1 off
int endy = /*starty +*/ (int)((info.clip.y - info.draw.y + info.clip.height)/(yScale*2.0)) + 4; // with rounding, height be as much as 1 off
//
//
// CAUTION!
//
// At some point we should triple check the math for rounding such
// that the margins are drawn properly
//
//
// next we determine if this is a DoubleGrid2D or an IntGrid2D
// final Rectangle clip = (graphics==null ? null : graphics.getClipBounds());
final boolean isDoubleGrid2D = (field instanceof DoubleGrid2D);
final double[][] doubleField = (isDoubleGrid2D ? ((DoubleGrid2D) field).field : null);
final int[][] intField = (isDoubleGrid2D ? null : ((IntGrid2D) field).field);
double xyC_x, xyC_y, xyC_ulx, xyC_uly, xyC_upx, xyC_upy, xyC_urx, xyC_ury, x0, y0, tx, ty;
if( startx < 0 ) startx = 0;
if( starty < 0 ) starty = 0;
if (endx > maxX) endx = maxX;
if (endy > maxY) endy = maxY;
for(int y=starty;y<endy;y++)
for(int x=startx;x<endx;x++)
{
//getxyC( x, y, xScale, yScale, info.draw.x, info.draw.y, xyC );
//getxyC( field.ulx(x,y), field.uly(x,y), xScale, yScale, info.draw.x, info.draw.y, xyC_ul );
//getxyC( field.upx(x,y), field.upy(x,y), xScale, yScale, info.draw.x, info.draw.y, xyC_up );
//getxyC( field.urx(x,y), field.ury(x,y), xScale, yScale, info.draw.x, info.draw.y, xyC_ur );
x0 = x; y0 = y; tx = info.draw.x; ty = info.draw.y;
xyC_x = tx + xScale * (1.5 * x0 + 1);
xyC_y = ty + yScale * (1.0 + 2.0 * y0 + (x0<0?(-x0)%2:x0%2) );
x0 = field.ulx(x,y); y0 = field.uly(x,y); tx = info.draw.x; ty = info.draw.y;
xyC_ulx = tx + xScale * (1.5 * x0 + 1);
xyC_uly = ty + yScale * (1.0 + 2.0 * y0 + (x0<0?(-x0)%2:x0%2) );
x0 = field.upx(x,y); y0 = field.upy(x,y); tx = info.draw.x; ty = info.draw.y;
xyC_upx = tx + xScale * (1.5 * x0 + 1);
xyC_upy = ty + yScale * (1.0 + 2.0 * y0 + (x0<0?(-x0)%2:x0%2) );
x0 = field.urx(x,y); y0 = field.ury(x,y); tx = info.draw.x; ty = info.draw.y;
xyC_urx = tx + xScale * (1.5 * x0 + 1);
xyC_ury = ty + yScale * (1.0 + 2.0 * y0 + (x0<0?(-x0)%2:x0%2) );
if (graphics == null)
{
xPointsf[0] = (float)(xyC_urx-0.5*xScale);
yPointsf[0] = (float)(xyC_ury+yScale);
xPointsf[1] = (float)(xyC_upx+0.5*xScale);
yPointsf[1] = (float)(xyC_upy+yScale);
xPointsf[2] = (float)(xyC_upx-0.5*xScale);
yPointsf[2] = (float)(xyC_upy+yScale);
xPointsf[3] = (float)(xyC_ulx+0.5*xScale);
yPointsf[3] = (float)(xyC_uly+yScale);
xPointsf[4] = (float)(xyC_x-0.5*xScale);
yPointsf[4] = (float)(xyC_y+yScale);
xPointsf[5] = (float)(xyC_x+0.5*xScale);
yPointsf[5] = (float)(xyC_y+yScale);
generalPath.reset();
generalPath.moveTo( xPointsf[0], yPointsf[0] );
for( int i = 1 ; i < 6 ; i++ )
generalPath.lineTo( xPointsf[i], yPointsf[i] );
generalPath.closePath();
Area area = new Area( generalPath );
if( area.intersects( info.clip.x, info.clip.y, info.clip.width, info.clip.height ) )
{
valueToPass.val = isDoubleGrid2D ? doubleField[x][y] : intField[x][y];
putInHere.add( getWrapper(valueToPass.val, new Int2D(x, y)) );
}
}
else if (info.precise)
{
xPointsf[0] = (float)(xyC_urx-0.5*xScale);
yPointsf[0] = (float)(xyC_ury+yScale);
xPointsf[1] = (float)(xyC_upx+0.5*xScale);
yPointsf[1] = (float)(xyC_upy+yScale);
xPointsf[2] = (float)(xyC_upx-0.5*xScale);
yPointsf[2] = (float)(xyC_upy+yScale);
xPointsf[3] = (float)(xyC_ulx+0.5*xScale);
yPointsf[3] = (float)(xyC_uly+yScale);
xPointsf[4] = (float)(xyC_x-0.5*xScale);
yPointsf[4] = (float)(xyC_y+yScale);
xPointsf[5] = (float)(xyC_x+0.5*xScale);
yPointsf[5] = (float)(xyC_y+yScale);
Color c = map.getColor(isDoubleGrid2D ? doubleField[x][y] : intField[x][y]);
if (c.getAlpha() == 0) continue;
graphics.setColor(c);
generalPath.reset();
generalPath.moveTo( xPointsf[0], yPointsf[0] );
for( int i = 1 ; i < 6 ; i++ )
generalPath.lineTo( xPointsf[i], yPointsf[i] );
generalPath.closePath();
graphics.fill(generalPath);
}
else
{
xPoints[0] = (int)(xyC_urx-0.5*xScale);
yPoints[0] = (int)(xyC_ury+yScale);
xPoints[1] = (int)(xyC_upx+0.5*xScale);
yPoints[1] = (int)(xyC_upy+yScale);
xPoints[2] = (int)(xyC_upx-0.5*xScale);
yPoints[2] = (int)(xyC_upy+yScale);
xPoints[3] = (int)(xyC_ulx+0.5*xScale);
yPoints[3] = (int)(xyC_uly+yScale);
xPoints[4] = (int)(xyC_x-0.5*xScale);
yPoints[4] = (int)(xyC_y+yScale);
xPoints[5] = (int)(xyC_x+0.5*xScale);
yPoints[5] = (int)(xyC_y+yScale);
Color c = map.getColor(isDoubleGrid2D ? doubleField[x][y] : intField[x][y]);
if (c.getAlpha() == 0) continue;
graphics.setColor(c);
// MacOS X 10.3 Panther has a bug which resets the clip, YUCK
// graphics.setClip(clip);
graphics.fillPolygon(xPoints,yPoints,6);
}
}
}
GeneralPath generalPath = new GeneralPath();
/** This is not supported by hexagonal portrayals. Throws an exception. */
public void setBorder(boolean on) { throw new RuntimeException("Border drawing is not supported by hexagonal portrayals."); }
/** This is not supported by hexagonal portrayals. Throws an exception. */
public void setGridLines(boolean on) { throw new RuntimeException("Grid line drawing is not supported by hexagonal portrayals."); }
}