/*
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.simple;
import sim.portrayal.*;
import java.awt.*;
import java.awt.geom.*;
/**
A simple portrayal for 2D visualization of java.awt.Shapes and java.awt.Polygons. It extends the SimplePortrayal2D and
it manages the drawing and hit-testing for shapes. Various X and Y point arrays for constructing
different default shapes are also provided.
*/
public class ShapePortrayal2D extends AbstractShapePortrayal2D
{
static final Stroke defaultStroke = new BasicStroke();
public Shape shape;
public Stroke stroke;
AffineTransform transform = new AffineTransform();
double[] xPoints = null;
double[] yPoints = null;
double[] scaledXPoints = null;
double[] scaledYPoints = null;
int[] translatedXPoints = null;
int[] translatedYPoints = null;
double scaling;
double bufferedWidth;
double bufferedHeight;
Shape bufferedShape;
public static final double[] X_POINTS_TRIANGLE_DOWN = new double[] {-0.5, 0, 0.5};
public static final double[] Y_POINTS_TRIANGLE_DOWN = new double[] {-0.5, 0.5, -0.5};
public static final double[] X_POINTS_TRIANGLE_UP = new double[] {-0.5, 0, 0.5};
public static final double[] Y_POINTS_TRIANGLE_UP = new double[] {0.5, -0.5, 0.5};
public static final double[] X_POINTS_TRIANGLE_RIGHT = new double[] {-0.5, -0.5, 0.5};
public static final double[] Y_POINTS_TRIANGLE_RIGHT = new double[] {-0.5, 0.5, 0};
public static final double[] X_POINTS_TRIANGLE_LEFT = new double[] {-0.5, 0.5, 0.5};
public static final double[] Y_POINTS_TRIANGLE_LEFT = new double[] {0, 0.5, -0.5};
public static final double[] X_POINTS_DIAMOND = new double[] {-0.5, 0, 0.5, 0};
public static final double[] Y_POINTS_DIAMOND = new double[] {0, 0.5, 0, -0.5};
public static final double[] X_POINTS_SQUARE = new double[] {-0.5, -0.5, 0.5, 0.5};
public static final double[] Y_POINTS_SQUARE = new double[] {-0.5, 0.5, 0.5, -0.5};
public static final double[] X_POINTS_BOWTIE = new double[] {-0.5, 0.5, 0.5, -0.5};
public static final double[] Y_POINTS_BOWTIE = new double[] {-0.5, 0.5, -0.5, 0.5};
public static final double[] X_POINTS_HOURGLASS = new double[] {-0.5, 0.5, -0.5, 0.5};
public static final double[] Y_POINTS_HOURGLASS = new double[] {-0.5, 0.5, 0.5, -0.5};
static final double OCT_COORD = (1.0 / (1.0 + Math.sqrt(2))) / 2.0; // About .2071067811, derived from Wikipedia's Octogon article :-)
public static final double[] X_POINTS_OCTAGON = new double[] {-0.5, -0.5, -OCT_COORD, OCT_COORD, 0.5, 0.5, OCT_COORD, -OCT_COORD};
public static final double[] Y_POINTS_OCTAGON = new double[] {-OCT_COORD, OCT_COORD, 0.5, 0.5, OCT_COORD, -OCT_COORD, -0.5, -0.5};
// This hexagon, unlike HexagonalPortrayal2D, fits inside a 1x1 square centered at (0,0) and so looks somewhat stretched
public static final double[] X_POINTS_HEXAGON = new double[] {-0.5, -0.25, 0.25, 0.5, 0.25, -0.25};
public static final double[] Y_POINTS_HEXAGON = new double[] {0, 0.5, 0.5, 0, -0.5, -0.5};
public static final double[] X_POINTS_HEXAGON_ROTATED = new double[] {0, 0.5, 0.5, 0, -0.5, -0.5};
public static final double[] Y_POINTS_HEXAGON_ROTATED = new double[] {-0.5, -0.25, 0.25, 0.5, 0.25, -0.25};
Shape buildPolygon(double[] xpoints, double[] ypoints)
{
GeneralPath path = new GeneralPath();
// general paths are only floats and not doubles in Java 1.4, 1.5
// in 1.6 it's been changed to doubles finally but we're not there yet.
if (xpoints.length > 0) path.moveTo((float)xpoints[0], (float)ypoints[0]);
for(int i=xpoints.length-1; i >= 0; i--)
path.lineTo((float)xpoints[i], (float)ypoints[i]);
return path;
}
public ShapePortrayal2D(double[] xpoints, double[] ypoints) { this(xpoints, ypoints, Color.gray,1.0,true); }
public ShapePortrayal2D(double[] xpoints, double[] ypoints, Paint paint) { this(xpoints, ypoints,paint,1.0,true); }
public ShapePortrayal2D(double[] xpoints, double[] ypoints, double scale) { this(xpoints, ypoints,Color.gray,scale,true); }
public ShapePortrayal2D(double[] xpoints, double[] ypoints, Paint paint, double scale) { this(xpoints, ypoints, paint,scale,true); }
public ShapePortrayal2D(double[] xpoints, double[] ypoints, boolean filled) { this(xpoints, ypoints,Color.gray,1.0,filled); }
public ShapePortrayal2D(double[] xpoints, double[] ypoints, Paint paint, boolean filled) { this(xpoints, ypoints,paint,1.0,filled); }
public ShapePortrayal2D(double[] xpoints, double[] ypoints, double scale, boolean filled) { this(xpoints, ypoints,Color.gray,scale,filled); }
public ShapePortrayal2D(double[] xpoints, double[] ypoints, Paint paint, double scale, boolean filled)
{
this(null, paint, scale, filled);
this.shape = buildPolygon(xpoints,ypoints);
this.xPoints = xpoints;
this.yPoints = ypoints;
this.scaledXPoints = new double[xpoints.length];
this.scaledYPoints = new double[ypoints.length];
this.translatedXPoints = new int[xpoints.length];
this.translatedYPoints = new int[ypoints.length];
}
public ShapePortrayal2D(Shape shape) { this(shape,Color.gray,1.0,true); }
public ShapePortrayal2D(Shape shape, Paint paint) { this(shape,paint,1.0,true); }
public ShapePortrayal2D(Shape shape, double scale) { this(shape,Color.gray,scale,true); }
public ShapePortrayal2D(Shape shape, Paint paint, double scale) { this(shape, paint,scale,true); }
public ShapePortrayal2D(Shape shape, boolean filled) { this(shape,Color.gray,1.0,filled); }
public ShapePortrayal2D(Shape shape, Paint paint, boolean filled) { this(shape,paint,1.0,filled); }
public ShapePortrayal2D(Shape shape, double scale, boolean filled) { this(shape,Color.gray,scale,filled); }
public ShapePortrayal2D(Shape shape, Paint paint, double scale, boolean filled)
{
this.shape = shape;
this.paint = paint;
this.scale = scale;
this.filled = filled;
setStroke(null);
}
public void setStroke(Stroke s)
{
stroke = s;
}
// assumes the graphics already has its color set
public void draw(Object object, Graphics2D graphics, DrawInfo2D info)
{
graphics.setPaint(paint);
if (true) // We turn this off because it's no longer much slower (only 1% slower). info.precise || xPoints == null || stroke != null)
{
final double width = info.draw.width*scale;
final double height = info.draw.height*scale;
if (bufferedShape == null || width != bufferedWidth || height != bufferedHeight)
{
transform.setToScale(bufferedWidth = width, bufferedHeight = height);
bufferedShape = transform.createTransformedShape(shape);
}
// we are doing a simple draw, so we ignore the info.clip
// draw centered on the origin
transform.setToTranslation(info.draw.x,info.draw.y);
if (filled)
{
graphics.fill(transform.createTransformedShape(bufferedShape));
}
else
{
graphics.setStroke(stroke == null ? defaultStroke : stroke);
graphics.draw(transform.createTransformedShape(bufferedShape));
}
}
else // faster by far // NOTE: Not any more. On the Mac it's about 1% faster, not enough to worry about.
{
int len = xPoints.length;
double[] scaledXPoints = this.scaledXPoints;
double[] scaledYPoints = this.scaledYPoints;
int[] translatedXPoints = this.translatedXPoints;
int[] translatedYPoints = this.translatedYPoints;
double x = info.draw.x;
double y = info.draw.y;
double width = scale * info.draw.width;
// do we need to scale?
if (scaling != width)
{
double[] xPoints = this.xPoints;
double[] yPoints = this.yPoints;
double height = scale * info.draw.height;
for(int i=0;i<len;i++)
{
scaledXPoints[i] = xPoints[i] * width;
scaledYPoints[i] = yPoints[i] * height;
}
scaling = width;
}
// always translate
for(int i=0;i<len;i++)
{
translatedXPoints[i] = (int)(scaledXPoints[i] + x);
translatedYPoints[i] = (int)(scaledYPoints[i] + y);
}
if (filled) graphics.fillPolygon(translatedXPoints, translatedYPoints,translatedXPoints.length);
else graphics.drawPolygon(translatedXPoints, translatedYPoints,translatedXPoints.length);
}
}
public boolean hitObject(Object object, DrawInfo2D range)
{
final double width = range.draw.width*scale;
final double height = range.draw.height*scale;
if (bufferedShape == null || width != bufferedWidth || height != bufferedHeight)
{
transform.setToScale(bufferedWidth = width, bufferedHeight = height);
bufferedShape = transform.createTransformedShape(shape);
}
// center on the origin
transform.setToTranslation(range.draw.x,range.draw.y);
// now hit-test
return new Area(transform.createTransformedShape(bufferedShape)).intersects(
range.clip.x, range.clip.y, range.clip.width, range.clip.height);
}
}